99 */
1010package org.mifospay.feature.send.money
1111
12+ import androidx.compose.animation.AnimatedVisibility
13+ import androidx.compose.animation.core.animateFloatAsState
14+ import androidx.compose.animation.core.repeatable
15+ import androidx.compose.animation.core.tween
16+ import androidx.compose.animation.fadeIn
17+ import androidx.compose.animation.fadeOut
1218import androidx.compose.foundation.background
1319import androidx.compose.foundation.layout.Arrangement
1420import androidx.compose.foundation.layout.Box
1521import androidx.compose.foundation.layout.Column
22+ import androidx.compose.foundation.layout.Row
1623import androidx.compose.foundation.layout.Spacer
1724import androidx.compose.foundation.layout.fillMaxWidth
1825import androidx.compose.foundation.layout.height
1926import androidx.compose.foundation.layout.padding
2027import androidx.compose.foundation.layout.size
28+ import androidx.compose.foundation.layout.width
29+ import androidx.compose.foundation.layout.wrapContentWidth
2130import androidx.compose.foundation.rememberScrollState
2231import androidx.compose.foundation.shape.CircleShape
2332import androidx.compose.foundation.shape.RoundedCornerShape
33+ import androidx.compose.foundation.text.BasicTextField
2434import androidx.compose.foundation.text.KeyboardOptions
2535import androidx.compose.foundation.verticalScroll
2636import androidx.compose.material3.Button
@@ -29,16 +39,23 @@ import androidx.compose.material3.Card
2939import androidx.compose.material3.CardDefaults
3040import androidx.compose.material3.ExperimentalMaterial3Api
3141import androidx.compose.material3.Icon
32- import androidx.compose.material3.OutlinedTextField
3342import androidx.compose.material3.Text
3443import androidx.compose.runtime.Composable
3544import androidx.compose.runtime.getValue
45+ import androidx.compose.runtime.remember
3646import androidx.compose.ui.Alignment
3747import androidx.compose.ui.Modifier
48+ import androidx.compose.ui.draw.clip
49+ import androidx.compose.ui.focus.FocusRequester
50+ import androidx.compose.ui.focus.focusRequester
51+ import androidx.compose.ui.graphics.graphicsLayer
52+ import androidx.compose.ui.text.TextStyle
3853import androidx.compose.ui.text.font.FontWeight
3954import androidx.compose.ui.text.input.KeyboardType
4055import androidx.compose.ui.text.style.TextAlign
4156import androidx.compose.ui.unit.dp
57+ import androidx.compose.ui.unit.sp
58+ import androidx.compose.ui.unit.times
4259import androidx.lifecycle.compose.collectAsStateWithLifecycle
4360import org.koin.compose.viewmodel.koinViewModel
4461import org.mifospay.core.designsystem.component.MifosGradientBackground
@@ -90,7 +107,7 @@ fun PayeeDetailsScreen(
90107
91108 PayeeProfileSection (state)
92109
93- Spacer (modifier = Modifier .height(KptTheme .spacing.lg ))
110+ Spacer (modifier = Modifier .height(KptTheme .spacing.xl ))
94111
95112 PaymentDetailsSection (
96113 state = state,
@@ -146,17 +163,48 @@ private fun PayeeProfileSection(
146163 ),
147164 contentAlignment = Alignment .Center ,
148165 ) {
149- Icon (
150- imageVector = MifosIcons .Person ,
151- contentDescription = " Payee Profile" ,
152- modifier = Modifier .size(40 .dp),
153- tint = KptTheme .colorScheme.onPrimaryContainer,
154- )
166+ if (state.payeeName.isNotEmpty() && state.payeeName != " UNKNOWN" ) {
167+ val firstLetter = state.payeeName
168+ .replace(" %20" , " " )
169+ .trim()
170+ .firstOrNull()
171+ ?.uppercase()
172+
173+ if (firstLetter != null ) {
174+ Text (
175+ text = firstLetter,
176+ style = KptTheme .typography.headlineLarge.copy(
177+ fontSize = 32 .sp,
178+ fontWeight = FontWeight .Bold ,
179+ ),
180+ color = KptTheme .colorScheme.onPrimaryContainer,
181+ textAlign = TextAlign .Center ,
182+ )
183+ } else {
184+ Icon (
185+ imageVector = MifosIcons .Person ,
186+ contentDescription = " Payee Profile" ,
187+ modifier = Modifier .size(40 .dp),
188+ tint = KptTheme .colorScheme.onPrimaryContainer,
189+ )
190+ }
191+ } else {
192+ Icon (
193+ imageVector = MifosIcons .Person ,
194+ contentDescription = " Payee Profile" ,
195+ modifier = Modifier .size(40 .dp),
196+ tint = KptTheme .colorScheme.onPrimaryContainer,
197+ )
198+ }
155199 }
156200
157- if (state.payeeName.isNotEmpty()) {
201+ if (state.payeeName.isNotEmpty() && state.payeeName != " UNKNOWN" ) {
202+ val decodedName = state.payeeName
203+ .replace(" %20" , " " )
204+ .trim()
205+
158206 Text (
159- text = state.payeeName ,
207+ text = " Paying ${decodedName.uppercase()} " ,
160208 style = KptTheme .typography.headlineSmall,
161209 fontWeight = FontWeight .SemiBold ,
162210 color = KptTheme .colorScheme.onSurface,
@@ -165,7 +213,7 @@ private fun PayeeProfileSection(
165213 }
166214
167215 val contactInfo = if (state.isUpiCode) {
168- state.upiId
216+ " UPI ID: ${ state.upiId} "
169217 } else {
170218 state.phoneNumber
171219 }
@@ -190,54 +238,191 @@ private fun PaymentDetailsSection(
190238 onNoteChange : (String ) -> Unit ,
191239 modifier : Modifier = Modifier ,
192240) {
193- Card (
241+ Column (
194242 modifier = modifier.fillMaxWidth(),
195- colors = CardDefaults .cardColors(
196- containerColor = KptTheme .colorScheme.surface,
197- ),
198- shape = RoundedCornerShape (KptTheme .spacing.md),
199- elevation = CardDefaults .cardElevation(defaultElevation = 4 .dp),
243+ horizontalAlignment = Alignment .CenterHorizontally ,
244+ verticalArrangement = Arrangement .spacedBy(KptTheme .spacing.lg),
200245 ) {
201- Column (
246+ ExpandableAmountInput (
247+ value = state.formattedAmount,
248+ onValueChange = onAmountChange,
249+ enabled = state.isAmountEditable,
250+ modifier = Modifier .wrapContentWidth(),
251+ )
252+
253+ AnimatedVisibility (
254+ visible = state.showMaxAmountMessage,
255+ enter = fadeIn(animationSpec = tween(300 )),
256+ exit = fadeOut(animationSpec = tween(300 )),
257+ ) {
258+ val vibrationOffset by animateFloatAsState(
259+ targetValue = if (state.showMaxAmountMessage) 1f else 0f ,
260+ animationSpec = repeatable(
261+ iterations = 3 ,
262+ animation = tween(100 , delayMillis = 0 ),
263+ ),
264+ label = " vibration" ,
265+ )
266+
267+ Text (
268+ text = " Amount cannot be more than ₹ 5,00,000" ,
269+ style = KptTheme .typography.bodySmall,
270+ color = KptTheme .colorScheme.error,
271+ modifier = Modifier
272+ .padding(top = KptTheme .spacing.xs)
273+ .graphicsLayer {
274+ translationX = if (state.showMaxAmountMessage) {
275+ (vibrationOffset * 10f * (if (vibrationOffset % 2 == 0f ) 1f else - 1f ))
276+ } else {
277+ 0f
278+ }
279+ },
280+ )
281+ }
282+
283+ ExpandableNoteInput (
284+ value = state.note,
285+ onValueChange = onNoteChange,
286+ modifier = Modifier .wrapContentWidth(),
287+ )
288+ }
289+ }
290+
291+ @Composable
292+ private fun ExpandableAmountInput (
293+ value : String ,
294+ onValueChange : (String ) -> Unit ,
295+ enabled : Boolean ,
296+ modifier : Modifier = Modifier ,
297+ ) {
298+ val focusRequester = remember { FocusRequester () }
299+ val displayValue = value.ifEmpty { " 0" }
300+
301+ Column (modifier = modifier) {
302+ Row (
202303 modifier = Modifier
203- .fillMaxWidth()
204- .padding(KptTheme .spacing.lg),
205- verticalArrangement = Arrangement .spacedBy(KptTheme .spacing.lg),
304+ .wrapContentWidth()
305+ .clip(RoundedCornerShape (KptTheme .spacing.sm))
306+ .background(
307+ color = KptTheme .colorScheme.surfaceVariant.copy(alpha = 0.3f ),
308+ shape = RoundedCornerShape (KptTheme .spacing.sm),
309+ )
310+ .padding(
311+ horizontal = KptTheme .spacing.md,
312+ vertical = KptTheme .spacing.sm,
313+ ),
314+ verticalAlignment = Alignment .CenterVertically ,
315+ horizontalArrangement = Arrangement .Center ,
206316 ) {
207317 Text (
208- text = " Payment Details" ,
209- style = KptTheme .typography.titleLarge,
210- fontWeight = FontWeight .SemiBold ,
211- color = KptTheme .colorScheme.onSurface,
318+ text = " ₹" ,
319+ style = TextStyle (
320+ fontSize = 24 .sp,
321+ fontWeight = FontWeight .Medium ,
322+ color = KptTheme .colorScheme.onSurface,
323+ ),
212324 )
213325
214- OutlinedTextField (
215- value = state.amount,
216- onValueChange = onAmountChange,
217- label = { Text (" Amount" ) },
218- enabled = state.isAmountEditable,
326+ Spacer (modifier = Modifier .width(KptTheme .spacing.sm))
327+
328+ BasicTextField (
329+ value = displayValue,
330+ 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+ }
337+ }
338+ },
339+ enabled = enabled,
219340 keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Number ),
220- modifier = Modifier .fillMaxWidth(),
221- leadingIcon = {
222- Icon (
223- imageVector = MifosIcons .Currency ,
224- contentDescription = " Amount" ,
225- tint = KptTheme .colorScheme.onSurfaceVariant,
341+ textStyle = TextStyle (
342+ fontSize = 24 .sp,
343+ fontWeight = FontWeight .Medium ,
344+ color = KptTheme .colorScheme.onSurface,
345+ textAlign = TextAlign .Center ,
346+ ),
347+ 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+ },
226355 )
227- },
356+ .focusRequester(focusRequester),
357+ singleLine = true ,
228358 )
359+ }
360+ }
361+ }
362+
363+ @Composable
364+ private fun ExpandableNoteInput (
365+ value : String ,
366+ onValueChange : (String ) -> Unit ,
367+ modifier : Modifier = Modifier ,
368+ ) {
369+ val focusRequester = remember { FocusRequester () }
229370
230- OutlinedTextField (
231- value = state.note,
371+ Column (modifier = modifier) {
372+ Row (
373+ modifier = Modifier
374+ .wrapContentWidth()
375+ .clip(RoundedCornerShape (KptTheme .spacing.sm))
376+ .background(
377+ color = KptTheme .colorScheme.surfaceVariant.copy(alpha = 0.3f ),
378+ shape = RoundedCornerShape (KptTheme .spacing.sm),
379+ )
380+ .padding(
381+ horizontal = KptTheme .spacing.md,
382+ vertical = KptTheme .spacing.sm,
383+ ),
384+ verticalAlignment = Alignment .CenterVertically ,
385+ ) {
386+ BasicTextField (
387+ value = value,
232388 onValueChange = { newValue ->
233389 if (newValue.length <= 50 ) {
234- onNoteChange(newValue)
390+ onValueChange(newValue)
391+ }
392+ },
393+ enabled = true ,
394+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Text ),
395+ textStyle = TextStyle (
396+ fontSize = 16 .sp,
397+ fontWeight = FontWeight .Normal ,
398+ color = if (value.isEmpty()) KptTheme .colorScheme.onSurfaceVariant else KptTheme .colorScheme.onSurface,
399+ textAlign = TextAlign .Center ,
400+ ),
401+ modifier = Modifier
402+ .width(
403+ when {
404+ value.length <= 7 -> 7 * 12 .dp
405+ value.length <= 28 -> (value.length + 1 ) * 12 .dp
406+ else -> 28 * 12 .dp
407+ },
408+ )
409+ .focusRequester(focusRequester),
410+ singleLine = value.length <= 28 ,
411+ maxLines = if (value.length > 28 ) 2 else 1 ,
412+ decorationBox = { innerTextField ->
413+ if (value.isEmpty()) {
414+ Text (
415+ text = " Add note" ,
416+ style = TextStyle (
417+ fontSize = 16 .sp,
418+ fontWeight = FontWeight .Normal ,
419+ color = KptTheme .colorScheme.onSurfaceVariant,
420+ textAlign = TextAlign .Center ,
421+ ),
422+ )
235423 }
424+ innerTextField()
236425 },
237- placeholder = { Text (" Add note" ) },
238- modifier = Modifier .fillMaxWidth(),
239- maxLines = 2 ,
240- singleLine = false ,
241426 )
242427 }
243428 }
@@ -249,7 +434,10 @@ private fun ProceedButton(
249434 onProceedClick : () -> Unit ,
250435 modifier : Modifier = Modifier ,
251436) {
252- val isAmountValid = state.amount.isNotEmpty() && state.amount.toDoubleOrNull() != null
437+ val isAmountValid = state.amount.isNotEmpty() &&
438+ state.amount.toLongOrNull() != null &&
439+ state.amount.toLong() > 0 &&
440+ ! state.isAmountExceedingMax
253441 val isContactValid = state.upiId.isNotEmpty() || state.phoneNumber.isNotEmpty()
254442
255443 Button (
0 commit comments