Reorganize settings
parent
35c158d35a
commit
9e5664da3a
@ -1,66 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
|
|
||||||
import org.koitharu.kotatsu.parsers.util.names
|
|
||||||
import java.net.Proxy
|
|
||||||
|
|
||||||
class NetworkSettingsFragment :
|
|
||||||
BasePreferenceFragment(R.string.network),
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
addPreferencesFromResource(R.xml.pref_network)
|
|
||||||
findPreference<ListPreference>(AppSettings.KEY_DOH)?.run {
|
|
||||||
entryValues = DoHProvider.entries.names()
|
|
||||||
setDefaultValueCompat(DoHProvider.NONE.name)
|
|
||||||
}
|
|
||||||
bindProxySummary()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
settings.subscribe(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
settings.unsubscribe(this)
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
|
|
||||||
when (key) {
|
|
||||||
AppSettings.KEY_SSL_BYPASS -> {
|
|
||||||
Snackbar.make(listView, R.string.settings_apply_restart_required, Snackbar.LENGTH_INDEFINITE).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_PROXY_TYPE,
|
|
||||||
AppSettings.KEY_PROXY_ADDRESS,
|
|
||||||
AppSettings.KEY_PROXY_PORT -> {
|
|
||||||
bindProxySummary()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindProxySummary() {
|
|
||||||
findPreference<Preference>(AppSettings.KEY_PROXY)?.run {
|
|
||||||
val type = settings.proxyType
|
|
||||||
val address = settings.proxyAddress
|
|
||||||
val port = settings.proxyPort
|
|
||||||
summary = when {
|
|
||||||
type == Proxy.Type.DIRECT -> context.getString(R.string.disabled)
|
|
||||||
address.isNullOrEmpty() || port == 0 -> context.getString(R.string.invalid_proxy_configuration)
|
|
||||||
else -> "$address:$port"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package org.koitharu.kotatsu.settings
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
|
||||||
|
import org.koitharu.kotatsu.parsers.util.names
|
||||||
|
import org.koitharu.kotatsu.settings.userdata.storage.StorageUsagePreference
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
class StorageAndNetworkSettingsFragment :
|
||||||
|
BasePreferenceFragment(R.string.storage_and_network),
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private val viewModel by viewModels<StorageAndNetworkSettingsViewModel>()
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
addPreferencesFromResource(R.xml.pref_network_storage)
|
||||||
|
findPreference<ListPreference>(AppSettings.KEY_DOH)?.run {
|
||||||
|
entryValues = DoHProvider.entries.names()
|
||||||
|
setDefaultValueCompat(DoHProvider.NONE.name)
|
||||||
|
}
|
||||||
|
bindProxySummary()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
||||||
|
settings.subscribe(this)
|
||||||
|
findPreference<StorageUsagePreference>(AppSettings.KEY_STORAGE_USAGE)?.let { pref ->
|
||||||
|
viewModel.storageUsage.observe(viewLifecycleOwner, pref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
settings.unsubscribe(this)
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
|
||||||
|
when (key) {
|
||||||
|
AppSettings.KEY_SSL_BYPASS -> {
|
||||||
|
Snackbar.make(listView, R.string.settings_apply_restart_required, Snackbar.LENGTH_INDEFINITE).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_PROXY_TYPE,
|
||||||
|
AppSettings.KEY_PROXY_ADDRESS,
|
||||||
|
AppSettings.KEY_PROXY_PORT -> {
|
||||||
|
bindProxySummary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindProxySummary() {
|
||||||
|
findPreference<Preference>(AppSettings.KEY_PROXY)?.run {
|
||||||
|
val type = settings.proxyType
|
||||||
|
val address = settings.proxyAddress
|
||||||
|
val port = settings.proxyPort
|
||||||
|
summary = when {
|
||||||
|
type == Proxy.Type.DIRECT -> context.getString(R.string.disabled)
|
||||||
|
address.isNullOrEmpty() || port == 0 -> context.getString(R.string.invalid_proxy_configuration)
|
||||||
|
else -> "$address:$port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package org.koitharu.kotatsu.settings
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.plus
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
|
import org.koitharu.kotatsu.settings.userdata.storage.StorageUsage
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class StorageAndNetworkSettingsViewModel @Inject constructor(
|
||||||
|
private val storageManager: LocalStorageManager,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val storageUsage: StateFlow<StorageUsage?> = flow {
|
||||||
|
emit(loadStorageUsage())
|
||||||
|
}.withErrorHandling()
|
||||||
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(1000), null)
|
||||||
|
|
||||||
|
private suspend fun loadStorageUsage(): StorageUsage {
|
||||||
|
val pagesCacheSize = storageManager.computeCacheSize(CacheDir.PAGES)
|
||||||
|
val otherCacheSize = storageManager.computeCacheSize() - pagesCacheSize
|
||||||
|
val storageSize = storageManager.computeStorageSize()
|
||||||
|
val availableSpace = storageManager.computeAvailableSize()
|
||||||
|
val totalBytes = pagesCacheSize + otherCacheSize + storageSize + availableSpace
|
||||||
|
return StorageUsage(
|
||||||
|
savedManga = StorageUsage.Item(
|
||||||
|
bytes = storageSize,
|
||||||
|
percent = (storageSize.toDouble() / totalBytes).toFloat(),
|
||||||
|
),
|
||||||
|
pagesCache = StorageUsage.Item(
|
||||||
|
bytes = pagesCacheSize,
|
||||||
|
percent = (pagesCacheSize.toDouble() / totalBytes).toFloat(),
|
||||||
|
),
|
||||||
|
otherCache = StorageUsage.Item(
|
||||||
|
bytes = otherCacheSize,
|
||||||
|
percent = (otherCacheSize.toDouble() / totalBytes).toFloat(),
|
||||||
|
),
|
||||||
|
available = StorageUsage.Item(
|
||||||
|
bytes = availableSpace,
|
||||||
|
percent = (availableSpace.toDouble() / totalBytes).toFloat(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.result.ActivityResultCallback
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.backups.domain.BackupUtils
|
||||||
|
import org.koitharu.kotatsu.backups.ui.backup.BackupService
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.nav.router
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class BackupsSettingsFragment : BasePreferenceFragment(R.string.backup_restore),
|
||||||
|
ActivityResultCallback<Uri?> {
|
||||||
|
|
||||||
|
private val viewModel: BackupsSettingsViewModel by viewModels()
|
||||||
|
|
||||||
|
private val backupSelectCall = registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument(),
|
||||||
|
this,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val backupCreateCall = registerForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument("application/zip"),
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
if (!BackupService.start(requireContext(), uri)) {
|
||||||
|
Snackbar.make(
|
||||||
|
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
addPreferencesFromResource(R.xml.pref_backups)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
bindPeriodicalBackupSummary()
|
||||||
|
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
|
return when (preference.key) {
|
||||||
|
AppSettings.KEY_BACKUP -> {
|
||||||
|
if (!backupCreateCall.tryLaunch(BackupUtils.generateFileName(preference.context))) {
|
||||||
|
Snackbar.make(
|
||||||
|
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_RESTORE -> {
|
||||||
|
if (!backupSelectCall.tryLaunch(arrayOf("*/*"))) {
|
||||||
|
Snackbar.make(
|
||||||
|
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(result: Uri?) {
|
||||||
|
if (result != null) {
|
||||||
|
router.showBackupRestoreDialog(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindPeriodicalBackupSummary() {
|
||||||
|
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_ENABLED) ?: return
|
||||||
|
val entries = resources.getStringArray(R.array.backup_frequency)
|
||||||
|
val entryValues = resources.getStringArray(R.array.values_backup_frequency)
|
||||||
|
viewModel.periodicalBackupFrequency.observe(viewLifecycleOwner) { freq ->
|
||||||
|
preference.summary = if (freq == 0L) {
|
||||||
|
getString(R.string.disabled)
|
||||||
|
} else {
|
||||||
|
val index = entryValues.indexOf(freq.toString())
|
||||||
|
entries.getOrNull(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class BackupsSettingsViewModel @Inject constructor(
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val periodicalBackupFrequency = settings.observeAsFlow(
|
||||||
|
key = AppSettings.KEY_BACKUP_PERIODICAL_ENABLED,
|
||||||
|
valueProducer = { isPeriodicalBackupEnabled },
|
||||||
|
).flatMapLatest { isEnabled ->
|
||||||
|
if (isEnabled) {
|
||||||
|
settings.observeAsFlow(
|
||||||
|
key = AppSettings.KEY_BACKUP_PERIODICAL_FREQUENCY,
|
||||||
|
valueProducer = { periodicalBackupFrequency },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
flowOf(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,172 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.userdata
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.result.ActivityResultCallback
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.MultiSelectListPreference
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.TwoStatePreference
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.backups.domain.BackupUtils
|
|
||||||
import org.koitharu.kotatsu.backups.ui.backup.BackupService
|
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
|
||||||
import org.koitharu.kotatsu.core.nav.router
|
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy
|
|
||||||
import org.koitharu.kotatsu.core.prefs.SearchSuggestionType
|
|
||||||
import org.koitharu.kotatsu.core.prefs.TriStateOption
|
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
|
||||||
import org.koitharu.kotatsu.core.util.FileSize
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
|
||||||
import org.koitharu.kotatsu.parsers.util.names
|
|
||||||
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
|
||||||
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privacy),
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
|
||||||
ActivityResultCallback<Uri?> {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var appShortcutManager: AppShortcutManager
|
|
||||||
|
|
||||||
private val viewModel: UserDataSettingsViewModel by viewModels()
|
|
||||||
|
|
||||||
private val backupSelectCall = registerForActivityResult(
|
|
||||||
ActivityResultContracts.OpenDocument(),
|
|
||||||
this,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val backupCreateCall = registerForActivityResult(
|
|
||||||
ActivityResultContracts.CreateDocument("application/zip"),
|
|
||||||
) { uri ->
|
|
||||||
if (uri != null) {
|
|
||||||
if (!BackupService.start(requireContext(), uri)) {
|
|
||||||
Snackbar.make(
|
|
||||||
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
addPreferencesFromResource(R.xml.pref_user_data)
|
|
||||||
findPreference<Preference>(AppSettings.KEY_SHORTCUTS)?.isVisible =
|
|
||||||
appShortcutManager.isDynamicShortcutsAvailable()
|
|
||||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
|
||||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
|
||||||
findPreference<ListPreference>(AppSettings.KEY_SCREENSHOTS_POLICY)?.run {
|
|
||||||
entryValues = ScreenshotsPolicy.entries.names()
|
|
||||||
setDefaultValueCompat(ScreenshotsPolicy.ALLOW.name)
|
|
||||||
}
|
|
||||||
findPreference<ListPreference>(AppSettings.KEY_INCOGNITO_NSFW)?.run {
|
|
||||||
entryValues = TriStateOption.entries.names()
|
|
||||||
setDefaultValueCompat(TriStateOption.ASK.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
bindPeriodicalBackupSummary()
|
|
||||||
findPreference<MultiSelectListPreference>(AppSettings.KEY_SEARCH_SUGGESTION_TYPES)?.let { pref ->
|
|
||||||
pref.entryValues = SearchSuggestionType.entries.names()
|
|
||||||
pref.entries = SearchSuggestionType.entries.map { pref.context.getString(it.titleResId) }.toTypedArray()
|
|
||||||
pref.summaryProvider = MultiSummaryProvider(R.string.none)
|
|
||||||
pref.values = settings.searchSuggestionTypes.mapToSet { it.name }
|
|
||||||
}
|
|
||||||
findPreference<Preference>(AppSettings.KEY_STORAGE_USAGE)?.let { pref ->
|
|
||||||
viewModel.storageUsage.observe(viewLifecycleOwner) { size ->
|
|
||||||
pref.summary = if (size < 0L) {
|
|
||||||
pref.context.getString(R.string.computing_)
|
|
||||||
} else {
|
|
||||||
FileSize.BYTES.format(pref.context, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
|
||||||
settings.subscribe(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
settings.unsubscribe(this)
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
|
||||||
return when (preference.key) {
|
|
||||||
AppSettings.KEY_BACKUP -> {
|
|
||||||
if (!backupCreateCall.tryLaunch(BackupUtils.generateFileName(preference.context))) {
|
|
||||||
Snackbar.make(
|
|
||||||
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_RESTORE -> {
|
|
||||||
if (!backupSelectCall.tryLaunch(arrayOf("*/*"))) {
|
|
||||||
Snackbar.make(
|
|
||||||
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_PROTECT_APP -> {
|
|
||||||
val pref = (preference as? TwoStatePreference ?: return false)
|
|
||||||
if (pref.isChecked) {
|
|
||||||
pref.isChecked = false
|
|
||||||
startActivity(Intent(preference.context, ProtectSetupActivity::class.java))
|
|
||||||
} else {
|
|
||||||
settings.appPassword = null
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
|
||||||
when (key) {
|
|
||||||
AppSettings.KEY_APP_PASSWORD -> {
|
|
||||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
|
||||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(result: Uri?) {
|
|
||||||
if (result != null) {
|
|
||||||
router.showBackupRestoreDialog(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindPeriodicalBackupSummary() {
|
|
||||||
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_ENABLED) ?: return
|
|
||||||
val entries = resources.getStringArray(R.array.backup_frequency)
|
|
||||||
val entryValues = resources.getStringArray(R.array.values_backup_frequency)
|
|
||||||
viewModel.periodicalBackupFrequency.observe(viewLifecycleOwner) { freq ->
|
|
||||||
preference.summary = if (freq == 0L) {
|
|
||||||
getString(R.string.disabled)
|
|
||||||
} else {
|
|
||||||
val index = entryValues.indexOf(freq.toString())
|
|
||||||
entries.getOrNull(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.userdata
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class UserDataSettingsViewModel @Inject constructor(
|
|
||||||
private val storageManager: LocalStorageManager,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
) : BaseViewModel() {
|
|
||||||
|
|
||||||
val storageUsage = MutableStateFlow(-1L)
|
|
||||||
|
|
||||||
val periodicalBackupFrequency = settings.observeAsFlow(
|
|
||||||
key = AppSettings.KEY_BACKUP_PERIODICAL_ENABLED,
|
|
||||||
valueProducer = { isPeriodicalBackupEnabled },
|
|
||||||
).flatMapLatest { isEnabled ->
|
|
||||||
if (isEnabled) {
|
|
||||||
settings.observeAsFlow(
|
|
||||||
key = AppSettings.KEY_BACKUP_PERIODICAL_FREQUENCY,
|
|
||||||
valueProducer = { periodicalBackupFrequency },
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
flowOf(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var storageUsageJob: Job? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadStorageUsage()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadStorageUsage(): Job {
|
|
||||||
val prevJob = storageUsageJob
|
|
||||||
return launchJob(Dispatchers.Default) {
|
|
||||||
prevJob?.cancelAndJoin()
|
|
||||||
val totalBytes = storageManager.computeCacheSize() + storageManager.computeStorageSize()
|
|
||||||
storageUsage.value = totalBytes
|
|
||||||
}.also {
|
|
||||||
storageUsageJob = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,173 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.userdata.storage
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
|
||||||
|
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||||
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class DataCleanupSettingsFragment : BasePreferenceFragment(R.string.data_removal) {
|
||||||
|
|
||||||
|
private val viewModel by viewModels<DataCleanupSettingsViewModel>()
|
||||||
|
private val loadingPrefs = HashSet<String>()
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
addPreferencesFromResource(R.xml.pref_data_cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.PAGES]))
|
||||||
|
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.THUMBS]))
|
||||||
|
findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindBytesSizeSummary(viewModel.httpCacheSize)
|
||||||
|
findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->
|
||||||
|
viewModel.searchHistoryCount.observe(viewLifecycleOwner) {
|
||||||
|
pref.summary = if (it < 0) {
|
||||||
|
view.context.getString(R.string.loading_)
|
||||||
|
} else {
|
||||||
|
pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findPreference<Preference>(AppSettings.KEY_UPDATES_FEED_CLEAR)?.let { pref ->
|
||||||
|
viewModel.feedItemsCount.observe(viewLifecycleOwner) {
|
||||||
|
pref.summary = if (it < 0) {
|
||||||
|
view.context.getString(R.string.loading_)
|
||||||
|
} else {
|
||||||
|
pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findPreference<Preference>(AppSettings.KEY_WEBVIEW_CLEAR)?.isVisible = viewModel.isBrowserDataCleanupEnabled
|
||||||
|
|
||||||
|
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
|
||||||
|
loadingPrefs.addAll(keys)
|
||||||
|
loadingPrefs.forEach { prefKey ->
|
||||||
|
findPreference<Preference>(prefKey)?.isEnabled = prefKey !in keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
||||||
|
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))
|
||||||
|
viewModel.onChaptersCleanedUp.observeEvent(viewLifecycleOwner, ::onChaptersCleanedUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {
|
||||||
|
AppSettings.KEY_COOKIES_CLEAR -> {
|
||||||
|
clearCookies()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
|
||||||
|
clearSearchHistory()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_PAGES_CACHE_CLEAR -> {
|
||||||
|
viewModel.clearCache(preference.key, CacheDir.PAGES)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
|
||||||
|
viewModel.clearCache(preference.key, CacheDir.THUMBS, CacheDir.FAVICONS)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_HTTP_CACHE_CLEAR -> {
|
||||||
|
viewModel.clearHttpCache()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_CHAPTERS_CLEAR -> {
|
||||||
|
cleanupChapters()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_WEBVIEW_CLEAR -> {
|
||||||
|
viewModel.clearBrowserData()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_CLEAR_MANGA_DATA -> {
|
||||||
|
viewModel.clearMangaData()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
|
||||||
|
viewModel.clearUpdatesFeed()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onChaptersCleanedUp(result: Pair<Int, Long>) {
|
||||||
|
val c = context ?: return
|
||||||
|
val text = if (result.first == 0 && result.second == 0L) {
|
||||||
|
c.getString(R.string.no_chapters_deleted)
|
||||||
|
} else {
|
||||||
|
c.getString(
|
||||||
|
R.string.chapters_deleted_pattern,
|
||||||
|
c.resources.getQuantityStringSafe(R.plurals.chapters, result.first, result.first),
|
||||||
|
FileSize.BYTES.format(c, result.second),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Snackbar.make(listView, text, Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Preference.bindBytesSizeSummary(stateFlow: StateFlow<Long>) {
|
||||||
|
stateFlow.observe(viewLifecycleOwner) { size ->
|
||||||
|
summary = if (size < 0) {
|
||||||
|
context.getString(R.string.computing_)
|
||||||
|
} else {
|
||||||
|
FileSize.BYTES.format(context, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearSearchHistory() {
|
||||||
|
buildAlertDialog(context ?: return) {
|
||||||
|
setTitle(R.string.clear_search_history)
|
||||||
|
setMessage(R.string.text_clear_search_history_prompt)
|
||||||
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
setPositiveButton(R.string.clear) { _, _ ->
|
||||||
|
viewModel.clearSearchHistory()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearCookies() {
|
||||||
|
buildAlertDialog(context ?: return) {
|
||||||
|
setTitle(R.string.clear_cookies)
|
||||||
|
setMessage(R.string.text_clear_cookies_prompt)
|
||||||
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
setPositiveButton(R.string.clear) { _, _ ->
|
||||||
|
viewModel.clearCookies()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanupChapters() {
|
||||||
|
buildAlertDialog(context ?: return) {
|
||||||
|
setTitle(R.string.delete_read_chapters)
|
||||||
|
setMessage(R.string.delete_read_chapters_prompt)
|
||||||
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
setPositiveButton(R.string.delete) { _, _ ->
|
||||||
|
viewModel.cleanupChapters()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,184 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.userdata.storage
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.webkit.WebStorage
|
||||||
|
import androidx.webkit.WebStorageCompat
|
||||||
|
import androidx.webkit.WebViewFeature
|
||||||
|
import coil3.ImageLoader
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import okhttp3.Cache
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
|
import org.koitharu.kotatsu.local.domain.DeleteReadChaptersUseCase
|
||||||
|
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
|
import java.util.EnumMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class DataCleanupSettingsViewModel @Inject constructor(
|
||||||
|
private val storageManager: LocalStorageManager,
|
||||||
|
private val httpCache: Cache,
|
||||||
|
private val searchRepository: MangaSearchRepository,
|
||||||
|
private val trackingRepository: TrackingRepository,
|
||||||
|
private val cookieJar: MutableCookieJar,
|
||||||
|
private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,
|
||||||
|
private val mangaDataRepositoryProvider: Provider<MangaDataRepository>,
|
||||||
|
private val coil: ImageLoader,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||||
|
val loadingKeys = MutableStateFlow(emptySet<String>())
|
||||||
|
|
||||||
|
val searchHistoryCount = MutableStateFlow(-1)
|
||||||
|
val feedItemsCount = MutableStateFlow(-1)
|
||||||
|
val httpCacheSize = MutableStateFlow(-1L)
|
||||||
|
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
|
||||||
|
|
||||||
|
val onChaptersCleanedUp = MutableEventFlow<Pair<Int, Long>>()
|
||||||
|
|
||||||
|
val isBrowserDataCleanupEnabled: Boolean
|
||||||
|
get() = WebViewFeature.isFeatureSupported(WebViewFeature.DELETE_BROWSING_DATA)
|
||||||
|
|
||||||
|
init {
|
||||||
|
CacheDir.entries.forEach {
|
||||||
|
cacheSizes[it] = MutableStateFlow(-1L)
|
||||||
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
searchHistoryCount.value = searchRepository.getSearchHistoryCount()
|
||||||
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
feedItemsCount.value = trackingRepository.getLogsCount()
|
||||||
|
}
|
||||||
|
CacheDir.entries.forEach { cache ->
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
httpCacheSize.value = runInterruptible { httpCache.size() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCache(key: String, vararg caches: CacheDir) {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + key }
|
||||||
|
for (cache in caches) {
|
||||||
|
storageManager.clearCache(cache)
|
||||||
|
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
||||||
|
if (cache == CacheDir.THUMBS) {
|
||||||
|
coil.memoryCache?.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - key }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearHttpCache() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + AppSettings.KEY_HTTP_CACHE_CLEAR }
|
||||||
|
val size = runInterruptible(Dispatchers.IO) {
|
||||||
|
httpCache.evictAll()
|
||||||
|
httpCache.size()
|
||||||
|
}
|
||||||
|
httpCacheSize.value = size
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - AppSettings.KEY_HTTP_CACHE_CLEAR }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSearchHistory() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
searchRepository.clearSearchHistory()
|
||||||
|
searchHistoryCount.value = searchRepository.getSearchHistoryCount()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.search_history_cleared, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCookies() {
|
||||||
|
launchJob {
|
||||||
|
cookieJar.clear()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.cookies_cleared, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RequiresFeature")
|
||||||
|
fun clearBrowserData() {
|
||||||
|
launchJob {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + AppSettings.KEY_WEBVIEW_CLEAR }
|
||||||
|
val storage = WebStorage.getInstance()
|
||||||
|
suspendCoroutine { cont ->
|
||||||
|
WebStorageCompat.deleteBrowsingData(storage) {
|
||||||
|
cont.resume(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - AppSettings.KEY_WEBVIEW_CLEAR }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearUpdatesFeed() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + AppSettings.KEY_UPDATES_FEED_CLEAR }
|
||||||
|
trackingRepository.clearLogs()
|
||||||
|
feedItemsCount.value = trackingRepository.getLogsCount()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - AppSettings.KEY_UPDATES_FEED_CLEAR }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearMangaData() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + AppSettings.KEY_CLEAR_MANGA_DATA }
|
||||||
|
trackingRepository.gc()
|
||||||
|
val repository = mangaDataRepositoryProvider.get()
|
||||||
|
repository.cleanupLocalManga()
|
||||||
|
repository.cleanupDatabase()
|
||||||
|
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - AppSettings.KEY_CLEAR_MANGA_DATA }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanupChapters() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
loadingKeys.update { it + AppSettings.KEY_CHAPTERS_CLEAR }
|
||||||
|
val oldSize = storageManager.computeStorageSize()
|
||||||
|
val chaptersCount = deleteReadChaptersUseCase.invoke()
|
||||||
|
val newSize = storageManager.computeStorageSize()
|
||||||
|
onChaptersCleanedUp.call(chaptersCount to oldSize - newSize)
|
||||||
|
} finally {
|
||||||
|
loadingKeys.update { it - AppSettings.KEY_CHAPTERS_CLEAR }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,173 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.userdata.storage
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
|
||||||
import org.koitharu.kotatsu.core.util.FileSize
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class StorageManageSettingsFragment : BasePreferenceFragment(R.string.storage_usage) {
|
|
||||||
|
|
||||||
private val viewModel by viewModels<StorageManageSettingsViewModel>()
|
|
||||||
private val loadingPrefs = HashSet<String>()
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
addPreferencesFromResource(R.xml.pref_storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.PAGES]))
|
|
||||||
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.THUMBS]))
|
|
||||||
findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindBytesSizeSummary(viewModel.httpCacheSize)
|
|
||||||
findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->
|
|
||||||
viewModel.searchHistoryCount.observe(viewLifecycleOwner) {
|
|
||||||
pref.summary = if (it < 0) {
|
|
||||||
view.context.getString(R.string.loading_)
|
|
||||||
} else {
|
|
||||||
pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
findPreference<Preference>(AppSettings.KEY_UPDATES_FEED_CLEAR)?.let { pref ->
|
|
||||||
viewModel.feedItemsCount.observe(viewLifecycleOwner) {
|
|
||||||
pref.summary = if (it < 0) {
|
|
||||||
view.context.getString(R.string.loading_)
|
|
||||||
} else {
|
|
||||||
pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
findPreference<StorageUsagePreference>(AppSettings.KEY_STORAGE_USAGE)?.let { pref ->
|
|
||||||
viewModel.storageUsage.observe(viewLifecycleOwner, pref)
|
|
||||||
}
|
|
||||||
findPreference<Preference>(AppSettings.KEY_WEBVIEW_CLEAR)?.isVisible = viewModel.isBrowserDataCleanupEnabled
|
|
||||||
|
|
||||||
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
|
|
||||||
loadingPrefs.addAll(keys)
|
|
||||||
loadingPrefs.forEach { prefKey ->
|
|
||||||
findPreference<Preference>(prefKey)?.isEnabled = prefKey !in keys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
|
||||||
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))
|
|
||||||
viewModel.onChaptersCleanedUp.observeEvent(viewLifecycleOwner, ::onChaptersCleanedUp)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {
|
|
||||||
AppSettings.KEY_COOKIES_CLEAR -> {
|
|
||||||
clearCookies()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
|
|
||||||
clearSearchHistory()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_PAGES_CACHE_CLEAR -> {
|
|
||||||
viewModel.clearCache(preference.key, CacheDir.PAGES)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
|
|
||||||
viewModel.clearCache(preference.key, CacheDir.THUMBS, CacheDir.FAVICONS)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_HTTP_CACHE_CLEAR -> {
|
|
||||||
viewModel.clearHttpCache()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_CHAPTERS_CLEAR -> {
|
|
||||||
cleanupChapters()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_WEBVIEW_CLEAR -> {
|
|
||||||
viewModel.clearBrowserData()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_CLEAR_MANGA_DATA -> {
|
|
||||||
viewModel.clearMangaData()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
|
|
||||||
viewModel.clearUpdatesFeed()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onChaptersCleanedUp(result: Pair<Int, Long>) {
|
|
||||||
val c = context ?: return
|
|
||||||
val text = if (result.first == 0 && result.second == 0L) {
|
|
||||||
c.getString(R.string.no_chapters_deleted)
|
|
||||||
} else {
|
|
||||||
c.getString(
|
|
||||||
R.string.chapters_deleted_pattern,
|
|
||||||
c.resources.getQuantityStringSafe(R.plurals.chapters, result.first, result.first),
|
|
||||||
FileSize.BYTES.format(c, result.second),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Snackbar.make(listView, text, Snackbar.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preference.bindBytesSizeSummary(stateFlow: StateFlow<Long>) {
|
|
||||||
stateFlow.observe(viewLifecycleOwner) { size ->
|
|
||||||
summary = if (size < 0) {
|
|
||||||
context.getString(R.string.computing_)
|
|
||||||
} else {
|
|
||||||
FileSize.BYTES.format(context, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearSearchHistory() {
|
|
||||||
MaterialAlertDialogBuilder(context ?: return)
|
|
||||||
.setTitle(R.string.clear_search_history)
|
|
||||||
.setMessage(R.string.text_clear_search_history_prompt)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.clear) { _, _ ->
|
|
||||||
viewModel.clearSearchHistory()
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearCookies() {
|
|
||||||
MaterialAlertDialogBuilder(context ?: return)
|
|
||||||
.setTitle(R.string.clear_cookies)
|
|
||||||
.setMessage(R.string.text_clear_cookies_prompt)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.clear) { _, _ ->
|
|
||||||
viewModel.clearCookies()
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cleanupChapters() {
|
|
||||||
MaterialAlertDialogBuilder(context ?: return)
|
|
||||||
.setTitle(R.string.delete_read_chapters)
|
|
||||||
.setMessage(R.string.delete_read_chapters_prompt)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.delete) { _, _ ->
|
|
||||||
viewModel.cleanupChapters()
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,226 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.userdata.storage
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.webkit.WebStorage
|
|
||||||
import androidx.webkit.WebStorageCompat
|
|
||||||
import androidx.webkit.WebViewFeature
|
|
||||||
import coil3.ImageLoader
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import okhttp3.Cache
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.firstNotNull
|
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
|
||||||
import org.koitharu.kotatsu.local.domain.DeleteReadChaptersUseCase
|
|
||||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
|
||||||
import java.util.EnumMap
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Provider
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class StorageManageSettingsViewModel @Inject constructor(
|
|
||||||
private val storageManager: LocalStorageManager,
|
|
||||||
private val httpCache: Cache,
|
|
||||||
private val searchRepository: MangaSearchRepository,
|
|
||||||
private val trackingRepository: TrackingRepository,
|
|
||||||
private val cookieJar: MutableCookieJar,
|
|
||||||
private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,
|
|
||||||
private val mangaDataRepositoryProvider: Provider<MangaDataRepository>,
|
|
||||||
private val coil: ImageLoader,
|
|
||||||
) : BaseViewModel() {
|
|
||||||
|
|
||||||
val onActionDone = MutableEventFlow<ReversibleAction>()
|
|
||||||
val loadingKeys = MutableStateFlow(emptySet<String>())
|
|
||||||
|
|
||||||
val searchHistoryCount = MutableStateFlow(-1)
|
|
||||||
val feedItemsCount = MutableStateFlow(-1)
|
|
||||||
val httpCacheSize = MutableStateFlow(-1L)
|
|
||||||
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
|
|
||||||
val storageUsage = MutableStateFlow<StorageUsage?>(null)
|
|
||||||
|
|
||||||
val onChaptersCleanedUp = MutableEventFlow<Pair<Int, Long>>()
|
|
||||||
|
|
||||||
val isBrowserDataCleanupEnabled: Boolean
|
|
||||||
get() = WebViewFeature.isFeatureSupported(WebViewFeature.DELETE_BROWSING_DATA)
|
|
||||||
|
|
||||||
private var storageUsageJob: Job? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
CacheDir.entries.forEach {
|
|
||||||
cacheSizes[it] = MutableStateFlow(-1L)
|
|
||||||
}
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
searchHistoryCount.value = searchRepository.getSearchHistoryCount()
|
|
||||||
}
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
feedItemsCount.value = trackingRepository.getLogsCount()
|
|
||||||
}
|
|
||||||
CacheDir.entries.forEach { cache ->
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
httpCacheSize.value = runInterruptible { httpCache.size() }
|
|
||||||
}
|
|
||||||
loadStorageUsage()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearCache(key: String, vararg caches: CacheDir) {
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
loadingKeys.update { it + key }
|
|
||||||
for (cache in caches) {
|
|
||||||
storageManager.clearCache(cache)
|
|
||||||
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
|
||||||
if (cache == CacheDir.THUMBS) {
|
|
||||||
coil.memoryCache?.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadStorageUsage()
|
|
||||||
} finally {
|
|
||||||
loadingKeys.update { it - key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearHttpCache() {
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
loadingKeys.update { it + AppSettings.KEY_HTTP_CACHE_CLEAR }
|
|
||||||
val size = runInterruptible(Dispatchers.IO) {
|
|
||||||
httpCache.evictAll()
|
|
||||||
httpCache.size()
|
|
||||||
}
|
|
||||||
httpCacheSize.value = size
|
|
||||||
loadStorageUsage()
|
|
||||||
} finally {
|
|
||||||
loadingKeys.update { it - AppSettings.KEY_HTTP_CACHE_CLEAR }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearSearchHistory() {
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
searchRepository.clearSearchHistory()
|
|
||||||
searchHistoryCount.value = searchRepository.getSearchHistoryCount()
|
|
||||||
onActionDone.call(ReversibleAction(R.string.search_history_cleared, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearCookies() {
|
|
||||||
launchJob {
|
|
||||||
cookieJar.clear()
|
|
||||||
onActionDone.call(ReversibleAction(R.string.cookies_cleared, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RequiresFeature")
|
|
||||||
fun clearBrowserData() {
|
|
||||||
launchJob {
|
|
||||||
try {
|
|
||||||
loadingKeys.update { it + AppSettings.KEY_WEBVIEW_CLEAR }
|
|
||||||
val storage = WebStorage.getInstance()
|
|
||||||
suspendCoroutine { cont ->
|
|
||||||
WebStorageCompat.deleteBrowsingData(storage) {
|
|
||||||
cont.resume(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
|
||||||
} finally {
|
|
||||||
loadingKeys.update { it - AppSettings.KEY_WEBVIEW_CLEAR }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearUpdatesFeed() {
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
loadingKeys.update { it + AppSettings.KEY_UPDATES_FEED_CLEAR }
|
|
||||||
trackingRepository.clearLogs()
|
|
||||||
feedItemsCount.value = trackingRepository.getLogsCount()
|
|
||||||
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
|
||||||
} finally {
|
|
||||||
loadingKeys.update { it - AppSettings.KEY_UPDATES_FEED_CLEAR }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearMangaData() {
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
loadingKeys.update { it + AppSettings.KEY_CLEAR_MANGA_DATA }
|
|
||||||
trackingRepository.gc()
|
|
||||||
val repository = mangaDataRepositoryProvider.get()
|
|
||||||
repository.cleanupLocalManga()
|
|
||||||
repository.cleanupDatabase()
|
|
||||||
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
|
||||||
} finally {
|
|
||||||
loadingKeys.update { it - AppSettings.KEY_CLEAR_MANGA_DATA }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cleanupChapters() {
|
|
||||||
launchJob(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
loadingKeys.update { it + AppSettings.KEY_CHAPTERS_CLEAR }
|
|
||||||
val oldSize = storageUsage.firstNotNull().savedManga.bytes
|
|
||||||
val chaptersCount = deleteReadChaptersUseCase.invoke()
|
|
||||||
loadStorageUsage().join()
|
|
||||||
val newSize = storageUsage.firstNotNull().savedManga.bytes
|
|
||||||
onChaptersCleanedUp.call(chaptersCount to oldSize - newSize)
|
|
||||||
} finally {
|
|
||||||
loadingKeys.update { it - AppSettings.KEY_CHAPTERS_CLEAR }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadStorageUsage(): Job {
|
|
||||||
val prevJob = storageUsageJob
|
|
||||||
return launchJob(Dispatchers.Default) {
|
|
||||||
prevJob?.cancelAndJoin()
|
|
||||||
val pagesCacheSize = storageManager.computeCacheSize(CacheDir.PAGES)
|
|
||||||
val otherCacheSize = storageManager.computeCacheSize() - pagesCacheSize
|
|
||||||
val storageSize = storageManager.computeStorageSize()
|
|
||||||
val availableSpace = storageManager.computeAvailableSize()
|
|
||||||
val totalBytes = pagesCacheSize + otherCacheSize + storageSize + availableSpace
|
|
||||||
storageUsage.value = StorageUsage(
|
|
||||||
savedManga = StorageUsage.Item(
|
|
||||||
bytes = storageSize,
|
|
||||||
percent = (storageSize.toDouble() / totalBytes).toFloat(),
|
|
||||||
),
|
|
||||||
pagesCache = StorageUsage.Item(
|
|
||||||
bytes = pagesCacheSize,
|
|
||||||
percent = (pagesCacheSize.toDouble() / totalBytes).toFloat(),
|
|
||||||
),
|
|
||||||
otherCache = StorageUsage.Item(
|
|
||||||
bytes = otherCacheSize,
|
|
||||||
percent = (otherCacheSize.toDouble() / totalBytes).toFloat(),
|
|
||||||
),
|
|
||||||
available = StorageUsage.Item(
|
|
||||||
bytes = availableSpace,
|
|
||||||
percent = (availableSpace.toDouble() / totalBytes).toFloat(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}.also {
|
|
||||||
storageUsageJob = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M13,2.05V5.08C16.39,5.57 19,8.47 19,12C19,12.9 18.82,13.75 18.5,14.54L21.12,16.07C21.68,14.83 22,13.45 22,12C22,6.82 18.05,2.55 13,2.05M12,19A7,7 0 0,1 5,12C5,8.47 7.61,5.57 11,5.08V2.05C5.94,2.55 2,6.81 2,12A10,10 0 0,0 12,22C15.3,22 18.23,20.39 20.05,17.91L17.45,16.38C16.17,18 14.21,19 12,19Z" />
|
||||||
|
</vector>
|
||||||
@ -1,122 +1,149 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:title="@string/appearance">
|
android:title="@string/appearance">
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.ThemeChooserPreference
|
<org.koitharu.kotatsu.settings.utils.ThemeChooserPreference
|
||||||
android:key="color_theme"
|
android:key="color_theme"
|
||||||
android:title="@string/color_theme" />
|
android:title="@string/color_theme" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="-1"
|
android:defaultValue="-1"
|
||||||
android:entries="@array/themes"
|
android:entries="@array/themes"
|
||||||
android:entryValues="@array/values_theme"
|
android:entryValues="@array/values_theme"
|
||||||
android:key="theme"
|
android:key="theme"
|
||||||
android:title="@string/theme"
|
android:title="@string/theme"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="amoled_theme"
|
android:key="amoled_theme"
|
||||||
android:summary="@string/black_dark_theme_summary"
|
android:summary="@string/black_dark_theme_summary"
|
||||||
android:title="@string/black_dark_theme" />
|
android:title="@string/black_dark_theme" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
||||||
android:key="app_locale"
|
android:key="app_locale"
|
||||||
android:title="@string/language" />
|
android:title="@string/language" />
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/manga_list">
|
<PreferenceCategory android:title="@string/manga_list">
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:entries="@array/list_modes"
|
android:entries="@array/list_modes"
|
||||||
android:key="list_mode_2"
|
android:key="list_mode_2"
|
||||||
android:title="@string/list_mode"
|
android:title="@string/list_mode"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||||
android:key="grid_size"
|
android:key="grid_size"
|
||||||
android:stepSize="5"
|
android:stepSize="5"
|
||||||
android:title="@string/grid_size"
|
android:title="@string/grid_size"
|
||||||
android:valueFrom="50"
|
android:valueFrom="50"
|
||||||
android:valueTo="150"
|
android:valueTo="150"
|
||||||
app:defaultValue="100" />
|
app:defaultValue="100" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="quick_filter"
|
android:key="quick_filter"
|
||||||
android:summary="@string/show_quick_filters_summary"
|
android:summary="@string/show_quick_filters_summary"
|
||||||
android:title="@string/show_quick_filters" />
|
android:title="@string/show_quick_filters" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:entries="@array/progress_indicators"
|
android:entries="@array/progress_indicators"
|
||||||
android:key="progress_indicators"
|
android:key="progress_indicators"
|
||||||
android:title="@string/show_reading_indicators"
|
android:title="@string/show_reading_indicators"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<MultiSelectListPreference
|
<MultiSelectListPreference
|
||||||
android:defaultValue="@array/values_list_badges"
|
android:defaultValue="@array/values_list_badges"
|
||||||
android:entries="@array/list_badges"
|
android:entries="@array/list_badges"
|
||||||
android:entryValues="@array/values_list_badges"
|
android:entryValues="@array/values_list_badges"
|
||||||
android:key="manga_list_badges"
|
android:key="manga_list_badges"
|
||||||
android:title="@string/badges_in_lists" />
|
android:title="@string/badges_in_lists" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/details">
|
<PreferenceCategory android:title="@string/details">
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="description_collapse"
|
android:key="description_collapse"
|
||||||
android:title="@string/collapse_long_description" />
|
android:title="@string/collapse_long_description" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="pages_tab"
|
android:key="pages_tab"
|
||||||
android:summary="@string/show_pages_thumbs_summary"
|
android:summary="@string/show_pages_thumbs_summary"
|
||||||
android:title="@string/show_pages_thumbs" />
|
android:title="@string/show_pages_thumbs" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="-1"
|
android:defaultValue="-1"
|
||||||
android:dependency="pages_tab"
|
android:dependency="pages_tab"
|
||||||
android:entries="@array/details_tabs"
|
android:entries="@array/details_tabs"
|
||||||
android:entryValues="@array/details_tabs_values"
|
android:entryValues="@array/details_tabs_values"
|
||||||
android:key="details_tab"
|
android:key="details_tab"
|
||||||
android:title="@string/default_tab"
|
android:title="@string/default_tab"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/main_screen">
|
<PreferenceCategory android:title="@string/main_screen">
|
||||||
|
|
||||||
<PreferenceScreen
|
<MultiSelectListPreference
|
||||||
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
|
android:key="search_suggest_types"
|
||||||
android:key="nav_main"
|
android:title="@string/search_suggestions" />
|
||||||
android:title="@string/main_screen_sections" />
|
|
||||||
|
<PreferenceScreen
|
||||||
<SwitchPreferenceCompat
|
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
|
||||||
android:defaultValue="true"
|
android:key="nav_main"
|
||||||
android:key="main_fab"
|
android:title="@string/main_screen_sections" />
|
||||||
android:summary="@string/main_screen_fab_summary"
|
|
||||||
android:title="@string/main_screen_fab" />
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
<SwitchPreferenceCompat
|
android:key="main_fab"
|
||||||
android:defaultValue="true"
|
android:summary="@string/main_screen_fab_summary"
|
||||||
android:key="nav_labels"
|
android:title="@string/main_screen_fab" />
|
||||||
android:title="@string/show_labels_in_navbar" />
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
<SwitchPreferenceCompat
|
android:defaultValue="true"
|
||||||
android:defaultValue="false"
|
android:key="nav_labels"
|
||||||
android:key="nav_pinned"
|
android:title="@string/show_labels_in_navbar" />
|
||||||
android:summary="@string/pin_navigation_ui_summary"
|
|
||||||
android:title="@string/pin_navigation_ui" />
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
<SwitchPreferenceCompat
|
android:key="nav_pinned"
|
||||||
android:defaultValue="false"
|
android:summary="@string/pin_navigation_ui_summary"
|
||||||
android:key="exit_confirm"
|
android:title="@string/pin_navigation_ui" />
|
||||||
android:summary="@string/exit_confirmation_summary"
|
|
||||||
android:title="@string/exit_confirmation" />
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
</PreferenceCategory>
|
android:key="exit_confirm"
|
||||||
|
android:summary="@string/exit_confirmation_summary"
|
||||||
|
android:title="@string/exit_confirmation" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:key="dynamic_shortcuts"
|
||||||
|
android:summary="@string/history_shortcuts_summary"
|
||||||
|
android:title="@string/history_shortcuts" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/privacy">
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="protect_app"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/protect_application_summary"
|
||||||
|
android:title="@string/protect_application" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="allow"
|
||||||
|
android:entries="@array/screenshots_policy"
|
||||||
|
android:key="screenshots_policy"
|
||||||
|
android:title="@string/screenshots_policy"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:title="@string/backup_restore">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="backup"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/backup_information"
|
||||||
|
android:title="@string/create_backup" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="restore"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/restore_summary"
|
||||||
|
android:title="@string/restore_backup" />
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
android:fragment="org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment"
|
||||||
|
android:key="backup_periodic"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/periodic_backups" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:title="@string/network">
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:defaultValue="0"
|
|
||||||
android:entries="@array/network_policy"
|
|
||||||
android:entryValues="@array/values_network_policy"
|
|
||||||
android:key="prefetch_content"
|
|
||||||
android:title="@string/prefetch_content"
|
|
||||||
app:useSimpleSummaryProvider="true"
|
|
||||||
tools:isPreferenceVisible="true" />
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:defaultValue="2"
|
|
||||||
android:entries="@array/network_policy"
|
|
||||||
android:entryValues="@array/values_network_policy"
|
|
||||||
android:key="pages_preload"
|
|
||||||
android:title="@string/preload_pages"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.koitharu.kotatsu.settings.ProxySettingsFragment"
|
|
||||||
android:key="proxy"
|
|
||||||
android:title="@string/proxy"
|
|
||||||
app:allowDividerAbove="true" />
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:entries="@array/doh_providers"
|
|
||||||
android:key="doh"
|
|
||||||
android:title="@string/dns_over_https"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="adblock"
|
|
||||||
android:summary="@string/adblock_summary"
|
|
||||||
android:title="@string/adblock" />
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:defaultValue="-1"
|
|
||||||
android:entries="@array/image_proxies"
|
|
||||||
android:entryValues="@array/values_image_proxies"
|
|
||||||
android:key="images_proxy_2"
|
|
||||||
android:title="@string/images_proxy_title"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="mirror_switching"
|
|
||||||
android:summary="@string/mirror_switching_summary"
|
|
||||||
android:title="@string/mirror_switching" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:key="ssl_bypass"
|
|
||||||
android:summary="@string/ignore_ssl_errors_summary"
|
|
||||||
android:title="@string/ignore_ssl_errors" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:key="no_offline"
|
|
||||||
android:summary="@string/disable_connectivity_check_summary"
|
|
||||||
android:title="@string/disable_connectivity_check" />
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:title="@string/network">
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/storage_usage">
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.settings.userdata.storage.StorageUsagePreference android:key="storage_usage" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:fragment="org.koitharu.kotatsu.settings.userdata.storage.DataCleanupSettingsFragment"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/data_removal" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/network_policy"
|
||||||
|
android:entryValues="@array/values_network_policy"
|
||||||
|
android:key="prefetch_content"
|
||||||
|
android:title="@string/prefetch_content"
|
||||||
|
app:allowDividerAbove="true"
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
|
tools:isPreferenceVisible="true" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="2"
|
||||||
|
android:entries="@array/network_policy"
|
||||||
|
android:entryValues="@array/values_network_policy"
|
||||||
|
android:key="pages_preload"
|
||||||
|
android:title="@string/preload_pages"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
android:fragment="org.koitharu.kotatsu.settings.ProxySettingsFragment"
|
||||||
|
android:key="proxy"
|
||||||
|
android:title="@string/proxy"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:entries="@array/doh_providers"
|
||||||
|
android:key="doh"
|
||||||
|
android:title="@string/dns_over_https"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="-1"
|
||||||
|
android:entries="@array/image_proxies"
|
||||||
|
android:entryValues="@array/values_image_proxies"
|
||||||
|
android:key="images_proxy_2"
|
||||||
|
android:title="@string/images_proxy_title"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="ssl_bypass"
|
||||||
|
android:summary="@string/ignore_ssl_errors_summary"
|
||||||
|
android:title="@string/ignore_ssl_errors" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="no_offline"
|
||||||
|
android:summary="@string/disable_connectivity_check_summary"
|
||||||
|
android:title="@string/disable_connectivity_check" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="adblock"
|
||||||
|
android:summary="@string/adblock_summary"
|
||||||
|
android:title="@string/adblock" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
@ -1,59 +1,59 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.AppearanceSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.AppearanceSettingsFragment"
|
||||||
android:icon="@drawable/ic_appearance"
|
android:icon="@drawable/ic_appearance"
|
||||||
android:key="appearance"
|
android:key="appearance"
|
||||||
android:title="@string/appearance" />
|
android:title="@string/appearance" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment"
|
||||||
android:icon="@drawable/ic_manga_source"
|
android:icon="@drawable/ic_manga_source"
|
||||||
android:key="remote_sources"
|
android:key="remote_sources"
|
||||||
android:title="@string/remote_sources" />
|
android:title="@string/remote_sources" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.ReaderSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.ReaderSettingsFragment"
|
||||||
android:icon="@drawable/ic_book_page"
|
android:icon="@drawable/ic_book_page"
|
||||||
android:key="reader"
|
android:key="reader"
|
||||||
android:title="@string/reader_settings" />
|
android:title="@string/reader_settings" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.NetworkSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.StorageAndNetworkSettingsFragment"
|
||||||
android:icon="@drawable/ic_web"
|
android:icon="@drawable/ic_usage"
|
||||||
android:key="network"
|
android:key="network"
|
||||||
android:title="@string/network" />
|
android:title="@string/storage_and_network" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.DownloadsSettingsFragment"
|
||||||
android:icon="@drawable/ic_data_privacy"
|
android:icon="@drawable/ic_download"
|
||||||
android:key="userdata"
|
android:key="downloads"
|
||||||
android:title="@string/data_and_privacy" />
|
android:title="@string/downloads" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.DownloadsSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment"
|
||||||
android:icon="@drawable/ic_download"
|
android:icon="@drawable/ic_feed"
|
||||||
android:key="downloads"
|
android:key="tracker"
|
||||||
android:title="@string/downloads" />
|
android:title="@string/check_for_new_chapters" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.ServicesSettingsFragment"
|
||||||
android:icon="@drawable/ic_feed"
|
android:icon="@drawable/ic_services"
|
||||||
android:key="tracker"
|
android:key="services"
|
||||||
android:title="@string/check_for_new_chapters" />
|
android:title="@string/services" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.ServicesSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.userdata.BackupsSettingsFragment"
|
||||||
android:icon="@drawable/ic_services"
|
android:icon="@drawable/ic_backup_restore"
|
||||||
android:key="services"
|
android:key="userdata"
|
||||||
android:title="@string/services" />
|
android:title="@string/backup_restore" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.about.AboutSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.about.AboutSettingsFragment"
|
||||||
android:icon="@drawable/ic_info_outline"
|
android:icon="@drawable/ic_info_outline"
|
||||||
android:key="about"
|
android:key="about"
|
||||||
android:title="@string/about" />
|
android:title="@string/about" />
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@ -1,54 +1,66 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.preference.PreferenceScreen
|
<androidx.preference.PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:title="@string/remote_sources">
|
android:title="@string/remote_sources">
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="sources_sort_order"
|
android:key="sources_sort_order"
|
||||||
android:title="@string/sort_order"
|
android:title="@string/sort_order"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="sources_grid"
|
android:key="sources_grid"
|
||||||
android:title="@string/show_in_grid_view" />
|
android:title="@string/show_in_grid_view" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment"
|
android:fragment="org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment"
|
||||||
android:key="remote_sources"
|
android:key="remote_sources"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/manage_sources" />
|
android:title="@string/manage_sources" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="sources_enabled_all"
|
android:key="sources_enabled_all"
|
||||||
android:summary="@string/enable_all_sources_summary"
|
android:summary="@string/enable_all_sources_summary"
|
||||||
android:title="@string/enable_all_sources"
|
android:title="@string/enable_all_sources"
|
||||||
app:allowDividerAbove="true" />
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="sources_catalog"
|
android:key="sources_catalog"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/sources_catalog" />
|
android:title="@string/sources_catalog" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="no_nsfw"
|
android:key="no_nsfw"
|
||||||
android:summary="@string/disable_nsfw_summary"
|
android:summary="@string/disable_nsfw_summary"
|
||||||
android:title="@string/disable_nsfw" />
|
android:title="@string/disable_nsfw" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<ListPreference
|
||||||
android:defaultValue="true"
|
android:entries="@array/incognito_nsfw_options"
|
||||||
android:key="tags_warnings"
|
android:key="incognito_nsfw"
|
||||||
android:summary="@string/tags_warnings_summary"
|
android:title="@string/incognito_for_nsfw"
|
||||||
android:title="@string/tags_warnings" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:key="handle_links"
|
android:defaultValue="true"
|
||||||
android:persistent="false"
|
android:key="tags_warnings"
|
||||||
android:summary="@string/handle_links_summary"
|
android:summary="@string/tags_warnings_summary"
|
||||||
android:title="@string/handle_links"
|
android:title="@string/tags_warnings" />
|
||||||
app:allowDividerAbove="true" />
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="mirror_switching"
|
||||||
|
android:summary="@string/mirror_switching_summary"
|
||||||
|
android:title="@string/mirror_switching"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="handle_links"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/handle_links_summary"
|
||||||
|
android:title="@string/handle_links" />
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:title="@string/data_and_privacy">
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:key="protect_app"
|
|
||||||
android:persistent="false"
|
|
||||||
android:summary="@string/protect_application_summary"
|
|
||||||
android:title="@string/protect_application" />
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:defaultValue="allow"
|
|
||||||
android:entries="@array/screenshots_policy"
|
|
||||||
android:key="screenshots_policy"
|
|
||||||
android:title="@string/screenshots_policy"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:entries="@array/incognito_nsfw_options"
|
|
||||||
android:key="incognito_nsfw"
|
|
||||||
android:title="@string/incognito_for_nsfw"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="true"
|
|
||||||
android:key="dynamic_shortcuts"
|
|
||||||
android:summary="@string/history_shortcuts_summary"
|
|
||||||
android:title="@string/history_shortcuts" />
|
|
||||||
|
|
||||||
<MultiSelectListPreference
|
|
||||||
android:key="search_suggest_types"
|
|
||||||
android:title="@string/search_suggestions" />
|
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/backup_restore">
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:key="backup"
|
|
||||||
android:persistent="false"
|
|
||||||
android:summary="@string/backup_information"
|
|
||||||
android:title="@string/create_backup" />
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:key="restore"
|
|
||||||
android:persistent="false"
|
|
||||||
android:summary="@string/restore_summary"
|
|
||||||
android:title="@string/restore_backup" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment"
|
|
||||||
android:key="backup_periodic"
|
|
||||||
android:persistent="false"
|
|
||||||
android:title="@string/periodic_backups" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.koitharu.kotatsu.settings.userdata.storage.StorageManageSettingsFragment"
|
|
||||||
android:key="storage_usage"
|
|
||||||
android:title="@string/storage_usage"
|
|
||||||
app:allowDividerAbove="true" />
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
|
||||||
Loading…
Reference in New Issue