mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
feat: Add restore functionality to composable data settings
This commit is contained in:
parent
981c92043f
commit
37e7e74c34
7 changed files with 185 additions and 31 deletions
|
@ -23,7 +23,9 @@ import dev.yokai.presentation.component.preference.PreferenceItem
|
|||
import dev.yokai.presentation.component.preference.widget.PreferenceGroupHeader
|
||||
import eu.kanade.tachiyomi.core.preference.collectAsState
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.compose.LocalAlertDialog
|
||||
import eu.kanade.tachiyomi.util.compose.LocalBackPress
|
||||
import eu.kanade.tachiyomi.util.compose.currentOrThrow
|
||||
import kotlinx.coroutines.delay
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
@ -38,10 +40,11 @@ fun SettingsScaffold(
|
|||
val preferences: PreferencesHelper by injectLazy()
|
||||
val useLargeAppBar by preferences.useLargeToolbar().collectAsState()
|
||||
val listState = rememberLazyListState()
|
||||
val onBackPress = LocalBackPress.current
|
||||
val onBackPress = LocalBackPress.currentOrThrow
|
||||
val alertDialog = LocalAlertDialog.currentOrThrow
|
||||
|
||||
YokaiScaffold(
|
||||
onNavigationIconClicked = onBackPress ?: {},
|
||||
onNavigationIconClicked = onBackPress,
|
||||
title = title,
|
||||
appBarType = appBarType ?: if (useLargeAppBar) AppBarType.LARGE else AppBarType.SMALL,
|
||||
actions = appBarActions,
|
||||
|
@ -50,6 +53,8 @@ fun SettingsScaffold(
|
|||
canScroll = { listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0 },
|
||||
),
|
||||
) { innerPadding ->
|
||||
alertDialog.content?.let { it() }
|
||||
|
||||
PreferenceScreen(
|
||||
items = itemsProvider(),
|
||||
listState = listState,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package dev.yokai.presentation.settings.screen
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
|
@ -32,12 +34,19 @@ import dev.yokai.presentation.component.preference.storageLocationText
|
|||
import dev.yokai.presentation.component.preference.widget.BasePreferenceWidget
|
||||
import dev.yokai.presentation.component.preference.widget.PrefsHorizontalPadding
|
||||
import dev.yokai.presentation.settings.ComposableSettings
|
||||
import dev.yokai.presentation.settings.screen.data.RestoreBackup
|
||||
import dev.yokai.presentation.settings.screen.data.StorageInfo
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob
|
||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.util.compose.LocalAlertDialog
|
||||
import eu.kanade.tachiyomi.util.compose.currentOrThrow
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.e
|
||||
import eu.kanade.tachiyomi.util.system.launchNonCancellable
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
|
@ -45,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.tryTakePersistableUriPermission
|
|||
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
@ -104,7 +114,35 @@ object SettingsDataScreen : ComposableSettings {
|
|||
|
||||
@Composable
|
||||
private fun getBackupAndRestoreGroup(preferences: PreferencesHelper): Preference.PreferenceGroup {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val alertDialog = LocalAlertDialog.currentOrThrow
|
||||
val extensionManager = remember { Injekt.get<ExtensionManager>() }
|
||||
|
||||
val chooseBackup = rememberLauncherForActivityResult(
|
||||
object : ActivityResultContracts.GetContent() {
|
||||
override fun createIntent(context: Context, input: String): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
return Intent.createChooser(intent, context.getString(R.string.select_backup_file))
|
||||
}
|
||||
},
|
||||
) {
|
||||
if (it == null) {
|
||||
context.toast(R.string.invalid_location_generic)
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
|
||||
val results = try {
|
||||
Pair(BackupFileValidator().validate(context, it), null)
|
||||
} catch (e: Exception) {
|
||||
Pair(null, e)
|
||||
}
|
||||
|
||||
alertDialog.content = {
|
||||
RestoreBackup(context = context, uri = it, pair = results)
|
||||
}
|
||||
}
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.backup_and_restore),
|
||||
|
@ -123,7 +161,17 @@ object SettingsDataScreen : ComposableSettings {
|
|||
SegmentedButton(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
checked = false,
|
||||
onCheckedChange = {},
|
||||
onCheckedChange = {
|
||||
if (!BackupRestoreJob.isRunning(context)) {
|
||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
||||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
context.toast("Not yet available")
|
||||
} else {
|
||||
context.toast(R.string.backup_in_progress)
|
||||
}
|
||||
},
|
||||
shape = SegmentedButtonDefaults.itemShape(0, 2),
|
||||
) {
|
||||
Text(stringResource(R.string.create_backup))
|
||||
|
@ -131,7 +179,18 @@ object SettingsDataScreen : ComposableSettings {
|
|||
SegmentedButton(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
checked = false,
|
||||
onCheckedChange = {},
|
||||
onCheckedChange = {
|
||||
if (!BackupRestoreJob.isRunning(context)) {
|
||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
||||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
scope.launch { extensionManager.getExtensionUpdates(true) }
|
||||
chooseBackup.launch("*/*")
|
||||
} else {
|
||||
context.toast(R.string.restore_in_progress)
|
||||
}
|
||||
},
|
||||
shape = SegmentedButtonDefaults.itemShape(1, 2),
|
||||
) {
|
||||
Text(stringResource(R.string.restore_backup))
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package dev.yokai.presentation.settings.screen.data
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator.Results
|
||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
|
||||
@Composable
|
||||
fun RestoreBackup(context: Context, uri: Uri, pair: Pair<Results?, Exception?>) {
|
||||
val (results, e) = pair
|
||||
if (results != null) {
|
||||
var message = stringResource(R.string.restore_content_full)
|
||||
if (results.missingSources.isNotEmpty()) {
|
||||
message += "\n\n${stringResource(R.string.restore_missing_sources)}\n${
|
||||
results.missingSources.joinToString(
|
||||
"\n",
|
||||
) { "- $it" }
|
||||
}"
|
||||
}
|
||||
if (results.missingTrackers.isNotEmpty()) {
|
||||
message += "\n\n${stringResource(R.string.restore_missing_trackers)}\n${
|
||||
results.missingTrackers.joinToString(
|
||||
"\n",
|
||||
) { "- $it" }
|
||||
}"
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
context.toast(R.string.restoring_backup)
|
||||
BackupRestoreJob.start(context, uri)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.restore))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {}) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = { Text(text = stringResource(R.string.restore_backup)) },
|
||||
text = { Text(text = message) },
|
||||
)
|
||||
} else {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {}) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = { Text(text = stringResource(R.string.invalid_backup_file)) },
|
||||
text = { e?.message?.let { Text(text = it) } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateBackup(context: Context) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {},
|
||||
)
|
||||
}
|
|
@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.source.Source
|
|||
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
|
||||
import eu.kanade.tachiyomi.util.system.e
|
||||
import eu.kanade.tachiyomi.util.system.launchNow
|
||||
import eu.kanade.tachiyomi.util.system.withIOContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -28,6 +29,7 @@ import kotlinx.parcelize.Parcelize
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* The manager of extensions installed as another apk which extend the available sources. It handles
|
||||
|
@ -127,6 +129,25 @@ class ExtensionManager(
|
|||
return ExtensionLoader.isExtensionInstalledByApp(context, extension.pkgName)
|
||||
}
|
||||
|
||||
suspend fun getExtensionUpdates(force: Boolean) {
|
||||
if ((force && availableExtensionsFlow.value.isEmpty()) ||
|
||||
Date().time >= preferences.lastExtCheck().get() + TimeUnit.HOURS.toMillis(6)
|
||||
) {
|
||||
withIOContext {
|
||||
try {
|
||||
findAvailableExtensions()
|
||||
val pendingUpdates = ExtensionApi().checkForUpdates(
|
||||
context,
|
||||
availableExtensionsFlow.value.takeIf { it.isNotEmpty() },
|
||||
)
|
||||
preferences.extensionUpdatesCount().set(pendingUpdates.size)
|
||||
preferences.lastExtCheck().set(Date().time)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the available extensions in the [api] and updates [availableExtensionsFlow].
|
||||
*/
|
||||
|
|
|
@ -89,7 +89,6 @@ import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
|||
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||
import eu.kanade.tachiyomi.databinding.MainActivityBinding
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionApi
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
|
||||
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
|
||||
|
@ -144,13 +143,10 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToLong
|
||||
|
@ -706,7 +702,9 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
|
|||
|
||||
splashScreen?.configure()
|
||||
|
||||
getExtensionUpdates(true)
|
||||
lifecycleScope.launchIO {
|
||||
extensionManager.getExtensionUpdates(true)
|
||||
}
|
||||
|
||||
preferences.extensionUpdatesCount()
|
||||
.changesIn(lifecycleScope) {
|
||||
|
@ -941,7 +939,9 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
checkForAppUpdates()
|
||||
getExtensionUpdates(false)
|
||||
lifecycleScope.launchIO {
|
||||
extensionManager.getExtensionUpdates(false)
|
||||
}
|
||||
setExtensionsBadge()
|
||||
DownloadJob.callListeners(downloadManager = downloadManager)
|
||||
showDLQueueTutorial()
|
||||
|
@ -1016,25 +1016,6 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
fun getExtensionUpdates(force: Boolean) {
|
||||
if ((force && extensionManager.availableExtensionsFlow.value.isEmpty()) ||
|
||||
Date().time >= preferences.lastExtCheck().get() + TimeUnit.HOURS.toMillis(6)
|
||||
) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
extensionManager.findAvailableExtensions()
|
||||
val pendingUpdates = ExtensionApi().checkForUpdates(
|
||||
this@MainActivity,
|
||||
extensionManager.availableExtensionsFlow.value.takeIf { it.isNotEmpty() },
|
||||
)
|
||||
preferences.extensionUpdatesCount().set(pendingUpdates.size)
|
||||
preferences.lastExtCheck().set(Date().time)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showNotificationPermissionPrompt(showAnyway: Boolean = false) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||
val notificationPermission = Manifest.permission.POST_NOTIFICATIONS
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import dev.yokai.domain.ComposableAlertDialog
|
||||
import dev.yokai.presentation.settings.ComposableSettings
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
|
||||
import eu.kanade.tachiyomi.util.compose.LocalAlertDialog
|
||||
import eu.kanade.tachiyomi.util.compose.LocalBackPress
|
||||
|
||||
abstract class SettingsComposeController: BaseComposeController(), SettingsControllerInterface {
|
||||
override fun getTitle(): String? = __getTitle()
|
||||
|
@ -14,6 +18,11 @@ abstract class SettingsComposeController: BaseComposeController(), SettingsContr
|
|||
|
||||
@Composable
|
||||
override fun ScreenContent() {
|
||||
getComposableSettings().Content()
|
||||
CompositionLocalProvider(
|
||||
LocalAlertDialog provides ComposableAlertDialog(null),
|
||||
LocalBackPress provides router::handleBack,
|
||||
) {
|
||||
getComposableSettings().Content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup
|
|||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
|
||||
import eu.kanade.tachiyomi.ui.setting.bindTo
|
||||
|
@ -55,6 +56,7 @@ class SettingsDataLegacyController : SettingsLegacyController() {
|
|||
private var backupFlags: BackupOptions = BackupOptions()
|
||||
internal val storagePreferences: StoragePreferences by injectLazy()
|
||||
internal val storageManager: StorageManager by injectLazy()
|
||||
internal val extensionManager: ExtensionManager by injectLazy()
|
||||
|
||||
private val coverCache: CoverCache by injectLazy()
|
||||
private val chapterCache: ChapterCache by injectLazy()
|
||||
|
@ -113,7 +115,9 @@ class SettingsDataLegacyController : SettingsLegacyController() {
|
|||
}
|
||||
|
||||
if (!BackupRestoreJob.isRunning(context)) {
|
||||
(activity as? MainActivity)?.getExtensionUpdates(true)
|
||||
(activity as? AppCompatActivity)?.lifecycleScope?.launchIO {
|
||||
extensionManager.getExtensionUpdates(true)
|
||||
}
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
storageManager.getBackupsDirectory()?.let {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue