Skip to content

Commit 9ffbe53

Browse files
authored
Merge pull request #25 from Abizer-R/release/rc4
Release - 4
2 parents 70f53ce + e0bfc84 commit 9ffbe53

7 files changed

Lines changed: 290 additions & 2 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ android {
1313
applicationId = "com.abizer_r.quickedit"
1414
minSdk = 24
1515
targetSdk = 34
16-
versionCode = 3
17-
versionName = "1.0.3"
16+
versionCode = 4
17+
versionName = "1.1.0"
1818

1919
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2020
vectorDrawables {
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package com.abizer_r.quickedit.ui.common.crop
2+
3+
import android.content.res.Configuration
4+
import androidx.annotation.StringRes
5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
10+
import androidx.compose.foundation.layout.Spacer
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.width
14+
import androidx.compose.foundation.shape.RoundedCornerShape
15+
import androidx.compose.foundation.text.KeyboardOptions
16+
import androidx.compose.material3.Button
17+
import androidx.compose.material3.MaterialTheme
18+
import androidx.compose.material3.OutlinedTextField
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.platform.LocalContext
28+
import androidx.compose.ui.res.stringResource
29+
import androidx.compose.ui.text.TextStyle
30+
import androidx.compose.ui.text.font.FontWeight
31+
import androidx.compose.ui.text.input.KeyboardType
32+
import androidx.compose.ui.tooling.preview.Preview
33+
import androidx.compose.ui.unit.dp
34+
import androidx.compose.ui.window.Dialog
35+
import androidx.compose.ui.window.DialogProperties
36+
import com.abizer_r.quickedit.R
37+
import com.abizer_r.quickedit.theme.DarkPanel
38+
import com.abizer_r.quickedit.theme.QuickEditTheme
39+
import com.abizer_r.quickedit.utils.defaultTextColor
40+
import com.abizer_r.quickedit.utils.toast
41+
42+
const val MIN_RATIO = 0.15f
43+
const val MAX_RATIO = 5.0f
44+
@Composable
45+
fun AspectRatioDialog(
46+
onDismiss: () -> Unit,
47+
onSetRatio: (Int, Int) -> Unit
48+
) {
49+
50+
val context = LocalContext.current
51+
52+
Dialog(
53+
properties = DialogProperties(
54+
dismissOnBackPress = false,
55+
dismissOnClickOutside = false
56+
),
57+
onDismissRequest = onDismiss,
58+
) {
59+
60+
61+
var aspectX by remember { mutableStateOf("1") }
62+
var aspectY by remember { mutableStateOf("1") }
63+
64+
val titleTextStyle = MaterialTheme.typography.titleMedium.copy(
65+
color = defaultTextColor()
66+
)
67+
val bodyTextStyle = MaterialTheme.typography.bodySmall.copy(
68+
color = defaultTextColor()
69+
)
70+
71+
Box(
72+
modifier = Modifier.background(
73+
color = DarkPanel,
74+
shape = RoundedCornerShape(10.dp)
75+
)
76+
) {
77+
78+
Column(
79+
modifier = Modifier.padding(16.dp),
80+
verticalArrangement = Arrangement.Center,
81+
) {
82+
83+
Text(
84+
text = stringResource(R.string.enter_aspect_ratio),
85+
style = titleTextStyle
86+
)
87+
Spacer(Modifier.height(8.dp))
88+
Row(
89+
horizontalArrangement = Arrangement.SpaceEvenly
90+
) {
91+
RatioInputField(
92+
modifier = Modifier.weight(1f),
93+
text = aspectX,
94+
onValueChange = { newValue -> aspectX = newValue },
95+
labelText = stringResource(R.string.x),
96+
bodyTextStyle = bodyTextStyle,
97+
)
98+
Spacer(Modifier.width(8.dp))
99+
RatioInputField(
100+
modifier = Modifier.weight(1f),
101+
text = aspectY,
102+
onValueChange = { newValue -> aspectY = newValue },
103+
labelText = stringResource(R.string.y),
104+
bodyTextStyle = bodyTextStyle,
105+
)
106+
}
107+
Spacer(Modifier.height(8.dp))
108+
Text(
109+
text = "Aspect Ratio = ${getAspectRatioText(aspectX, aspectY)}",
110+
style = bodyTextStyle
111+
)
112+
Spacer(Modifier.height(16.dp))
113+
Text(
114+
text = "Min Allowed Aspect Ratio = $MIN_RATIO",
115+
style = bodyTextStyle
116+
)
117+
Spacer(Modifier.height(4.dp))
118+
Text(
119+
text = "Max Allowed Aspect Ratio = $MAX_RATIO",
120+
style = bodyTextStyle
121+
)
122+
Spacer(Modifier.height(16.dp))
123+
Button(
124+
modifier = Modifier.align(Alignment.CenterHorizontally),
125+
onClick = {
126+
val validation = validateRatioItem(aspectX, aspectY)
127+
if (validation.isValid) {
128+
onSetRatio(aspectX.toInt(), aspectY.toInt())
129+
} else {
130+
context.toast(context.getString(validation.errorResId ?: R.string.something_went_wrong))
131+
}
132+
}
133+
) {
134+
Text(
135+
modifier = Modifier.padding(horizontal = 16.dp),
136+
text = stringResource(R.string.select),
137+
style = bodyTextStyle.copy(
138+
color = MaterialTheme.colorScheme.background,
139+
fontWeight = FontWeight.Bold
140+
)
141+
)
142+
}
143+
}
144+
}
145+
}
146+
}
147+
148+
private fun getAspectRatioText(x: String, y: String): String {
149+
return getAspectRatio(x, y)?.toString() ?: "Invalid"
150+
}
151+
152+
data class RatioItemValidationResult(
153+
val isValid: Boolean,
154+
@StringRes val errorResId: Int? = null
155+
)
156+
157+
private fun validateRatioItem(x: String, y: String): RatioItemValidationResult {
158+
if (x.isBlank() || y.isBlank())
159+
return RatioItemValidationResult(false, R.string.fields_cannot_be_empty)
160+
val xF = x.toIntOrNull()
161+
val yF = y.toIntOrNull()
162+
if (xF == null || yF == null)
163+
return RatioItemValidationResult(false, R.string.not_a_valid_number)
164+
val ratio = (xF.toFloat() / yF.toFloat())
165+
return when {
166+
ratio < MIN_RATIO -> RatioItemValidationResult(false, R.string.ratio_lesser_than_minimum)
167+
ratio > MAX_RATIO -> RatioItemValidationResult(false, R.string.ratio_greater_than_minimum)
168+
else -> RatioItemValidationResult(true)
169+
}
170+
}
171+
172+
private fun getAspectRatio(x: String, y: String): Float? {
173+
if (x.isBlank() || y.isBlank())
174+
return null
175+
val xF = x.toFloatOrNull() ?: return null
176+
val yF = y.toFloatOrNull() ?: return null
177+
return xF / yF
178+
}
179+
180+
@Composable
181+
private fun RatioInputField(
182+
modifier: Modifier,
183+
text: String,
184+
// onValueChange: (TextFieldValue) -> Unit,
185+
onValueChange: (String) -> Unit,
186+
labelText: String,
187+
bodyTextStyle: TextStyle
188+
) {
189+
OutlinedTextField(
190+
modifier = modifier,
191+
singleLine = true,
192+
// value = TextFieldValue(
193+
// text = text,
194+
// selection = TextRange(text.length)
195+
// ),
196+
// onValueChange = onValueChange,
197+
value = text,
198+
onValueChange = { newValue ->
199+
// Allow only digits and empty value
200+
if (newValue.isEmpty() || newValue.all { it.isDigit() }) {
201+
onValueChange(newValue)
202+
}
203+
},
204+
textStyle = bodyTextStyle,
205+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
206+
leadingIcon = { Text(text = labelText, style = bodyTextStyle) }
207+
)
208+
}
209+
210+
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
211+
@Composable
212+
fun PreviewAspectRatioDialog() {
213+
QuickEditTheme {
214+
AspectRatioDialog(
215+
onDismiss = {},
216+
onSetRatio = { _, _ -> }
217+
)
218+
}
219+
}

quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package com.abizer_r.quickedit.ui.cropMode
33
import android.content.res.Configuration
44
import android.graphics.Bitmap
55
import android.view.ViewGroup
6+
import android.view.WindowManager
67
import androidx.activity.compose.BackHandler
8+
import androidx.compose.animation.AnimatedVisibility
79
import androidx.compose.animation.ExperimentalSharedTransitionApi
810
import androidx.compose.foundation.background
911
import androidx.compose.foundation.layout.Box
1012
import androidx.compose.foundation.layout.fillMaxSize
1113
import androidx.compose.foundation.layout.padding
1214
import androidx.compose.material3.MaterialTheme
1315
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.DisposableEffect
1417
import androidx.compose.runtime.LaunchedEffect
1518
import androidx.compose.runtime.getValue
1619
import androidx.compose.runtime.mutableIntStateOf
@@ -33,15 +36,18 @@ import com.abizer_r.quickedit.theme.QuickEditTheme
3336
import com.abizer_r.quickedit.utils.defaultErrorToast
3437
import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer
3538
import com.abizer_r.quickedit.ui.common.bottomToolbarModifier
39+
import com.abizer_r.quickedit.ui.common.crop.AspectRatioDialog
3640
import com.abizer_r.quickedit.ui.common.topToolbarModifier
3741
import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOption
3842
import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOptionsFullWidth
3943
import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_LARGE
4044
import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL
4145
import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar
4246
import com.abizer_r.quickedit.utils.editorScreen.CropModeUtils
47+
import com.abizer_r.quickedit.utils.getActivity
4348
import com.abizer_r.quickedit.utils.other.anim.AnimUtils
4449
import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap
50+
import com.abizer_r.quickedit.utils.toast
4551
import com.canhub.cropper.CropImageOptions
4652
import com.canhub.cropper.CropImageView
4753
import com.canhub.cropper.CropImageView.OnCropImageCompleteListener
@@ -57,6 +63,7 @@ fun CropperScreen(
5763
) {
5864

5965
val context = LocalContext.current
66+
val activity = LocalContext.current.getActivity()
6067
val lifeCycleOwner = LocalLifecycleOwner.current
6168

6269
val colorOnBackground = MaterialTheme.colorScheme.onBackground
@@ -71,7 +78,22 @@ fun CropperScreen(
7178
toolbarVisible = true
7279
}
7380

81+
// Save the previous state
82+
val previousSoftInputMode = remember {
83+
activity?.window?.attributes?.softInputMode ?: WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED
84+
}
85+
86+
// Set the desired windowSoftInputMode
87+
DisposableEffect(Unit) {
88+
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
89+
onDispose {
90+
// Restore the previous windowSoftInputMode
91+
activity?.window?.setSoftInputMode(previousSoftInputMode)
92+
}
93+
}
94+
7495
var shouldCrop by remember { mutableStateOf(false) }
96+
var showCropRatioDialog by remember { mutableStateOf(false) }
7597
val cropperOptionsList = remember { CropModeUtils.getCropperOptionsList() }
7698
var selectedCropOption by remember { mutableIntStateOf(0) }
7799
var cropImageOptions by remember {
@@ -121,6 +143,9 @@ fun CropperScreen(
121143
aspectRatioY = 1
122144
)
123145
}
146+
-2f -> {
147+
showCropRatioDialog = true
148+
}
124149
else -> {
125150
cropImageOptions = cropImageOptions.copy(
126151
fixAspectRatio = true,
@@ -194,6 +219,23 @@ fun CropperScreen(
194219
onItemClicked = onCropOptionItemClicked
195220
)
196221
}
222+
223+
AnimatedVisibility(
224+
visible = showCropRatioDialog,
225+
) {
226+
AspectRatioDialog(
227+
onDismiss = { showCropRatioDialog = false },
228+
onSetRatio = { x, y ->
229+
context.toast("x = $x, y = $x. r = ${x.toFloat() / y.toFloat()}")
230+
cropImageOptions = cropImageOptions.copy(
231+
fixAspectRatio = true,
232+
aspectRatioX = x,
233+
aspectRatioY = y
234+
)
235+
showCropRatioDialog = false
236+
}
237+
)
238+
}
197239
}
198240

199241
}

quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ fun CropperOptionView(
117117
color = MaterialTheme.colorScheme.onBackground
118118
)
119119
)
120+
} else if (cropperOption.aspectRatioX == -2f) {
121+
Image(
122+
modifier = Modifier.fillMaxSize(),
123+
imageVector = ImageVector.vectorResource(id = com.abizer_r.quickedit.R.drawable.ic_crop),
124+
contentDescription = null,
125+
colorFilter = ColorFilter.tint(
126+
color = MaterialTheme.colorScheme.onBackground
127+
)
128+
)
120129
} else {
121130
val ratio = cropperOption.aspectRatioX / cropperOption.aspectRatioY
122131
Box(

quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ object CropModeUtils {
1212
aspectRatioY = -1f,
1313
label = "free"
1414
),
15+
CropperOption(
16+
aspectRatioX = -2f,
17+
aspectRatioY = -2f,
18+
label = "custom"
19+
),
1520
CropperOption(
1621
aspectRatioX = 1f,
1722
aspectRatioY = 1f,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<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">
2+
3+
<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"/>
4+
5+
</vector>

quickedit/src/main/res/values/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@
3636
<string name="perm_item_storage">- Storage\n</string>
3737
<string name="app_not_found">App not found</string>
3838
<string name="fail_to_insert_uri_in_content_resolver">Couldn\'t insert uri in content resolver</string>
39+
<string name="enter_aspect_ratio">Enter Aspect Ratio</string>
40+
<string name="x">X</string>
41+
<string name="y">Y</string>
42+
<string name="select">Select</string>
43+
<string name="not_a_valid_number">Not a valid number</string>
44+
<string name="fields_cannot_be_empty">Fields cannot be empty</string>
45+
<string name="ratio_lesser_than_minimum">Ratio is lesser than minimum allowed</string>
46+
<string name="ratio_greater_than_minimum">Ratio is greater than maximum allowed</string>
3947
</resources>

0 commit comments

Comments
 (0)