Skip to content

Commit

Permalink
Optimize backup & restore UI
Browse files Browse the repository at this point in the history
[Fixed UI issue if screen size not enough]
[Added click sound to button]
[Moved description texts to xml]
[Added top bar to the layout]
  • Loading branch information
Z-Siqi committed Nov 21, 2024
1 parent 3a8193c commit 3df45bc
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 52 deletions.
61 changes: 31 additions & 30 deletions app/src/main/java/com/sqz/checklist/database/DatabaseIO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ class DatabaseIO(
_dbState = IOdbState.Processing
// Import
uri?.let { url ->
//importState(IOdbState.Default)
try {
context.contentResolver.openInputStream(url)?.use { input ->
closeDatabase()
Expand Down Expand Up @@ -198,35 +197,37 @@ fun ImportTaskDatabaseAction(
val dbPath = view.context.getDatabasePath(taskDatabaseName).absolutePath
val databaseIO = DatabaseIO(dbPath, view.context)
val coroutineScope = rememberCoroutineScope()
databaseIO.importDatabase(
uri = uri,
closeDatabase = {
taskDatabase.close()
coroutineScope.launch { // merge database checkpoint ("PRAGMA wal_checkpoint(FULL)")
mergeDatabaseCheckpoint(taskDatabase)
}
},
reOpenDatabase = {
taskDatabase = Room.databaseBuilder(
view.context, TaskDatabase::class.java, taskDatabaseName
).build()
if (!isDatabaseValid(dbPath)) {
Log.e("ChecklistDatabase", "Failed to import database: Invalid file!")
dbState(IOdbState.Error)
Log.w("ChecklistDatabase", "Trying to restore to backup..")
DatabaseIO(dbPath, view.context, "").importDatabase(
databaseIO.preBackupFileUri, { taskDatabase.close() }, {
taskDatabase = Room.databaseBuilder(
view.context, TaskDatabase::class.java, taskDatabaseName
).build()
true
}
) {}
}
true
},
importState = { dbState(it) }
)
LaunchedEffect(true) {
databaseIO.importDatabase(
uri = uri,
closeDatabase = {
taskDatabase.close()
coroutineScope.launch { // merge database checkpoint ("PRAGMA wal_checkpoint(FULL)")
mergeDatabaseCheckpoint(taskDatabase)
}
},
reOpenDatabase = {
taskDatabase = Room.databaseBuilder(
view.context, TaskDatabase::class.java, taskDatabaseName
).build()
if (!isDatabaseValid(dbPath)) {
Log.e("ChecklistDatabase", "Failed to import database: Invalid file!")
dbState(IOdbState.Error)
Log.w("ChecklistDatabase", "Trying to restore to backup..")
DatabaseIO(dbPath, view.context, "").importDatabase(
databaseIO.preBackupFileUri, { taskDatabase.close() }, {
taskDatabase = Room.databaseBuilder(
view.context, TaskDatabase::class.java, taskDatabaseName
).build()
true
}
)
}
true
},
importState = { dbState(it) }
)
}
}

private fun isDatabaseValid(databasePath: String): Boolean {
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/java/com/sqz/checklist/ui/MainLayout.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.util.Log
import android.view.SoundEffectConstants
import android.view.View
import android.widget.Toast
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -26,10 +27,12 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.sqz.checklist.R
import com.sqz.checklist.ui.main.NavBarLayout
import com.sqz.checklist.ui.main.NavExtendedButtonData
import com.sqz.checklist.ui.main.NavMode
import com.sqz.checklist.ui.main.backup.BackupAndRestoreLayout
import com.sqz.checklist.ui.main.backup.BackupRestoreTopBar
import com.sqz.checklist.ui.main.task.TaskLayoutViewModel
import com.sqz.checklist.ui.main.task.history.HistoryTopBar
import com.sqz.checklist.ui.main.task.history.TaskHistory
Expand All @@ -53,6 +56,7 @@ fun MainLayout(context: Context, view: View, modifier: Modifier = Modifier) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
var disableBackButton by rememberSaveable { mutableStateOf(false) }

// ViewModel
val taskLayoutViewModel: TaskLayoutViewModel = viewModel()
Expand Down Expand Up @@ -84,6 +88,14 @@ fun MainLayout(context: Context, view: View, modifier: Modifier = Modifier) {
view.playSoundEffect(SoundEffectConstants.CLICK)
})
}
val backupRestoreTopBar = @Composable {
BackupRestoreTopBar(onClick = {
if (!disableBackButton) navController.popBackStack() else Toast.makeText(
context, context.getString(R.string.back_disabled_notice), Toast.LENGTH_SHORT
).show()
view.playSoundEffect(SoundEffectConstants.CLICK)
})
}

// Navigation bar
val mainNavigationBar: @Composable (mode: NavMode) -> Unit = { mode ->
Expand Down Expand Up @@ -130,6 +142,7 @@ fun MainLayout(context: Context, view: View, modifier: Modifier = Modifier) {
topBar = when (currentRoute) {
MainLayoutNav.TaskLayout.name -> taskLayoutTopBar
MainLayoutNav.TaskHistory.name -> taskHistoryTopBar
MainLayoutNav.BackupRestore.name -> backupRestoreTopBar
else -> nul
},
bottomBar = {
Expand Down Expand Up @@ -164,7 +177,9 @@ fun MainLayout(context: Context, view: View, modifier: Modifier = Modifier) {
TaskHistory(historyState = taskHistoryViewModel)
}
composable(MainLayoutNav.BackupRestore.name) {
BackupAndRestoreLayout(view = view)
BackupAndRestoreLayout(view = view) {
disableBackButton = it
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.sqz.checklist.ui.main.backup

import android.content.Context
import android.media.AudioManager
import android.net.Uri
import android.os.VibrationEffect
import android.os.Vibrator
import android.util.Log
import android.view.SoundEffectConstants
import android.view.View
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
Expand All @@ -11,6 +18,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
Expand All @@ -32,29 +41,48 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.getSystemService
import com.sqz.checklist.R
import com.sqz.checklist.database.ExportTaskDatabase
import com.sqz.checklist.database.GetUri
import com.sqz.checklist.database.IOdbState
import com.sqz.checklist.database.ImportTaskDatabaseAction
import com.sqz.checklist.database.taskDatabaseName

/**
* Backup & Restore layout
*/
@Composable
fun BackupAndRestoreLayout(
view: View,
modifier: Modifier = Modifier,
dbPath: String = view.context.getDatabasePath(taskDatabaseName).absolutePath
dbPath: String = view.context.getDatabasePath(taskDatabaseName).absolutePath,
disableBackHandlerState: (Boolean) -> Unit = {}
) {
val audioManager = view.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

var disableBackHandler by rememberSaveable { mutableStateOf(false) }
if (disableBackHandler) BackHandler(enabled = true) {
Toast.makeText(
view.context, view.context.getString(R.string.back_disabled_notice), Toast.LENGTH_SHORT
).show()
}
disableBackHandlerState(disableBackHandler)

val backupCardLayout: @Composable ColumnScope.() -> Unit = {
var mode by rememberSaveable { mutableIntStateOf(0) }
val list = listOf(
ListData(0, "Export to file"), ListData(1, "Export by share")
ListData(0, stringResource(R.string.export_to_file)),
ListData(1, stringResource(R.string.export_by_share))
)
Text(
text = "Select a backup method",
text = stringResource(R.string.select_backup_method),
modifier = Modifier
.padding(start = 17.dp, top = 10.dp, bottom = 8.dp)
.align(Alignment.Start),
Expand All @@ -72,6 +100,7 @@ fun BackupAndRestoreLayout(
selected = index.index == mode,
onClick = {
mode = index.index
view.playSoundEffect(SoundEffectConstants.CLICK)
},
shape = SegmentedButtonDefaults.itemShape(
index = index.index, count = list.size
Expand All @@ -82,12 +111,13 @@ fun BackupAndRestoreLayout(
}
Spacer(modifier = Modifier.weight(1f))
var onClick by remember { mutableStateOf(false) }
Button(modifier = modifier
Button(modifier = Modifier
.padding(8.dp)
.align(Alignment.End), onClick = {
clickFeedback(view, audioManager)
onClick = true
}) {
Text(text = "Export")
Text(text = stringResource(R.string.export))
ExportTaskDatabase(onClick, mode == 1, view, dbPath) {
onClick = false
}
Expand All @@ -96,7 +126,7 @@ fun BackupAndRestoreLayout(

val restoreCardLayout: @Composable ColumnScope.() -> Unit = {
Text(
text = "Select a backup file to restore",
text = stringResource(R.string.select_backup_file_restore),
modifier = Modifier
.padding(start = 17.dp, top = 10.dp, bottom = 12.dp)
.align(Alignment.Start),
Expand All @@ -109,17 +139,20 @@ fun BackupAndRestoreLayout(
uri = it
selectUri = false
}
var onClick by remember { mutableStateOf(false) }
var onClick by rememberSaveable { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.height(70.dp)
.padding(start = 20.dp, end = 20.dp, top = 8.dp),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
onClick = { selectUri = true }
onClick = {
view.playSoundEffect(SoundEffectConstants.CLICK)
selectUri = true
}
) {
val text = if (uri == null) "Click to select a file to import" else {
var selected by rememberSaveable { mutableStateOf("Selected!") }
val text = if (uri == null) stringResource(R.string.click_select_file_import) else {
var selected by rememberSaveable { mutableStateOf(view.context.getString(R.string.selected)) }
try {
selected = uri!!.path.toString().replace("/document/primary:", "")
} catch (e: Exception) {
Expand All @@ -136,20 +169,30 @@ fun BackupAndRestoreLayout(
)
}
Spacer(modifier = Modifier.weight(1f))
Button(modifier = modifier
Button(modifier = Modifier
.padding(8.dp)
.align(Alignment.End), onClick = {
if (uri != null) onClick = true
clickFeedback(view, audioManager)
if (uri != null) onClick = true else Toast.makeText(
view.context, view.context.getString(R.string.select_file_to_import),
Toast.LENGTH_SHORT
).show()
}) {
if (onClick) {
ImportTaskDatabaseAction(uri, view) {
Log.i("TEST", "$it")
}
onClick = false
Text(text = stringResource(R.string.import_text))
if (onClick) ImportTaskDatabaseAction(uri, view) {
if (it == IOdbState.Error) Toast.makeText(
view.context, view.context.getString(R.string.error_try_undo_import),
Toast.LENGTH_SHORT
).show()
disableBackHandler = it == IOdbState.Processing
if (it == IOdbState.Finished) Toast.makeText(
view.context, view.context.getString(R.string.finished), Toast.LENGTH_SHORT
).show()
if (it == IOdbState.Finished || it == IOdbState.Error) onClick = false
}
Text(text = "Import")
}
}

Surface(
modifier = modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.surfaceContainerLow
Expand All @@ -159,11 +202,12 @@ fun BackupAndRestoreLayout(
.height(220.dp)
.padding(top = 8.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
TitleText(
text = "Backup",
text = stringResource(R.string.backup),
modifier = Modifier.align(Alignment.Start)
)
OutlinedCard(
Expand All @@ -172,7 +216,7 @@ fun BackupAndRestoreLayout(
)
HorizontalDivider(modifier = Modifier.padding(start = 16.dp, end = 16.dp))
TitleText(
text = "Restore",
text = stringResource(R.string.restore),
modifier = Modifier.align(Alignment.Start)
)
OutlinedCard(
Expand All @@ -197,7 +241,16 @@ private fun TitleText(text: String, modifier: Modifier = Modifier) = Text(
fontSize = 17.sp
)

@Preview(showBackground = true)
private fun clickFeedback(view: View, audioManager: AudioManager) {
if (audioManager.ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
val vibrate = view.context?.let { getSystemService(it, Vibrator::class.java) }
vibrate?.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
} else {
view.playSoundEffect(SoundEffectConstants.CLICK)
}
}

@Preview(showBackground = true, locale = "EN")
@Composable
private fun Preview() {
BackupAndRestoreLayout(LocalView.current, dbPath = "")
Expand Down
Loading

0 comments on commit 3df45bc

Please sign in to comment.