|
|
|
|
@@ -10,35 +10,29 @@ import androidx.activity.result.ActivityResultCallback
|
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
|
import androidx.appcompat.app.AppCompatDelegate
|
|
|
|
|
import androidx.core.view.postDelayed
|
|
|
|
|
import androidx.lifecycle.Lifecycle
|
|
|
|
|
import androidx.fragment.app.viewModels
|
|
|
|
|
import androidx.preference.Preference
|
|
|
|
|
import androidx.preference.TwoStatePreference
|
|
|
|
|
import androidx.preference.forEach
|
|
|
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
|
|
|
import com.google.android.material.snackbar.Snackbar
|
|
|
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
|
|
|
import kotlinx.coroutines.CancellationException
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import kotlinx.coroutines.runInterruptible
|
|
|
|
|
import okhttp3.Cache
|
|
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
|
|
|
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
|
|
|
|
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
|
|
|
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
|
|
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
|
|
|
|
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
|
|
|
|
|
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
|
|
|
|
import org.koitharu.kotatsu.core.util.FileSize
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.awaitStateAtLeast
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.observe
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
|
|
|
|
import org.koitharu.kotatsu.local.data.CacheDir
|
|
|
|
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
|
|
|
|
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
|
|
|
|
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
|
|
|
|
|
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
|
|
|
|
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
|
|
|
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
|
|
@AndroidEntryPoint
|
|
|
|
|
@@ -46,27 +40,14 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|
|
|
|
SharedPreferences.OnSharedPreferenceChangeListener,
|
|
|
|
|
ActivityResultCallback<Uri?> {
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var trackerRepo: TrackingRepository
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var searchRepository: MangaSearchRepository
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var storageManager: LocalStorageManager
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var cookieJar: MutableCookieJar
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var cache: Cache
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var appShortcutManager: AppShortcutManager
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var activityRecreationHandle: ActivityRecreationHandle
|
|
|
|
|
|
|
|
|
|
private val viewModel: UserDataSettingsViewModel by viewModels()
|
|
|
|
|
|
|
|
|
|
private val backupSelectCall = registerForActivityResult(
|
|
|
|
|
ActivityResultContracts.OpenDocument(),
|
|
|
|
|
this,
|
|
|
|
|
@@ -82,23 +63,26 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|
|
|
|
|
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindSummaryToCacheSize(CacheDir.PAGES)
|
|
|
|
|
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindSummaryToCacheSize(CacheDir.THUMBS)
|
|
|
|
|
findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindSummaryToHttpCacheSize()
|
|
|
|
|
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 ->
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
lifecycle.awaitStateAtLeast(Lifecycle.State.RESUMED)
|
|
|
|
|
val items = searchRepository.getSearchHistoryCount()
|
|
|
|
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, items, items)
|
|
|
|
|
viewModel.searchHistoryCount.observe(viewLifecycleOwner) {
|
|
|
|
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, it, it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
findPreference<Preference>(AppSettings.KEY_UPDATES_FEED_CLEAR)?.let { pref ->
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
lifecycle.awaitStateAtLeast(Lifecycle.State.RESUMED)
|
|
|
|
|
val items = trackerRepo.getLogsCount()
|
|
|
|
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, items, items)
|
|
|
|
|
viewModel.feedItemsCount.observe(viewLifecycleOwner) {
|
|
|
|
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, it, it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
|
|
|
|
|
preferenceScreen.forEach { pref ->
|
|
|
|
|
pref.isEnabled = pref.key !in keys
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))
|
|
|
|
|
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))
|
|
|
|
|
settings.subscribe(this)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -110,12 +94,12 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|
|
|
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
|
|
|
|
return when (preference.key) {
|
|
|
|
|
AppSettings.KEY_PAGES_CACHE_CLEAR -> {
|
|
|
|
|
clearCache(preference, CacheDir.PAGES)
|
|
|
|
|
viewModel.clearCache(preference.key, CacheDir.PAGES)
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
|
|
|
|
|
clearCache(preference, CacheDir.THUMBS)
|
|
|
|
|
viewModel.clearCache(preference.key, CacheDir.THUMBS)
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -125,26 +109,17 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
|
|
|
|
|
clearSearchHistory(preference)
|
|
|
|
|
clearSearchHistory()
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AppSettings.KEY_HTTP_CACHE_CLEAR -> {
|
|
|
|
|
clearHttpCache()
|
|
|
|
|
viewModel.clearHttpCache()
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AppSettings.KEY_UPDATES_FEED_CLEAR -> {
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
trackerRepo.clearLogs()
|
|
|
|
|
preference.summary = preference.context.resources
|
|
|
|
|
.getQuantityString(R.plurals.items, 0, 0)
|
|
|
|
|
Snackbar.make(
|
|
|
|
|
view ?: return@launch,
|
|
|
|
|
R.string.updates_feed_cleared,
|
|
|
|
|
Snackbar.LENGTH_SHORT,
|
|
|
|
|
).show()
|
|
|
|
|
}
|
|
|
|
|
viewModel.clearUpdatesFeed()
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -208,71 +183,23 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun clearCache(preference: Preference, cache: CacheDir) {
|
|
|
|
|
val ctx = preference.context.applicationContext
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
try {
|
|
|
|
|
preference.isEnabled = false
|
|
|
|
|
storageManager.clearCache(cache)
|
|
|
|
|
val size = storageManager.computeCacheSize(cache)
|
|
|
|
|
preference.summary = FileSize.BYTES.format(ctx, size)
|
|
|
|
|
} catch (e: CancellationException) {
|
|
|
|
|
throw e
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
preference.summary = e.getDisplayMessage(ctx.resources)
|
|
|
|
|
} finally {
|
|
|
|
|
preference.isEnabled = true
|
|
|
|
|
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 Preference.bindSummaryToCacheSize(dir: CacheDir) = viewLifecycleScope.launch {
|
|
|
|
|
val size = storageManager.computeCacheSize(dir)
|
|
|
|
|
summary = FileSize.BYTES.format(context, size)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Preference.bindSummaryToHttpCacheSize() = viewLifecycleScope.launch {
|
|
|
|
|
val size = runInterruptible(Dispatchers.IO) { cache.size() }
|
|
|
|
|
summary = FileSize.BYTES.format(context, size)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun clearHttpCache() {
|
|
|
|
|
val preference = findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR) ?: return
|
|
|
|
|
val ctx = preference.context.applicationContext
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
try {
|
|
|
|
|
preference.isEnabled = false
|
|
|
|
|
val size = runInterruptible(Dispatchers.IO) {
|
|
|
|
|
cache.evictAll()
|
|
|
|
|
cache.size()
|
|
|
|
|
}
|
|
|
|
|
preference.summary = FileSize.BYTES.format(ctx, size)
|
|
|
|
|
} catch (e: CancellationException) {
|
|
|
|
|
throw e
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
preference.summary = e.getDisplayMessage(ctx.resources)
|
|
|
|
|
} finally {
|
|
|
|
|
preference.isEnabled = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun clearSearchHistory(preference: Preference) {
|
|
|
|
|
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) { _, _ ->
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
searchRepository.clearSearchHistory()
|
|
|
|
|
preference.summary = preference.context.resources
|
|
|
|
|
.getQuantityString(R.plurals.items, 0, 0)
|
|
|
|
|
Snackbar.make(
|
|
|
|
|
view ?: return@launch,
|
|
|
|
|
R.string.search_history_cleared,
|
|
|
|
|
Snackbar.LENGTH_SHORT,
|
|
|
|
|
).show()
|
|
|
|
|
}
|
|
|
|
|
viewModel.clearSearchHistory()
|
|
|
|
|
}.show()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -282,14 +209,7 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|
|
|
|
.setMessage(R.string.text_clear_cookies_prompt)
|
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
.setPositiveButton(R.string.clear) { _, _ ->
|
|
|
|
|
viewLifecycleScope.launch {
|
|
|
|
|
cookieJar.clear()
|
|
|
|
|
Snackbar.make(
|
|
|
|
|
listView ?: return@launch,
|
|
|
|
|
R.string.cookies_cleared,
|
|
|
|
|
Snackbar.LENGTH_SHORT,
|
|
|
|
|
).show()
|
|
|
|
|
}
|
|
|
|
|
viewModel.clearCookies()
|
|
|
|
|
}.show()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|