Skip to content

Commit d6465a7

Browse files
authored
feat: implement maintenance mode with dedicated activity and UI components (#56)
1 parent ebd6cc3 commit d6465a7

14 files changed

Lines changed: 655 additions & 14 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
<activity
3939
android:name=".activity.VITEventsActivity"
4040
android:exported="false" />
41+
<activity
42+
android:name=".activity.MaintenanceActivity"
43+
android:exported="false" />
4144
<activity
4245
android:name=".activity.NavigationActivity"
4346
android:exported="false" />

app/src/main/java/com/dscvit/vitty/activity/AuthActivity.kt

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.view.View
1010
import android.widget.Toast
1111
import androidx.activity.result.ActivityResultLauncher
1212
import androidx.activity.result.contract.ActivityResultContracts
13+
import androidx.activity.OnBackPressedCallback
1314
import androidx.appcompat.app.AppCompatActivity
1415
import androidx.core.content.edit
1516
import androidx.databinding.DataBindingUtil
@@ -24,6 +25,7 @@ import com.dscvit.vitty.util.Constants.TOKEN
2425
import com.dscvit.vitty.util.Constants.UID
2526
import com.dscvit.vitty.util.Constants.USER_INFO
2627
import com.dscvit.vitty.util.NotificationPermissionHelper
28+
import com.dscvit.vitty.util.MaintenanceChecker
2729
import com.google.android.gms.auth.api.signin.GoogleSignIn
2830
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
2931
import com.google.android.gms.auth.api.signin.GoogleSignInClient
@@ -62,6 +64,22 @@ class AuthActivity : AppCompatActivity() {
6264

6365
configureGoogleSignIn()
6466
setupUI()
67+
setupBackPressedHandler()
68+
}
69+
70+
private fun setupBackPressedHandler() {
71+
val callback = object : OnBackPressedCallback(true) {
72+
override fun handleOnBackPressed() {
73+
binding.apply {
74+
if (introPager.currentItem == 0 || loginClick) {
75+
finish()
76+
} else {
77+
introPager.currentItem--
78+
}
79+
}
80+
}
81+
}
82+
onBackPressedDispatcher.addCallback(this, callback)
6583
}
6684

6785
private fun setupNotificationPermissionLauncher() {
@@ -266,8 +284,9 @@ class AuthActivity : AppCompatActivity() {
266284
val email = firebaseAuth.currentUser?.email
267285
Timber.d("Firebase authentication successful - uid: $uid, email: $email")
268286
saveInfo(acct.idToken, uid)
269-
authViewModel.signInAndGetTimeTable("", "", uid ?: "", "")
270-
leadToNextPage()
287+
288+
// Quick maintenance check for new login
289+
checkMaintenanceBeforeProceed()
271290
} else {
272291
Timber.e("Firebase authentication failed: ${authResult.exception?.message}")
273292
logoutFailed()
@@ -278,6 +297,20 @@ class AuthActivity : AppCompatActivity() {
278297
}
279298
}
280299

300+
private fun checkMaintenanceBeforeProceed() {
301+
MaintenanceChecker.checkMaintenanceStatusAsync(this) { isUnderMaintenance ->
302+
if (isUnderMaintenance) {
303+
binding.loadingView.visibility = View.GONE
304+
val intent = Intent(this, MaintenanceActivity::class.java)
305+
startActivity(intent)
306+
finish()
307+
} else {
308+
authViewModel.signInAndGetTimeTable("", "", firebaseAuth.currentUser?.uid ?: "", "")
309+
leadToNextPage()
310+
}
311+
}
312+
}
313+
281314
private fun leadToNextPage() {
282315
authViewModel.signInResponse.observe(this) {
283316
if (it != null) {
@@ -323,14 +356,4 @@ class AuthActivity : AppCompatActivity() {
323356
}
324357
}
325358
}
326-
327-
override fun onBackPressed() {
328-
binding.apply {
329-
if (introPager.currentItem == 0 || loginClick) {
330-
super.onBackPressed()
331-
} else {
332-
introPager.currentItem--
333-
}
334-
}
335-
}
336359
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.dscvit.vitty.activity
2+
3+
import android.content.Intent
4+
import android.os.Bundle
5+
import androidx.compose.ui.platform.ViewCompositionStrategy
6+
import androidx.databinding.DataBindingUtil
7+
import androidx.fragment.app.FragmentActivity
8+
import com.dscvit.vitty.R
9+
import com.dscvit.vitty.databinding.ActivityMaintenanceBinding
10+
import com.dscvit.vitty.theme.VittyTheme
11+
import com.dscvit.vitty.ui.maintenance.MaintenanceScreen
12+
import com.dscvit.vitty.util.MaintenanceChecker
13+
14+
class MaintenanceActivity : FragmentActivity() {
15+
16+
private lateinit var binding: ActivityMaintenanceBinding
17+
18+
override fun onCreate(savedInstanceState: Bundle?) {
19+
super.onCreate(savedInstanceState)
20+
21+
binding = DataBindingUtil.setContentView(this, R.layout.activity_maintenance)
22+
23+
binding.composeView.apply {
24+
setViewCompositionStrategy(
25+
ViewCompositionStrategy.DisposeOnLifecycleDestroyed(this@MaintenanceActivity)
26+
)
27+
setContent {
28+
VittyTheme {
29+
MaintenanceScreen(
30+
onRetryClick = { retryConnection() },
31+
onExitClick = { exitApp() }
32+
)
33+
}
34+
}
35+
}
36+
}
37+
38+
private fun retryConnection() {
39+
MaintenanceChecker.checkMaintenanceStatusAsync(this) { isUnderMaintenance ->
40+
if (!isUnderMaintenance) {
41+
val intent = Intent(this, InstructionsActivity::class.java)
42+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
43+
startActivity(intent)
44+
finish()
45+
}
46+
}
47+
}
48+
49+
private fun exitApp() {
50+
finishAffinity()
51+
}
52+
}

app/src/main/java/com/dscvit/vitty/network/api/community/APICommunity.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import retrofit2.http.Path
2929
import retrofit2.http.Query
3030

3131
interface APICommunity {
32+
@GET("/")
33+
fun checkServerStatus(): Call<String>
34+
3235
@Headers("Content-Type: application/json")
3336
@POST("/api/v3/auth/check-username")
3437
fun checkUsername(

app/src/main/java/com/dscvit/vitty/network/api/community/APICommunityRestClient.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,4 +1050,30 @@ class APICommunityRestClient {
10501050
},
10511051
)
10521052
}
1053+
1054+
fun checkServerStatus(
1055+
retrofitServerStatusListener: RetrofitServerStatusListener,
1056+
) {
1057+
mApiUser = retrofit.create<APICommunity>(APICommunity::class.java)
1058+
val apiServerStatusCall = mApiUser!!.checkServerStatus()
1059+
apiServerStatusCall.enqueue(
1060+
object : Callback<String> {
1061+
override fun onResponse(
1062+
call: Call<String>,
1063+
response: Response<String>,
1064+
) {
1065+
Timber.d("ServerStatus: ${response.body()}")
1066+
retrofitServerStatusListener.onSuccess(call, response.body(), response.isSuccessful)
1067+
}
1068+
1069+
override fun onFailure(
1070+
call: Call<String>,
1071+
t: Throwable,
1072+
) {
1073+
Timber.d("ServerStatusError: ${t.message}")
1074+
retrofitServerStatusListener.onError(call, t)
1075+
}
1076+
},
1077+
)
1078+
}
10531079
}

app/src/main/java/com/dscvit/vitty/network/api/community/RetrofitCommunityListener.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,15 @@ interface RetrofitActiveFriendsListener {
169169
t: Throwable,
170170
)
171171
}
172+
173+
interface RetrofitServerStatusListener {
174+
fun onSuccess(
175+
call: Call<String>,
176+
response: String?,
177+
isSuccessful: Boolean
178+
)
179+
fun onError(
180+
call: Call<String>,
181+
t: Throwable
182+
)
183+
}

app/src/main/java/com/dscvit/vitty/ui/main/MainComposeApp.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ import com.dscvit.vitty.ui.schedule.ScheduleViewModel
103103
import com.dscvit.vitty.util.Analytics
104104
import com.dscvit.vitty.util.Constants
105105
import com.dscvit.vitty.util.LogoutHelper
106+
import com.dscvit.vitty.util.MaintenanceChecker
106107
import com.dscvit.vitty.util.SemesterUtils
107108
import com.dscvit.vitty.util.UtilFunctions
109+
import com.dscvit.vitty.ui.maintenance.MaintenanceBannerDialog
108110
import com.google.gson.Gson
109111
import com.google.gson.reflect.TypeToken
110112
import java.net.URLDecoder
@@ -113,6 +115,7 @@ import java.nio.charset.StandardCharsets
113115
import kotlinx.coroutines.Dispatchers
114116
import kotlinx.coroutines.launch
115117
import kotlinx.coroutines.withContext
118+
import timber.log.Timber
116119

117120
@Composable
118121
fun MainComposeApp() {
@@ -129,6 +132,9 @@ fun MainComposeApp() {
129132

130133
var campus by remember { mutableStateOf(prefs.getString(Constants.COMMUNITY_CAMPUS, "") ?: "") }
131134
var showCampusDialog by remember { mutableStateOf(campus.isEmpty()) }
135+
136+
// Maintenance banner state
137+
var showMaintenanceBanner by remember { mutableStateOf(false) }
132138

133139
DisposableEffect(Unit) {
134140
val listener =
@@ -208,13 +214,24 @@ fun MainComposeApp() {
208214
}
209215
}
210216

217+
LaunchedEffect(Unit) {
218+
if (UtilFunctions.isNetworkAvailable(context)) {
219+
MaintenanceChecker.checkMaintenanceStatusAsync(context) { isUnderMaintenance ->
220+
Timber.d("Dialog check result: isUnderMaintenance=%s", isUnderMaintenance)
221+
showMaintenanceBanner = isUnderMaintenance
222+
}
223+
} else {
224+
Timber.d("No network available")
225+
}
226+
}
227+
211228
VittyTheme {
212229
ModalNavigationDrawer(
213230
drawerState = drawerState,
214231
drawerContent = {
215232
DrawerContent(
216233
navController = navController,
217-
onCloseDrawer = { scope.launch { drawerState.close() } },
234+
onCloseDrawer = { scope.launch { drawerState.close() } }
218235
)
219236
},
220237
) {
@@ -866,6 +883,19 @@ fun MainComposeApp() {
866883
onDismiss = { showCampusDialog = false },
867884
)
868885
}
886+
887+
// Maintenance Banner Dialog
888+
MaintenanceBannerDialog(
889+
isVisible = showMaintenanceBanner,
890+
onDismiss = {
891+
showMaintenanceBanner = false
892+
},
893+
onRetryClick = {
894+
MaintenanceChecker.checkMaintenanceStatusAsync(context) { isUnderMaintenance ->
895+
showMaintenanceBanner = isUnderMaintenance
896+
}
897+
}
898+
)
869899
}
870900
}
871901
}
@@ -1091,7 +1121,7 @@ fun NavigationItem(
10911121
@Composable
10921122
fun DrawerContent(
10931123
navController: NavHostController,
1094-
onCloseDrawer: () -> Unit,
1124+
onCloseDrawer: () -> Unit
10951125
) {
10961126
val context = LocalContext.current
10971127
val prefs = remember { context.getSharedPreferences(Constants.USER_INFO, 0) }

0 commit comments

Comments
 (0)