|
@@ -0,0 +1,398 @@
|
|
|
+package eu.kanade.presentation.more.settings.screen
|
|
|
+
|
|
|
+import android.annotation.SuppressLint
|
|
|
+import android.content.ActivityNotFoundException
|
|
|
+import android.content.Intent
|
|
|
+import android.provider.Settings
|
|
|
+import android.webkit.WebStorage
|
|
|
+import android.webkit.WebView
|
|
|
+import androidx.compose.material3.AlertDialog
|
|
|
+import androidx.compose.material3.Text
|
|
|
+import androidx.compose.material3.TextButton
|
|
|
+import androidx.compose.runtime.Composable
|
|
|
+import androidx.compose.runtime.ReadOnlyComposable
|
|
|
+import androidx.compose.runtime.getValue
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
+import androidx.compose.runtime.remember
|
|
|
+import androidx.compose.runtime.rememberCoroutineScope
|
|
|
+import androidx.compose.runtime.saveable.rememberSaveable
|
|
|
+import androidx.compose.runtime.setValue
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
|
+import androidx.compose.ui.res.stringResource
|
|
|
+import androidx.core.net.toUri
|
|
|
+import cafe.adriel.voyager.navigator.LocalNavigator
|
|
|
+import cafe.adriel.voyager.navigator.currentOrThrow
|
|
|
+import eu.kanade.domain.base.BasePreferences
|
|
|
+import eu.kanade.domain.library.service.LibraryPreferences
|
|
|
+import eu.kanade.domain.manga.repository.MangaRepository
|
|
|
+import eu.kanade.domain.ui.UiPreferences
|
|
|
+import eu.kanade.domain.ui.model.TabletUiMode
|
|
|
+import eu.kanade.presentation.more.settings.Preference
|
|
|
+import eu.kanade.presentation.util.collectAsState
|
|
|
+import eu.kanade.tachiyomi.R
|
|
|
+import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
|
+import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|
|
+import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
|
+import eu.kanade.tachiyomi.data.track.TrackManager
|
|
|
+import eu.kanade.tachiyomi.network.NetworkHelper
|
|
|
+import eu.kanade.tachiyomi.network.NetworkPreferences
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_360
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_ADGUARD
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_ALIDNS
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_CONTROLD
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_DNSPOD
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_MULLVAD
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_NJALLA
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
|
|
|
+import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
|
|
|
+import eu.kanade.tachiyomi.util.CrashLogUtil
|
|
|
+import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
|
|
+import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
|
+import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
|
+import eu.kanade.tachiyomi.util.system.isDevFlavor
|
|
|
+import eu.kanade.tachiyomi.util.system.isPackageInstalled
|
|
|
+import eu.kanade.tachiyomi.util.system.logcat
|
|
|
+import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
|
+import eu.kanade.tachiyomi.util.system.powerManager
|
|
|
+import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
|
|
+import eu.kanade.tachiyomi.util.system.toast
|
|
|
+import logcat.LogPriority
|
|
|
+import rikka.sui.Sui
|
|
|
+import uy.kohesive.injekt.Injekt
|
|
|
+import uy.kohesive.injekt.api.get
|
|
|
+import java.io.File
|
|
|
+
|
|
|
+class SettingsAdvancedScreen : SearchableSettings {
|
|
|
+ @ReadOnlyComposable
|
|
|
+ @Composable
|
|
|
+ override fun getTitle(): String = stringResource(id = R.string.pref_category_advanced)
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ override fun getPreferences(): List<Preference> {
|
|
|
+ val scope = rememberCoroutineScope()
|
|
|
+ val context = LocalContext.current
|
|
|
+ val basePreferences = remember { Injekt.get<BasePreferences>() }
|
|
|
+ val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
|
|
+
|
|
|
+ return listOf(
|
|
|
+ Preference.PreferenceItem.SwitchPreference(
|
|
|
+ pref = basePreferences.acraEnabled(),
|
|
|
+ title = stringResource(id = R.string.pref_enable_acra),
|
|
|
+ subtitle = stringResource(id = R.string.pref_acra_summary),
|
|
|
+ enabled = !isDevFlavor,
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_dump_crash_logs),
|
|
|
+ subtitle = stringResource(id = R.string.pref_dump_crash_logs_summary),
|
|
|
+ onClick = {
|
|
|
+ scope.launchNonCancellable {
|
|
|
+ CrashLogUtil(context).dumpLogs()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.SwitchPreference(
|
|
|
+ pref = networkPreferences.verboseLogging(),
|
|
|
+ title = stringResource(id = R.string.pref_verbose_logging),
|
|
|
+ subtitle = stringResource(id = R.string.pref_verbose_logging_summary),
|
|
|
+ onValueChanged = {
|
|
|
+ context.toast(R.string.requires_app_restart)
|
|
|
+ true
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ getBackgroundActivityGroup(),
|
|
|
+ getDataGroup(),
|
|
|
+ getNetworkGroup(networkPreferences = networkPreferences),
|
|
|
+ getLibraryGroup(),
|
|
|
+ getExtensionsGroup(basePreferences = basePreferences),
|
|
|
+ getDisplayGroup(),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun getBackgroundActivityGroup(): Preference.PreferenceGroup {
|
|
|
+ val context = LocalContext.current
|
|
|
+ return Preference.PreferenceGroup(
|
|
|
+ title = stringResource(id = R.string.label_background_activity),
|
|
|
+ preferenceItems = listOf(
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_disable_battery_optimization),
|
|
|
+ subtitle = stringResource(id = R.string.pref_disable_battery_optimization_summary),
|
|
|
+ onClick = {
|
|
|
+ val packageName: String = context.packageName
|
|
|
+ if (!context.powerManager.isIgnoringBatteryOptimizations(packageName)) {
|
|
|
+ try {
|
|
|
+ @SuppressLint("BatteryLife")
|
|
|
+ val intent = Intent().apply {
|
|
|
+ action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
|
|
+ data = "package:$packageName".toUri()
|
|
|
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
+ }
|
|
|
+ context.startActivity(intent)
|
|
|
+ } catch (e: ActivityNotFoundException) {
|
|
|
+ context.toast(R.string.battery_optimization_setting_activity_not_found)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ context.toast(R.string.battery_optimization_disabled)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = "Don't kill my app!",
|
|
|
+ subtitle = stringResource(id = R.string.about_dont_kill_my_app),
|
|
|
+ onClick = { context.openInBrowser("https://dontkillmyapp.com/") },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun getDataGroup(): Preference.PreferenceGroup {
|
|
|
+ val scope = rememberCoroutineScope()
|
|
|
+ val context = LocalContext.current
|
|
|
+ val navigator = LocalNavigator.currentOrThrow
|
|
|
+ val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
|
|
+
|
|
|
+ val chapterCache = remember { Injekt.get<ChapterCache>() }
|
|
|
+ var readableSizeSema by remember { mutableStateOf(0) }
|
|
|
+ val readableSize = remember(readableSizeSema) { chapterCache.readableSize }
|
|
|
+
|
|
|
+ return Preference.PreferenceGroup(
|
|
|
+ title = stringResource(id = R.string.label_data),
|
|
|
+ preferenceItems = listOf(
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_clear_chapter_cache),
|
|
|
+ subtitle = stringResource(id = R.string.used_cache, readableSize),
|
|
|
+ onClick = {
|
|
|
+ scope.launchNonCancellable {
|
|
|
+ try {
|
|
|
+ val deletedFiles = chapterCache.clear()
|
|
|
+ withUIContext {
|
|
|
+ context.toast(context.getString(R.string.cache_deleted, deletedFiles))
|
|
|
+ readableSizeSema++
|
|
|
+ }
|
|
|
+ } catch (e: Throwable) {
|
|
|
+ logcat(LogPriority.ERROR, e)
|
|
|
+ withUIContext { context.toast(R.string.cache_delete_error) }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.SwitchPreference(
|
|
|
+ pref = libraryPreferences.autoClearChapterCache(),
|
|
|
+ title = stringResource(id = R.string.pref_auto_clear_chapter_cache),
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_clear_database),
|
|
|
+ subtitle = stringResource(id = R.string.pref_clear_database_summary),
|
|
|
+ onClick = { navigator.push(ClearDatabaseScreen()) },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun getNetworkGroup(
|
|
|
+ networkPreferences: NetworkPreferences,
|
|
|
+ ): Preference.PreferenceGroup {
|
|
|
+ val context = LocalContext.current
|
|
|
+ val networkHelper = remember { Injekt.get<NetworkHelper>() }
|
|
|
+
|
|
|
+ val userAgentPref = networkPreferences.defaultUserAgent()
|
|
|
+ val userAgent by userAgentPref.collectAsState()
|
|
|
+
|
|
|
+ return Preference.PreferenceGroup(
|
|
|
+ title = stringResource(id = R.string.label_network),
|
|
|
+ preferenceItems = listOf(
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_clear_cookies),
|
|
|
+ onClick = {
|
|
|
+ networkHelper.cookieManager.removeAll()
|
|
|
+ context.toast(R.string.cookies_cleared)
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_clear_webview_data),
|
|
|
+ onClick = {
|
|
|
+ try {
|
|
|
+ WebView(context).run {
|
|
|
+ setDefaultSettings()
|
|
|
+ clearCache(true)
|
|
|
+ clearFormData()
|
|
|
+ clearHistory()
|
|
|
+ clearSslPreferences()
|
|
|
+ }
|
|
|
+ WebStorage.getInstance().deleteAllData()
|
|
|
+ context.applicationInfo?.dataDir?.let { File("$it/app_webview/").deleteRecursively() }
|
|
|
+ context.toast(R.string.webview_data_deleted)
|
|
|
+ } catch (e: Throwable) {
|
|
|
+ logcat(LogPriority.ERROR, e)
|
|
|
+ context.toast(R.string.cache_delete_error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.ListPreference(
|
|
|
+ pref = networkPreferences.dohProvider(),
|
|
|
+ title = stringResource(id = R.string.pref_dns_over_https),
|
|
|
+ entries = mapOf(
|
|
|
+ -1 to stringResource(id = R.string.disabled),
|
|
|
+ PREF_DOH_CLOUDFLARE to "Cloudflare",
|
|
|
+ PREF_DOH_GOOGLE to "Google",
|
|
|
+ PREF_DOH_ADGUARD to "AdGuard",
|
|
|
+ PREF_DOH_QUAD9 to "Quad9",
|
|
|
+ PREF_DOH_ALIDNS to "AliDNS",
|
|
|
+ PREF_DOH_DNSPOD to "DNSPod",
|
|
|
+ PREF_DOH_360 to "360",
|
|
|
+ PREF_DOH_QUAD101 to "Quad 101",
|
|
|
+ PREF_DOH_MULLVAD to "Mullvad",
|
|
|
+ PREF_DOH_CONTROLD to "Control D",
|
|
|
+ PREF_DOH_NJALLA to "Njalla",
|
|
|
+ ),
|
|
|
+ onValueChanged = {
|
|
|
+ context.toast(R.string.requires_app_restart)
|
|
|
+ true
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.EditTextPreference(
|
|
|
+ pref = userAgentPref,
|
|
|
+ title = stringResource(id = R.string.pref_user_agent_string),
|
|
|
+ onValueChanged = {
|
|
|
+ if (it.isBlank()) {
|
|
|
+ context.toast(R.string.error_user_agent_string_blank)
|
|
|
+ return@EditTextPreference false
|
|
|
+ }
|
|
|
+ context.toast(R.string.requires_app_restart)
|
|
|
+ true
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_reset_user_agent_string),
|
|
|
+ enabled = remember(userAgent) { userAgent != userAgentPref.defaultValue() },
|
|
|
+ onClick = {
|
|
|
+ userAgentPref.delete()
|
|
|
+ context.toast(R.string.requires_app_restart)
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun getLibraryGroup(): Preference.PreferenceGroup {
|
|
|
+ val scope = rememberCoroutineScope()
|
|
|
+ val context = LocalContext.current
|
|
|
+ val trackManager = remember { Injekt.get<TrackManager>() }
|
|
|
+
|
|
|
+ return Preference.PreferenceGroup(
|
|
|
+ title = stringResource(id = R.string.label_library),
|
|
|
+ preferenceItems = listOf(
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_refresh_library_covers),
|
|
|
+ onClick = { LibraryUpdateService.start(context, target = LibraryUpdateService.Target.COVERS) },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_refresh_library_tracking),
|
|
|
+ subtitle = stringResource(id = R.string.pref_refresh_library_tracking_summary),
|
|
|
+ enabled = trackManager.hasLoggedServices(),
|
|
|
+ onClick = { LibraryUpdateService.start(context, target = LibraryUpdateService.Target.TRACKING) },
|
|
|
+ ),
|
|
|
+ Preference.PreferenceItem.TextPreference(
|
|
|
+ title = stringResource(id = R.string.pref_reset_viewer_flags),
|
|
|
+ subtitle = stringResource(id = R.string.pref_reset_viewer_flags_summary),
|
|
|
+ onClick = {
|
|
|
+ scope.launchNonCancellable {
|
|
|
+ val success = Injekt.get<MangaRepository>().resetViewerFlags()
|
|
|
+ withUIContext {
|
|
|
+ val message = if (success) {
|
|
|
+ R.string.pref_reset_viewer_flags_success
|
|
|
+ } else {
|
|
|
+ R.string.pref_reset_viewer_flags_error
|
|
|
+ }
|
|
|
+ context.toast(message)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun getExtensionsGroup(
|
|
|
+ basePreferences: BasePreferences,
|
|
|
+ ): Preference.PreferenceGroup {
|
|
|
+ val context = LocalContext.current
|
|
|
+ var shizukuMissing by rememberSaveable { mutableStateOf(false) }
|
|
|
+ if (shizukuMissing) {
|
|
|
+ val dismiss = { shizukuMissing = false }
|
|
|
+ AlertDialog(
|
|
|
+ onDismissRequest = dismiss,
|
|
|
+ title = { Text(text = stringResource(id = R.string.ext_installer_shizuku)) },
|
|
|
+ text = { Text(text = stringResource(id = R.string.ext_installer_shizuku_unavailable_dialog)) },
|
|
|
+ dismissButton = {
|
|
|
+ TextButton(onClick = dismiss) {
|
|
|
+ Text(text = stringResource(id = android.R.string.cancel))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ confirmButton = {
|
|
|
+ TextButton(
|
|
|
+ onClick = {
|
|
|
+ dismiss()
|
|
|
+ context.openInBrowser("https://shizuku.rikka.app/download")
|
|
|
+ },
|
|
|
+ ) {
|
|
|
+ Text(text = stringResource(id = android.R.string.ok))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+ return Preference.PreferenceGroup(
|
|
|
+ title = stringResource(id = R.string.label_extensions),
|
|
|
+ preferenceItems = listOf(
|
|
|
+ Preference.PreferenceItem.ListPreference(
|
|
|
+ pref = basePreferences.extensionInstaller(),
|
|
|
+ title = stringResource(id = R.string.ext_installer_pref),
|
|
|
+ entries = PreferenceValues.ExtensionInstaller.values()
|
|
|
+ .run {
|
|
|
+ if (DeviceUtil.isMiui) {
|
|
|
+ filter { it != PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER }
|
|
|
+ } else {
|
|
|
+ toList()
|
|
|
+ }
|
|
|
+ }.associateWith { stringResource(id = it.titleResId) },
|
|
|
+ onValueChanged = {
|
|
|
+ if (it == PreferenceValues.ExtensionInstaller.SHIZUKU &&
|
|
|
+ !(context.isPackageInstalled("moe.shizuku.privileged.api") || Sui.isSui())
|
|
|
+ ) {
|
|
|
+ shizukuMissing = true
|
|
|
+ false
|
|
|
+ } else {
|
|
|
+ true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun getDisplayGroup(): Preference.PreferenceGroup {
|
|
|
+ val context = LocalContext.current
|
|
|
+ val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
|
|
+ return Preference.PreferenceGroup(
|
|
|
+ title = stringResource(id = R.string.pref_category_display),
|
|
|
+ preferenceItems = listOf(
|
|
|
+ Preference.PreferenceItem.ListPreference(
|
|
|
+ pref = uiPreferences.tabletUiMode(),
|
|
|
+ title = stringResource(id = R.string.pref_tablet_ui_mode),
|
|
|
+ entries = TabletUiMode.values().associateWith { stringResource(id = it.titleResId) },
|
|
|
+ onValueChanged = {
|
|
|
+ context.toast(R.string.requires_app_restart)
|
|
|
+ true
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|