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 androidx.compose.ui.res.stringResource
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import dev.yokai.domain.storage.StorageManager
import dev.yokai.domain.storage.StoragePreferences import dev.yokai.domain.storage.StoragePreferences
import dev.yokai.presentation.component.preference.Preference import dev.yokai.presentation.component.preference.Preference
import dev.yokai.presentation.component.preference.storageLocationText import dev.yokai.presentation.component.preference.storageLocationText
import dev.yokai.presentation.component.preference.widget.BasePreferenceWidget import dev.yokai.presentation.component.preference.widget.BasePreferenceWidget
import dev.yokai.presentation.component.preference.widget.PrefsHorizontalPadding import dev.yokai.presentation.component.preference.widget.PrefsHorizontalPadding
import dev.yokai.presentation.settings.ComposableSettings 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.RestoreBackup
import dev.yokai.presentation.settings.screen.data.StorageInfo import dev.yokai.presentation.settings.screen.data.StorageInfo
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -118,6 +120,7 @@ object SettingsDataScreen : ComposableSettings {
val context = LocalContext.current val context = LocalContext.current
val alertDialog = LocalAlertDialog.currentOrThrow val alertDialog = LocalAlertDialog.currentOrThrow
val extensionManager = remember { Injekt.get<ExtensionManager>() } val extensionManager = remember { Injekt.get<ExtensionManager>() }
val storageManager = remember { Injekt.get<StorageManager>() }
val chooseBackup = rememberLauncherForActivityResult( val chooseBackup = rememberLauncherForActivityResult(
object : ActivityResultContracts.GetContent() { object : ActivityResultContracts.GetContent() {
@ -129,7 +132,7 @@ object SettingsDataScreen : ComposableSettings {
}, },
) { ) {
if (it == null) { if (it == null) {
context.toast(R.string.invalid_location_generic) context.toast(R.string.backup_restore_invalid_uri)
return@rememberLauncherForActivityResult return@rememberLauncherForActivityResult
} }
@ -174,7 +177,19 @@ object SettingsDataScreen : ComposableSettings {
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG) 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 { } else {
context.toast(R.string.backup_in_progress) 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.content.Context
import android.net.Uri 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.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable 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 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.R
import eu.kanade.tachiyomi.data.backup.BackupFileValidator.Results 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.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList
@Composable @Composable
fun RestoreBackup( fun RestoreBackup(
@ -73,12 +89,50 @@ fun RestoreBackup(
} }
@Composable @Composable
private fun CreateBackup( fun CreateBackup(
context: Context, context: Context,
uri: Uri,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
var options by remember { mutableStateOf(BackupOptions()) }
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, 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 package eu.kanade.tachiyomi.data.backup.create
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.collections.immutable.persistentListOf
data class BackupOptions( data class BackupOptions(
val libraryEntries: Boolean = true, val libraryEntries: Boolean = true,
val category: Boolean = true, val categories: Boolean = true,
val chapter: Boolean = true, val chapters: Boolean = true,
val tracking: Boolean = true, val tracking: Boolean = true,
val history: Boolean = true, val history: Boolean = true,
val appPrefs: Boolean = true, val appPrefs: Boolean = true,
@ -16,8 +18,8 @@ data class BackupOptions(
) { ) {
fun asBooleanArray() = booleanArrayOf( fun asBooleanArray() = booleanArrayOf(
libraryEntries, libraryEntries,
category, categories,
chapter, chapters,
tracking, tracking,
history, history,
appPrefs, appPrefs,
@ -28,7 +30,7 @@ data class BackupOptions(
) )
companion object { companion object {
fun getOptions() = arrayOf( fun getOptions() = persistentListOf(
R.string.library_entries, R.string.library_entries,
R.string.categories, R.string.categories,
R.string.chapters, R.string.chapters,
@ -41,6 +43,65 @@ data class BackupOptions(
R.string.backup_private_pref, 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( fun fromBooleanArray(array: BooleanArray): BackupOptions = BackupOptions(
array[0], array[0],
array[1], array[1],
@ -54,4 +115,11 @@ data class BackupOptions(
array[9], 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) val mangaObject = BackupManga.copyFrom(manga, if (options.customInfo) customMangaManager else null)
// Check if user wants chapter information in backup // Check if user wants chapter information in backup
if (options.chapter) { if (options.chapters) {
// Backup all the chapters // Backup all the chapters
val chapters = db.getChapters(manga).executeAsBlocking() val chapters = db.getChapters(manga).executeAsBlocking()
if (chapters.isNotEmpty()) { if (chapters.isNotEmpty()) {
@ -42,7 +42,7 @@ class MangaBackupCreator(
} }
// Check if user wants category information in backup // Check if user wants category information in backup
if (options.category) { if (options.categories) {
// Backup categories for this manga // Backup categories for this manga
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking() val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
if (categoriesForManga.isNotEmpty()) { if (categoriesForManga.isNotEmpty()) {

View file

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