Skip to content

Commit c20daa5

Browse files
committed
Added per user last cloud backup time UI
1 parent 26a048a commit c20daa5

3 files changed

Lines changed: 85 additions & 15 deletions

File tree

app/src/main/java/in/hridayan/ashell/settings/presentation/page/backup/screens/BackupAndRestoreScreen.kt

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import `in`.hridayan.ashell.core.presentation.components.shape.CardCornerShape.g
5151
import `in`.hridayan.ashell.core.utils.getFileNameFromUri
5252
import `in`.hridayan.ashell.core.utils.showToast
5353
import `in`.hridayan.ashell.settings.domain.model.BackupOption
54+
import `in`.hridayan.ashell.settings.domain.model.GoogleUserState
5455
import `in`.hridayan.ashell.settings.presentation.components.dialog.BackupDestinationDialog
5556
import `in`.hridayan.ashell.settings.presentation.components.dialog.CloudOperationDialog
5657
import `in`.hridayan.ashell.settings.presentation.components.dialog.ResetSettingsDialog
@@ -76,6 +77,7 @@ fun BackupAndRestoreScreen(
7677
val backupTime by backupAndRestoreViewModel.backupTime.collectAsState()
7778
val lastLocalBackupTime = LocalSettings.current.lastLocalBackupTime
7879
val lastCloudBackupTime = LocalSettings.current.lastCloudBackupTime
80+
val isFetchingCloudBackupTime by backupAndRestoreViewModel.isFetchingCloudBackupTime.collectAsState()
7981

8082
val googleUserState by backupAndRestoreViewModel.googleUserState.collectAsState()
8183
val isSigningIn by backupAndRestoreViewModel.isSigningIn.collectAsState()
@@ -250,7 +252,9 @@ fun BackupAndRestoreScreen(
250252
.fillMaxWidth()
251253
.padding(top = 20.dp),
252254
lastLocalBackupTime = lastLocalBackupTime,
253-
lastCloudBackupTime = lastCloudBackupTime
255+
lastCloudBackupTime = lastCloudBackupTime,
256+
userState = googleUserState,
257+
isFetching = isFetchingCloudBackupTime,
254258
)
255259
}
256260
}
@@ -334,8 +338,31 @@ fun BackupAndRestoreScreen(
334338
private fun LastBackupTimeCard(
335339
modifier: Modifier = Modifier,
336340
lastLocalBackupTime: String,
337-
lastCloudBackupTime: String
341+
lastCloudBackupTime: String,
342+
userState: GoogleUserState,
343+
isFetching: Boolean
338344
) {
345+
val perUserLastCloudBackupTime = when {
346+
!userState.isSignedIn -> stringResource(R.string.not_signed_in)
347+
348+
isFetching -> stringResource(R.string.fetching_backup_time)
349+
350+
lastCloudBackupTime.isNotEmpty() ->
351+
lastCloudBackupTime.split(" ").let { parts ->
352+
val date = parts.getOrNull(0).orEmpty()
353+
val time = parts.getOrNull(1).orEmpty()
354+
"$date | $time"
355+
}
356+
else -> stringResource(R.string.none)
357+
}
358+
359+
val formattedLastLocalBackupTime =
360+
lastLocalBackupTime.split(" ").let { parts ->
361+
val date = parts.getOrNull(0).orEmpty()
362+
val time = parts.getOrNull(1).orEmpty()
363+
"$date | $time"
364+
}
365+
339366
Column(modifier = modifier) {
340367
RoundedCornerCard(
341368
modifier = Modifier.fillMaxWidth(),
@@ -354,15 +381,15 @@ private fun LastBackupTimeCard(
354381
roundedCornerShape = CardCornerShape.MIDDLE_CARD,
355382
icon = painterResource(R.drawable.ic_mobile),
356383
title = stringResource(R.string.device_backup_local),
357-
timeDescription = lastLocalBackupTime
384+
timeDescription = formattedLastLocalBackupTime
358385
)
359386

360387
TimeCard(
361388
modifier = Modifier.fillMaxWidth(),
362389
roundedCornerShape = CardCornerShape.LAST_CARD,
363390
icon = painterResource(R.drawable.ic_cloud_done),
364391
title = stringResource(R.string.cloud_backup_google_drive),
365-
timeDescription = lastCloudBackupTime
392+
timeDescription = perUserLastCloudBackupTime
366393
)
367394
}
368395
}
@@ -374,16 +401,8 @@ private fun TimeCard(
374401
roundedCornerShape: RoundedCornerShape,
375402
icon: Painter,
376403
title: String,
377-
timeDescription: String
404+
timeDescription: String,
378405
) {
379-
380-
val (date, time) = (timeDescription).split(" ").let {
381-
Pair(
382-
it.getOrNull(0) ?: "",
383-
it.getOrNull(1) ?: ""
384-
)
385-
}
386-
387406
RoundedCornerCard(
388407
modifier = modifier,
389408
roundedCornerShape = roundedCornerShape
@@ -412,7 +431,7 @@ private fun TimeCard(
412431
)
413432

414433
Text(
415-
text = if (timeDescription.isEmpty()) stringResource(R.string.none) else "$date | $time",
434+
text = timeDescription,
416435
style = MaterialTheme.typography.bodySmall,
417436
modifier = Modifier.alpha(0.9f)
418437
)

app/src/main/java/in/hridayan/ashell/settings/presentation/page/backup/viewmodel/BackupAndRestoreViewModel.kt

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class BackupAndRestoreViewModel @Inject constructor(
6464
private val _cloudBackupTime = MutableStateFlow<String?>(null)
6565
val cloudBackupTime: StateFlow<String?> = _cloudBackupTime.asStateFlow()
6666

67+
private val _isFetchingCloudBackupTime = MutableStateFlow(false)
68+
val isFetchingCloudBackupTime: StateFlow<Boolean> = _isFetchingCloudBackupTime.asStateFlow()
69+
6770
private val _showCloudRestoreConfirm = MutableStateFlow(false)
6871
val showCloudRestoreConfirm: StateFlow<Boolean> = _showCloudRestoreConfirm.asStateFlow()
6972

@@ -157,7 +160,12 @@ class BackupAndRestoreViewModel @Inject constructor(
157160

158161
// Immediately request Drive scope so consent appears right after sign-in
159162
Log.d(TAG, "signInWithGoogle: pre-authorizing Drive scope...")
160-
googleDriveRepository.ensureAuthorized()
163+
val authorized = googleDriveRepository.ensureAuthorized()
164+
165+
if (authorized) {
166+
// This is done to show correct last backup time per Google account
167+
syncCloudBackupTimeSilently()
168+
}
161169
},
162170
onFailure = { error ->
163171
Log.e(TAG, "signInWithGoogle: failed — ${error.message}")
@@ -196,6 +204,7 @@ class BackupAndRestoreViewModel @Inject constructor(
196204

197205
null -> {
198206
Log.d(TAG, "onConsentGranted: no pending operation to retry")
207+
syncCloudBackupTimeSilently()
199208
}
200209
}
201210
}
@@ -286,6 +295,46 @@ class BackupAndRestoreViewModel @Inject constructor(
286295
}
287296
}
288297

298+
private fun syncCloudBackupTimeSilently() {
299+
viewModelScope.launch {
300+
Log.d(TAG, "syncCloudBackupTimeSilently: starting...")
301+
302+
_isFetchingCloudBackupTime.value = true
303+
304+
val result = googleDriveRepository.downloadBackup()
305+
306+
if (result == null) {
307+
if (googleDriveRepository.isConsentPending) {
308+
Log.d(TAG, "syncCloudBackupTimeSilently: consent required, skipping for now")
309+
_isFetchingCloudBackupTime.value = false
310+
return@launch
311+
}
312+
313+
Log.d(TAG, "syncCloudBackupTimeSilently: no backup found")
314+
settingsRepository.setString(SettingsKeys.LAST_CLOUD_BACKUP_TIME, "")
315+
_isFetchingCloudBackupTime.value = false
316+
return@launch
317+
}
318+
319+
val (bytes, _) = result
320+
321+
val time = backupAndRestoreRepository.getBackupTimeFromBytes(bytes)
322+
323+
if (time != null) {
324+
Log.d(TAG, "syncCloudBackupTimeSilently: extracted time=$time")
325+
326+
settingsRepository.setString(
327+
SettingsKeys.LAST_CLOUD_BACKUP_TIME,
328+
time
329+
)
330+
} else {
331+
Log.e(TAG, "syncCloudBackupTimeSilently: failed to extract time")
332+
}
333+
334+
_isFetchingCloudBackupTime.value = false
335+
}
336+
}
337+
289338
/** Step 2: User confirmed — apply the downloaded restore */
290339
fun confirmCloudRestore() {
291340
viewModelScope.launch {

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@
303303
<string name="no_search_results_found">No search results found</string>
304304
<string name="no_wifi_connection">No WiFi connection</string>
305305
<string name="none">None</string>
306+
<string name ="not_signed_in">Not signed in</string>
307+
<string name="fetching_backup_time">Fetching backup time …</string>
306308
<string name="nothing_to_clear">Nothing to clear</string>
307309
<string name="nothing_to_search">Nothing to search</string>
308310
<string name="notification_access_not_granted">To complete the pairing process you will need to interact with a notification from aShell You. Please allow notification access to aShell You.</string>

0 commit comments

Comments
 (0)