Storage usage preference

This commit is contained in:
Koitharu
2023-07-20 15:50:32 +03:00
parent 513aa1a285
commit bc273bfb8f
15 changed files with 153 additions and 466 deletions

View File

@@ -69,11 +69,14 @@ fun <I> ActivityResultLauncher<I>.resolve(context: Context, input: I): ResolveIn
return pm.resolveActivity(intent, 0)
}
fun <I> ActivityResultLauncher<I>.tryLaunch(input: I, options: ActivityOptionsCompat? = null): Boolean {
return runCatching {
launch(input, options)
}.isSuccess
}
fun <I> ActivityResultLauncher<I>.tryLaunch(
input: I,
options: ActivityOptionsCompat? = null,
): Boolean = runCatching {
launch(input, options)
}.onFailure { e ->
e.printStackTraceDebug()
}.isSuccess
fun SharedPreferences.observe() = callbackFlow<String> {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->

View File

@@ -32,6 +32,7 @@ import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourcesListFragment
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment
@AndroidEntryPoint
class SettingsActivity :

View File

@@ -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()
}
}

View File

@@ -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(),
),
)
}
}

View File

@@ -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,
)
}

View File

@@ -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
}
}

View File

@@ -1,48 +1,53 @@
package org.koitharu.kotatsu.settings.tools.views
package org.koitharu.kotatsu.settings.userdata
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.widget.TextViewCompat
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
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.core.ui.widgets.SegmentedBarView
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.databinding.LayoutMemoryUsageBinding
import org.koitharu.kotatsu.settings.tools.model.StorageUsage
import org.koitharu.kotatsu.databinding.PreferenceMemoryUsageBinding
import com.google.android.material.R as materialR
class MemoryUsageView @JvmOverloads constructor(
class StorageUsagePreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
attrs: AttributeSet? = null,
) : 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 var usage: StorageUsage? = null
init {
orientation = VERTICAL
bind(null)
layoutResource = R.layout.preference_memory_usage
isSelectable = false
isPersistent = false
}
fun setManageButtonOnClickListener(listener: OnClickListener?) {
binding.buttonManage.setOnClickListener(listener)
}
fun bind(usage: StorageUsage?) {
val storageSegment = SegmentedBarView.Segment(usage?.savedManga?.percent ?: 0f, segmentColor(com.google.android.material.R.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))
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val binding = PreferenceMemoryUsageBinding.bind(holder.itemView)
val storageSegment = SegmentedBarView.Segment(
usage?.savedManga?.percent ?: 0f,
segmentColor(materialR.attr.colorPrimary),
)
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) {
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(
item: StorageUsage.Item?,
@StringRes labelResId: Int,
@@ -91,7 +101,7 @@ class MemoryUsageView @JvmOverloads constructor(
val colorHex = String.format("%06x", context.getThemeColor(resId))
val hue = getHue(colorHex)
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)
}
}

View File

@@ -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.SharedPreferences
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.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.tryLaunch
import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
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)
}
}
findPreference<StorageUsagePreference>("storage_usage")?.let { pref ->
viewModel.storageUsage.observe(viewLifecycleOwner, pref)
}
viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->
preferenceScreen.forEach { pref ->
pref.isEnabled = pref.key !in keys
@@ -129,10 +131,7 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
}
AppSettings.KEY_RESTORE -> {
try {
backupSelectCall.launch(arrayOf("*/*"))
} catch (e: ActivityNotFoundException) {
e.printStackTraceDebug()
if (!backupSelectCall.tryLaunch(arrayOf("*/*"))) {
Snackbar.make(
listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,
).show()

View File

@@ -1,7 +1,9 @@
package org.koitharu.kotatsu.settings
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.update
import kotlinx.coroutines.runInterruptible
@@ -36,6 +38,9 @@ class UserDataSettingsViewModel @Inject constructor(
val feedItemsCount = MutableStateFlow(-1)
val httpCacheSize = MutableStateFlow(-1L)
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
val storageUsage = MutableStateFlow<StorageUsage?>(null)
private var storageUsageJob: Job? = null
init {
CacheDir.values().forEach {
@@ -55,6 +60,7 @@ class UserDataSettingsViewModel @Inject constructor(
launchJob(Dispatchers.Default) {
httpCacheSize.value = runInterruptible { httpCache.size() }
}
loadStorageUsage()
}
fun clearCache(key: String, cache: CacheDir) {
@@ -63,6 +69,7 @@ class UserDataSettingsViewModel @Inject constructor(
loadingKeys.update { it + key }
storageManager.clearCache(cache)
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
loadStorageUsage()
} finally {
loadingKeys.update { it - key }
}
@@ -78,6 +85,7 @@ class UserDataSettingsViewModel @Inject constructor(
httpCache.size()
}
httpCacheSize.value = size
loadStorageUsage()
} finally {
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))
}
}
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(),
),
)
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,42 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
<LinearLayout
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="wrap_content"
tools:ignore="RtlSymmetry"
tools:orientation="vertical"
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>
android:orientation="vertical"
tools:ignore="RtlSymmetry">
<org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="18dp"
android:layout_marginStart="@dimen/screen_padding"
android:layout_marginTop="@dimen/screen_padding"
android:layout_marginEnd="@dimen/screen_padding"
android:background="?colorSecondaryContainer" />
@@ -89,4 +66,4 @@
app:drawableStartCompat="@drawable/bg_rounded_square"
app:drawableTint="?colorSecondaryContainer" />
</merge>
</LinearLayout>

View File

@@ -27,7 +27,7 @@
android:title="@string/network" />
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.UserDataSettingsFragment"
android:fragment="org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment"
android:icon="@drawable/ic_data_privacy"
android:key="userdata"
android:title="@string/data_and_privacy" />

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<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
android:key="protect_app"
@@ -35,13 +36,16 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/data_deletion">
<PreferenceCategory android:title="@string/storage_usage">
<org.koitharu.kotatsu.settings.userdata.StorageUsagePreference android:key="storage_usage" />
<Preference
android:key="search_history_clear"
android:persistent="false"
android:summary="@string/loading_"
android:title="@string/clear_search_history" />
android:title="@string/clear_search_history"
app:allowDividerAbove="true" />
<Preference
android:key="updates_feed_clear"