diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ByteUtils.kt b/app/src/main/java/ru/tech/imageresizershrinker/core/utils/ByteUtils.kt similarity index 89% rename from app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ByteUtils.kt rename to app/src/main/java/ru/tech/imageresizershrinker/core/utils/ByteUtils.kt index 9fe633dabb..be3bd90389 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ByteUtils.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/core/utils/ByteUtils.kt @@ -1,4 +1,4 @@ -package ru.tech.imageresizershrinker.presentation.root.utils.helper +package ru.tech.imageresizershrinker.core.utils import java.text.CharacterIterator import java.text.StringCharacterIterator diff --git a/app/src/main/java/ru/tech/imageresizershrinker/data/saving/FileControllerImpl.kt b/app/src/main/java/ru/tech/imageresizershrinker/data/saving/FileControllerImpl.kt index 752a3a28a1..aaf28f6222 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/data/saving/FileControllerImpl.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/data/saving/FileControllerImpl.kt @@ -24,7 +24,9 @@ import androidx.exifinterface.media.ExifInterface import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import ru.tech.imageresizershrinker.R +import ru.tech.imageresizershrinker.core.utils.readableByteCount import ru.tech.imageresizershrinker.data.keys.Keys.ADD_ORIGINAL_NAME_TO_FILENAME import ru.tech.imageresizershrinker.data.keys.Keys.ADD_SEQ_NUM_TO_FILENAME import ru.tech.imageresizershrinker.data.keys.Keys.ADD_SIZE_TO_FILENAME @@ -280,6 +282,36 @@ class FileControllerImpl @Inject constructor( }.$extension" } + override fun clearCache(onComplete: (String) -> Unit) = context.clearCache(onComplete) + + override fun getReadableCacheSize(): String = context.cacheSize() + + private fun Context.clearCache(onComplete: (cache: String) -> Unit = {}) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + cacheDir.deleteRecursively() + codeCacheDir.deleteRecursively() + externalCacheDir?.deleteRecursively() + externalCacheDirs.forEach { + it.deleteRecursively() + } + } + onComplete(cacheSize()) + } + } + + private fun Context.cacheSize(): String { + return kotlin.runCatching { + val cache = cacheDir.walkTopDown().filter { it.isFile }.map { it.length() }.sum() + val code = codeCacheDir.walkTopDown().filter { it.isFile }.map { it.length() }.sum() + var size = cache + code + externalCacheDirs.forEach { file -> + size += file.walkTopDown().filter { it.isFile }.map { it.length() }.sum() + } + readableByteCount(size) + }.getOrNull() ?: "0 B" + } + private fun getFileDescriptorFor(uri: Uri?) = uri?.let { context.contentResolver.openFileDescriptor(uri, "rw") } diff --git a/app/src/main/java/ru/tech/imageresizershrinker/domain/saving/FileController.kt b/app/src/main/java/ru/tech/imageresizershrinker/domain/saving/FileController.kt index 395a4203f8..4e3be26e40 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/domain/saving/FileController.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/domain/saving/FileController.kt @@ -4,7 +4,17 @@ import ru.tech.imageresizershrinker.domain.saving.model.ImageSaveTarget interface FileController { val savingPath: String - suspend fun save(saveTarget: SaveTarget, keepMetadata: Boolean): SaveResult + + suspend fun save( + saveTarget: SaveTarget, + keepMetadata: Boolean + ): SaveResult + fun getSize(uri: String): Long? + fun constructImageFilename(saveTarget: ImageSaveTarget<*>): String + + fun clearCache(onComplete: (String) -> Unit = {}) + + fun getReadableCacheSize(): String } \ No newline at end of file diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/file_cipher_screen/FileCipherScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/file_cipher_screen/FileCipherScreen.kt index 8f26ddca4f..9521b5e711 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/file_cipher_screen/FileCipherScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/file_cipher_screen/FileCipherScreen.kt @@ -95,6 +95,7 @@ import androidx.compose.ui.unit.sp import dev.olshevski.navigation.reimagined.hilt.hiltViewModel import kotlinx.coroutines.launch import ru.tech.imageresizershrinker.R +import ru.tech.imageresizershrinker.core.utils.readableByteCount import ru.tech.imageresizershrinker.presentation.draw_screen.components.materialShadow import ru.tech.imageresizershrinker.presentation.file_cipher_screen.components.CipherTipSheet import ru.tech.imageresizershrinker.presentation.file_cipher_screen.viewModel.FileCipherViewModel @@ -106,7 +107,6 @@ import ru.tech.imageresizershrinker.presentation.root.theme.outlineVariant import ru.tech.imageresizershrinker.presentation.root.utils.confetti.LocalConfettiController import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.getFileName import ru.tech.imageresizershrinker.presentation.root.utils.helper.ImageUtils.fileSize -import ru.tech.imageresizershrinker.presentation.root.utils.helper.readableByteCount import ru.tech.imageresizershrinker.presentation.root.utils.helper.showReview import ru.tech.imageresizershrinker.presentation.root.widget.controls.EnhancedButton import ru.tech.imageresizershrinker.presentation.root.widget.controls.EnhancedIconButton diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/image_stitching_screen/components/ImageReorderCarousel.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/image_stitching_screen/components/ImageReorderCarousel.kt index a9c609780d..9262d4224c 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/image_stitching_screen/components/ImageReorderCarousel.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/image_stitching_screen/components/ImageReorderCarousel.kt @@ -107,7 +107,7 @@ fun ImageReorderCarousel( contentColor = MaterialTheme.colorScheme.onSurfaceVariant, forceMinimumInteractiveComponentSize = false, modifier = Modifier - .padding(start = 8.dp) + .padding(start = 8.dp, end = 8.dp) .size(30.dp), ) { Icon( diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/MainActivity.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/MainActivity.kt index dd2ac5e703..c73b12426e 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/MainActivity.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/MainActivity.kt @@ -39,7 +39,6 @@ import ru.tech.imageresizershrinker.presentation.main_screen.viewModel.MainViewM import ru.tech.imageresizershrinker.presentation.root.model.toUiState import ru.tech.imageresizershrinker.presentation.root.theme.ImageToolboxTheme import ru.tech.imageresizershrinker.presentation.root.utils.confetti.LocalConfettiController -import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.clearCache import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.isInstalledFromPlayStore import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.parseImageFromIntent import ru.tech.imageresizershrinker.presentation.root.utils.navigation.LocalNavController @@ -61,8 +60,6 @@ class MainActivity : M3Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.autoClearCache { clearCache() } - parseImage(intent) setContentWithWindowSizeClass { @@ -160,7 +157,12 @@ class MainActivity : M3Activity() { ToastHost(hostState = LocalToastHost.current) - SideEffect { viewModel.tryGetUpdate(installedFromMarket = isInstalledFromPlayStore()) } + SideEffect { + viewModel.tryGetUpdate( + installedFromMarket = isInstalledFromPlayStore(), + newRequest = true + ) + } PermissionDialog() diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/components/SettingsBlock.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/components/SettingsBlock.kt index 7c66166769..77a5ff2d5b 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/components/SettingsBlock.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/components/SettingsBlock.kt @@ -148,8 +148,6 @@ import ru.tech.imageresizershrinker.presentation.root.theme.blend import ru.tech.imageresizershrinker.presentation.root.theme.inverse import ru.tech.imageresizershrinker.presentation.root.theme.outlineVariant import ru.tech.imageresizershrinker.presentation.root.utils.confetti.LocalConfettiController -import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.cacheSize -import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.clearCache import ru.tech.imageresizershrinker.presentation.root.utils.helper.ContextUtils.isInstalledFromPlayStore import ru.tech.imageresizershrinker.presentation.root.utils.helper.plus import ru.tech.imageresizershrinker.presentation.root.utils.helper.toUiPath @@ -1005,7 +1003,7 @@ fun SettingsBlock( ) { PreferenceItem( shape = defaultShape, - onClick = { onEditPresets() }, + onClick = onEditPresets, title = stringResource(R.string.values), subtitle = settingsState.presets.joinToString(", "), color = MaterialTheme @@ -1232,12 +1230,12 @@ fun SettingsBlock( var cache by remember( context, LocalLifecycleOwner.current.lifecycle.observeAsState().value - ) { mutableStateOf(context.cacheSize()) } + ) { mutableStateOf(viewModel.getReadableCacheSize()) } PreferenceItem( shape = topShape, onClick = { - context.clearCache { cache = it } + viewModel.clearCache { cache = it } }, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/viewModel/MainViewModel.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/viewModel/MainViewModel.kt index ba69736976..ff36dc6de0 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/viewModel/MainViewModel.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/main_screen/viewModel/MainViewModel.kt @@ -29,6 +29,7 @@ import ru.tech.imageresizershrinker.domain.image.ImageManager import ru.tech.imageresizershrinker.domain.model.FontFam import ru.tech.imageresizershrinker.domain.model.NightMode import ru.tech.imageresizershrinker.domain.model.SettingsState +import ru.tech.imageresizershrinker.domain.saving.FileController import ru.tech.imageresizershrinker.domain.use_case.backup_and_restore.CreateBackupFileUseCase import ru.tech.imageresizershrinker.domain.use_case.backup_and_restore.CreateBackupFilenameUseCase import ru.tech.imageresizershrinker.domain.use_case.backup_and_restore.RestoreFromBackupFileUseCase @@ -75,6 +76,7 @@ import javax.xml.parsers.DocumentBuilderFactory @HiltViewModel class MainViewModel @Inject constructor( private val imageManager: ImageManager, + private val fileController: FileController, private val getSettingsStateUseCase: GetSettingsStateUseCase, getSettingsStateFlowUseCase: GetSettingsStateFlowUseCase, private val toggleAddSequenceNumberUseCase: ToggleAddSequenceNumberUseCase, @@ -143,6 +145,8 @@ class MainViewModel @Inject constructor( val toastHostState = ToastHostState() init { + if (settingsState.clearCacheOnLaunch) clearCache() + runBlocking { registerAppOpenUseCase() _settingsState.value = getSettingsStateUseCase() @@ -152,6 +156,10 @@ class MainViewModel @Inject constructor( }.launchIn(viewModelScope) } + fun getReadableCacheSize(): String = fileController.getReadableCacheSize() + + fun clearCache(onComplete: (String) -> Unit = {}) = fileController.clearCache(onComplete) + fun toggleAddSequenceNumber() { viewModelScope.launch { toggleAddSequenceNumberUseCase() @@ -261,7 +269,7 @@ class MainViewModel @Inject constructor( val showDialog = settingsState.showDialogOnStartup if (installedFromMarket) { if (showDialog) { - _showUpdateDialog.value = true + _showUpdateDialog.value = newRequest } } else { if (!_cancelledUpdate.value || newRequest) { @@ -454,14 +462,6 @@ class MainViewModel @Inject constructor( } } - private var alreadyCleared: Boolean = false - fun autoClearCache(function: () -> Unit) { - if (settingsState.clearCacheOnLaunch && !alreadyCleared) { - function() - alreadyCleared = true - } - } - fun toggleAllowBetas(installedFromMarket: Boolean) { viewModelScope.launch { toggleAllowBetasUseCase() diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ContextUtils.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ContextUtils.kt index e8f39b2e1e..87ccfe7fd1 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ContextUtils.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/utils/helper/ContextUtils.kt @@ -19,10 +19,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.documentfile.provider.DocumentFile -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import ru.tech.imageresizershrinker.BuildConfig import ru.tech.imageresizershrinker.R import ru.tech.imageresizershrinker.presentation.root.utils.helper.IntentUtils.parcelable @@ -196,32 +192,6 @@ object ContextUtils { else -> null } - fun Context.clearCache(onComplete: (cache: String) -> Unit = {}) { - CoroutineScope(Dispatchers.Main).launch { - withContext(Dispatchers.IO) { - cacheDir.deleteRecursively() - codeCacheDir.deleteRecursively() - externalCacheDir?.deleteRecursively() - externalCacheDirs.forEach { - it.deleteRecursively() - } - } - onComplete(cacheSize()) - } - } - - fun Context.cacheSize(): String { - return kotlin.runCatching { - val cache = cacheDir.walkTopDown().filter { it.isFile }.map { it.length() }.sum() - val code = codeCacheDir.walkTopDown().filter { it.isFile }.map { it.length() }.sum() - var size = cache + code - externalCacheDirs.forEach { file -> - size += file.walkTopDown().filter { it.isFile }.map { it.length() }.sum() - } - readableByteCount(size) - }.getOrNull() ?: "0 B" - } - /** Save a text into the clipboard. */ fun Context.copyToClipboard(label: String, value: String) { val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/Marquee.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/Marquee.kt index 1791ed8d64..474d194438 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/Marquee.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/Marquee.kt @@ -10,9 +10,8 @@ import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -23,6 +22,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.withFrameNanos +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Brush @@ -116,7 +116,6 @@ fun Marquee( content() }.first().measure(infiniteWidthConstraints) - var gradient: Placeable? = null var secondPlaceableWithOffset: Pair? = null if (main.width <= constraints.maxWidth) { @@ -138,22 +137,23 @@ fun Marquee( content() }.first().measure(infiniteWidthConstraints) to secondTextOffset } - gradient = if (params.gradientEnabled) subcompose(MarqueeLayers.EdgesGradient) { - Row { - GradientEdge( - width = params.gradientEdgeWidth, - startColor = params.gradientEdgeColor, - endColor = Color.Transparent - ) - Spacer(modifier = Modifier.weight(1f)) - GradientEdge( - width = params.gradientEdgeWidth, - startColor = Color.Transparent, - endColor = params.gradientEdgeColor - ) - } - }.first().measure(constraints = constraints.copy(maxHeight = main.height)) else null } + val gradient = subcompose(MarqueeLayers.EdgesGradient) { + Box(Modifier.fillMaxWidth()) { + GradientEdge( + modifier = Modifier.align(Alignment.CenterStart), + width = params.gradientEdgeWidth, + startColor = params.gradientEdgeColor, + endColor = Color.Transparent + ) + GradientEdge( + modifier = Modifier.align(Alignment.CenterEnd), + width = params.gradientEdgeWidth, + startColor = Color.Transparent, + endColor = params.gradientEdgeColor + ) + } + }.first().measure(constraints = constraints.copy(maxHeight = main.height)) layout( width = constraints.maxWidth, height = main.height @@ -162,7 +162,9 @@ fun Marquee( secondPlaceableWithOffset?.let { it.first.place(it.second, 0) } - gradient?.place(0, 0) + if (params.gradientEnabled && layoutInfoState.value?.let { it.width > it.containerWidth } == true) { + gradient.place(0, 0) + } } } } @@ -209,12 +211,13 @@ fun defaultMarqueeParams( @Composable private fun GradientEdge( + modifier: Modifier, width: Dp, startColor: Color, endColor: Color ) { Box( - modifier = Modifier + modifier = modifier .width(width) .fillMaxHeight() .background( diff --git a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/TopAppBarTitle.kt b/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/TopAppBarTitle.kt index cd9e5fd644..38dc6e282c 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/TopAppBarTitle.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/presentation/root/widget/text/TopAppBarTitle.kt @@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import ru.tech.imageresizershrinker.R -import ru.tech.imageresizershrinker.presentation.root.utils.helper.readableByteCount +import ru.tech.imageresizershrinker.core.utils.readableByteCount @Composable fun TopAppBarTitle(