From f827e75b1e13913937ab866584cf26a84dbc5f51 Mon Sep 17 00:00:00 2001 From: kizzy Date: Fri, 26 Jun 2026 00:26:17 +0700 Subject: [PATCH] added manual entry and route to password for necessary transactions, integrated cz host --- .../ui/dashboard/DashboardScreen.kt | 2 +- .../ui/dashboard/DashboardViewModel.kt | 9 +- .../ui/manual_entry/ManualEntryRoute.kt | 438 ++++++++++++++++++ .../utsmyanmar/ui/navigation/AppNavGraph.kt | 75 ++- .../mob/utsmyanmar/ui/navigation/Routes.kt | 3 + .../ui/sending_to_host/SendingToHostRoute.kt | 22 +- .../ui/sending_to_host/SendingToHostScreen.kt | 39 +- .../viewmodel/TransProcessViewModel.kt | 5 +- 8 files changed, 579 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt index 8d07841..50382ca 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt @@ -494,7 +494,7 @@ private fun AdvertisingArea( modifier = modifier, shape = RoundedCornerShape(0.dp), colors = CardDefaults.cardColors(containerColor = Color.White), -) { + ) { HorizontalPager( state = pageState, modifier = Modifier.fillMaxSize() ) { page -> diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt index f77384c..9081301 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt @@ -14,6 +14,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.text.DecimalFormat import javax.inject.Inject @HiltViewModel @@ -57,7 +59,7 @@ class DashboardViewModel @Inject constructor( private fun PayDetail.toRecord(): TrnxRecord { val type = transType?.takeIf { it.isNotBlank() }?.let { formatTypeLabel(it) } ?: "Transaction" - val amount = "MMK %,d".format(amount) + val amount = "MMK ${formatAmount(amount)}" val card = CardNo?.takeIf { it.length >= 4 }?.let { "**** ${it.takeLast(4)}" } ?: "----" val dt = buildDateTime(TradeDate, TradeTime) val approved = tradeAnswerCode == "00" || approvalCode?.isNotBlank() == true @@ -73,6 +75,11 @@ private fun PayDetail.toRecord(): TrnxRecord { ) } + +fun formatAmount(amount: Long): String { + val scaled = BigDecimal(amount).movePointLeft(2) + return DecimalFormat("#,###.00").format(scaled) +} private fun formatTypeLabel(raw: String): String = when { raw.contains("SALE", ignoreCase = true) -> "Sale" raw.contains("VOID", ignoreCase = true) -> "Void" diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt new file mode 100644 index 0000000..eb607ec --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt @@ -0,0 +1,438 @@ +package com.mob.utsmyanmar.ui.manual_entry + +import androidx.compose.animation.AnimatedVisibility +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material.icons.outlined.CreditCard +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.preview.P3Preview +import com.mob.utsmyanmar.ui.theme.Color +import com.mob.utsmyanmar.utils.TransactionUtil +import com.mob.utsmyanmar.viewmodel.SharedViewModel +import com.mob.utsmyanmar.viewmodel.TransProcessViewModel +import com.utsmyanmar.checkxread.model.CardDataX +import com.utsmyanmar.checkxread.util.CardTypeX +import com.utsmyanmar.paylibs.model.TradeData +import com.utsmyanmar.paylibs.utils.POSUtil +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import sunmi.sunmiui.utils.LogUtil + +private const val PAN_RAW_MAX = 16 +private const val EXPIRY_RAW_MAX = 4 + + +@Composable +fun InputManualCardRoute( + transProcessViewModel: TransProcessViewModel, + sharedViewModel: SharedViewModel, + pinPadViewModel: PinPadViewModel, + onBack: () -> Unit, + onNavigatePinPad: () -> Unit +) { + var pan by rememberSaveable { mutableStateOf("") } + var expiry by rememberSaveable { mutableStateOf("") } + var panError by rememberSaveable { mutableStateOf(null) } + var expiryError by rememberSaveable { mutableStateOf(null) } + val focusManager = LocalFocusManager.current + + LaunchedEffect(Unit) { + pan = ""; expiry = "" + panError = null; expiryError = null + } + + fun validateAndConfirm() { + panError = null; expiryError = null + val rawPan = pan.filter { it.isDigit() } + val rawExp = expiry.filter { it.isDigit() } + + if (rawPan.length < 13 || rawPan.length > PAN_RAW_MAX) { + panError = "Invalid card number"; return + } + if (!luhnCheck(rawPan)) { + panError = "Invalid card number"; return + } + if (rawExp.length != EXPIRY_RAW_MAX) { + expiryError = "Enter MM/YY"; return + } + val month = rawExp.take(2).toIntOrNull() ?: 0 + if (month < 1 || month > 12) { + expiryError = "Invalid month"; return + } + + focusManager.clearFocus() + + LogUtil.d("Tag","exp date : $rawExp") + val cardNo = rawPan.replace("\\s","") + val expDate = + POSUtil.getInstance().getISOFormatExpDate(rawExp) + + val mpuCardData = CardDataX() + mpuCardData.setPan(cardNo) + mpuCardData.setExp(expDate) + mpuCardData.setCardHolderName("MANUAL ENTRY") + + val tradeData = + TransactionUtil.initMPUTransaction(mpuCardData, CardTypeX.MANUAL) + transProcessViewModel.setTransType(sharedViewModel.transactionsType.value) + transProcessViewModel.setTradeData(tradeData) + pinPadViewModel.setTradeData(tradeData) + onNavigatePinPad() + } + + ManualCardEntryScreen( + pan = pan, + expiry = expiry, + panError = panError, + expiryError = expiryError, + onPanChange = { input -> + val digits = input.filter { it.isDigit() }.take(PAN_RAW_MAX) + pan = digits + panError = null + }, + onExpiryChange = { input -> + val digits = input.filter { it.isDigit() }.take(EXPIRY_RAW_MAX) + expiry = digits + expiryError = null + }, + onBackClick = onBack, + onCancelClick = onBack, + onConfirmClick = ::validateAndConfirm + ) +} + + +@Composable +private fun ManualCardEntryScreen( + pan: String, + expiry: String, + panError: String?, + expiryError: String?, + onPanChange: (String) -> Unit, + onExpiryChange: (String) -> Unit, + onBackClick: () -> Unit, + onCancelClick: () -> Unit, + onConfirmClick: () -> Unit +) { + val panDisplay = remember(pan) { + formatPan(pan) + } + + val expiryDisplay = remember(expiry) { + formatExpiry(expiry) + } + val cardScheme = detectCardScheme(pan) + val confirmEnabled = pan.length >= 13 && expiry.length == 4 + + val expiryFocusRequester = remember { FocusRequester() } + + LaunchedEffect(pan) { + if (pan.length == PAN_RAW_MAX) expiryFocusRequester.requestFocus() + } + + Scaffold( + topBar = { AppBar(title = "Manual Entry", icon = Icons.AutoMirrored.Rounded.ArrowBack, onIconClick = onBackClick) }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(horizontal = 20.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + + Surface( + shape = MaterialTheme.shapes.medium, + tonalElevation = 1.dp, + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.padding(16.dp)) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Icon( + imageVector = Icons.Outlined.CreditCard, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.width(8.dp)) + Text( + "Card details", + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (cardScheme != null) { + Spacer(Modifier.weight(1f)) + SchemeBadge(cardScheme) + } + } + + OutlinedTextField( + value = panDisplay, + onValueChange = { + onPanChange(it.text) + }, + label = { Text("Card number") }, + placeholder = { Text("0000 0000 0000 0000") }, + isError = panError != null, + supportingText = { + Text(panError ?: " ") // reserve space to avoid layout jump + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next + ), + keyboardActions = KeyboardActions( + onNext = { expiryFocusRequester.requestFocus() } + ), + singleLine = true, + modifier = Modifier.fillMaxWidth(), + textStyle = MaterialTheme.typography.bodyLarge.copy( + letterSpacing = 2.sp, + fontFamily = FontFamily.Monospace + ) + ) + + Spacer(Modifier.height(4.dp)) + + OutlinedTextField( + value = expiryDisplay, + onValueChange = { + onExpiryChange(it.text) + }, + label = { Text("Expiry date") }, + placeholder = { Text("MM/YY") }, + isError = expiryError != null, + supportingText = { + Text(expiryError ?: " ") + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { onConfirmClick() } + ), + singleLine = true, + modifier = Modifier + .width(160.dp) + .focusRequester(expiryFocusRequester), + textStyle = MaterialTheme.typography.bodyLarge.copy( + letterSpacing = 2.sp, + fontFamily = FontFamily.Monospace + ) + ) + } + } + + AnimatedVisibility(visible = pan.length >= 4) { + Surface( + shape = MaterialTheme.shapes.small, + color = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "**** **** **** ${pan.takeLast(4)}", + style = MaterialTheme.typography.bodyMedium, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.5.sp + ) + if (expiry.length == 4) { + Text( + text = "Exp ${expiryDisplay.text}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } + + Spacer(Modifier.weight(1f)) + + Box( + modifier = Modifier + .fillMaxWidth() + ){ + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = onBackClick, + modifier = Modifier + .weight(1f) + .height(56.dp), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.LegacyRed + ) + ) { + Text("Cancel") + } + + Button( + onClick = onConfirmClick, + modifier = Modifier + .weight(1f) + .height(56.dp), + + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.LegacyRed, + contentColor = Color.White, + disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f), + disabledContentColor = Color.White + ) + ) { + Text( + text = "Confirm", + fontSize = 14.sp, + fontWeight = FontWeight.Medium + ) + } + } + } + } + } +} + + +@Composable +private fun SchemeBadge(scheme: String) { + Surface( + color = MaterialTheme.colorScheme.secondaryContainer, + shape = MaterialTheme.shapes.small + ) { + Text( + text = scheme, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + ) + } +} + +private fun formatPan(raw: String): TextFieldValue { + val formatted = raw.chunked(4).joinToString(" ") + + val cursorPos = buildString { + raw.forEachIndexed { index, c -> + append(c) + if ((index + 1) % 4 == 0 && index != raw.lastIndex) { + append(" ") + } + } + }.length + + return TextFieldValue( + text = formatted, + selection = TextRange(cursorPos) + ) +} + +private fun formatExpiry(raw: String): TextFieldValue { + val formatted = when { + raw.length > 2 -> "${raw.take(2)}/${raw.drop(2)}" + else -> raw + } + + val cursorPos = when { + raw.length <= 2 -> raw.length + else -> raw.length + 1 // account for '/' + } + + return TextFieldValue( + text = formatted, + selection = TextRange(cursorPos) + ) +} +private fun detectCardScheme(pan: String): String? = when { + pan.startsWith("4") -> "Visa" + pan.length >= 2 && (pan.take(2).toInt() in 51..55 || pan.take(4).toIntOrNull() in 2221..2720) -> "Mastercard" + pan.length >= 2 && pan.take(2) in listOf("34", "37") -> "Amex" + pan.startsWith("6") -> "UnionPay" + else -> null +} + +private fun luhnCheck(pan: String): Boolean { + var sum = 0; var alt = false + for (i in pan.length - 1 downTo 0) { + var n = pan[i].digitToIntOrNull() ?: return false + if (alt) { n *= 2; if (n > 9) n -= 9 } + sum += n; alt = !alt + } + return sum % 10 == 0 +} + +@P2Preview +@P3Preview +@Composable +private fun ManualCardEntryScreenPreview() { + MaterialTheme { + ManualCardEntryScreen( + pan = "4111111111111111", + expiry = "1228", + panError = null, + expiryError = null, + onPanChange = {}, + onExpiryChange = {}, + onBackClick = {}, + onCancelClick = {}, + onConfirmClick = {} + ) + } +} + diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt index d44d51f..5b07eaa 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt @@ -23,6 +23,7 @@ import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel import com.mob.utsmyanmar.ui.disable.DisableScreen import com.mob.utsmyanmar.ui.functions.FunctionsScreen import com.mob.utsmyanmar.ui.input_amount.AmountRoute +import com.mob.utsmyanmar.ui.manual_entry.InputManualCardRoute import com.mob.utsmyanmar.ui.notification.NotificationDetailScreen import com.mob.utsmyanmar.ui.notification.NotificationListScreen import com.mob.utsmyanmar.ui.notification.NotificationViewModel @@ -34,6 +35,7 @@ import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel import com.mob.utsmyanmar.ui.refund_rrn.InputRrnRoute +import com.mob.utsmyanmar.ui.refund_rrn.RRNViewModel import com.mob.utsmyanmar.ui.sale_void.TranDetailPage import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen import com.mob.utsmyanmar.ui.sale_void.VoidViewModel @@ -52,7 +54,9 @@ import com.mob.utsmyanmar.viewmodel.CardReaderViewModel import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel +import com.utsmyanmar.paylibs.model.TradeData import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import sunmi.sunmiui.utils.LogUtil @SuppressLint("ContextCastToActivity") @Composable @@ -156,7 +160,24 @@ fun AppNavGraph( }, onNavigateAction = { action -> when (action) { - "Void" -> navController.navigate(Routes.VoidTrace.route) { launchSingleTop = true } + "Void" -> { + sharedViewModel.transactionsType.value = TransactionsType.VOID + navController.navigate(Routes.Password.createRoute(Routes.VoidTrace.route, PasswordType.SYSTEM)) { + launchSingleTop = true + } + } + "Pre-Auth Void" -> navController.navigate(Routes.Password.createRoute(Routes.Amount.createRoute(action), PasswordType.SYSTEM)) { + launchSingleTop = true + } + "Pre-Auth Complete Void" -> { + sharedViewModel.transactionsType.value = TransactionsType.PRE_AUTH_COMPLETE_VOID + navController.navigate(Routes.Password.createRoute(Routes.VoidTrace.route, PasswordType.SYSTEM)) { + launchSingleTop = true + } + } + "Refund" -> navController.navigate(Routes.Password.createRoute(Routes.Amount.createRoute(action), PasswordType.SYSTEM)) { + launchSingleTop = true + } else -> navController.navigate(Routes.Amount.createRoute(action)) { launchSingleTop = true } } }, @@ -206,9 +227,10 @@ fun AppNavGraph( composable(Routes.VoidTrace.route) { val voidViewModel: VoidViewModel = hiltViewModel() - + val sharedViewModel: SharedViewModel = hiltViewModel(activity) VoidTraceScreen( voidViewModel = voidViewModel, + sharedViewModel = sharedViewModel, onNavigateTranDetail = { trace -> navController.navigate(Routes.VoidTranDetail.createRoute(trace)) { launchSingleTop = true @@ -256,8 +278,18 @@ fun AppNavGraph( trace = trace, onBack = { navController.popBackStack() }, onProceedVoid = { payDetail -> - sharedViewModel.transactionsType.value = TransactionsType.VOID - transProcessViewModel.setTransType(TransactionsType.VOID) + if(sharedViewModel.transactionsType.value == TransactionsType.VOID) { + transProcessViewModel.setTransType(TransactionsType.VOID) + payDetail.transType = TransactionsType.VOID.name + payDetail.transactionType = TransactionsType.VOID.value + } else { + transProcessViewModel.setTransType(TransactionsType.PRE_AUTH_COMPLETE_VOID) + payDetail.transType = TransactionsType.PRE_AUTH_COMPLETE_VOID.name + payDetail.transactionType = TransactionsType.PRE_AUTH_COMPLETE_VOID.value + } + val tradeData = TradeData() + tradeData.payDetail = payDetail + transProcessViewModel.setTradeData(tradeData) sharedViewModel.payDetail.value = payDetail navController.navigate(Routes.Processing.route) { popUpTo(Routes.VoidTranDetail.route) { @@ -358,7 +390,14 @@ fun AppNavGraph( CardWaitingScreen( viewModel = cardWaitingViewModel, amount = formatAmountForDisplay(sharedViewModel.amount.value), - onManualEntry = {}, + onManualEntry = { + navController.navigate(Routes.ManualEntry.route) { + popUpTo(Routes.CardWaiting.route) { + inclusive = true + } + launchSingleTop = true + } + }, onProcessingCard = { navController.navigate(Routes.ProcessingCard.route) { popUpTo(Routes.CardWaiting.route) { @@ -454,8 +493,11 @@ fun AppNavGraph( composable(Routes.InputRrn.route) { val sharedViewModel: SharedViewModel = hiltViewModel(activity) - + val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity) + val rrnViewModel: RRNViewModel = hiltViewModel(activity) InputRrnRoute( + rrnViewModel = rrnViewModel, + transProcessViewModel = transProcessViewModel, sharedViewModel = sharedViewModel, onBack = { navController.popBackStack() }, onNavigateProcessing = { @@ -469,6 +511,27 @@ fun AppNavGraph( ) } + composable(Routes.ManualEntry.route) { + val sharedViewModel: SharedViewModel = hiltViewModel(activity) + val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity) + val pinPadViewModel: PinPadViewModel = hiltViewModel(activity) + InputManualCardRoute( + transProcessViewModel = transProcessViewModel, + sharedViewModel = sharedViewModel, + pinPadViewModel = pinPadViewModel, + onBack = { navController.popBackStack() }, + onNavigatePinPad = { + pinPadViewModel.resetSessionState() + navController.navigate(Routes.PinPad.route) { + popUpTo(Routes.ProcessingCard.route) { + inclusive = true + } + launchSingleTop = true + } + }, + ) + } + composable(Routes.Processing.route) { val sharedViewModel: SharedViewModel = hiltViewModel(activity) val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt index 65b2ea2..51cccb7 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt @@ -25,6 +25,9 @@ sealed class Routes(val route: String) { data object PinPad : Routes("pin_pad") data object InputRrn : Routes("input_rrn") data object Processing : Routes("processing") + data object ManualEntry : Routes("manual_entry") + + data object TransactionResult : Routes("transaction_result") data object PrintReceipt : Routes("print_receipt") data object Version : Routes("version") diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt index ff5d20e..fc82ead 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt @@ -1,5 +1,9 @@ package com.mob.utsmyanmar.ui.sending_to_host +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -13,6 +17,10 @@ import com.mob.utsmyanmar.viewmodel.ProcessingTransaction import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier import com.mob.utsmyanmar.model.TransactionStatus import com.mob.utsmyanmar.ui.settlement.SettlementViewModel import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType @@ -35,6 +43,9 @@ fun ProcessingRoute( val settlementStatus by settlementViewModel.uiState.collectAsStateWithLifecycle() + var showReversalDialog by rememberSaveable { mutableStateOf(false) } + var reversalMessage by rememberSaveable { mutableStateOf("") } + if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) { LaunchedEffect(Unit) { settlementViewModel.startSettlementProcess() @@ -52,7 +63,6 @@ fun ProcessingRoute( } } else { LaunchedEffect(Unit) { -// transProcessViewModel.resetTransactionStatus() transProcessViewModel.startOnlineProcess() } @@ -67,10 +77,15 @@ fun ProcessingRoute( sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value onNavigateTransactionResult() } - TransResultStatus.REVERSAL_SUCCESS -> onNavigateMain() - TransResultStatus.REVERSAL_FAIL -> onNavigateMain() + TransResultStatus.REVERSAL_SUCCESS , + TransResultStatus.REVERSAL_FAIL -> { + delay(2500) + onNavigateMain() + } else -> {} } + + } } @@ -79,7 +94,6 @@ fun ProcessingRoute( - if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) { ProcessingScreen(settlementStatus.processingState) } else { diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostScreen.kt index e6c5040..fb5082c 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostScreen.kt @@ -9,8 +9,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,8 +25,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.input_amount.InputAmount +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.preview.P3Preview import com.mob.utsmyanmar.ui.processing_card.ProcessingCardState import com.mob.utsmyanmar.ui.theme.Black +import com.mob.utsmyanmar.ui.theme.Color import com.mob.utsmyanmar.ui.theme.Primary import com.mob.utsmyanmar.ui.theme.White @@ -53,7 +62,28 @@ fun ProcessingScreen(state: ProcessingState) { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - CircularProgressIndicator(color = White) + when (state.title) { + "Reversal Failure" -> { + Icon( + imageVector = Icons.Rounded.Close, + contentDescription = "Failed", + tint = Color.White, + modifier = Modifier.size(64.dp) + ) + } + "Reversal Success" -> { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = "Success", + tint = Color.White, + modifier = Modifier.size(64.dp) + ) + } + else -> { + CircularProgressIndicator(color = White) + } + } + Spacer(modifier = Modifier.height(20.dp)) @@ -86,3 +116,10 @@ fun ProcessingScreen(state: ProcessingState) { } } } + +@P2Preview +@P3Preview +@Composable +fun PreviewSendingToHostScreen(){ + ProcessingScreen(ProcessingState("Processing")) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt index ad40565..36b957d 100644 --- a/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt @@ -317,7 +317,7 @@ class TransProcessViewModel @Inject constructor( _transResultStatus.value = TransResultStatus.REVERSAL_SUCCESS - + _state.value = _state.value.copy("Reversal Success") payDetail?.let { repository.insertPayDetail(it) } @@ -351,12 +351,14 @@ class TransProcessViewModel @Inject constructor( _transResultStatus.value = TransResultStatus.REVERSAL_FAIL + _state.value = _state.value.copy("Reversal Failure") } } else { _transResultStatus.value = TransResultStatus.REVERSAL_FAIL + _state.value = _state.value.copy("Reversal Failure") } } @@ -364,6 +366,7 @@ class TransProcessViewModel @Inject constructor( _transResultStatus.value = TransResultStatus.REVERSAL_FAIL + _state.value = _state.value.copy("Reversal Failure") } }) }