2022-10-15 17:38:01 +02:00
|
|
|
package eu.kanade.presentation.more.settings.screen
|
|
|
|
|
2023-10-26 19:43:42 +02:00
|
|
|
import android.content.ActivityNotFoundException
|
2023-12-30 18:09:55 +01:00
|
|
|
import android.content.Context
|
2022-10-15 17:38:01 +02:00
|
|
|
import android.content.Intent
|
|
|
|
import android.net.Uri
|
2023-12-09 22:50:02 +01:00
|
|
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
2022-10-15 17:38:01 +02:00
|
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
2023-12-22 04:47:23 +01:00
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
2023-10-29 21:57:38 +01:00
|
|
|
import androidx.compose.foundation.layout.padding
|
2023-12-22 04:47:23 +01:00
|
|
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
|
|
|
import androidx.compose.material3.SegmentedButton
|
|
|
|
import androidx.compose.material3.SegmentedButtonDefaults
|
2022-10-15 17:38:01 +02:00
|
|
|
import androidx.compose.material3.Text
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
import androidx.compose.runtime.ReadOnlyComposable
|
|
|
|
import androidx.compose.runtime.getValue
|
2023-05-27 15:22:31 +02:00
|
|
|
import androidx.compose.runtime.mutableIntStateOf
|
2022-10-15 17:38:01 +02:00
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
|
|
import androidx.compose.runtime.setValue
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
2023-10-26 19:43:42 +02:00
|
|
|
import androidx.core.net.toUri
|
2023-11-05 23:13:51 +01:00
|
|
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
|
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
2023-10-26 19:43:42 +02:00
|
|
|
import com.hippo.unifile.UniFile
|
2022-10-15 17:38:01 +02:00
|
|
|
import eu.kanade.presentation.more.settings.Preference
|
2023-11-05 23:13:51 +01:00
|
|
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
2023-12-22 04:47:23 +01:00
|
|
|
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
2023-12-26 00:11:22 +01:00
|
|
|
import eu.kanade.presentation.more.settings.screen.data.StorageInfo
|
2023-10-29 21:57:38 +01:00
|
|
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
|
|
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
2023-10-29 17:03:46 +01:00
|
|
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
2023-12-16 17:43:18 +01:00
|
|
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
2023-12-30 18:09:55 +01:00
|
|
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
2023-10-27 21:06:56 +02:00
|
|
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
2023-12-30 18:09:55 +01:00
|
|
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
2022-10-15 17:38:01 +02:00
|
|
|
import eu.kanade.tachiyomi.util.system.toast
|
2023-12-26 00:11:22 +01:00
|
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
|
|
import kotlinx.collections.immutable.persistentMapOf
|
2023-10-27 21:06:56 +02:00
|
|
|
import logcat.LogPriority
|
2023-11-19 01:41:33 +01:00
|
|
|
import tachiyomi.core.i18n.stringResource
|
2023-10-27 21:06:56 +02:00
|
|
|
import tachiyomi.core.util.lang.launchNonCancellable
|
|
|
|
import tachiyomi.core.util.lang.withUIContext
|
|
|
|
import tachiyomi.core.util.system.logcat
|
2023-03-05 18:38:31 +01:00
|
|
|
import tachiyomi.domain.backup.service.BackupPreferences
|
2023-10-27 21:06:56 +02:00
|
|
|
import tachiyomi.domain.library.service.LibraryPreferences
|
2023-10-26 19:43:42 +02:00
|
|
|
import tachiyomi.domain.storage.service.StoragePreferences
|
2023-11-18 19:54:56 +01:00
|
|
|
import tachiyomi.i18n.MR
|
2023-11-19 01:41:33 +01:00
|
|
|
import tachiyomi.presentation.core.i18n.stringResource
|
2023-07-20 03:57:22 +02:00
|
|
|
import tachiyomi.presentation.core.util.collectAsState
|
2022-10-15 17:38:01 +02:00
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
|
import uy.kohesive.injekt.api.get
|
|
|
|
|
2023-10-27 21:06:56 +02:00
|
|
|
object SettingsDataScreen : SearchableSettings {
|
2022-10-15 17:58:24 +02:00
|
|
|
|
2023-12-24 22:35:18 +01:00
|
|
|
val restorePreferenceKeyString = MR.strings.label_backup
|
|
|
|
|
2022-10-15 17:38:01 +02:00
|
|
|
@ReadOnlyComposable
|
|
|
|
@Composable
|
2023-11-18 19:54:56 +01:00
|
|
|
override fun getTitleRes() = MR.strings.label_data_storage
|
2022-10-15 17:38:01 +02:00
|
|
|
|
|
|
|
@Composable
|
|
|
|
override fun getPreferences(): List<Preference> {
|
|
|
|
val backupPreferences = Injekt.get<BackupPreferences>()
|
2023-10-26 19:43:42 +02:00
|
|
|
val storagePreferences = Injekt.get<StoragePreferences>()
|
2022-10-15 17:38:01 +02:00
|
|
|
|
2023-12-26 00:11:22 +01:00
|
|
|
return persistentListOf(
|
2023-10-26 19:43:42 +02:00
|
|
|
getStorageLocationPref(storagePreferences = storagePreferences),
|
|
|
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)),
|
|
|
|
|
2023-10-27 21:06:56 +02:00
|
|
|
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
|
|
|
|
getDataGroup(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-10-26 19:43:42 +02:00
|
|
|
@Composable
|
2023-12-09 22:50:02 +01:00
|
|
|
fun storageLocationPicker(
|
|
|
|
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
|
|
|
): ManagedActivityResultLauncher<Uri?, Uri?> {
|
2023-10-26 19:43:42 +02:00
|
|
|
val context = LocalContext.current
|
2023-12-09 22:50:02 +01:00
|
|
|
|
|
|
|
return rememberLauncherForActivityResult(
|
2023-10-26 19:43:42 +02:00
|
|
|
contract = ActivityResultContracts.OpenDocumentTree(),
|
|
|
|
) { uri ->
|
|
|
|
if (uri != null) {
|
|
|
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
|
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
|
|
|
|
|
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
|
|
|
|
2023-11-28 04:21:40 +01:00
|
|
|
UniFile.fromUri(context, uri)?.let {
|
|
|
|
storageDirPref.set(it.uri.toString())
|
|
|
|
}
|
2023-10-26 19:43:42 +02:00
|
|
|
}
|
|
|
|
}
|
2023-12-09 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
fun storageLocationText(
|
|
|
|
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
|
|
|
): String {
|
|
|
|
val context = LocalContext.current
|
|
|
|
val storageDir by storageDirPref.collectAsState()
|
|
|
|
|
2023-12-09 23:49:35 +01:00
|
|
|
if (storageDir == storageDirPref.defaultValue()) {
|
|
|
|
return stringResource(MR.strings.no_location_set)
|
|
|
|
}
|
|
|
|
|
2023-12-09 22:50:02 +01:00
|
|
|
return remember(storageDir) {
|
|
|
|
val file = UniFile.fromUri(context, storageDir.toUri())
|
|
|
|
file?.filePath ?: file?.uri?.toString()
|
|
|
|
} ?: stringResource(MR.strings.invalid_location, storageDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
private fun getStorageLocationPref(
|
|
|
|
storagePreferences: StoragePreferences,
|
|
|
|
): Preference.PreferenceItem.TextPreference {
|
|
|
|
val context = LocalContext.current
|
|
|
|
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
|
2023-10-26 19:43:42 +02:00
|
|
|
|
|
|
|
return Preference.PreferenceItem.TextPreference(
|
|
|
|
title = stringResource(MR.strings.pref_storage_location),
|
2023-12-09 22:50:02 +01:00
|
|
|
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
|
2023-10-26 19:43:42 +02:00
|
|
|
onClick = {
|
|
|
|
try {
|
|
|
|
pickStorageLocation.launch(null)
|
|
|
|
} catch (e: ActivityNotFoundException) {
|
|
|
|
context.toast(MR.strings.file_picker_error)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-10-27 21:06:56 +02:00
|
|
|
@Composable
|
|
|
|
private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
|
|
|
val context = LocalContext.current
|
2023-12-22 04:47:23 +01:00
|
|
|
val navigator = LocalNavigator.currentOrThrow
|
|
|
|
|
2023-10-29 17:03:46 +01:00
|
|
|
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
|
2023-10-27 21:06:56 +02:00
|
|
|
|
2023-12-30 18:09:55 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-12-30 22:02:36 +01:00
|
|
|
navigator.push(RestoreBackupScreen(it.toString()))
|
2023-12-30 18:09:55 +01:00
|
|
|
}
|
|
|
|
|
2023-10-27 21:06:56 +02:00
|
|
|
return Preference.PreferenceGroup(
|
2023-11-19 01:41:33 +01:00
|
|
|
title = stringResource(MR.strings.label_backup),
|
2023-12-26 00:11:22 +01:00
|
|
|
preferenceItems = persistentListOf(
|
2023-10-27 21:06:56 +02:00
|
|
|
// Manual actions
|
2023-12-22 04:47:23 +01:00
|
|
|
Preference.PreferenceItem.CustomPreference(
|
2023-12-24 22:35:18 +01:00
|
|
|
title = stringResource(restorePreferenceKeyString),
|
2023-12-22 04:47:23 +01:00
|
|
|
) {
|
|
|
|
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,
|
2023-12-30 18:09:55 +01:00
|
|
|
onCheckedChange = {
|
|
|
|
if (!BackupRestoreJob.isRunning(context)) {
|
|
|
|
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
|
|
context.toast(MR.strings.restore_miui_warning)
|
|
|
|
}
|
|
|
|
|
|
|
|
// no need to catch because it's wrapped with a chooser
|
|
|
|
chooseBackup.launch("*/*")
|
|
|
|
} else {
|
|
|
|
context.toast(MR.strings.restore_in_progress)
|
|
|
|
}
|
|
|
|
},
|
2023-12-22 04:47:23 +01:00
|
|
|
shape = SegmentedButtonDefaults.itemShape(1, 2),
|
|
|
|
) {
|
|
|
|
Text(stringResource(MR.strings.pref_restore_backup))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
2023-10-27 21:06:56 +02:00
|
|
|
|
|
|
|
// Automatic backups
|
|
|
|
Preference.PreferenceItem.ListPreference(
|
2023-11-19 21:17:19 +01:00
|
|
|
pref = backupPreferences.backupInterval(),
|
2023-11-19 01:41:33 +01:00
|
|
|
title = stringResource(MR.strings.pref_backup_interval),
|
2023-12-26 00:11:22 +01:00
|
|
|
entries = persistentMapOf(
|
2023-11-19 01:41:33 +01:00
|
|
|
0 to stringResource(MR.strings.off),
|
|
|
|
6 to stringResource(MR.strings.update_6hour),
|
|
|
|
12 to stringResource(MR.strings.update_12hour),
|
|
|
|
24 to stringResource(MR.strings.update_24hour),
|
|
|
|
48 to stringResource(MR.strings.update_48hour),
|
|
|
|
168 to stringResource(MR.strings.update_weekly),
|
2023-10-27 21:06:56 +02:00
|
|
|
),
|
|
|
|
onValueChanged = {
|
|
|
|
BackupCreateJob.setupTask(context, it)
|
|
|
|
true
|
|
|
|
},
|
|
|
|
),
|
2023-10-29 17:03:46 +01:00
|
|
|
Preference.PreferenceItem.InfoPreference(
|
2023-11-19 01:41:33 +01:00
|
|
|
stringResource(MR.strings.backup_info) + "\n\n" +
|
|
|
|
stringResource(MR.strings.last_auto_backup_info, relativeTimeSpanString(lastAutoBackup)),
|
2023-10-29 17:03:46 +01:00
|
|
|
),
|
2023-10-27 21:06:56 +02:00
|
|
|
),
|
2022-10-15 17:38:01 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Composable
|
2023-10-27 21:06:56 +02:00
|
|
|
private fun getDataGroup(): Preference.PreferenceGroup {
|
2022-10-15 17:38:01 +02:00
|
|
|
val context = LocalContext.current
|
2023-12-26 00:11:22 +01:00
|
|
|
val scope = rememberCoroutineScope()
|
2023-10-27 21:06:56 +02:00
|
|
|
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
2022-10-15 17:38:01 +02:00
|
|
|
|
2023-10-27 21:06:56 +02:00
|
|
|
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
2023-10-29 21:57:38 +01:00
|
|
|
var cacheReadableSizeSema by remember { mutableIntStateOf(0) }
|
|
|
|
val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
|
2022-10-15 17:38:01 +02:00
|
|
|
|
|
|
|
return Preference.PreferenceGroup(
|
2023-12-26 19:13:33 +01:00
|
|
|
title = stringResource(MR.strings.pref_storage_usage),
|
2023-12-26 00:11:22 +01:00
|
|
|
preferenceItems = persistentListOf(
|
|
|
|
Preference.PreferenceItem.CustomPreference(
|
|
|
|
title = stringResource(MR.strings.pref_storage_usage),
|
|
|
|
) {
|
|
|
|
BasePreferenceWidget(
|
|
|
|
subcomponent = {
|
|
|
|
StorageInfo(
|
|
|
|
modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
|
|
|
|
)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
2023-10-29 21:57:38 +01:00
|
|
|
|
2022-10-15 17:38:01 +02:00
|
|
|
Preference.PreferenceItem.TextPreference(
|
2023-11-19 01:41:33 +01:00
|
|
|
title = stringResource(MR.strings.pref_clear_chapter_cache),
|
|
|
|
subtitle = stringResource(MR.strings.used_cache, cacheReadableSize),
|
2022-10-20 23:48:13 +02:00
|
|
|
onClick = {
|
2023-10-27 21:06:56 +02:00
|
|
|
scope.launchNonCancellable {
|
|
|
|
try {
|
|
|
|
val deletedFiles = chapterCache.clear()
|
|
|
|
withUIContext {
|
2023-11-19 01:41:33 +01:00
|
|
|
context.toast(context.stringResource(MR.strings.cache_deleted, deletedFiles))
|
2023-10-29 21:57:38 +01:00
|
|
|
cacheReadableSizeSema++
|
2023-10-27 21:06:56 +02:00
|
|
|
}
|
|
|
|
} catch (e: Throwable) {
|
|
|
|
logcat(LogPriority.ERROR, e)
|
2023-11-18 19:54:56 +01:00
|
|
|
withUIContext { context.toast(MR.strings.cache_delete_error) }
|
2023-10-27 21:06:56 +02:00
|
|
|
}
|
2022-10-20 23:48:13 +02:00
|
|
|
}
|
|
|
|
},
|
2022-10-15 17:38:01 +02:00
|
|
|
),
|
2023-10-27 21:06:56 +02:00
|
|
|
Preference.PreferenceItem.SwitchPreference(
|
|
|
|
pref = libraryPreferences.autoClearChapterCache(),
|
2023-11-19 01:41:33 +01:00
|
|
|
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
|
2022-10-15 17:38:01 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|