feat: Finally add functionality for backup on composable data settings

This commit is contained in:
Ahmad Ansori Palembani 2024-06-10 20:54:40 +07:00
parent 27f42de963
commit 82ff1a49d5
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
7 changed files with 182 additions and 25 deletions

View file

@ -28,12 +28,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import co.touchlab.kermit.Logger
import com.hippo.unifile.UniFile
import dev.yokai.domain.storage.StorageManager
import dev.yokai.domain.storage.StoragePreferences
import dev.yokai.presentation.component.preference.Preference
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.CreateBackup
import dev.yokai.presentation.settings.screen.data.RestoreBackup
import dev.yokai.presentation.settings.screen.data.StorageInfo
import eu.kanade.tachiyomi.R
@ -118,6 +120,7 @@ object SettingsDataScreen : ComposableSettings {
val context = LocalContext.current
val alertDialog = LocalAlertDialog.currentOrThrow
val extensionManager = remember { Injekt.get<ExtensionManager>() }
val storageManager = remember { Injekt.get<StorageManager>() }
val chooseBackup = rememberLauncherForActivityResult(
object : ActivityResultContracts.GetContent() {
@ -129,7 +132,7 @@ object SettingsDataScreen : ComposableSettings {
},
) {
if (it == null) {
context.toast(R.string.invalid_location_generic)
context.toast(R.string.backup_restore_invalid_uri)
return@rememberLauncherForActivityResult
}
@ -174,7 +177,19 @@ object SettingsDataScreen : ComposableSettings {
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
}
context.toast("Not yet available")
val dir = storageManager.getBackupsDirectory()
if (dir == null) {
context.toast(R.string.invalid_location_generic)
return@SegmentedButton
}
alertDialog.content = {
CreateBackup(
context = context,
uri = dir.uri,
onDismissRequest = { alertDialog.content = null },
)
}
} else {
context.toast(R.string.backup_in_progress)
}

View file

@ -2,15 +2,31 @@ package dev.yokai.presentation.settings.screen.data
import android.content.Context
import android.net.Uri
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.hippo.unifile.UniFile
import dev.yokai.presentation.component.LabeledCheckbox
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupFileValidator.Results
import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob
import eu.kanade.tachiyomi.data.backup.create.BackupOptions
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList
@Composable
fun RestoreBackup(
@ -73,12 +89,50 @@ fun RestoreBackup(
}
@Composable
private fun CreateBackup(
fun CreateBackup(
context: Context,
uri: Uri,
onDismissRequest: () -> Unit,
) {
var options by remember { mutableStateOf(BackupOptions()) }
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {},
confirmButton = {
val actualUri =
UniFile.fromUri(context, uri)?.createFile(Backup.getBackupFilename())?.uri ?: return@AlertDialog
context.toast(R.string.creating_backup)
BackupCreatorJob.startNow(context, actualUri, options)
onDismissRequest()
},
text = {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
) {
options(BackupOptions.getEntries(), options) { option, enabled ->
options = option.setter(options, enabled)
}
}
},
)
}
private fun LazyListScope.options(
options: ImmutableList<BackupOptions.Entry>,
state: BackupOptions,
onChange: (BackupOptions.Entry, Boolean) -> Unit,
) {
items(options.size) {
val option = options[it]
LabeledCheckbox(
label = stringResource(option.label),
checked = option.getter(state),
onCheckedChange = {
onChange(option, it)
},
enabled = option.enabled(state),
)
}
}

View file

@ -1,11 +1,13 @@
package eu.kanade.tachiyomi.data.backup.create
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import kotlinx.collections.immutable.persistentListOf
data class BackupOptions(
val libraryEntries: Boolean = true,
val category: Boolean = true,
val chapter: Boolean = true,
val categories: Boolean = true,
val chapters: Boolean = true,
val tracking: Boolean = true,
val history: Boolean = true,
val appPrefs: Boolean = true,
@ -16,8 +18,8 @@ data class BackupOptions(
) {
fun asBooleanArray() = booleanArrayOf(
libraryEntries,
category,
chapter,
categories,
chapters,
tracking,
history,
appPrefs,
@ -28,7 +30,7 @@ data class BackupOptions(
)
companion object {
fun getOptions() = arrayOf(
fun getOptions() = persistentListOf(
R.string.library_entries,
R.string.categories,
R.string.chapters,
@ -41,6 +43,65 @@ data class BackupOptions(
R.string.backup_private_pref,
)
fun getEntries() = persistentListOf(
Entry(
label = R.string.library_entries,
getter = BackupOptions::libraryEntries,
setter = { options, enabled -> options.copy(libraryEntries = enabled) },
),
Entry(
label = R.string.categories,
getter = BackupOptions::categories,
setter = { options, enabled -> options.copy(categories = enabled) },
enabled = { it.libraryEntries },
),
Entry(
label = R.string.chapters,
getter = BackupOptions::chapters,
setter = { options, enabled -> options.copy(chapters = enabled) },
enabled = { it.libraryEntries },
),
Entry(
label = R.string.tracking,
getter = BackupOptions::tracking,
setter = { options, enabled -> options.copy(tracking = enabled) },
enabled = { it.libraryEntries },
),
Entry(
label = R.string.history,
getter = BackupOptions::history,
setter = { options, enabled -> options.copy(history = enabled) },
enabled = { it.libraryEntries },
),
Entry(
label = R.string.custom_manga_info,
getter = BackupOptions::customInfo,
setter = { options, enabled -> options.copy(customInfo = enabled) },
enabled = { it.libraryEntries },
),
Entry(
label = R.string.all_read_manga,
getter = BackupOptions::readManga,
setter = { options, enabled -> options.copy(readManga = enabled) },
enabled = { it.libraryEntries },
),
Entry(
label = R.string.app_settings,
getter = BackupOptions::appPrefs,
setter = { options, enabled -> options.copy(appPrefs = enabled) },
),
Entry(
label = R.string.source_settings,
getter = BackupOptions::sourcePrefs,
setter = { options, enabled -> options.copy(sourcePrefs = enabled) },
),
Entry(
label = R.string.backup_private_pref,
getter = BackupOptions::includePrivate,
setter = { options, enabled -> options.copy(includePrivate = enabled) },
),
)
fun fromBooleanArray(array: BooleanArray): BackupOptions = BackupOptions(
array[0],
array[1],
@ -54,4 +115,11 @@ data class BackupOptions(
array[9],
)
}
data class Entry(
@StringRes val label: Int,
val getter: (BackupOptions) -> Boolean,
val setter: (BackupOptions, Boolean) -> BackupOptions,
val enabled: (BackupOptions) -> Boolean = { true },
)
}

View file

@ -33,7 +33,7 @@ class MangaBackupCreator(
val mangaObject = BackupManga.copyFrom(manga, if (options.customInfo) customMangaManager else null)
// Check if user wants chapter information in backup
if (options.chapter) {
if (options.chapters) {
// Backup all the chapters
val chapters = db.getChapters(manga).executeAsBlocking()
if (chapters.isNotEmpty()) {
@ -42,7 +42,7 @@ class MangaBackupCreator(
}
// Check if user wants category information in backup
if (options.category) {
if (options.categories) {
// Backup categories for this manga
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
if (categoriesForManga.isNotEmpty()) {

View file

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.main
import android.Manifest
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
@ -10,7 +9,6 @@ import android.app.assist.AssistContent
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.Rect
import android.net.Uri
@ -39,7 +37,6 @@ import androidx.appcompat.view.menu.MenuItemImpl
import androidx.appcompat.widget.ActionMenuView
import androidx.appcompat.widget.Toolbar
import androidx.core.animation.doOnEnd
import androidx.core.app.ActivityCompat
import androidx.core.content.getSystemService
import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri
@ -111,6 +108,7 @@ import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import eu.kanade.tachiyomi.util.showNotificationPermissionPrompt
import eu.kanade.tachiyomi.util.system.contextCompatDrawable
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.e
@ -1016,17 +1014,12 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
}
}
fun showNotificationPermissionPrompt(showAnyway: Boolean = false) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
val notificationPermission = Manifest.permission.POST_NOTIFICATIONS
val hasPermission = ActivityCompat.checkSelfPermission(this, notificationPermission)
if (hasPermission != PackageManager.PERMISSION_GRANTED &&
(!preferences.hasShownNotifPermission().get() || showAnyway)
) {
preferences.hasShownNotifPermission().set(true)
requestNotificationPermissionLauncher.launch((notificationPermission))
}
}
fun showNotificationPermissionPrompt(showAnyway: Boolean = false) =
this.showNotificationPermissionPrompt(
requestNotificationPermissionLauncher,
showAnyway,
preferences,
)
fun showColourProfilePicker() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

View file

@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.injectLazy
@Deprecated("Migrating to compose")
class SettingsDataLegacyController : SettingsLegacyController() {
/**

View file

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.util
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
fun Context.showNotificationPermissionPrompt(
requestNotificationPermissionLauncher: ActivityResultLauncher<String>,
showAnyway: Boolean = false,
preferences: PreferencesHelper = Injekt.get(),
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
val notificationPermission = Manifest.permission.POST_NOTIFICATIONS
val hasPermission = this.checkSelfPermission(notificationPermission)
if (hasPermission != PackageManager.PERMISSION_GRANTED &&
(!preferences.hasShownNotifPermission().get() || showAnyway)
) {
preferences.hasShownNotifPermission().set(true)
requestNotificationPermissionLauncher.launch((notificationPermission))
}
}