mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-06-28 08:36:02 +02:00
![arkon](/assets/img/avatar_default.png)
Wasn't working correctly in release build, _probably_ because of R8 despite kotlin-reflect shipping with Proguard rules and us already keeping all Tachiyomi classes.
241 lines
8.7 KiB
Kotlin
241 lines
8.7 KiB
Kotlin
package eu.kanade.presentation.more.settings.screen.data
|
|
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.lazy.LazyListScope
|
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Text
|
|
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 androidx.compose.ui.text.SpanStyle
|
|
import androidx.compose.ui.text.buildAnnotatedString
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.withStyle
|
|
import androidx.core.net.toUri
|
|
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 kotlinx.coroutines.flow.update
|
|
import tachiyomi.i18n.MR
|
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
|
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
|
import tachiyomi.presentation.core.components.SectionCard
|
|
import tachiyomi.presentation.core.components.material.Scaffold
|
|
import tachiyomi.presentation.core.components.material.padding
|
|
import tachiyomi.presentation.core.i18n.stringResource
|
|
|
|
class RestoreBackupScreen(
|
|
private val uri: String,
|
|
) : Screen() {
|
|
|
|
@Composable
|
|
override fun Content() {
|
|
val context = LocalContext.current
|
|
val navigator = LocalNavigator.currentOrThrow
|
|
val model = rememberScreenModel { RestoreBackupScreenModel(context, uri) }
|
|
val state by model.state.collectAsState()
|
|
|
|
Scaffold(
|
|
topBar = {
|
|
AppBar(
|
|
title = stringResource(MR.strings.pref_restore_backup),
|
|
navigateUp = navigator::pop,
|
|
scrollBehavior = it,
|
|
)
|
|
},
|
|
) { contentPadding ->
|
|
LazyColumnWithAction(
|
|
contentPadding = contentPadding,
|
|
actionLabel = stringResource(MR.strings.action_restore),
|
|
actionEnabled = state.canRestore && state.options.anyEnabled(),
|
|
onClickAction = {
|
|
model.startRestore()
|
|
navigator.pop()
|
|
},
|
|
) {
|
|
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
item {
|
|
WarningBanner(MR.strings.restore_miui_warning)
|
|
}
|
|
}
|
|
|
|
if (state.canRestore) {
|
|
item {
|
|
SectionCard {
|
|
RestoreOptions.options.forEach { option ->
|
|
LabeledCheckbox(
|
|
label = stringResource(option.label),
|
|
checked = option.getter(state.options),
|
|
onCheckedChange = {
|
|
model.toggle(option.setter, it)
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state.error != null) {
|
|
errorMessageItem(state.error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun LazyListScope.errorMessageItem(
|
|
error: Any?,
|
|
) {
|
|
item {
|
|
SectionCard {
|
|
Column(
|
|
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
|
|
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
|
) {
|
|
val msg = buildAnnotatedString {
|
|
when (error) {
|
|
is MissingRestoreComponents -> {
|
|
appendLine(stringResource(MR.strings.backup_restore_content_full))
|
|
if (error.sources.isNotEmpty()) {
|
|
appendLine()
|
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
appendLine(stringResource(MR.strings.backup_restore_missing_sources))
|
|
}
|
|
error.sources.joinTo(
|
|
this,
|
|
separator = "\n- ",
|
|
prefix = "- ",
|
|
)
|
|
}
|
|
if (error.trackers.isNotEmpty()) {
|
|
appendLine()
|
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
appendLine(stringResource(MR.strings.backup_restore_missing_trackers))
|
|
}
|
|
error.trackers.joinTo(
|
|
this,
|
|
separator = "\n- ",
|
|
prefix = "- ",
|
|
)
|
|
}
|
|
}
|
|
|
|
is InvalidRestore -> {
|
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
appendLine(stringResource(MR.strings.invalid_backup_file))
|
|
}
|
|
appendLine(error.uri.toString())
|
|
|
|
appendLine()
|
|
|
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
appendLine(stringResource(MR.strings.invalid_backup_file_error))
|
|
}
|
|
appendLine(error.message)
|
|
}
|
|
|
|
else -> {
|
|
appendLine(error.toString())
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectionContainer {
|
|
Text(text = msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private class RestoreBackupScreenModel(
|
|
private val context: Context,
|
|
private val uri: String,
|
|
) : StateScreenModel<RestoreBackupScreenModel.State>(State()) {
|
|
|
|
init {
|
|
validate(uri.toUri())
|
|
}
|
|
|
|
fun toggle(setter: (RestoreOptions, Boolean) -> RestoreOptions, enabled: Boolean) {
|
|
mutableState.update {
|
|
it.copy(
|
|
options = setter(it.options, enabled),
|
|
)
|
|
}
|
|
}
|
|
|
|
fun startRestore() {
|
|
BackupRestoreJob.start(
|
|
context = context,
|
|
uri = uri.toUri(),
|
|
options = state.value.options,
|
|
)
|
|
}
|
|
|
|
private fun validate(uri: Uri) {
|
|
val results = try {
|
|
BackupFileValidator(context).validate(uri)
|
|
} catch (e: Exception) {
|
|
setError(
|
|
error = InvalidRestore(uri, e.message.toString()),
|
|
canRestore = false,
|
|
)
|
|
return
|
|
}
|
|
|
|
if (results.missingSources.isNotEmpty() || results.missingTrackers.isNotEmpty()) {
|
|
setError(
|
|
error = MissingRestoreComponents(uri, results.missingSources, results.missingTrackers),
|
|
canRestore = true,
|
|
)
|
|
return
|
|
}
|
|
|
|
setError(error = null, canRestore = true)
|
|
}
|
|
|
|
private fun setError(error: Any?, canRestore: Boolean) {
|
|
mutableState.update {
|
|
it.copy(
|
|
error = error,
|
|
canRestore = canRestore,
|
|
)
|
|
}
|
|
}
|
|
|
|
@Immutable
|
|
data class State(
|
|
val error: Any? = null,
|
|
val canRestore: Boolean = false,
|
|
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,
|
|
)
|