mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-30 00:51:48 +01:00
Initial move of restore backup into a separate screen
This commit is contained in:
parent
565317d99c
commit
9f90ee358b
app/src/main/java/eu/kanade
presentation/more/settings/screen
tachiyomi/data/backup/restore
@ -1,28 +1,24 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.material3.SegmentedButton
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@ -34,17 +30,13 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
|||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
import eu.kanade.presentation.util.relativeTimeSpanString
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
|
||||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
|
||||||
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
|
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.i18n.stringResource
|
import tachiyomi.core.i18n.stringResource
|
||||||
@ -142,14 +134,42 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
|
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.label_backup),
|
title = stringResource(MR.strings.label_backup),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
// Manual actions
|
// Manual actions
|
||||||
getCreateBackupPref(),
|
Preference.PreferenceItem.CustomPreference(
|
||||||
getRestoreBackupPref(),
|
title = stringResource(MR.strings.label_backup),
|
||||||
|
) {
|
||||||
|
BasePreferenceWidget(
|
||||||
|
subcomponent = {
|
||||||
|
MultiChoiceSegmentedButtonRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = PrefsHorizontalPadding),
|
||||||
|
) {
|
||||||
|
SegmentedButton(
|
||||||
|
checked = false,
|
||||||
|
onCheckedChange = { navigator.push(CreateBackupScreen()) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(0, 2),
|
||||||
|
) {
|
||||||
|
Text(stringResource(MR.strings.pref_create_backup))
|
||||||
|
}
|
||||||
|
SegmentedButton(
|
||||||
|
checked = false,
|
||||||
|
onCheckedChange = { navigator.push(RestoreBackupScreen()) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(1, 2),
|
||||||
|
) {
|
||||||
|
Text(stringResource(MR.strings.pref_restore_backup))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
// Automatic backups
|
// Automatic backups
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
@ -176,156 +196,6 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getCreateBackupPref(): Preference.PreferenceItem.TextPreference {
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
return Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(MR.strings.pref_create_backup),
|
|
||||||
subtitle = stringResource(MR.strings.pref_create_backup_summ),
|
|
||||||
onClick = { navigator.push(CreateBackupScreen()) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
|
||||||
val context = LocalContext.current
|
|
||||||
var error by remember { mutableStateOf<Any?>(null) }
|
|
||||||
if (error != null) {
|
|
||||||
val onDismissRequest = { error = null }
|
|
||||||
when (val err = error) {
|
|
||||||
is InvalidRestore -> {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(MR.strings.invalid_backup_file)) },
|
|
||||||
text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) },
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
context.copyToClipboard(err.message, err.message)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(MR.strings.action_copy_to_clipboard))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(MR.strings.action_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is MissingRestoreComponents -> {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(MR.strings.pref_restore_backup)) },
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
|
||||||
val msg = buildString {
|
|
||||||
append(stringResource(MR.strings.backup_restore_content_full))
|
|
||||||
if (err.sources.isNotEmpty()) {
|
|
||||||
append("\n\n").append(stringResource(MR.strings.backup_restore_missing_sources))
|
|
||||||
err.sources.joinTo(
|
|
||||||
this,
|
|
||||||
separator = "\n- ",
|
|
||||||
prefix = "\n- ",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (err.trackers.isNotEmpty()) {
|
|
||||||
append(
|
|
||||||
"\n\n",
|
|
||||||
).append(stringResource(MR.strings.backup_restore_missing_trackers))
|
|
||||||
err.trackers.joinTo(
|
|
||||||
this,
|
|
||||||
separator = "\n- ",
|
|
||||||
prefix = "\n- ",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(text = msg)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
BackupRestoreJob.start(
|
|
||||||
context = context,
|
|
||||||
uri = err.uri,
|
|
||||||
// TODO: allow user-selectable restore options
|
|
||||||
options = RestoreOptions(
|
|
||||||
appSettings = true,
|
|
||||||
sourceSettings = true,
|
|
||||||
library = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(MR.strings.action_restore))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> error = null // Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val chooseBackup = rememberLauncherForActivityResult(
|
|
||||||
object : ActivityResultContracts.GetContent() {
|
|
||||||
override fun createIntent(context: Context, input: String): Intent {
|
|
||||||
val intent = super.createIntent(context, input)
|
|
||||||
return Intent.createChooser(intent, context.stringResource(MR.strings.file_select_backup))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
if (it == null) {
|
|
||||||
context.toast(MR.strings.file_null_uri_error)
|
|
||||||
return@rememberLauncherForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val results = try {
|
|
||||||
BackupFileValidator().validate(context, it)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = InvalidRestore(it, e.message.toString())
|
|
||||||
return@rememberLauncherForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
|
|
||||||
BackupRestoreJob.start(
|
|
||||||
context = context,
|
|
||||||
uri = it,
|
|
||||||
// TODO: allow user-selectable restore options
|
|
||||||
options = RestoreOptions(
|
|
||||||
appSettings = true,
|
|
||||||
sourceSettings = true,
|
|
||||||
library = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return@rememberLauncherForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
error = MissingRestoreComponents(it, results.missingSources, results.missingTrackers)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(MR.strings.pref_restore_backup),
|
|
||||||
subtitle = stringResource(MR.strings.pref_restore_backup_summ),
|
|
||||||
onClick = {
|
|
||||||
if (!BackupRestoreJob.isRunning(context)) {
|
|
||||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
||||||
context.toast(MR.strings.restore_miui_warning, Toast.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
// no need to catch because it's wrapped with a chooser
|
|
||||||
chooseBackup.launch("*/*")
|
|
||||||
} else {
|
|
||||||
context.toast(MR.strings.restore_in_progress)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDataGroup(): Preference.PreferenceGroup {
|
private fun getDataGroup(): Preference.PreferenceGroup {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@ -394,14 +264,3 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class MissingRestoreComponents(
|
|
||||||
val uri: Uri,
|
|
||||||
val sources: List<String>,
|
|
||||||
val trackers: List<String>,
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class InvalidRestore(
|
|
||||||
val uri: Uri? = null,
|
|
||||||
val message: String,
|
|
||||||
)
|
|
||||||
|
@ -0,0 +1,242 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.screen.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||||
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||||
|
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
|
||||||
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import tachiyomi.core.i18n.stringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
class RestoreBackupScreen : Screen() {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val model = rememberScreenModel { RestoreBackupScreenModel() }
|
||||||
|
val state by model.state.collectAsState()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(MR.strings.pref_restore_backup),
|
||||||
|
navigateUp = navigator::pop,
|
||||||
|
scrollBehavior = it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
if (state.error != null) {
|
||||||
|
val onDismissRequest = model::clearError
|
||||||
|
when (val err = state.error) {
|
||||||
|
is InvalidRestore -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(MR.strings.invalid_backup_file)) },
|
||||||
|
text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
context.copyToClipboard(err.message, err.message)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_copy_to_clipboard))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is MissingRestoreComponents -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(MR.strings.pref_restore_backup)) },
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
val msg = buildString {
|
||||||
|
append(stringResource(MR.strings.backup_restore_content_full))
|
||||||
|
if (err.sources.isNotEmpty()) {
|
||||||
|
append(
|
||||||
|
"\n\n",
|
||||||
|
).append(stringResource(MR.strings.backup_restore_missing_sources))
|
||||||
|
err.sources.joinTo(
|
||||||
|
this,
|
||||||
|
separator = "\n- ",
|
||||||
|
prefix = "\n- ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (err.trackers.isNotEmpty()) {
|
||||||
|
append(
|
||||||
|
"\n\n",
|
||||||
|
).append(stringResource(MR.strings.backup_restore_missing_trackers))
|
||||||
|
err.trackers.joinTo(
|
||||||
|
this,
|
||||||
|
separator = "\n- ",
|
||||||
|
prefix = "\n- ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(text = msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
BackupRestoreJob.start(
|
||||||
|
context = context,
|
||||||
|
uri = err.uri,
|
||||||
|
options = state.options,
|
||||||
|
)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_restore))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> onDismissRequest() // Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooseBackup = rememberLauncherForActivityResult(
|
||||||
|
object : ActivityResultContracts.GetContent() {
|
||||||
|
override fun createIntent(context: Context, input: String): Intent {
|
||||||
|
val intent = super.createIntent(context, input)
|
||||||
|
return Intent.createChooser(intent, context.stringResource(MR.strings.file_select_backup))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (it == null) {
|
||||||
|
context.toast(MR.strings.file_null_uri_error)
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
val results = try {
|
||||||
|
BackupFileValidator().validate(context, it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
model.setError(InvalidRestore(it, e.message.toString()))
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
|
||||||
|
BackupRestoreJob.start(
|
||||||
|
context = context,
|
||||||
|
uri = it,
|
||||||
|
options = state.options,
|
||||||
|
)
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
model.setError(MissingRestoreComponents(it, results.missingSources, results.missingTrackers))
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(contentPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
||||||
|
item {
|
||||||
|
WarningBanner(MR.strings.restore_miui_warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
if (!BackupRestoreJob.isRunning(context)) {
|
||||||
|
// no need to catch because it's wrapped with a chooser
|
||||||
|
chooseBackup.launch("*/*")
|
||||||
|
} else {
|
||||||
|
context.toast(MR.strings.restore_in_progress)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(stringResource(MR.strings.pref_restore_backup))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: show validation errors inline
|
||||||
|
// TODO: show options for what to restore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RestoreBackupScreenModel : StateScreenModel<RestoreBackupScreenModel.State>(State()) {
|
||||||
|
|
||||||
|
fun setError(error: Any) {
|
||||||
|
mutableState.update {
|
||||||
|
it.copy(error = error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearError() {
|
||||||
|
mutableState.update {
|
||||||
|
it.copy(error = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class State(
|
||||||
|
val error: Any? = null,
|
||||||
|
// TODO: allow user-selectable restore options
|
||||||
|
val options: RestoreOptions = RestoreOptions(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class MissingRestoreComponents(
|
||||||
|
val uri: Uri,
|
||||||
|
val sources: List<String>,
|
||||||
|
val trackers: List<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class InvalidRestore(
|
||||||
|
val uri: Uri? = null,
|
||||||
|
val message: String,
|
||||||
|
)
|
@ -172,9 +172,9 @@ class BackupRestorer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class RestoreOptions(
|
data class RestoreOptions(
|
||||||
val appSettings: Boolean,
|
val appSettings: Boolean = true,
|
||||||
val sourceSettings: Boolean,
|
val sourceSettings: Boolean = true,
|
||||||
val library: Boolean,
|
val library: Boolean = true,
|
||||||
) {
|
) {
|
||||||
fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library)
|
fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user