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