@@ -19,8 +19,10 @@ import androidx.compose.foundation.background
1919import androidx.compose.foundation.layout.Arrangement
2020import androidx.compose.foundation.layout.Box
2121import androidx.compose.foundation.layout.Column
22+ import androidx.compose.foundation.layout.PaddingValues
2223import androidx.compose.foundation.layout.Row
2324import androidx.compose.foundation.layout.Spacer
25+ import androidx.compose.foundation.layout.fillMaxSize
2426import androidx.compose.foundation.layout.fillMaxWidth
2527import androidx.compose.foundation.layout.height
2628import androidx.compose.foundation.layout.padding
@@ -41,13 +43,17 @@ import androidx.compose.material3.ExperimentalMaterial3Api
4143import androidx.compose.material3.Icon
4244import androidx.compose.material3.Text
4345import androidx.compose.runtime.Composable
46+ import androidx.compose.runtime.LaunchedEffect
4447import androidx.compose.runtime.getValue
48+ import androidx.compose.runtime.mutableStateOf
4549import androidx.compose.runtime.remember
50+ import androidx.compose.runtime.setValue
4651import androidx.compose.ui.Alignment
4752import androidx.compose.ui.Modifier
4853import androidx.compose.ui.draw.clip
4954import androidx.compose.ui.focus.FocusRequester
5055import androidx.compose.ui.focus.focusRequester
56+ import androidx.compose.ui.focus.onFocusChanged
5157import androidx.compose.ui.graphics.graphicsLayer
5258import androidx.compose.ui.text.TextStyle
5359import androidx.compose.ui.text.font.FontWeight
@@ -95,40 +101,52 @@ fun PayeeDetailsScreen(
95101 )
96102 },
97103 ) { paddingValues ->
98- Column (
104+ Box (
99105 modifier = Modifier
100- .fillMaxWidth()
101- .padding(paddingValues)
102- .padding(horizontal = KptTheme .spacing.lg)
103- .verticalScroll(rememberScrollState()),
104- verticalArrangement = Arrangement .spacedBy(KptTheme .spacing.lg),
106+ .fillMaxSize()
107+ .padding(paddingValues),
105108 ) {
106- Spacer (modifier = Modifier .height(KptTheme .spacing.md))
109+ Column (
110+ modifier = Modifier
111+ .fillMaxWidth()
112+ .padding(horizontal = KptTheme .spacing.lg)
113+ .verticalScroll(rememberScrollState()),
114+ verticalArrangement = Arrangement .spacedBy(KptTheme .spacing.lg),
115+ ) {
116+ Spacer (modifier = Modifier .height(KptTheme .spacing.md))
107117
108- PayeeProfileSection (state)
118+ PayeeProfileSection (state)
109119
110- Spacer (modifier = Modifier .height(KptTheme .spacing.xl ))
120+ Spacer (modifier = Modifier .height(KptTheme .spacing.xs ))
111121
112- PaymentDetailsSection (
113- state = state,
114- onAmountChange = { amount ->
115- viewModel.trySendAction(PayeeDetailsAction .UpdateAmount (amount))
116- },
117- onNoteChange = { note ->
118- viewModel.trySendAction(PayeeDetailsAction .UpdateNote (note))
119- },
120- )
122+ PaymentDetailsSection (
123+ state = state,
124+ onAmountChange = { amount ->
125+ viewModel.trySendAction(PayeeDetailsAction .UpdateAmount (amount))
126+ },
127+ onNoteChange = { note ->
128+ viewModel.trySendAction(PayeeDetailsAction .UpdateNote (note))
129+ },
130+ onNoteFieldFocused = {
131+ viewModel.trySendAction(PayeeDetailsAction .NoteFieldFocused )
132+ },
133+ )
121134
122- Spacer (modifier = Modifier .height(KptTheme .spacing.xl))
135+ Spacer (modifier = Modifier .height(KptTheme .spacing.xl))
136+ }
123137
124138 ProceedButton (
125139 state = state,
126140 onProceedClick = {
127141 viewModel.trySendAction(PayeeDetailsAction .ProceedToPayment )
128142 },
143+ modifier = Modifier
144+ .align(Alignment .BottomEnd )
145+ .padding(
146+ end = KptTheme .spacing.lg,
147+ bottom = KptTheme .spacing.lg,
148+ ),
129149 )
130-
131- Spacer (modifier = Modifier .height(KptTheme .spacing.lg))
132150 }
133151 }
134152 }
@@ -236,6 +254,7 @@ private fun PaymentDetailsSection(
236254 state : PayeeDetailsState ,
237255 onAmountChange : (String ) -> Unit ,
238256 onNoteChange : (String ) -> Unit ,
257+ onNoteFieldFocused : () -> Unit ,
239258 modifier : Modifier = Modifier ,
240259) {
241260 Column (
@@ -283,11 +302,13 @@ private fun PaymentDetailsSection(
283302 ExpandableNoteInput (
284303 value = state.note,
285304 onValueChange = onNoteChange,
305+ onFieldFocused = onNoteFieldFocused,
286306 modifier = Modifier .wrapContentWidth(),
287307 )
288308 }
289309}
290310
311+ // TODO improve amount validation and UI/UX
291312@Composable
292313private fun ExpandableAmountInput (
293314 value : String ,
@@ -298,6 +319,31 @@ private fun ExpandableAmountInput(
298319 val focusRequester = remember { FocusRequester () }
299320 val displayValue = value.ifEmpty { " 0" }
300321
322+ /* *
323+ * Calculate width based on the display value
324+ * When showing "0" (single digit), use minimal width
325+ * When user enters decimal or additional digits, expand dynamically
326+ * Maximum amount is ₹5,00,000 (6 digits + decimal + up to 2 decimal places = max 9 characters)
327+ */
328+ val textFieldWidth = when {
329+ displayValue == " 0" -> 24 .dp
330+ displayValue.length == 2 -> 32 .dp
331+ displayValue.length == 3 -> 48 .dp
332+ displayValue.length == 4 -> 64 .dp
333+ displayValue.length == 5 -> 80 .dp
334+ displayValue.length == 6 -> 96 .dp
335+ displayValue.length == 7 -> 112 .dp
336+ displayValue.length == 8 -> 128 .dp
337+ displayValue.length == 9 -> 144 .dp
338+ else -> 144 .dp // Maximum width for ₹5,00,000.00
339+ }
340+
341+ LaunchedEffect (enabled) {
342+ if (enabled) {
343+ focusRequester.requestFocus()
344+ }
345+ }
346+
301347 Column (modifier = modifier) {
302348 Row (
303349 modifier = Modifier
@@ -314,59 +360,55 @@ private fun ExpandableAmountInput(
314360 verticalAlignment = Alignment .CenterVertically ,
315361 horizontalArrangement = Arrangement .Center ,
316362 ) {
317- Text (
318- text = " ₹" ,
319- style = TextStyle (
320- fontSize = 24 .sp,
321- fontWeight = FontWeight .Medium ,
322- color = KptTheme .colorScheme.onSurface,
323- ),
363+ Icon (
364+ imageVector = MifosIcons .CurrencyRupee ,
365+ contentDescription = " Rupee Icon" ,
366+ tint = KptTheme .colorScheme.onSurface,
324367 )
325368
326369 Spacer (modifier = Modifier .width(KptTheme .spacing.sm))
327370
328371 BasicTextField (
329372 value = displayValue,
330373 onValueChange = { newValue ->
331- val cleanValue = newValue.replace(" ," , " " ).replace(" ." , " " )
332- if (cleanValue.isEmpty() || cleanValue.toLongOrNull() != null ) {
333- val amount = cleanValue.toLongOrNull() ? : 0L
334- if (amount <= 500000 ) {
335- onValueChange(cleanValue)
336- }
374+ val cleanValue = newValue.replace(" ," , " " )
375+ if (cleanValue.isEmpty() || cleanValue.toDoubleOrNull() != null ) {
376+ val amount = cleanValue.toDoubleOrNull() ? : 0.0
377+
378+ /* *
379+ * Allow the input to be processed by ViewModel for error handling
380+ * The ViewModel will show error message briefly for invalid amounts
381+ */
382+ onValueChange(cleanValue)
337383 }
338384 },
339385 enabled = enabled,
340- keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
386+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Decimal ),
341387 textStyle = TextStyle (
342388 fontSize = 24 .sp,
343389 fontWeight = FontWeight .Medium ,
344390 color = KptTheme .colorScheme.onSurface,
345391 textAlign = TextAlign .Center ,
346392 ),
347393 modifier = Modifier
348- .width(
349- when {
350- displayValue.length <= 1 -> 24 .dp
351- displayValue.length <= 3 -> displayValue.length * 16 .dp
352- displayValue.length <= 6 -> displayValue.length * 14 .dp
353- else -> displayValue.length * 12 .dp
354- },
355- )
394+ .width(textFieldWidth)
356395 .focusRequester(focusRequester),
357396 singleLine = true ,
358397 )
359398 }
360399 }
361400}
362401
402+ // TODO improve add note UI/UX
363403@Composable
364404private fun ExpandableNoteInput (
365405 value : String ,
366406 onValueChange : (String ) -> Unit ,
407+ onFieldFocused : () -> Unit ,
367408 modifier : Modifier = Modifier ,
368409) {
369410 val focusRequester = remember { FocusRequester () }
411+ var isFocused by remember { mutableStateOf(false ) }
370412
371413 Column (modifier = modifier) {
372414 Row (
@@ -406,7 +448,13 @@ private fun ExpandableNoteInput(
406448 else -> 28 * 12 .dp
407449 },
408450 )
409- .focusRequester(focusRequester),
451+ .focusRequester(focusRequester)
452+ .onFocusChanged { focusState ->
453+ if (focusState.isFocused && ! isFocused) {
454+ isFocused = true
455+ onFieldFocused()
456+ }
457+ },
410458 singleLine = value.length <= 28 ,
411459 maxLines = if (value.length > 28 ) 2 else 1 ,
412460 decorationBox = { innerTextField ->
@@ -428,33 +476,51 @@ private fun ExpandableNoteInput(
428476 }
429477}
430478
479+ // TODO improve UI/UX of proceed button
431480@Composable
432481private fun ProceedButton (
433482 state : PayeeDetailsState ,
434483 onProceedClick : () -> Unit ,
435484 modifier : Modifier = Modifier ,
436485) {
437- val isAmountValid = state.amount.isNotEmpty() &&
438- state.amount.toLongOrNull() != null &&
439- state.amount.toLong() > 0 &&
440- ! state.isAmountExceedingMax
486+ val isAmountValid = if (state.isUpiCode) {
487+ state.amount.isNotEmpty() &&
488+ state.amount.toDoubleOrNull() != null &&
489+ state.amount.toDouble() >= 0 &&
490+ ! state.isAmountExceedingMax
491+ } else {
492+ state.amount.isNotEmpty() &&
493+ state.amount.toDoubleOrNull() != null &&
494+ state.amount.toDouble() > 0 &&
495+ ! state.isAmountExceedingMax
496+ }
441497 val isContactValid = state.upiId.isNotEmpty() || state.phoneNumber.isNotEmpty()
498+ val isAmountPrefilled = ! state.isAmountEditable
499+ val showCheckMark = isAmountValid && isContactValid && (isAmountPrefilled || state.hasNoteFieldBeenFocused)
442500
443501 Button (
444502 onClick = onProceedClick,
445503 enabled = isAmountValid && isContactValid,
446- modifier = modifier.fillMaxWidth( ),
504+ modifier = modifier.size( 56 .dp ),
447505 colors = ButtonDefaults .buttonColors(
448- containerColor = KptTheme .colorScheme.primary,
449- contentColor = KptTheme .colorScheme.onPrimary,
506+ containerColor = if (isAmountValid && isContactValid) {
507+ KptTheme .colorScheme.primary
508+ } else {
509+ KptTheme .colorScheme.surfaceVariant
510+ },
511+ contentColor = if (isAmountValid && isContactValid) {
512+ KptTheme .colorScheme.onPrimary
513+ } else {
514+ KptTheme .colorScheme.onSurfaceVariant
515+ },
450516 ),
451517 shape = RoundedCornerShape (KptTheme .spacing.sm),
518+ contentPadding = PaddingValues (0 .dp),
452519 ) {
453- Text (
454- text = if (state.isUpiCode) " Proceed to UPI Payment" else " Proceed to Payment" ,
455- style = KptTheme .typography.titleMedium,
456- fontWeight = FontWeight .SemiBold ,
457- modifier = Modifier .padding(vertical = KptTheme .spacing.sm),
520+ Icon (
521+ imageVector = if (showCheckMark) MifosIcons .Check else MifosIcons .ArrowForward ,
522+ contentDescription = if (showCheckMark) " Proceed" else " Next" ,
523+ modifier = Modifier .size(32 .dp),
458524 )
459525 }
460526}
0 commit comments