Storage usage preference
This commit is contained in:
@@ -13,6 +13,7 @@ disabled_rules = no-wildcard-imports, no-unused-imports
|
|||||||
|
|
||||||
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
|
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
|
||||||
ij_continuation_indent_size = 4
|
ij_continuation_indent_size = 4
|
||||||
|
ij_xml_attribute_wrap = on_every_item
|
||||||
|
|
||||||
[{*.kt,*.kts}]
|
[{*.kt,*.kts}]
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
|
|||||||
@@ -69,11 +69,14 @@ fun <I> ActivityResultLauncher<I>.resolve(context: Context, input: I): ResolveIn
|
|||||||
return pm.resolveActivity(intent, 0)
|
return pm.resolveActivity(intent, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <I> ActivityResultLauncher<I>.tryLaunch(input: I, options: ActivityOptionsCompat? = null): Boolean {
|
fun <I> ActivityResultLauncher<I>.tryLaunch(
|
||||||
return runCatching {
|
input: I,
|
||||||
launch(input, options)
|
options: ActivityOptionsCompat? = null,
|
||||||
}.isSuccess
|
): Boolean = runCatching {
|
||||||
}
|
launch(input, options)
|
||||||
|
}.onFailure { e ->
|
||||||
|
e.printStackTraceDebug()
|
||||||
|
}.isSuccess
|
||||||
|
|
||||||
fun SharedPreferences.observe() = callbackFlow<String> {
|
fun SharedPreferences.observe() = callbackFlow<String> {
|
||||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
|
|||||||
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
|
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
|
||||||
import org.koitharu.kotatsu.settings.sources.SourcesListFragment
|
import org.koitharu.kotatsu.settings.sources.SourcesListFragment
|
||||||
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
|
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
|
||||||
|
import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SettingsActivity :
|
class SettingsActivity :
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.tools
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.CompoundButton
|
|
||||||
import androidx.core.graphics.Insets
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.github.AppVersion
|
|
||||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.setChecked
|
|
||||||
import org.koitharu.kotatsu.databinding.FragmentToolsBinding
|
|
||||||
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
|
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
|
||||||
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class ToolsFragment :
|
|
||||||
BaseFragment<FragmentToolsBinding>(),
|
|
||||||
CompoundButton.OnCheckedChangeListener,
|
|
||||||
View.OnClickListener {
|
|
||||||
|
|
||||||
private val viewModel by viewModels<ToolsViewModel>()
|
|
||||||
|
|
||||||
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentToolsBinding {
|
|
||||||
return FragmentToolsBinding.inflate(inflater, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewBindingCreated(binding: FragmentToolsBinding, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
|
||||||
binding.buttonSettings.setOnClickListener(this)
|
|
||||||
binding.buttonDownloads.setOnClickListener(this)
|
|
||||||
binding.cardUpdate.buttonChangelog.setOnClickListener(this)
|
|
||||||
binding.cardUpdate.buttonDownload.setOnClickListener(this)
|
|
||||||
binding.switchIncognito.setOnCheckedChangeListener(this)
|
|
||||||
binding.memoryUsageView.setManageButtonOnClickListener(this)
|
|
||||||
|
|
||||||
binding.chart?.setDataChart(
|
|
||||||
listOf(
|
|
||||||
Pair(5, "Категория 1"),
|
|
||||||
Pair(3, "Категория 2"),
|
|
||||||
Pair(7, "Категория 3"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
binding.chart?.startAnimation()
|
|
||||||
|
|
||||||
viewModel.isIncognitoModeEnabled.observe(viewLifecycleOwner) {
|
|
||||||
binding.switchIncognito.setChecked(it, false)
|
|
||||||
}
|
|
||||||
viewModel.storageUsage.observe(viewLifecycleOwner) {
|
|
||||||
binding.memoryUsageView.bind(it)
|
|
||||||
}
|
|
||||||
viewModel.appUpdate.observe(viewLifecycleOwner, ::onAppUpdateAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
when (v.id) {
|
|
||||||
R.id.button_settings -> startActivity(SettingsActivity.newIntent(v.context))
|
|
||||||
R.id.button_manage -> startActivity(SettingsActivity.newHistorySettingsIntent(v.context))
|
|
||||||
R.id.button_downloads -> startActivity(DownloadsActivity.newIntent(v.context))
|
|
||||||
R.id.button_download -> {
|
|
||||||
val url = viewModel.appUpdate.value?.apkUrl ?: return
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
|
||||||
intent.data = url.toUri()
|
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.open_in_browser)))
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.button_changelog -> {
|
|
||||||
val version = viewModel.appUpdate.value ?: return
|
|
||||||
AppUpdateDialog(v.context).show(version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCheckedChanged(button: CompoundButton?, isChecked: Boolean) {
|
|
||||||
viewModel.toggleIncognitoMode(isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
|
||||||
requireViewBinding().root.updatePadding(
|
|
||||||
bottom = insets.bottom,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onAppUpdateAvailable(version: AppVersion?) {
|
|
||||||
if (version == null) {
|
|
||||||
requireViewBinding().cardUpdate.root.isVisible = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requireViewBinding().cardUpdate.textSecondary.text = getString(R.string.new_version_s, version.name)
|
|
||||||
requireViewBinding().cardUpdate.root.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun newInstance() = ToolsFragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.tools
|
|
||||||
|
|
||||||
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.github.AppUpdateRepository
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
|
||||||
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.tools.model.StorageUsage
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class ToolsViewModel @Inject constructor(
|
|
||||||
private val storageManager: LocalStorageManager,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
appUpdateRepository: AppUpdateRepository,
|
|
||||||
) : BaseViewModel() {
|
|
||||||
|
|
||||||
val appUpdate = appUpdateRepository.observeAvailableUpdate()
|
|
||||||
|
|
||||||
val storageUsage: StateFlow<StorageUsage?> = flow {
|
|
||||||
emit(collectStorageUsage())
|
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
|
||||||
|
|
||||||
val isIncognitoModeEnabled = settings.observeAsStateFlow(
|
|
||||||
scope = viewModelScope + Dispatchers.Default,
|
|
||||||
key = AppSettings.KEY_INCOGNITO_MODE,
|
|
||||||
valueProducer = { isIncognitoModeEnabled },
|
|
||||||
)
|
|
||||||
|
|
||||||
fun toggleIncognitoMode(isEnabled: Boolean) {
|
|
||||||
settings.isIncognitoModeEnabled = isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun collectStorageUsage(): 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(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.tools.model
|
|
||||||
|
|
||||||
class StorageUsage(
|
|
||||||
val savedManga: Item,
|
|
||||||
val pagesCache: Item,
|
|
||||||
val otherCache: Item,
|
|
||||||
val available: Item,
|
|
||||||
) {
|
|
||||||
|
|
||||||
class Item(
|
|
||||||
val bytes: Long,
|
|
||||||
val percent: Float,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
|
class StorageUsage(
|
||||||
|
val savedManga: Item,
|
||||||
|
val pagesCache: Item,
|
||||||
|
val otherCache: Item,
|
||||||
|
val available: Item,
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Item(
|
||||||
|
val bytes: Long,
|
||||||
|
val percent: Float,
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as Item
|
||||||
|
|
||||||
|
if (bytes != other.bytes) return false
|
||||||
|
return percent == other.percent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = bytes.hashCode()
|
||||||
|
result = 31 * result + percent.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as StorageUsage
|
||||||
|
|
||||||
|
if (savedManga != other.savedManga) return false
|
||||||
|
if (pagesCache != other.pagesCache) return false
|
||||||
|
if (otherCache != other.otherCache) return false
|
||||||
|
return available == other.available
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = savedManga.hashCode()
|
||||||
|
result = 31 * result + pagesCache.hashCode()
|
||||||
|
result = 31 * result + otherCache.hashCode()
|
||||||
|
result = 31 * result + available.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +1,53 @@
|
|||||||
package org.koitharu.kotatsu.settings.tools.views
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceViewHolder
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import okio.ByteString.Companion.decodeHex
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
|
import org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
|
||||||
import org.koitharu.kotatsu.core.util.FileSize
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
import org.koitharu.kotatsu.databinding.LayoutMemoryUsageBinding
|
import org.koitharu.kotatsu.databinding.PreferenceMemoryUsageBinding
|
||||||
import org.koitharu.kotatsu.settings.tools.model.StorageUsage
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
class StorageUsagePreference @JvmOverloads constructor(
|
||||||
class MemoryUsageView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null
|
attrs: AttributeSet? = null,
|
||||||
) : LinearLayout(context, attrs) {
|
) : Preference(context, attrs), FlowCollector<StorageUsage?> {
|
||||||
|
|
||||||
private val binding = LayoutMemoryUsageBinding.inflate(LayoutInflater.from(context), this)
|
|
||||||
private val labelPattern = context.getString(R.string.memory_usage_pattern)
|
private val labelPattern = context.getString(R.string.memory_usage_pattern)
|
||||||
|
private var usage: StorageUsage? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
orientation = VERTICAL
|
layoutResource = R.layout.preference_memory_usage
|
||||||
bind(null)
|
isSelectable = false
|
||||||
|
isPersistent = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setManageButtonOnClickListener(listener: OnClickListener?) {
|
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||||
binding.buttonManage.setOnClickListener(listener)
|
super.onBindViewHolder(holder)
|
||||||
}
|
val binding = PreferenceMemoryUsageBinding.bind(holder.itemView)
|
||||||
|
val storageSegment = SegmentedBarView.Segment(
|
||||||
fun bind(usage: StorageUsage?) {
|
usage?.savedManga?.percent ?: 0f,
|
||||||
val storageSegment = SegmentedBarView.Segment(usage?.savedManga?.percent ?: 0f, segmentColor(com.google.android.material.R.attr.colorPrimary))
|
segmentColor(materialR.attr.colorPrimary),
|
||||||
val pagesSegment = SegmentedBarView.Segment(usage?.pagesCache?.percent ?: 0f, segmentColor(com.google.android.material.R.attr.colorOnPrimaryContainer))
|
)
|
||||||
val otherSegment = SegmentedBarView.Segment(usage?.otherCache?.percent ?: 0f, segmentColor(com.google.android.material.R.attr.colorTertiary))
|
val pagesSegment = SegmentedBarView.Segment(
|
||||||
|
usage?.pagesCache?.percent ?: 0f,
|
||||||
|
segmentColor(materialR.attr.colorSecondary),
|
||||||
|
)
|
||||||
|
val otherSegment = SegmentedBarView.Segment(
|
||||||
|
usage?.otherCache?.percent ?: 0f,
|
||||||
|
segmentColor(materialR.attr.colorTertiary),
|
||||||
|
)
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
bar.animateSegments(listOf(storageSegment, pagesSegment, otherSegment).filter { it.percent > 0f })
|
bar.animateSegments(listOf(storageSegment, pagesSegment, otherSegment).filter { it.percent > 0f })
|
||||||
@@ -57,6 +62,11 @@ class MemoryUsageView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun emit(value: StorageUsage?) {
|
||||||
|
usage = value
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
private fun formatLabel(
|
private fun formatLabel(
|
||||||
item: StorageUsage.Item?,
|
item: StorageUsage.Item?,
|
||||||
@StringRes labelResId: Int,
|
@StringRes labelResId: Int,
|
||||||
@@ -91,7 +101,7 @@ class MemoryUsageView @JvmOverloads constructor(
|
|||||||
val colorHex = String.format("%06x", context.getThemeColor(resId))
|
val colorHex = String.format("%06x", context.getThemeColor(resId))
|
||||||
val hue = getHue(colorHex)
|
val hue = getHue(colorHex)
|
||||||
val color = ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
|
val color = ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
|
||||||
val backgroundColor = context.getThemeColor(com.google.android.material.R.attr.colorSurfaceContainerHigh)
|
val backgroundColor = context.getThemeColor(materialR.attr.colorSurfaceContainerHigh)
|
||||||
return MaterialColors.harmonize(color, backgroundColor)
|
return MaterialColors.harmonize(color, backgroundColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.settings
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -28,7 +27,7 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
|||||||
import org.koitharu.kotatsu.core.util.FileSize
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
|
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
|
||||||
@@ -76,6 +75,9 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, it, it)
|
pref.summary = pref.context.resources.getQuantityString(R.plurals.items, it, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
findPreference<StorageUsagePreference>("storage_usage")?.let { pref ->
|
||||||
|
viewModel.storageUsage.observe(viewLifecycleOwner, pref)
|
||||||
|
}
|
||||||
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
|
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
|
||||||
preferenceScreen.forEach { pref ->
|
preferenceScreen.forEach { pref ->
|
||||||
pref.isEnabled = pref.key !in keys
|
pref.isEnabled = pref.key !in keys
|
||||||
@@ -129,10 +131,7 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppSettings.KEY_RESTORE -> {
|
AppSettings.KEY_RESTORE -> {
|
||||||
try {
|
if (!backupSelectCall.tryLaunch(arrayOf("*/*"))) {
|
||||||
backupSelectCall.launch(arrayOf("*/*"))
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
e.printStackTraceDebug()
|
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
|
||||||
).show()
|
).show()
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.koitharu.kotatsu.settings
|
package org.koitharu.kotatsu.settings.userdata
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.runInterruptible
|
||||||
@@ -36,6 +38,9 @@ class UserDataSettingsViewModel @Inject constructor(
|
|||||||
val feedItemsCount = MutableStateFlow(-1)
|
val feedItemsCount = MutableStateFlow(-1)
|
||||||
val httpCacheSize = MutableStateFlow(-1L)
|
val httpCacheSize = MutableStateFlow(-1L)
|
||||||
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
|
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
|
||||||
|
val storageUsage = MutableStateFlow<StorageUsage?>(null)
|
||||||
|
|
||||||
|
private var storageUsageJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
CacheDir.values().forEach {
|
CacheDir.values().forEach {
|
||||||
@@ -55,6 +60,7 @@ class UserDataSettingsViewModel @Inject constructor(
|
|||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
httpCacheSize.value = runInterruptible { httpCache.size() }
|
httpCacheSize.value = runInterruptible { httpCache.size() }
|
||||||
}
|
}
|
||||||
|
loadStorageUsage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearCache(key: String, cache: CacheDir) {
|
fun clearCache(key: String, cache: CacheDir) {
|
||||||
@@ -63,6 +69,7 @@ class UserDataSettingsViewModel @Inject constructor(
|
|||||||
loadingKeys.update { it + key }
|
loadingKeys.update { it + key }
|
||||||
storageManager.clearCache(cache)
|
storageManager.clearCache(cache)
|
||||||
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
||||||
|
loadStorageUsage()
|
||||||
} finally {
|
} finally {
|
||||||
loadingKeys.update { it - key }
|
loadingKeys.update { it - key }
|
||||||
}
|
}
|
||||||
@@ -78,6 +85,7 @@ class UserDataSettingsViewModel @Inject constructor(
|
|||||||
httpCache.size()
|
httpCache.size()
|
||||||
}
|
}
|
||||||
httpCacheSize.value = size
|
httpCacheSize.value = size
|
||||||
|
loadStorageUsage()
|
||||||
} finally {
|
} finally {
|
||||||
loadingKeys.update { it - AppSettings.KEY_HTTP_CACHE_CLEAR }
|
loadingKeys.update { it - AppSettings.KEY_HTTP_CACHE_CLEAR }
|
||||||
}
|
}
|
||||||
@@ -106,4 +114,34 @@ class UserDataSettingsViewModel @Inject constructor(
|
|||||||
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadStorageUsage() {
|
||||||
|
val prevJob = storageUsageJob
|
||||||
|
storageUsageJob = 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(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
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:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
|
||||||
android:id="@+id/guideline"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintGuide_percent="0.5" />
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/card_update"
|
|
||||||
layout="@layout/layout_app_update"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="@dimen/margin_normal"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.tools.views.MemoryUsageView
|
|
||||||
android:id="@+id/memory_usage_view"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/guideline"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
|
||||||
android:id="@+id/switch_incognito"
|
|
||||||
style="?listItemTextViewStyle"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:drawableStart="@drawable/ic_incognito"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
|
||||||
android:text="@string/incognito_mode"
|
|
||||||
android:textAppearance="?attr/textAppearanceButton"
|
|
||||||
android:textColor="?attr/colorControlNormal"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/card_update"
|
|
||||||
app:layout_goneMarginTop="8dp" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
|
||||||
android:id="@+id/button_downloads"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
|
||||||
android:drawableStart="@drawable/ic_download"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
|
||||||
android:text="@string/downloads"
|
|
||||||
android:textAppearance="?attr/textAppearanceButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/switch_incognito" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
|
||||||
android:id="@+id/button_settings"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
|
||||||
android:drawableStart="@drawable/ic_settings"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
|
||||||
android:text="@string/settings"
|
|
||||||
android:textAppearance="?attr/textAppearanceButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/button_downloads" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
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:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/card_update"
|
|
||||||
layout="@layout/layout_app_update"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
|
||||||
app:cardBackgroundColor="?attr/colorSurfaceContainerLow"
|
|
||||||
app:cardCornerRadius="21dp">
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.PieChart
|
|
||||||
android:id="@+id/chart"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:layout_marginVertical="16dp"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
app:pieChartCircleSectionSpace="6"
|
|
||||||
app:pieChartCircleStrokeWidth="18dp"
|
|
||||||
app:pieChartColors="@array/chart_colors"
|
|
||||||
app:pieChartTextAmount="Всего"
|
|
||||||
app:pieChartTextAmountColor="?attr/colorControlNormal"
|
|
||||||
app:pieChartTextDescriptionColor="?android:attr/textColorHint"
|
|
||||||
app:pieChartTextDescriptionSize="14sp"
|
|
||||||
app:pieChartTextNumberColor="?attr/colorControlNormal"
|
|
||||||
app:pieChartTextNumberSize="16sp" />
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
|
||||||
app:cardBackgroundColor="?attr/colorSurfaceContainerLow"
|
|
||||||
app:cardCornerRadius="21dp">
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.settings.tools.views.MemoryUsageView
|
|
||||||
android:id="@+id/memory_usage_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
|
||||||
app:cardBackgroundColor="?attr/colorSurfaceContainerLow"
|
|
||||||
app:cardCornerRadius="21dp"
|
|
||||||
app:contentPaddingBottom="8dp"
|
|
||||||
app:contentPaddingTop="8dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
|
||||||
android:id="@+id/switch_incognito"
|
|
||||||
style="?listItemTextViewStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
|
||||||
android:drawableStart="@drawable/ic_incognito"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
|
||||||
android:text="@string/incognito_mode"
|
|
||||||
android:textAppearance="?attr/textAppearanceButton"
|
|
||||||
android:textColor="?attr/colorControlNormal" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
|
||||||
android:id="@+id/button_downloads"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
|
||||||
android:drawableStart="@drawable/ic_download"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
|
||||||
android:text="@string/downloads"
|
|
||||||
android:textAppearance="?attr/textAppearanceButton" />
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
|
||||||
android:id="@+id/button_settings"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
|
||||||
android:drawableStart="@drawable/ic_settings"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
|
||||||
android:text="@string/settings"
|
|
||||||
android:textAppearance="?attr/textAppearanceButton" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
@@ -1,42 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<merge
|
<LinearLayout
|
||||||
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"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:ignore="RtlSymmetry"
|
android:orientation="vertical"
|
||||||
tools:orientation="vertical"
|
tools:ignore="RtlSymmetry">
|
||||||
tools:parentTag="android.widget.LinearLayout">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/screen_padding"
|
|
||||||
android:layout_marginTop="4dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/storage_usage"
|
|
||||||
android:textAppearance="?textAppearanceTitleMedium" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/button_manage"
|
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="@string/manage" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
|
<org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
|
||||||
android:id="@+id/bar"
|
android:id="@+id/bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
android:layout_marginStart="@dimen/screen_padding"
|
android:layout_marginStart="@dimen/screen_padding"
|
||||||
|
android:layout_marginTop="@dimen/screen_padding"
|
||||||
android:layout_marginEnd="@dimen/screen_padding"
|
android:layout_marginEnd="@dimen/screen_padding"
|
||||||
android:background="?colorSecondaryContainer" />
|
android:background="?colorSecondaryContainer" />
|
||||||
|
|
||||||
@@ -89,4 +66,4 @@
|
|||||||
app:drawableStartCompat="@drawable/bg_rounded_square"
|
app:drawableStartCompat="@drawable/bg_rounded_square"
|
||||||
app:drawableTint="?colorSecondaryContainer" />
|
app:drawableTint="?colorSecondaryContainer" />
|
||||||
|
|
||||||
</merge>
|
</LinearLayout>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
android:title="@string/network" />
|
android:title="@string/network" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.UserDataSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment"
|
||||||
android:icon="@drawable/ic_data_privacy"
|
android:icon="@drawable/ic_data_privacy"
|
||||||
android:key="userdata"
|
android:key="userdata"
|
||||||
android:title="@string/data_and_privacy" />
|
android:title="@string/data_and_privacy" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?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">
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:key="protect_app"
|
android:key="protect_app"
|
||||||
@@ -35,13 +36,16 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/data_deletion">
|
<PreferenceCategory android:title="@string/storage_usage">
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.settings.userdata.StorageUsagePreference android:key="storage_usage" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="search_history_clear"
|
android:key="search_history_clear"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/loading_"
|
android:summary="@string/loading_"
|
||||||
android:title="@string/clear_search_history" />
|
android:title="@string/clear_search_history"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="updates_feed_clear"
|
android:key="updates_feed_clear"
|
||||||
|
|||||||
Reference in New Issue
Block a user