mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-06-23 22:06:05 +02:00
300 lines
13 KiB
Kotlin
300 lines
13 KiB
Kotlin
package eu.kanade.presentation.more.settings.screen
|
|
|
|
import android.content.ActivityNotFoundException
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.net.Uri
|
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.compose.foundation.layout.RowScope
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.IconButton
|
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
|
import androidx.compose.material3.SegmentedButton
|
|
import androidx.compose.material3.SegmentedButtonDefaults
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.ReadOnlyComposable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableIntStateOf
|
|
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
|
|
import androidx.compose.ui.platform.LocalUriHandler
|
|
import androidx.core.net.toUri
|
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
import com.hippo.unifile.UniFile
|
|
import eu.kanade.presentation.more.settings.Preference
|
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
|
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
|
import eu.kanade.presentation.more.settings.screen.data.StorageInfo
|
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
|
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
|
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
import eu.kanade.tachiyomi.util.system.toast
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
import kotlinx.collections.immutable.persistentMapOf
|
|
import logcat.LogPriority
|
|
import tachiyomi.core.i18n.stringResource
|
|
import tachiyomi.core.storage.displayablePath
|
|
import tachiyomi.core.util.lang.launchNonCancellable
|
|
import tachiyomi.core.util.lang.withUIContext
|
|
import tachiyomi.core.util.system.logcat
|
|
import tachiyomi.domain.backup.service.BackupPreferences
|
|
import tachiyomi.domain.library.service.LibraryPreferences
|
|
import tachiyomi.domain.storage.service.StoragePreferences
|
|
import tachiyomi.i18n.MR
|
|
import tachiyomi.presentation.core.i18n.stringResource
|
|
import tachiyomi.presentation.core.util.collectAsState
|
|
import uy.kohesive.injekt.Injekt
|
|
import uy.kohesive.injekt.api.get
|
|
|
|
object SettingsDataScreen : SearchableSettings {
|
|
|
|
val restorePreferenceKeyString = MR.strings.label_backup
|
|
const val HELP_URL = "https://tachiyomi.org/docs/faq/storage"
|
|
|
|
@ReadOnlyComposable
|
|
@Composable
|
|
override fun getTitleRes() = MR.strings.label_data_storage
|
|
|
|
@Composable
|
|
override fun RowScope.AppBarAction() {
|
|
val uriHandler = LocalUriHandler.current
|
|
IconButton(onClick = { uriHandler.openUri(HELP_URL) }) {
|
|
Icon(
|
|
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
|
|
contentDescription = stringResource(MR.strings.tracking_guide),
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
override fun getPreferences(): List<Preference> {
|
|
val backupPreferences = Injekt.get<BackupPreferences>()
|
|
val storagePreferences = Injekt.get<StoragePreferences>()
|
|
|
|
return persistentListOf(
|
|
getStorageLocationPref(storagePreferences = storagePreferences),
|
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)),
|
|
|
|
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
|
|
getDataGroup(),
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
fun storageLocationPicker(
|
|
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
|
): ManagedActivityResultLauncher<Uri?, Uri?> {
|
|
val context = LocalContext.current
|
|
|
|
return rememberLauncherForActivityResult(
|
|
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)
|
|
|
|
UniFile.fromUri(context, uri)?.let {
|
|
storageDirPref.set(it.uri.toString())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun storageLocationText(
|
|
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
|
): String {
|
|
val context = LocalContext.current
|
|
val storageDir by storageDirPref.collectAsState()
|
|
|
|
if (storageDir == storageDirPref.defaultValue()) {
|
|
return stringResource(MR.strings.no_location_set)
|
|
}
|
|
|
|
return remember(storageDir) {
|
|
val file = UniFile.fromUri(context, storageDir.toUri())
|
|
file?.displayablePath
|
|
} ?: stringResource(MR.strings.invalid_location, storageDir)
|
|
}
|
|
|
|
@Composable
|
|
private fun getStorageLocationPref(
|
|
storagePreferences: StoragePreferences,
|
|
): Preference.PreferenceItem.TextPreference {
|
|
val context = LocalContext.current
|
|
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
|
|
|
|
return Preference.PreferenceItem.TextPreference(
|
|
title = stringResource(MR.strings.pref_storage_location),
|
|
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
|
|
onClick = {
|
|
try {
|
|
pickStorageLocation.launch(null)
|
|
} catch (e: ActivityNotFoundException) {
|
|
context.toast(MR.strings.file_picker_error)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
|
val context = LocalContext.current
|
|
val navigator = LocalNavigator.currentOrThrow
|
|
|
|
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
|
|
|
|
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
|
|
}
|
|
|
|
navigator.push(RestoreBackupScreen(it.toString()))
|
|
}
|
|
|
|
return Preference.PreferenceGroup(
|
|
title = stringResource(MR.strings.label_backup),
|
|
preferenceItems = persistentListOf(
|
|
// Manual actions
|
|
Preference.PreferenceItem.CustomPreference(
|
|
title = stringResource(restorePreferenceKeyString),
|
|
) {
|
|
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,
|
|
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)
|
|
}
|
|
},
|
|
shape = SegmentedButtonDefaults.itemShape(1, 2),
|
|
) {
|
|
Text(stringResource(MR.strings.pref_restore_backup))
|
|
}
|
|
}
|
|
},
|
|
)
|
|
},
|
|
|
|
// Automatic backups
|
|
Preference.PreferenceItem.ListPreference(
|
|
pref = backupPreferences.backupInterval(),
|
|
title = stringResource(MR.strings.pref_backup_interval),
|
|
entries = persistentMapOf(
|
|
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),
|
|
),
|
|
onValueChanged = {
|
|
BackupCreateJob.setupTask(context, it)
|
|
true
|
|
},
|
|
),
|
|
Preference.PreferenceItem.InfoPreference(
|
|
stringResource(MR.strings.backup_info) + "\n\n" +
|
|
stringResource(MR.strings.last_auto_backup_info, relativeTimeSpanString(lastAutoBackup)),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
private fun getDataGroup(): Preference.PreferenceGroup {
|
|
val context = LocalContext.current
|
|
val scope = rememberCoroutineScope()
|
|
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
|
|
|
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
|
var cacheReadableSizeSema by remember { mutableIntStateOf(0) }
|
|
val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
|
|
|
|
return Preference.PreferenceGroup(
|
|
title = stringResource(MR.strings.pref_storage_usage),
|
|
preferenceItems = persistentListOf(
|
|
Preference.PreferenceItem.CustomPreference(
|
|
title = stringResource(MR.strings.pref_storage_usage),
|
|
) {
|
|
BasePreferenceWidget(
|
|
subcomponent = {
|
|
StorageInfo(
|
|
modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
|
|
)
|
|
},
|
|
)
|
|
},
|
|
|
|
Preference.PreferenceItem.TextPreference(
|
|
title = stringResource(MR.strings.pref_clear_chapter_cache),
|
|
subtitle = stringResource(MR.strings.used_cache, cacheReadableSize),
|
|
onClick = {
|
|
scope.launchNonCancellable {
|
|
try {
|
|
val deletedFiles = chapterCache.clear()
|
|
withUIContext {
|
|
context.toast(context.stringResource(MR.strings.cache_deleted, deletedFiles))
|
|
cacheReadableSizeSema++
|
|
}
|
|
} catch (e: Throwable) {
|
|
logcat(LogPriority.ERROR, e)
|
|
withUIContext { context.toast(MR.strings.cache_delete_error) }
|
|
}
|
|
}
|
|
},
|
|
),
|
|
Preference.PreferenceItem.SwitchPreference(
|
|
pref = libraryPreferences.autoClearChapterCache(),
|
|
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
}
|