Storage screen
parent
98451e55e2
commit
2a6edad196
@ -0,0 +1,44 @@
|
|||||||
|
package org.xtimms.tokusho.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.selection.toggleable
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DialogCheckBoxItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.toggleable(
|
||||||
|
value = checked,
|
||||||
|
enabled = true,
|
||||||
|
onValueChange = { onClick() },
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
modifier = Modifier.clearAndSetSemantics { },
|
||||||
|
checked = checked, onCheckedChange = { onClick() },
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
package org.xtimms.tokusho.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
|
||||||
|
private val DialogVerticalPadding = PaddingValues(vertical = 24.dp)
|
||||||
|
private val IconPadding = PaddingValues(bottom = 16.dp)
|
||||||
|
private val DialogHorizontalPadding = PaddingValues(horizontal = 24.dp)
|
||||||
|
private val TitlePadding = PaddingValues(bottom = 16.dp)
|
||||||
|
private val TextPadding = PaddingValues(bottom = 24.dp)
|
||||||
|
private val ButtonsMainAxisSpacing = Arrangement.spacedBy(8.dp, Alignment.Start)
|
||||||
|
private val ButtonsCrossAxisSpacing = Arrangement.spacedBy(12.dp, Alignment.Top)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun TokushoDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
confirmButton: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
dismissButton: @Composable (() -> Unit)? = null,
|
||||||
|
icon: @Composable (() -> Unit)? = null,
|
||||||
|
title: @Composable (() -> Unit)? = null,
|
||||||
|
text: @Composable (() -> Unit)? = null,
|
||||||
|
shape: Shape = AlertDialogDefaults.shape,
|
||||||
|
containerColor: Color = AlertDialogDefaults.containerColor,
|
||||||
|
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
|
||||||
|
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
|
||||||
|
textContentColor: Color = AlertDialogDefaults.textContentColor,
|
||||||
|
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
|
||||||
|
properties: DialogProperties = DialogProperties()
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier,
|
||||||
|
properties = properties
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = shape,
|
||||||
|
color = containerColor,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(DialogVerticalPadding)
|
||||||
|
) {
|
||||||
|
icon?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides iconContentColor) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.padding(IconPadding)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides titleContentColor) {
|
||||||
|
val textStyle = MaterialTheme.typography.headlineSmall
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
Box(
|
||||||
|
// Align the title to the center when an icon is present.
|
||||||
|
Modifier
|
||||||
|
.padding(TitlePadding)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
.align(
|
||||||
|
if (icon == null) {
|
||||||
|
Alignment.Start
|
||||||
|
} else {
|
||||||
|
Alignment.CenterHorizontally
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
title()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text?.let {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides textContentColor) {
|
||||||
|
val textStyle =
|
||||||
|
MaterialTheme.typography.bodyMedium
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.weight(weight = 1f, fill = false)
|
||||||
|
.padding(TextPadding)
|
||||||
|
.align(Alignment.Start)
|
||||||
|
) {
|
||||||
|
text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End)
|
||||||
|
.padding(DialogHorizontalPadding)
|
||||||
|
) {
|
||||||
|
val textStyle =
|
||||||
|
MaterialTheme.typography.labelLarge
|
||||||
|
ProvideTextStyle(value = textStyle) {
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = ButtonsMainAxisSpacing,
|
||||||
|
verticalArrangement = ButtonsCrossAxisSpacing
|
||||||
|
) {
|
||||||
|
dismissButton?.invoke()
|
||||||
|
confirmButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
package org.xtimms.tokusho.sections.settings.storage
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.CleaningServices
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import org.xtimms.tokusho.R
|
||||||
|
import org.xtimms.tokusho.core.components.ConfirmButton
|
||||||
|
import org.xtimms.tokusho.core.components.DialogCheckBoxItem
|
||||||
|
import org.xtimms.tokusho.core.components.DismissButton
|
||||||
|
import org.xtimms.tokusho.core.components.TokushoDialog
|
||||||
|
import org.xtimms.tokusho.utils.FileSize
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CleanDialog(
|
||||||
|
onDismissRequest: () -> Unit = {},
|
||||||
|
isPagesCacheSelected: Boolean,
|
||||||
|
isThumbnailsCacheSelected: Boolean,
|
||||||
|
isNetworkCacheSelected: Boolean,
|
||||||
|
onConfirm: (isPagesCacheSelected: Boolean, isThumbnailCacheSelected: Boolean, isNetworkCacheSelected: Boolean) -> Unit = { _, _, _ -> }
|
||||||
|
) {
|
||||||
|
|
||||||
|
val viewModel: StorageViewModel = hiltViewModel()
|
||||||
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
var pagesCache by remember {
|
||||||
|
mutableStateOf(isPagesCacheSelected)
|
||||||
|
}
|
||||||
|
var thumbnailsCache by remember {
|
||||||
|
mutableStateOf(isThumbnailsCacheSelected)
|
||||||
|
}
|
||||||
|
var networkCache by remember {
|
||||||
|
mutableStateOf(isNetworkCacheSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
TokushoDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
ConfirmButton {
|
||||||
|
onConfirm(pagesCache, thumbnailsCache, networkCache)
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
DismissButton {
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.free_up_space
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
icon = { Icon(imageVector = Icons.Outlined.CleaningServices, contentDescription = null) },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp))
|
||||||
|
DialogCheckBoxItem(
|
||||||
|
text = stringResource(id = R.string.pages_cache),
|
||||||
|
checked = pagesCache
|
||||||
|
) {
|
||||||
|
pagesCache = !pagesCache
|
||||||
|
}
|
||||||
|
DialogCheckBoxItem(
|
||||||
|
text = stringResource(id = R.string.thumbnails_cache),
|
||||||
|
checked = thumbnailsCache
|
||||||
|
) {
|
||||||
|
thumbnailsCache = !thumbnailsCache
|
||||||
|
}
|
||||||
|
DialogCheckBoxItem(
|
||||||
|
text = stringResource(id = R.string.network_cache),
|
||||||
|
checked = networkCache
|
||||||
|
) {
|
||||||
|
networkCache = !networkCache
|
||||||
|
}
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp))
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
val summary = StringBuilder().run {
|
||||||
|
append(FileSize.BYTES.format(LocalContext.current, uiState.pagesCache + uiState.thumbnailsCache + uiState.httpCacheSize))
|
||||||
|
append("")
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.free_up_space_summary) + " " + summary,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp),
|
||||||
|
// style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun DirectoryPreferenceDialogPreview() {
|
||||||
|
CleanDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
isPagesCacheSelected = false,
|
||||||
|
isThumbnailsCacheSelected = false,
|
||||||
|
isNetworkCacheSelected = false
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package org.xtimms.tokusho.sections.settings.storage
|
||||||
|
|
||||||
|
import org.xtimms.tokusho.core.base.state.UiState
|
||||||
|
|
||||||
|
data class StorageUiState(
|
||||||
|
val pagesCache: Long = -1L,
|
||||||
|
val thumbnailsCache: Long = -1L,
|
||||||
|
val availableSpace: Long = -1L,
|
||||||
|
val httpCacheSize: Long = -1L,
|
||||||
|
override val isLoading: Boolean = false,
|
||||||
|
override val message: String? = null,
|
||||||
|
) : UiState() {
|
||||||
|
override fun setLoading(value: Boolean) = copy(isLoading = value)
|
||||||
|
override fun setMessage(value: String?) = copy(message = value)
|
||||||
|
}
|
||||||
@ -0,0 +1,114 @@
|
|||||||
|
package org.xtimms.tokusho.sections.settings.storage
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.AutoStories
|
||||||
|
import androidx.compose.material.icons.outlined.CleaningServices
|
||||||
|
import androidx.compose.material.icons.outlined.Image
|
||||||
|
import androidx.compose.material.icons.outlined.NetworkWifi
|
||||||
|
import androidx.compose.material.icons.outlined.SdStorage
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import org.xtimms.tokusho.R
|
||||||
|
import org.xtimms.tokusho.core.cache.CacheDir
|
||||||
|
import org.xtimms.tokusho.core.components.PreferenceStorageHeader
|
||||||
|
import org.xtimms.tokusho.core.components.PreferenceStorageItem
|
||||||
|
import org.xtimms.tokusho.core.components.PreferencesHintCard
|
||||||
|
import org.xtimms.tokusho.core.components.ScaffoldWithTopAppBar
|
||||||
|
|
||||||
|
const val STORAGE_DESTINATION = "storage"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StorageView(
|
||||||
|
navigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val viewModel: StorageViewModel = hiltViewModel()
|
||||||
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
var showCleanDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
ScaffoldWithTopAppBar(
|
||||||
|
title = stringResource(R.string.storage),
|
||||||
|
navigateBack = navigateBack
|
||||||
|
) { padding ->
|
||||||
|
if (!uiState.isLoading) LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(padding)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
PreferenceStorageHeader(
|
||||||
|
used = uiState.httpCacheSize + uiState.thumbnailsCache + uiState.pagesCache,
|
||||||
|
total = uiState.availableSpace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferencesHintCard(
|
||||||
|
title = stringResource(id = R.string.free_up_space),
|
||||||
|
description = stringResource(id = R.string.free_up_space_hint),
|
||||||
|
icon = Icons.Outlined.CleaningServices
|
||||||
|
) {
|
||||||
|
showCleanDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceStorageItem(
|
||||||
|
total = uiState.availableSpace,
|
||||||
|
title = stringResource(id = R.string.saved_manga),
|
||||||
|
icon = Icons.Outlined.SdStorage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceStorageItem(
|
||||||
|
total = uiState.availableSpace,
|
||||||
|
title = stringResource(id = R.string.pages_cache),
|
||||||
|
icon = Icons.Outlined.AutoStories,
|
||||||
|
used = uiState.pagesCache
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceStorageItem(
|
||||||
|
total = uiState.availableSpace,
|
||||||
|
title = stringResource(id = R.string.thumbnails_cache),
|
||||||
|
icon = Icons.Outlined.Image,
|
||||||
|
used = uiState.thumbnailsCache
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceStorageItem(
|
||||||
|
total = uiState.availableSpace,
|
||||||
|
title = stringResource(id = R.string.network_cache),
|
||||||
|
icon = Icons.Outlined.NetworkWifi,
|
||||||
|
used = uiState.httpCacheSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (showCleanDialog) {
|
||||||
|
CleanDialog(
|
||||||
|
onDismissRequest = { showCleanDialog = false },
|
||||||
|
isPagesCacheSelected = false,
|
||||||
|
isNetworkCacheSelected = false,
|
||||||
|
isThumbnailsCacheSelected = false,
|
||||||
|
onConfirm = { isPagesCacheSelected, isThumbnailCacheSelected, isNetworkCacheSelected ->
|
||||||
|
if (isPagesCacheSelected) viewModel.clearCache(CacheDir.PAGES)
|
||||||
|
if (isThumbnailCacheSelected) viewModel.clearCache(CacheDir.THUMBS)
|
||||||
|
if (isNetworkCacheSelected) viewModel.clearHttpCache()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package org.xtimms.tokusho.sections.settings.storage
|
||||||
|
|
||||||
|
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.xtimms.tokusho.core.base.viewmodel.BaseViewModel
|
||||||
|
import org.xtimms.tokusho.core.cache.CacheDir
|
||||||
|
import org.xtimms.tokusho.data.LocalStorageManager
|
||||||
|
import java.util.EnumMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class StorageViewModel @Inject constructor(
|
||||||
|
private val storageManager: LocalStorageManager,
|
||||||
|
private val httpCache: Cache,
|
||||||
|
) : BaseViewModel<StorageUiState>() {
|
||||||
|
|
||||||
|
val httpCacheSize = MutableStateFlow(-1L)
|
||||||
|
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
|
||||||
|
|
||||||
|
init {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
setLoading(true)
|
||||||
|
httpCacheSize.value = runInterruptible { httpCache.size() }
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(
|
||||||
|
availableSpace = storageManager.computeAvailableSize(),
|
||||||
|
pagesCache = storageManager.computeCacheSize(CacheDir.PAGES),
|
||||||
|
thumbnailsCache = storageManager.computeCacheSize(CacheDir.THUMBS),
|
||||||
|
httpCacheSize = httpCacheSize.value,
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCache(cache: CacheDir) {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
storageManager.clearCache(cache)
|
||||||
|
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(
|
||||||
|
availableSpace = storageManager.computeAvailableSize(),
|
||||||
|
pagesCache = storageManager.computeCacheSize(CacheDir.PAGES),
|
||||||
|
thumbnailsCache = storageManager.computeCacheSize(CacheDir.THUMBS),
|
||||||
|
httpCacheSize = httpCacheSize.value,
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearHttpCache() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val size = runInterruptible(Dispatchers.IO) {
|
||||||
|
httpCache.evictAll()
|
||||||
|
httpCache.size()
|
||||||
|
}
|
||||||
|
httpCacheSize.value = size
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(
|
||||||
|
availableSpace = storageManager.computeAvailableSize(),
|
||||||
|
pagesCache = storageManager.computeCacheSize(CacheDir.PAGES),
|
||||||
|
thumbnailsCache = storageManager.computeCacheSize(CacheDir.THUMBS),
|
||||||
|
httpCacheSize = httpCacheSize.value,
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val mutableUiState = MutableStateFlow(StorageUiState())
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
package org.xtimms.tokusho.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.xtimms.tokusho.R
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import kotlin.math.log10
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
enum class FileSize(private val multiplier: Int) {
|
||||||
|
|
||||||
|
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024);
|
||||||
|
|
||||||
|
fun convert(amount: Long, target: FileSize): Long = amount * multiplier / target.multiplier
|
||||||
|
|
||||||
|
fun freeFormat(context: Context, amount: Long): String {
|
||||||
|
val bytes = amount * multiplier
|
||||||
|
val units = context.getString(R.string.text_file_sizes_free).split('|')
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return "0 ${units.first()}"
|
||||||
|
}
|
||||||
|
val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()
|
||||||
|
return buildString {
|
||||||
|
append(
|
||||||
|
DecimalFormat("#,##0.#").format(
|
||||||
|
bytes / 1024.0.pow(digitGroups.toDouble()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val unit = units.getOrNull(digitGroups)
|
||||||
|
if (unit != null) {
|
||||||
|
append(' ')
|
||||||
|
append(unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun totalFormat(context: Context, amount: Long): String {
|
||||||
|
val bytes = amount * multiplier
|
||||||
|
val units = context.getString(R.string.text_file_sizes_total).split('|')
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return "0 ${units.first()}"
|
||||||
|
}
|
||||||
|
val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()
|
||||||
|
return buildString {
|
||||||
|
append(
|
||||||
|
DecimalFormat("#,##0.#").format(
|
||||||
|
bytes / 1024.0.pow(digitGroups.toDouble()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val unit = units.getOrNull(digitGroups)
|
||||||
|
if (unit != null) {
|
||||||
|
append(' ')
|
||||||
|
append(unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showUnit(context: Context, amount: Long): String {
|
||||||
|
val bytes = amount * multiplier
|
||||||
|
val units = context.getString(R.string.text_file_sizes_used).split('|')
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return units.first()
|
||||||
|
}
|
||||||
|
val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()
|
||||||
|
return buildString {
|
||||||
|
val unit = units.getOrNull(digitGroups)
|
||||||
|
if (unit != null) {
|
||||||
|
append(' ')
|
||||||
|
append(unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun format(context: Context, amount: Long): String {
|
||||||
|
val bytes = amount * multiplier
|
||||||
|
val units = context.getString(R.string.text_file_sizes).split('|')
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return "0 ${units.first()}"
|
||||||
|
}
|
||||||
|
val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()
|
||||||
|
return buildString {
|
||||||
|
append(
|
||||||
|
DecimalFormat("#,##0.#").format(
|
||||||
|
bytes / 1024.0.pow(digitGroups.toDouble()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val unit = units.getOrNull(digitGroups)
|
||||||
|
if (unit != null) {
|
||||||
|
append(' ')
|
||||||
|
append(unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatWithoutUnits(amount: Long): String {
|
||||||
|
val bytes = amount * multiplier
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()
|
||||||
|
return buildString {
|
||||||
|
append(
|
||||||
|
DecimalFormat("#,##0.#").format(
|
||||||
|
bytes / 1024.0.pow(digitGroups.toDouble()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package org.xtimms.tokusho.utils.storage
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import org.xtimms.tokusho.BuildConfig
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the uri of a file
|
|
||||||
*
|
|
||||||
* @param context context of application
|
|
||||||
*/
|
|
||||||
fun File.getUriCompat(context: Context): Uri {
|
|
||||||
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", this)
|
|
||||||
}
|
|
||||||
@ -1,5 +1,26 @@
|
|||||||
package org.xtimms.tokusho.utils.system
|
package org.xtimms.tokusho.utils.system
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import org.xtimms.tokusho.BuildConfig
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
|
import kotlin.io.path.walk
|
||||||
|
|
||||||
|
fun File.getUriCompat(context: Context): Uri {
|
||||||
|
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", this)
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.getFileProvider() = "$packageName.provider"
|
fun Context.getFileProvider() = "$packageName.provider"
|
||||||
|
|
||||||
|
suspend fun File.computeSize(): Long = runInterruptible(Dispatchers.IO) {
|
||||||
|
walkCompat().sumOf { it.length() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
fun File.walkCompat() =
|
||||||
|
// Use lazy loading on Android 8.0 and later
|
||||||
|
toPath().walk().map { it.toFile() }
|
||||||
|
|||||||
Loading…
Reference in New Issue