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+ }
0 commit comments