Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 = { _, _ -> }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ 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
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
Expand All @@ -33,15 +36,18 @@ 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
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
Expand All @@ -57,6 +63,7 @@ fun CropperScreen(
) {

val context = LocalContext.current
val activity = LocalContext.current.getActivity()
val lifeCycleOwner = LocalLifecycleOwner.current

val colorOnBackground = MaterialTheme.colorScheme.onBackground
Expand All @@ -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 {
Expand Down Expand Up @@ -121,6 +143,9 @@ fun CropperScreen(
aspectRatioY = 1
)
}
-2f -> {
showCropRatioDialog = true
}
else -> {
cropImageOptions = cropImageOptions.copy(
fixAspectRatio = true,
Expand Down Expand Up @@ -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
}
)
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ object CropModeUtils {
aspectRatioY = -1f,
label = "free"
),
CropperOption(
aspectRatioX = -2f,
aspectRatioY = -2f,
label = "custom"
),
CropperOption(
aspectRatioX = 1f,
aspectRatioY = 1f,
Expand Down
5 changes: 5 additions & 0 deletions quickedit/src/main/res/drawable/ic_crop.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M17,15h2V7c0,-1.1 -0.9,-2 -2,-2H9v2h8v8zM7,17V1H5v4H1v2h4v10c0,1.1 0.9,2 2,2h10v4h2v-4h4v-2H7z"/>

</vector>
8 changes: 8 additions & 0 deletions quickedit/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@
<string name="perm_item_storage">- Storage\n</string>
<string name="app_not_found">App not found</string>
<string name="fail_to_insert_uri_in_content_resolver">Couldn\'t insert uri in content resolver</string>
<string name="enter_aspect_ratio">Enter Aspect Ratio</string>
<string name="x">X</string>
<string name="y">Y</string>
<string name="select">Select</string>
<string name="not_a_valid_number">Not a valid number</string>
<string name="fields_cannot_be_empty">Fields cannot be empty</string>
<string name="ratio_lesser_than_minimum">Ratio is lesser than minimum allowed</string>
<string name="ratio_greater_than_minimum">Ratio is greater than maximum allowed</string>
</resources>