Skip to content

Commit 1706373

Browse files
committed
WIP
1 parent f86dfdd commit 1706373

11 files changed

Lines changed: 140 additions & 64 deletions

File tree

app/src/main/java/in/hridayan/ashell/core/common/CompositionLocals.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ fun CompositionLocals(
107107

108108
val isNewCommandsAvailable by settingsViewModel.booleanState(SettingsKeys.NEW_COMMANDS_AVAILABLE)
109109

110+
val lastLocalBackupTime by settingsViewModel.stringState(SettingsKeys.LAST_LOCAL_BACKUP_TIME)
111+
112+
val lastCloudBackupTime by settingsViewModel.stringState(SettingsKeys.LAST_CLOUD_BACKUP_TIME)
113+
110114
val state =
111115
remember(
112116
autoUpdate,
@@ -130,7 +134,9 @@ fun CompositionLocals(
130134
bookmarkSortType,
131135
commandSortType,
132136
terminalFontStyle,
133-
isNewCommandsAvailable
137+
isNewCommandsAvailable,
138+
lastLocalBackupTime,
139+
lastCloudBackupTime
134140
) {
135141
SettingsState(
136142
isAutoUpdate = autoUpdate,
@@ -154,7 +160,9 @@ fun CompositionLocals(
154160
bookmarkSortType = bookmarkSortType,
155161
commandsSortType = commandSortType,
156162
terminalFontStyle = terminalFontStyle,
157-
isNewCommandsAvailable = isNewCommandsAvailable
163+
isNewCommandsAvailable = isNewCommandsAvailable,
164+
lastLocalBackupTime = lastLocalBackupTime,
165+
lastCloudBackupTime = lastCloudBackupTime
158166
)
159167
}
160168

app/src/main/java/in/hridayan/ashell/core/common/LocalSettings.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ val LocalSettings = compositionLocalOf {
3333
isFirstLaunch = true,
3434
bookmarkSortType = SortType.AZ,
3535
commandsSortType = SortType.AZ,
36-
isNewCommandsAvailable = true
36+
isNewCommandsAvailable = true,
37+
lastLocalBackupTime ="",
38+
lastCloudBackupTime = ""
3739
)
3840
}

app/src/main/java/in/hridayan/ashell/settings/data/SettingsKeys.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ enum class SettingsKeys(val default: Any?) {
3838
BACKUP_APP_DATA(null),
3939
RESTORE_APP_DATA(null),
4040
RESET_APP_SETTINGS(null),
41-
LAST_BACKUP_TIME(""),
41+
LAST_LOCAL_BACKUP_TIME(""),
4242
CLEAR_OUTPUT_CONFIRMATION(true),
4343
OUTPUT_SAVE_DIRECTORY(
4444
Environment.getExternalStoragePublicDirectory(

app/src/main/java/in/hridayan/ashell/settings/data/datastore/SettingsDataStore.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class SettingsDataStore @Inject constructor(
126126

127127
suspend fun resetAndRestoreDefaults(): Boolean {
128128
val preserveKeys = setOf(
129-
SettingsKeys.LAST_BACKUP_TIME.name,
129+
SettingsKeys.LAST_LOCAL_BACKUP_TIME.name,
130130
SettingsKeys.SAVED_VERSION_CODE.name,
131131
SettingsKeys.FIRST_LAUNCH.name
132132
)

app/src/main/java/in/hridayan/ashell/settings/data/repository/BackupAndRestoreRepositoryImpl.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class BackupAndRestoreRepositoryImpl @Inject constructor(
2727
@param:ApplicationContext private val context: Context
2828
) : BackupAndRestoreRepository {
2929

30-
override suspend fun backupDataToFile(uri: Uri, option: BackupOption): Boolean =
30+
override suspend fun backupToDevice(uri: Uri, option: BackupOption): Boolean =
3131
withContext(Dispatchers.IO) {
3232
try {
3333
val backupData = getBackupData(option)
@@ -38,7 +38,10 @@ class BackupAndRestoreRepositoryImpl @Inject constructor(
3838
outputStream.write(encryptedBytes)
3939
}
4040

41-
settingsRepository.setString(SettingsKeys.LAST_BACKUP_TIME, backupData.backupTime)
41+
settingsRepository.setString(
42+
SettingsKeys.LAST_LOCAL_BACKUP_TIME,
43+
backupData.backupTime
44+
)
4245

4346
true
4447
} catch (e: Exception) {

app/src/main/java/in/hridayan/ashell/settings/domain/model/SettingsState.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ data class SettingsState(
2424
val bookmarkSortType: Int,
2525
val commandsSortType: Int,
2626
val terminalFontStyle: Int,
27-
val isNewCommandsAvailable: Boolean
27+
val isNewCommandsAvailable: Boolean,
28+
val lastLocalBackupTime: String,
29+
val lastCloudBackupTime: String
2830
)

app/src/main/java/in/hridayan/ashell/settings/domain/repository/BackupAndRestoreRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.net.Uri
44
import `in`.hridayan.ashell.settings.domain.model.BackupOption
55

66
interface BackupAndRestoreRepository {
7-
suspend fun backupDataToFile(uri: Uri, option: BackupOption): Boolean
7+
suspend fun backupToDevice(uri: Uri, option: BackupOption): Boolean
88
suspend fun restoreDataFromFile(uri: Uri): Boolean
99
suspend fun getBackupTimeFromFile(uri: Uri): String?
1010

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

Lines changed: 97 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ package `in`.hridayan.ashell.settings.presentation.page.backup.screens
44

55
import androidx.activity.compose.rememberLauncherForActivityResult
66
import androidx.activity.result.contract.ActivityResultContracts
7-
import androidx.compose.animation.AnimatedVisibility
8-
import androidx.compose.animation.scaleIn
9-
import androidx.compose.animation.scaleOut
7+
import androidx.compose.foundation.layout.Arrangement
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
1010
import androidx.compose.foundation.layout.Spacer
1111
import androidx.compose.foundation.layout.fillMaxWidth
1212
import androidx.compose.foundation.layout.height
1313
import androidx.compose.foundation.layout.padding
1414
import androidx.compose.foundation.lazy.LazyColumn
1515
import androidx.compose.foundation.lazy.itemsIndexed
1616
import androidx.compose.foundation.lazy.rememberLazyListState
17-
import androidx.compose.material3.Card
18-
import androidx.compose.material3.CardDefaults
17+
import androidx.compose.foundation.shape.RoundedCornerShape
1918
import androidx.compose.material3.ExperimentalMaterial3Api
2019
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
20+
import androidx.compose.material3.Icon
2121
import androidx.compose.material3.MaterialTheme
2222
import androidx.compose.material3.Text
2323
import androidx.compose.runtime.Composable
@@ -27,24 +27,30 @@ import androidx.compose.runtime.getValue
2727
import androidx.compose.runtime.mutableStateOf
2828
import androidx.compose.runtime.saveable.rememberSaveable
2929
import androidx.compose.runtime.setValue
30+
import androidx.compose.ui.Alignment
3031
import androidx.compose.ui.Modifier
31-
import androidx.compose.ui.draw.clip
32+
import androidx.compose.ui.draw.alpha
33+
import androidx.compose.ui.graphics.painter.Painter
3234
import androidx.compose.ui.input.nestedscroll.nestedScroll
3335
import androidx.compose.ui.platform.LocalContext
3436
import androidx.compose.ui.platform.LocalResources
37+
import androidx.compose.ui.res.painterResource
3538
import androidx.compose.ui.res.stringResource
39+
import androidx.compose.ui.text.font.FontWeight
3640
import androidx.compose.ui.unit.dp
3741
import androidx.core.net.toUri
3842
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
3943
import `in`.hridayan.ashell.R
4044
import `in`.hridayan.ashell.core.common.LocalDialogManager
45+
import `in`.hridayan.ashell.core.common.LocalSettings
46+
import `in`.hridayan.ashell.core.presentation.components.card.RoundedCornerCard
4147
import `in`.hridayan.ashell.core.presentation.components.dialog.DialogKey
4248
import `in`.hridayan.ashell.core.presentation.components.dialog.createDialog
49+
import `in`.hridayan.ashell.core.presentation.components.shape.CardCornerShape
4350
import `in`.hridayan.ashell.core.presentation.components.shape.CardCornerShape.getRoundedShape
44-
import `in`.hridayan.ashell.core.presentation.components.text.AutoResizeableText
4551
import `in`.hridayan.ashell.core.utils.getFileNameFromUri
4652
import `in`.hridayan.ashell.core.utils.showToast
47-
import `in`.hridayan.ashell.settings.data.SettingsKeys
53+
import `in`.hridayan.ashell.settings.domain.model.BackupOption
4854
import `in`.hridayan.ashell.settings.presentation.components.dialog.BackupDestinationDialog
4955
import `in`.hridayan.ashell.settings.presentation.components.dialog.CloudOperationDialog
5056
import `in`.hridayan.ashell.settings.presentation.components.dialog.ResetSettingsDialog
@@ -68,8 +74,8 @@ fun BackupAndRestoreScreen(
6874
val settings = settingsViewModel.backupPageList
6975
val dialogManager = LocalDialogManager.current
7076
val backupTime by backupAndRestoreViewModel.backupTime.collectAsState()
71-
val lastBackupTime by settingsViewModel.getString(SettingsKeys.LAST_BACKUP_TIME)
72-
.collectAsState(initial = "")
77+
val lastLocalBackupTime = LocalSettings.current.lastLocalBackupTime
78+
val lastCloudBackupTime = LocalSettings.current.lastCloudBackupTime
7379

7480
val googleUserState by backupAndRestoreViewModel.googleUserState.collectAsState()
7581
val isSigningIn by backupAndRestoreViewModel.isSigningIn.collectAsState()
@@ -84,7 +90,7 @@ fun BackupAndRestoreScreen(
8490
val launcherBackup = rememberLauncherForActivityResult(
8591
contract = ActivityResultContracts.CreateDocument("application/octet-stream")
8692
) { uri ->
87-
uri?.let { backupAndRestoreViewModel.performBackup(it) }
93+
uri?.let { backupAndRestoreViewModel.performLocalBackup(it) }
8894
}
8995

9096
val launcherRestore = rememberLauncherForActivityResult(
@@ -240,7 +246,11 @@ fun BackupAndRestoreScreen(
240246

241247
"last_backup_time" -> {
242248
LastBackupTimeCard(
243-
lastBackupTime = lastBackupTime
249+
modifier = Modifier
250+
.fillMaxWidth()
251+
.padding(top = 20.dp),
252+
lastLocalBackupTime = lastLocalBackupTime,
253+
lastCloudBackupTime = lastCloudBackupTime
244254
)
245255
}
246256
}
@@ -274,16 +284,14 @@ fun BackupAndRestoreScreen(
274284
)
275285
}
276286

277-
// Backup destination dialog (Local vs Google Drive)
278287
DialogKey.Settings.BackupDestination(
279288
backupOption = backupAndRestoreViewModel.run {
280-
// Default option — the actual option comes from the dialog key
281-
`in`.hridayan.ashell.settings.domain.model.BackupOption.SETTINGS_AND_DATABASE
289+
BackupOption.SETTINGS_AND_DATABASE
282290
}
283291
).createDialog { dialogViewModel ->
284292
val activeKey = dialogManager.activeDialog
285293
val backupOption = (activeKey as? DialogKey.Settings.BackupDestination)?.backupOption
286-
?: `in`.hridayan.ashell.settings.domain.model.BackupOption.SETTINGS_AND_DATABASE
294+
?: BackupOption.SETTINGS_AND_DATABASE
287295

288296
BackupDestinationDialog(
289297
onDismiss = { dialogViewModel.dismiss() },
@@ -326,53 +334,92 @@ fun BackupAndRestoreScreen(
326334
}
327335

328336
@Composable
329-
private fun LastBackupTimeCard(modifier: Modifier = Modifier, lastBackupTime: String) {
337+
private fun LastBackupTimeCard(
338+
modifier: Modifier = Modifier,
339+
lastLocalBackupTime: String,
340+
lastCloudBackupTime: String
341+
) {
342+
Column(modifier = modifier) {
343+
RoundedCornerCard(
344+
modifier = Modifier.fillMaxWidth(),
345+
roundedCornerShape = CardCornerShape.FIRST_CARD
346+
) {
347+
Text(
348+
modifier = Modifier.padding(horizontal = 25.dp, vertical = 10.dp),
349+
text = stringResource(R.string.last_backup_details),
350+
style = MaterialTheme.typography.labelLarge,
351+
color = MaterialTheme.colorScheme.primary
352+
)
353+
}
354+
355+
TimeCard(
356+
modifier = Modifier.fillMaxWidth(),
357+
roundedCornerShape = CardCornerShape.MIDDLE_CARD,
358+
icon = painterResource(R.drawable.ic_mobile),
359+
title = stringResource(R.string.device_backup_local),
360+
timeDescription = lastLocalBackupTime
361+
)
362+
363+
TimeCard(
364+
modifier = Modifier.fillMaxWidth(),
365+
roundedCornerShape = CardCornerShape.LAST_CARD,
366+
icon = painterResource(R.drawable.ic_cloud_done),
367+
title = stringResource(R.string.cloud_backup_google_drive),
368+
timeDescription = lastCloudBackupTime
369+
)
370+
}
371+
}
372+
330373

331-
val (date, time) = (lastBackupTime).split(" ").let {
374+
@Composable
375+
private fun TimeCard(
376+
modifier: Modifier = Modifier,
377+
roundedCornerShape: RoundedCornerShape,
378+
icon: Painter,
379+
title: String,
380+
timeDescription: String
381+
) {
382+
383+
val (date, time) = (timeDescription).split(" ").let {
332384
Pair(
333385
it.getOrNull(0) ?: "",
334386
it.getOrNull(1) ?: ""
335387
)
336388
}
337389

338-
AnimatedVisibility(
339-
visible = date.isNotEmpty() && time.isNotEmpty(),
340-
enter = scaleIn(
341-
animationSpec = MaterialTheme.motionScheme.slowEffectsSpec(),
342-
),
343-
exit = scaleOut(animationSpec = MaterialTheme.motionScheme.slowEffectsSpec())
390+
RoundedCornerCard(
391+
modifier = modifier,
392+
roundedCornerShape = roundedCornerShape
344393
) {
345-
Card(
346-
modifier = modifier
394+
Row(
395+
modifier = Modifier
347396
.fillMaxWidth()
348-
.padding(start = 20.dp, end = 20.dp, top = 15.dp, bottom = 20.dp)
349-
.clip(
350-
MaterialTheme.shapes.large
351-
),
352-
shape = MaterialTheme.shapes.large,
353-
colors = CardDefaults.cardColors(
354-
containerColor = MaterialTheme.colorScheme.surface,
355-
contentColor = MaterialTheme.colorScheme.onSurface
356-
),
357-
border = CardDefaults.outlinedCardBorder()
397+
.padding(horizontal = 20.dp, vertical = 17.dp),
398+
verticalAlignment = Alignment.CenterVertically,
399+
horizontalArrangement = Arrangement.spacedBy(15.dp)
358400
) {
359-
AutoResizeableText(
360-
text = stringResource(R.string.last_backup_time) + " : " + time,
361-
style = MaterialTheme.typography.labelLarge,
362-
color = MaterialTheme.colorScheme.primary,
363-
modifier = Modifier.padding(horizontal = 25.dp, vertical = 15.dp)
401+
Icon(
402+
painter = icon,
403+
contentDescription = null,
404+
tint = MaterialTheme.colorScheme.primary
364405
)
365406

366-
AutoResizeableText(
367-
text = stringResource(R.string.last_backup_date) + " : " + date,
368-
style = MaterialTheme.typography.labelLarge,
369-
color = MaterialTheme.colorScheme.primary,
370-
modifier = Modifier.padding(
371-
start = 25.dp,
372-
end = 25.dp,
373-
bottom = 20.dp
407+
Column(
408+
modifier = Modifier.weight(1f),
409+
verticalArrangement = Arrangement.spacedBy(7.dp)
410+
) {
411+
Text(
412+
text = title,
413+
fontWeight = FontWeight.SemiBold,
414+
style = MaterialTheme.typography.titleMediumEmphasized,
374415
)
375-
)
416+
417+
Text(
418+
text = if (timeDescription.isEmpty()) stringResource(R.string.none) else "$date | $time",
419+
style = MaterialTheme.typography.bodySmall,
420+
modifier = Modifier.alpha(0.9f)
421+
)
422+
}
376423
}
377424
}
378425
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import kotlinx.coroutines.flow.StateFlow
2424
import kotlinx.coroutines.flow.asSharedFlow
2525
import kotlinx.coroutines.flow.asStateFlow
2626
import kotlinx.coroutines.launch
27+
import java.time.LocalDateTime
28+
import java.time.format.DateTimeFormatter
2729
import javax.inject.Inject
2830

2931
@HiltViewModel
@@ -95,9 +97,9 @@ class BackupAndRestoreViewModel @Inject constructor(
9597
currentBackupOption = option
9698
}
9799

98-
fun performBackup(uri: Uri) {
100+
fun performLocalBackup(uri: Uri) {
99101
viewModelScope.launch {
100-
val success = backupAndRestoreRepository.backupDataToFile(uri, currentBackupOption)
102+
val success = backupAndRestoreRepository.backupToDevice(uri, currentBackupOption)
101103

102104
val message =
103105
if (success) context.getString(R.string.backup_successful) else context.getString(R.string.backup_failed)
@@ -237,9 +239,8 @@ class BackupAndRestoreViewModel @Inject constructor(
237239

238240
if (success) {
239241
Log.d(TAG, "backupToGoogleDrive: upload SUCCESS")
240-
val formatter = java.time.format.DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm")
241-
val backupTime = java.time.LocalDateTime.now().format(formatter)
242-
settingsRepository.setString(SettingsKeys.LAST_BACKUP_TIME, backupTime)
242+
val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm")
243+
val backupTime = LocalDateTime.now().format(formatter)
243244
settingsRepository.setString(SettingsKeys.LAST_CLOUD_BACKUP_TIME, backupTime)
244245
_uiEvent.emit(SettingsUiEvent.ShowToast(context.getString(R.string.cloud_backup_successful)))
245246
} else {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:tint="?attr/colorControlNormal"
5+
android:viewportWidth="960"
6+
android:viewportHeight="960">
7+
<path
8+
android:fillColor="@android:color/white"
9+
android:pathData="M413,565L357,509Q345,497 329,497Q313,497 301,509Q289,521 289,537.5Q289,554 301,566L386,652Q398,664 414,664Q430,664 442,652L611,483Q623,471 623,454Q623,437 611,425Q599,413 582,413Q565,413 553,425L413,565ZM260,800Q169,800 104.5,737Q40,674 40,583Q40,505 87,444Q134,383 210,366Q235,274 310,217Q385,160 480,160Q597,160 678.5,241.5Q760,323 760,440L760,440L760,440Q829,448 874.5,499.5Q920,551 920,620Q920,695 867.5,747.5Q815,800 740,800L260,800ZM260,720L740,720Q782,720 811,691Q840,662 840,620Q840,578 811,549Q782,520 740,520L680,520L680,440Q680,357 621.5,298.5Q563,240 480,240Q397,240 338.5,298.5Q280,357 280,440L280,440L260,440Q202,440 161,481Q120,522 120,580Q120,638 161,679Q202,720 260,720ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
10+
</vector>

0 commit comments

Comments
 (0)