From 3b61fdc0944fbf27eb19247b4cf6c47d06548a0a Mon Sep 17 00:00:00 2001 From: rabiz Date: Wed, 4 Dec 2024 20:43:23 +0530 Subject: [PATCH 1/2] Version update - 4 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c85e79..616d2ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,8 +13,8 @@ android { applicationId = "com.abizer_r.quickedit" minSdk = 24 targetSdk = 34 - versionCode = 3 - versionName = "1.0.3" + versionCode = 4 + versionName = "1.1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 1fc86270992fa192d1e5bdd32fb46e62fb0f9cd1 Mon Sep 17 00:00:00 2001 From: rabiz Date: Mon, 9 Dec 2024 10:38:42 +0530 Subject: [PATCH 2/2] Added option to enter custom ratio in cropper --- .../ui/common/crop/AspectRatioDialog.kt | 219 ++++++++++++++++++ .../quickedit/ui/cropMode/CropperScreen.kt | 42 ++++ .../cropperOptions/CropperOptionsFullWidth.kt | 9 + .../quickedit/utils/cropMode/CropModeUtils.kt | 5 + quickedit/src/main/res/drawable/ic_crop.xml | 5 + quickedit/src/main/res/values/strings.xml | 8 + 6 files changed, 288 insertions(+) create mode 100644 quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt create mode 100644 quickedit/src/main/res/drawable/ic_crop.xml diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt new file mode 100644 index 0000000..b881fa3 --- /dev/null +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt @@ -0,0 +1,219 @@ +package com.abizer_r.quickedit.ui.common.crop + +import android.content.res.Configuration +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.abizer_r.quickedit.R +import com.abizer_r.quickedit.theme.DarkPanel +import com.abizer_r.quickedit.theme.QuickEditTheme +import com.abizer_r.quickedit.utils.defaultTextColor +import com.abizer_r.quickedit.utils.toast + +const val MIN_RATIO = 0.15f +const val MAX_RATIO = 5.0f +@Composable +fun AspectRatioDialog( + onDismiss: () -> Unit, + onSetRatio: (Int, Int) -> Unit +) { + + val context = LocalContext.current + + Dialog( + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false + ), + onDismissRequest = onDismiss, + ) { + + + var aspectX by remember { mutableStateOf("1") } + var aspectY by remember { mutableStateOf("1") } + + val titleTextStyle = MaterialTheme.typography.titleMedium.copy( + color = defaultTextColor() + ) + val bodyTextStyle = MaterialTheme.typography.bodySmall.copy( + color = defaultTextColor() + ) + + Box( + modifier = Modifier.background( + color = DarkPanel, + shape = RoundedCornerShape(10.dp) + ) + ) { + + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.Center, + ) { + + Text( + text = stringResource(R.string.enter_aspect_ratio), + style = titleTextStyle + ) + Spacer(Modifier.height(8.dp)) + Row( + horizontalArrangement = Arrangement.SpaceEvenly + ) { + RatioInputField( + modifier = Modifier.weight(1f), + text = aspectX, + onValueChange = { newValue -> aspectX = newValue }, + labelText = stringResource(R.string.x), + bodyTextStyle = bodyTextStyle, + ) + Spacer(Modifier.width(8.dp)) + RatioInputField( + modifier = Modifier.weight(1f), + text = aspectY, + onValueChange = { newValue -> aspectY = newValue }, + labelText = stringResource(R.string.y), + bodyTextStyle = bodyTextStyle, + ) + } + Spacer(Modifier.height(8.dp)) + Text( + text = "Aspect Ratio = ${getAspectRatioText(aspectX, aspectY)}", + style = bodyTextStyle + ) + Spacer(Modifier.height(16.dp)) + Text( + text = "Min Allowed Aspect Ratio = $MIN_RATIO", + style = bodyTextStyle + ) + Spacer(Modifier.height(4.dp)) + Text( + text = "Max Allowed Aspect Ratio = $MAX_RATIO", + style = bodyTextStyle + ) + Spacer(Modifier.height(16.dp)) + Button( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = { + val validation = validateRatioItem(aspectX, aspectY) + if (validation.isValid) { + onSetRatio(aspectX.toInt(), aspectY.toInt()) + } else { + context.toast(context.getString(validation.errorResId ?: R.string.something_went_wrong)) + } + } + ) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.select), + style = bodyTextStyle.copy( + color = MaterialTheme.colorScheme.background, + fontWeight = FontWeight.Bold + ) + ) + } + } + } + } +} + +private fun getAspectRatioText(x: String, y: String): String { + return getAspectRatio(x, y)?.toString() ?: "Invalid" +} + +data class RatioItemValidationResult( + val isValid: Boolean, + @StringRes val errorResId: Int? = null +) + +private fun validateRatioItem(x: String, y: String): RatioItemValidationResult { + if (x.isBlank() || y.isBlank()) + return RatioItemValidationResult(false, R.string.fields_cannot_be_empty) + val xF = x.toIntOrNull() + val yF = y.toIntOrNull() + if (xF == null || yF == null) + return RatioItemValidationResult(false, R.string.not_a_valid_number) + val ratio = (xF.toFloat() / yF.toFloat()) + return when { + ratio < MIN_RATIO -> RatioItemValidationResult(false, R.string.ratio_lesser_than_minimum) + ratio > MAX_RATIO -> RatioItemValidationResult(false, R.string.ratio_greater_than_minimum) + else -> RatioItemValidationResult(true) + } +} + +private fun getAspectRatio(x: String, y: String): Float? { + if (x.isBlank() || y.isBlank()) + return null + val xF = x.toFloatOrNull() ?: return null + val yF = y.toFloatOrNull() ?: return null + return xF / yF +} + +@Composable +private fun RatioInputField( + modifier: Modifier, + text: String, +// onValueChange: (TextFieldValue) -> Unit, + onValueChange: (String) -> Unit, + labelText: String, + bodyTextStyle: TextStyle +) { + OutlinedTextField( + modifier = modifier, + singleLine = true, +// value = TextFieldValue( +// text = text, +// selection = TextRange(text.length) +// ), +// onValueChange = onValueChange, + value = text, + onValueChange = { newValue -> + // Allow only digits and empty value + if (newValue.isEmpty() || newValue.all { it.isDigit() }) { + onValueChange(newValue) + } + }, + textStyle = bodyTextStyle, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + leadingIcon = { Text(text = labelText, style = bodyTextStyle) } + ) +} + +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun PreviewAspectRatioDialog() { + QuickEditTheme { + AspectRatioDialog( + onDismiss = {}, + onSetRatio = { _, _ -> } + ) + } +} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt index 138497f..e7f2f15 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt @@ -3,7 +3,9 @@ package com.abizer_r.quickedit.ui.cropMode import android.content.res.Configuration import android.graphics.Bitmap import android.view.ViewGroup +import android.view.WindowManager import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -33,6 +36,7 @@ import com.abizer_r.quickedit.theme.QuickEditTheme import com.abizer_r.quickedit.utils.defaultErrorToast import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer import com.abizer_r.quickedit.ui.common.bottomToolbarModifier +import com.abizer_r.quickedit.ui.common.crop.AspectRatioDialog import com.abizer_r.quickedit.ui.common.topToolbarModifier import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOption import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOptionsFullWidth @@ -40,8 +44,10 @@ import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_LARGE import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar import com.abizer_r.quickedit.utils.editorScreen.CropModeUtils +import com.abizer_r.quickedit.utils.getActivity import com.abizer_r.quickedit.utils.other.anim.AnimUtils import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap +import com.abizer_r.quickedit.utils.toast import com.canhub.cropper.CropImageOptions import com.canhub.cropper.CropImageView import com.canhub.cropper.CropImageView.OnCropImageCompleteListener @@ -57,6 +63,7 @@ fun CropperScreen( ) { val context = LocalContext.current + val activity = LocalContext.current.getActivity() val lifeCycleOwner = LocalLifecycleOwner.current val colorOnBackground = MaterialTheme.colorScheme.onBackground @@ -71,7 +78,22 @@ fun CropperScreen( toolbarVisible = true } + // Save the previous state + val previousSoftInputMode = remember { + activity?.window?.attributes?.softInputMode ?: WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED + } + + // Set the desired windowSoftInputMode + DisposableEffect(Unit) { + activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) + onDispose { + // Restore the previous windowSoftInputMode + activity?.window?.setSoftInputMode(previousSoftInputMode) + } + } + var shouldCrop by remember { mutableStateOf(false) } + var showCropRatioDialog by remember { mutableStateOf(false) } val cropperOptionsList = remember { CropModeUtils.getCropperOptionsList() } var selectedCropOption by remember { mutableIntStateOf(0) } var cropImageOptions by remember { @@ -121,6 +143,9 @@ fun CropperScreen( aspectRatioY = 1 ) } + -2f -> { + showCropRatioDialog = true + } else -> { cropImageOptions = cropImageOptions.copy( fixAspectRatio = true, @@ -194,6 +219,23 @@ fun CropperScreen( onItemClicked = onCropOptionItemClicked ) } + + AnimatedVisibility( + visible = showCropRatioDialog, + ) { + AspectRatioDialog( + onDismiss = { showCropRatioDialog = false }, + onSetRatio = { x, y -> + context.toast("x = $x, y = $x. r = ${x.toFloat() / y.toFloat()}") + cropImageOptions = cropImageOptions.copy( + fixAspectRatio = true, + aspectRatioX = x, + aspectRatioY = y + ) + showCropRatioDialog = false + } + ) + } } } diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt index f7bdd05..06b9d1a 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt @@ -117,6 +117,15 @@ fun CropperOptionView( color = MaterialTheme.colorScheme.onBackground ) ) + } else if (cropperOption.aspectRatioX == -2f) { + Image( + modifier = Modifier.fillMaxSize(), + imageVector = ImageVector.vectorResource(id = com.abizer_r.quickedit.R.drawable.ic_crop), + contentDescription = null, + colorFilter = ColorFilter.tint( + color = MaterialTheme.colorScheme.onBackground + ) + ) } else { val ratio = cropperOption.aspectRatioX / cropperOption.aspectRatioY Box( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt b/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt index 731476c..edea4bb 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt @@ -12,6 +12,11 @@ object CropModeUtils { aspectRatioY = -1f, label = "free" ), + CropperOption( + aspectRatioX = -2f, + aspectRatioY = -2f, + label = "custom" + ), CropperOption( aspectRatioX = 1f, aspectRatioY = 1f, diff --git a/quickedit/src/main/res/drawable/ic_crop.xml b/quickedit/src/main/res/drawable/ic_crop.xml new file mode 100644 index 0000000..6083389 --- /dev/null +++ b/quickedit/src/main/res/drawable/ic_crop.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/quickedit/src/main/res/values/strings.xml b/quickedit/src/main/res/values/strings.xml index 6c04c52..b7a4916 100644 --- a/quickedit/src/main/res/values/strings.xml +++ b/quickedit/src/main/res/values/strings.xml @@ -36,4 +36,12 @@ - Storage\n App not found Couldn\'t insert uri in content resolver + Enter Aspect Ratio + X + Y + Select + Not a valid number + Fields cannot be empty + Ratio is lesser than minimum allowed + Ratio is greater than maximum allowed \ No newline at end of file