From 32f1a4557bf7df9d1c5a15f65445245609d1e923 Mon Sep 17 00:00:00 2001 From: ShubertMunthali Date: Wed, 12 Jul 2023 14:23:45 +0200 Subject: [PATCH 1/5] Compression mode control --- .../presentation/edit/EditScreen.kt | 3 + .../presentation/edit/EditViewModel.kt | 18 ++- .../presentation/edit/SavePathDialog.kt | 126 +++++++++++++++--- app/src/main/res/values/strings.xml | 5 + 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditScreen.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditScreen.kt index b211bf5..7d871c6 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditScreen.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditScreen.kt @@ -350,6 +350,9 @@ private fun BoxScope.TopMenu( onPositiveClick = { savePath -> viewModel.saveImage(savePath) viewModel.showSavePathDialog = false + }, + onCompressFormatChanged = { + viewModel.compressionFormat = it } ) if (viewModel.showMoreOptionsPopup) diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt index b77348f..5ef941c 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt @@ -3,6 +3,7 @@ package space.taran.arkretouch.presentation.edit import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.Bitmap.CompressFormat import android.graphics.Matrix import android.graphics.drawable.Drawable import android.net.Uri @@ -46,8 +47,11 @@ import space.taran.arkretouch.presentation.drawing.EditManager import space.taran.arkretouch.presentation.edit.resize.ResizeOperation import timber.log.Timber import java.io.File +import java.net.URI import java.nio.file.Path +import kotlin.io.path.extension import kotlin.io.path.outputStream +import kotlin.io.path.toPath import kotlin.system.measureTimeMillis class EditViewModel( @@ -72,6 +76,7 @@ class EditViewModel( var showEyeDropperHint by mutableStateOf(false) val showConfirmClearDialog = mutableStateOf(false) var isLoaded by mutableStateOf(false) + var compressionFormat by mutableStateOf(CompressFormat.PNG) var exitConfirmed = false private set val bottomButtonsScrollIsAtStart = mutableStateOf(true) @@ -113,6 +118,7 @@ class EditViewModel( imagePath, editManager ) + extractCompressionFormat(it.extension) return } imageUri?.let { @@ -121,6 +127,7 @@ class EditViewModel( imageUri, editManager ) + extractCompressionFormat(URI.create(it).toPath().extension) return } editManager.scaleToFit() @@ -133,7 +140,7 @@ class EditViewModel( savePath.outputStream().use { out -> combinedBitmap.asAndroidBitmap() - .compress(Bitmap.CompressFormat.PNG, 100, out) + .compress(compressionFormat, 100, out) } imageSaved = true isSavingImage = false @@ -392,6 +399,15 @@ class EditViewModel( } } + private fun extractCompressionFormat(extension: String) { + compressionFormat = when (extension) { + ImageExtensions.PNG -> CompressFormat.PNG + ImageExtensions.JPEG -> CompressFormat.JPEG + ImageExtensions.WEBP -> CompressFormat.WEBP + else -> CompressFormat.PNG + } + } + companion object { private const val KEEP_USED_COLORS = 20 } diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt index 2f1add6..0c7f3e5 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt @@ -1,5 +1,7 @@ package space.taran.arkretouch.presentation.edit +import android.graphics.Bitmap.CompressFormat +import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.Checkbox @@ -42,18 +45,22 @@ import space.taran.arkretouch.R import space.taran.arkretouch.presentation.utils.findNotExistCopyName import kotlin.io.path.name import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.runtime.key -import androidx.compose.ui.ExperimentalComposeUiApi import java.nio.file.Files +import java.util.Locale import kotlin.streams.toList -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SavePathDialog( initialImagePath: Path?, fragmentManager: FragmentManager, onDismissClick: () -> Unit, - onPositiveClick: (Path) -> Unit + onPositiveClick: (Path) -> Unit, + onCompressFormatChanged: (CompressFormat) -> Unit ) { var currentPath by remember { mutableStateOf(initialImagePath?.parent) } var imagePath by remember { mutableStateOf(initialImagePath) } @@ -66,6 +73,8 @@ fun SavePathDialog( } ?: "image.png" ) } + var compressionFormat by remember { mutableStateOf("PNG") } + var showCompressionFormats by remember { mutableStateOf(false) } val lifecycleOwner = LocalLifecycleOwner.current @@ -123,25 +132,48 @@ fun SavePathDialog( ?: stringResource(R.string.pick_folder) ) } - OutlinedTextField( - modifier = Modifier.padding(5.dp), - value = name, - onValueChange = { - name = it - currentPath?.let { path -> - imagePath = path.resolve(name) - showOverwriteCheckbox.value = Files.list(path).toList() - .contains(imagePath) - if (showOverwriteCheckbox.value) { - name = path.findNotExistCopyName( - imagePath?.fileName!! - ).name + Row(Modifier.fillMaxWidth()) { + OutlinedTextField( + modifier = Modifier.padding(5.dp), + value = name.substringBeforeLast('.'), + onValueChange = { + name = "$it." + + compressionFormat.lowercase(Locale.getDefault()) + currentPath?.let { path -> + imagePath = path.resolve(name) + showOverwriteCheckbox.value = + Files.list(path).toList() + .contains(imagePath) + if (showOverwriteCheckbox.value) { + name = path.findNotExistCopyName( + imagePath?.fileName!! + ).name + } } + }, + label = { Text(text = stringResource(R.string.name)) }, + singleLine = true + ) + Column( + Modifier.clickable { + showCompressionFormats = !showCompressionFormats } - }, - label = { Text(text = stringResource(R.string.name)) }, - singleLine = true - ) + ) { + Text(compressionFormat) + Icon( + if (showCompressionFormats) Icons.Filled.KeyboardArrowUp + else Icons.Filled.KeyboardArrowDown, + null, + ) + if (showCompressionFormats) { + CompressionFormats { name, format -> + compressionFormat = name + onCompressFormatChanged(format) + showCompressionFormats = false + } + } + } + } if (showOverwriteCheckbox.value) { Row( modifier = Modifier @@ -175,7 +207,7 @@ fun SavePathDialog( Button( modifier = Modifier.padding(5.dp), onClick = { - if (currentPath != null && name != null) + if (currentPath != null) onPositiveClick(currentPath!!.resolve(name)) } ) { @@ -202,9 +234,61 @@ fun SaveProgress() { } } +@Composable +fun CompressionFormats(onFormatClick: (String, CompressFormat) -> Unit) { + Column( + Modifier.wrapContentSize() + ) { + val png = stringResource(R.string.png) + val jpeg = stringResource(R.string.jpeg) + val webpLossless = stringResource(R.string.webp_lossless) + val webpLossy = stringResource(R.string.webp_lossy) + val webp = stringResource(R.string.webp) + Text( + png, + Modifier.clickable { + onFormatClick(png, CompressFormat.PNG) + } + ) + Text( + jpeg, + Modifier.clickable { + onFormatClick(jpeg, CompressFormat.JPEG) + } + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Text( + webpLossless, + Modifier.clickable { + onFormatClick(webpLossless, CompressFormat.WEBP_LOSSLESS) + } + ) + Text( + webpLossy, + Modifier.clickable { + onFormatClick(webpLossy, CompressFormat.WEBP_LOSSY) + } + ) + } else { + Text( + webp, + Modifier.clickable { + onFormatClick(webp, CompressFormat.WEBP) + } + ) + } + } +} + fun folderFilePickerConfig(initialPath: Path?) = ArkFilePickerConfig( mode = ArkFilePickerMode.FOLDER, initialPath = initialPath, showRoots = true, rootsFirstPage = true ) + +object ImageExtensions { + const val PNG = "png" + const val JPEG = "jpeg" + const val WEBP = "webp" +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6254c1c..f167218 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,4 +34,9 @@ Width cannot be %s Please enter width Please enter height + WEBP_LOSSY + WEBP_LOSSLESS + JPEG + PNG + WEBP From 88cb5b816e8405c001187ba297fcfdfd6e6aea1f Mon Sep 17 00:00:00 2001 From: ShubertMunthali Date: Thu, 13 Jul 2023 11:37:28 +0200 Subject: [PATCH 2/5] Compression mode control --- .../presentation/edit/EditViewModel.kt | 2 +- .../presentation/edit/SavePathDialog.kt | 177 ++++++++++++------ 2 files changed, 125 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt index 5ef941c..67148c8 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt @@ -402,7 +402,7 @@ class EditViewModel( private fun extractCompressionFormat(extension: String) { compressionFormat = when (extension) { ImageExtensions.PNG -> CompressFormat.PNG - ImageExtensions.JPEG -> CompressFormat.JPEG + ImageExtensions.JPEG, ImageExtensions.JPG -> CompressFormat.JPEG ImageExtensions.WEBP -> CompressFormat.WEBP else -> CompressFormat.PNG } diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt index 0c7f3e5..22525a6 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt @@ -50,8 +50,13 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.runtime.key +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import space.taran.arkretouch.presentation.picker.toPx import java.nio.file.Files import java.util.Locale +import kotlin.io.path.extension import kotlin.streams.toList @Composable @@ -73,7 +78,22 @@ fun SavePathDialog( } ?: "image.png" ) } - var compressionFormat by remember { mutableStateOf("PNG") } + var compressionFormat by remember { + var format = initialImagePath?.let { + when (it.extension) { + ImageExtensions.PNG, + ImageExtensions.JPEG, + ImageExtensions.WEBP -> it.extension + ImageExtensions.JPG -> ImageExtensions.JPEG + else -> ImageExtensions.PNG + } + } ?: ImageExtensions.PNG + if (format == ImageExtensions.WEBP) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + format = ImageExtensions.Webp.WEBP_LOSSLESS + } + mutableStateOf(format.uppercase(Locale.getDefault())) + } var showCompressionFormats by remember { mutableStateOf(false) } val lifecycleOwner = LocalLifecycleOwner.current @@ -132,13 +152,24 @@ fun SavePathDialog( ?: stringResource(R.string.pick_folder) ) } - Row(Modifier.fillMaxWidth()) { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { OutlinedTextField( - modifier = Modifier.padding(5.dp), + modifier = Modifier + .fillMaxWidth(0.8f) + .padding(5.dp), value = name.substringBeforeLast('.'), onValueChange = { - name = "$it." + + var extension = compressionFormat.lowercase(Locale.getDefault()) + if ( + extension == ImageExtensions.Webp.WEBP_LOSSLESS || + extension == ImageExtensions.Webp.WEBP_LOSSY + ) extension = ImageExtensions.WEBP + + name = "$it.$extension" currentPath?.let { path -> imagePath = path.resolve(name) showOverwriteCheckbox.value = @@ -155,23 +186,22 @@ fun SavePathDialog( singleLine = true ) Column( - Modifier.clickable { - showCompressionFormats = !showCompressionFormats - } + Modifier + .wrapContentHeight() + .fillMaxWidth() + .clickable { + showCompressionFormats = !showCompressionFormats + }, + horizontalAlignment = Alignment.CenterHorizontally ) { - Text(compressionFormat) Icon( - if (showCompressionFormats) Icons.Filled.KeyboardArrowUp - else Icons.Filled.KeyboardArrowDown, + if (showCompressionFormats) + Icons.Filled.KeyboardArrowDown + else Icons.Filled.KeyboardArrowUp, null, + Modifier.size(32.dp) ) - if (showCompressionFormats) { - CompressionFormats { name, format -> - compressionFormat = name - onCompressFormatChanged(format) - showCompressionFormats = false - } - } + Text(compressionFormat) } } if (showOverwriteCheckbox.value) { @@ -215,6 +245,16 @@ fun SavePathDialog( } } } + if (showCompressionFormats) { + CompressionFormats( + { name, format -> + compressionFormat = name + onCompressFormatChanged(format) + showCompressionFormats = false + }, + { showCompressionFormats = false } + ) + } } } } @@ -235,47 +275,73 @@ fun SaveProgress() { } @Composable -fun CompressionFormats(onFormatClick: (String, CompressFormat) -> Unit) { - Column( - Modifier.wrapContentSize() +fun CompressionFormats( + onFormatClick: (String, CompressFormat) -> Unit, + onDismiss: () -> Unit +) { + Popup( + alignment = Alignment.TopEnd, + offset = IntOffset( + -5.dp.toPx().toInt(), + -10.dp.toPx().toInt() + ), + onDismissRequest = onDismiss, + properties = PopupProperties(focusable = true) ) { - val png = stringResource(R.string.png) - val jpeg = stringResource(R.string.jpeg) - val webpLossless = stringResource(R.string.webp_lossless) - val webpLossy = stringResource(R.string.webp_lossy) - val webp = stringResource(R.string.webp) - Text( - png, - Modifier.clickable { - onFormatClick(png, CompressFormat.PNG) - } - ) - Text( - jpeg, - Modifier.clickable { - onFormatClick(jpeg, CompressFormat.JPEG) - } - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Text( - webpLossless, - Modifier.clickable { - onFormatClick(webpLossless, CompressFormat.WEBP_LOSSLESS) - } - ) + Column( + Modifier + .wrapContentSize() + .background(Color.LightGray, RoundedCornerShape(5)), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val png = stringResource(R.string.png) + val jpeg = stringResource(R.string.jpeg) + val webpLossless = stringResource(R.string.webp_lossless) + val webpLossy = stringResource(R.string.webp_lossy) + val webp = stringResource(R.string.webp) Text( - webpLossy, - Modifier.clickable { - onFormatClick(webpLossy, CompressFormat.WEBP_LOSSY) - } + png, + Modifier + .padding(8.dp) + .clickable { + onFormatClick(png, CompressFormat.PNG) + } ) - } else { Text( - webp, - Modifier.clickable { - onFormatClick(webp, CompressFormat.WEBP) - } + jpeg, + Modifier + .padding(8.dp) + .clickable { + onFormatClick(jpeg, CompressFormat.JPEG) + } ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Text( + webpLossless, + Modifier + .padding(8.dp) + .clickable { + onFormatClick(webpLossless, CompressFormat.WEBP_LOSSLESS) + } + ) + Text( + webpLossy, + Modifier + .padding(8.dp) + .clickable { + onFormatClick(webpLossy, CompressFormat.WEBP_LOSSY) + } + ) + } else { + Text( + webp, + Modifier + .padding(8.dp) + .clickable { + onFormatClick(webp, CompressFormat.WEBP) + } + ) + } } } } @@ -290,5 +356,10 @@ fun folderFilePickerConfig(initialPath: Path?) = ArkFilePickerConfig( object ImageExtensions { const val PNG = "png" const val JPEG = "jpeg" + const val JPG = "jpg" const val WEBP = "webp" + object Webp { + const val WEBP_LOSSLESS = "webp_lossless" + const val WEBP_LOSSY = "webp_lossy" + } } From ca792a1d3e9c9e892cf6e37fd47826ef83e71b05 Mon Sep 17 00:00:00 2001 From: ShubertMunthali Date: Thu, 3 Aug 2023 22:06:29 +0200 Subject: [PATCH 3/5] Fix intent editing --- .../taran/arkretouch/presentation/edit/EditViewModel.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt index 67148c8..93b7546 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/EditViewModel.kt @@ -47,11 +47,10 @@ import space.taran.arkretouch.presentation.drawing.EditManager import space.taran.arkretouch.presentation.edit.resize.ResizeOperation import timber.log.Timber import java.io.File -import java.net.URI import java.nio.file.Path +import kotlin.io.path.Path import kotlin.io.path.extension import kotlin.io.path.outputStream -import kotlin.io.path.toPath import kotlin.system.measureTimeMillis class EditViewModel( @@ -127,7 +126,9 @@ class EditViewModel( imageUri, editManager ) - extractCompressionFormat(URI.create(it).toPath().extension) + Uri.parse(it)?.path?.let { path -> + extractCompressionFormat(Path(path).extension) + } return } editManager.scaleToFit() From 4e4c36557d95fa814ef755973fd7f721371fb03f Mon Sep 17 00:00:00 2001 From: ShubertMunthali Date: Sat, 5 Aug 2023 12:18:16 +0200 Subject: [PATCH 4/5] Fix compression mode inconsistency --- .../presentation/edit/SavePathDialog.kt | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt b/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt index 22525a6..6060368 100644 --- a/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt +++ b/app/src/main/java/space/taran/arkretouch/presentation/edit/SavePathDialog.kt @@ -98,6 +98,29 @@ fun SavePathDialog( val lifecycleOwner = LocalLifecycleOwner.current + fun updateImagePath(imageName: String) { + var extension = + compressionFormat.lowercase(Locale.getDefault()) + if ( + extension == ImageExtensions.Webp.WEBP_LOSSLESS || + extension == ImageExtensions.Webp.WEBP_LOSSY + ) extension = ImageExtensions.WEBP + + name = "$imageName.$extension" + + currentPath?.let { path -> + imagePath = path.resolve(name) + showOverwriteCheckbox.value = + Files.list(path).toList() + .contains(imagePath) + if (showOverwriteCheckbox.value) { + name = path.findNotExistCopyName( + imagePath?.fileName!! + ).name + } + } + } + LaunchedEffect(overwriteOriginalPath) { if (overwriteOriginalPath) { imagePath?.let { @@ -160,27 +183,11 @@ fun SavePathDialog( modifier = Modifier .fillMaxWidth(0.8f) .padding(5.dp), - value = name.substringBeforeLast('.'), + value = name.substringBeforeLast( + ImageExtensions.Delimeters.PERIOD + ), onValueChange = { - var extension = - compressionFormat.lowercase(Locale.getDefault()) - if ( - extension == ImageExtensions.Webp.WEBP_LOSSLESS || - extension == ImageExtensions.Webp.WEBP_LOSSY - ) extension = ImageExtensions.WEBP - - name = "$it.$extension" - currentPath?.let { path -> - imagePath = path.resolve(name) - showOverwriteCheckbox.value = - Files.list(path).toList() - .contains(imagePath) - if (showOverwriteCheckbox.value) { - name = path.findNotExistCopyName( - imagePath?.fileName!! - ).name - } - } + updateImagePath(it) }, label = { Text(text = stringResource(R.string.name)) }, singleLine = true @@ -201,7 +208,7 @@ fun SavePathDialog( null, Modifier.size(32.dp) ) - Text(compressionFormat) + Text(compressionFormat, maxLines = 1) } } if (showOverwriteCheckbox.value) { @@ -247,8 +254,13 @@ fun SavePathDialog( } if (showCompressionFormats) { CompressionFormats( - { name, format -> - compressionFormat = name + { formatName, format -> + compressionFormat = formatName + updateImagePath( + name.substringBeforeLast( + ImageExtensions.Delimeters.PERIOD + ) + ) onCompressFormatChanged(format) showCompressionFormats = false }, @@ -362,4 +374,8 @@ object ImageExtensions { const val WEBP_LOSSLESS = "webp_lossless" const val WEBP_LOSSY = "webp_lossy" } + + object Delimeters { + const val PERIOD = '.' + } } From 4b36b9b104f208132a63dc80e080f43a602b35b8 Mon Sep 17 00:00:00 2001 From: Shubert Munthali Date: Mon, 12 Feb 2024 12:15:46 +0200 Subject: [PATCH 5/5] Update target SDK --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0e0c11e..cb8a36a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ android { applicationId "space.taran.arkretouch2" minSdk 26 - targetSdk 32 + targetSdk 33 versionCode 1 versionName "1.0" setProperty("archivesBaseName", "ark-retouch")