added manual entry and route to password for necessary transactions, integrated cz host

This commit is contained in:
kizzy 2026-06-26 00:26:17 +07:00
parent 6af4b79702
commit f827e75b1e
8 changed files with 579 additions and 14 deletions

View File

@ -494,7 +494,7 @@ private fun AdvertisingArea(
modifier = modifier, modifier = modifier,
shape = RoundedCornerShape(0.dp), shape = RoundedCornerShape(0.dp),
colors = CardDefaults.cardColors(containerColor = Color.White), colors = CardDefaults.cardColors(containerColor = Color.White),
) { ) {
HorizontalPager( HorizontalPager(
state = pageState, modifier = Modifier.fillMaxSize() state = pageState, modifier = Modifier.fillMaxSize()
) { page -> ) { page ->

View File

@ -14,6 +14,8 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.text.DecimalFormat
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -57,7 +59,7 @@ class DashboardViewModel @Inject constructor(
private fun PayDetail.toRecord(): TrnxRecord { private fun PayDetail.toRecord(): TrnxRecord {
val type = transType?.takeIf { it.isNotBlank() }?.let { formatTypeLabel(it) } ?: "Transaction" 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 card = CardNo?.takeIf { it.length >= 4 }?.let { "**** ${it.takeLast(4)}" } ?: "----"
val dt = buildDateTime(TradeDate, TradeTime) val dt = buildDateTime(TradeDate, TradeTime)
val approved = tradeAnswerCode == "00" || approvalCode?.isNotBlank() == true 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 { private fun formatTypeLabel(raw: String): String = when {
raw.contains("SALE", ignoreCase = true) -> "Sale" raw.contains("SALE", ignoreCase = true) -> "Sale"
raw.contains("VOID", ignoreCase = true) -> "Void" raw.contains("VOID", ignoreCase = true) -> "Void"

View File

@ -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<String?>(null) }
var expiryError by rememberSaveable { mutableStateOf<String?>(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 = {}
)
}
}

View File

@ -23,6 +23,7 @@ import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
import com.mob.utsmyanmar.ui.disable.DisableScreen import com.mob.utsmyanmar.ui.disable.DisableScreen
import com.mob.utsmyanmar.ui.functions.FunctionsScreen import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.input_amount.AmountRoute 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.NotificationDetailScreen
import com.mob.utsmyanmar.ui.notification.NotificationListScreen import com.mob.utsmyanmar.ui.notification.NotificationListScreen
import com.mob.utsmyanmar.ui.notification.NotificationViewModel 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.ProcessingCardRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel
import com.mob.utsmyanmar.ui.refund_rrn.InputRrnRoute 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.TranDetailPage
import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen
import com.mob.utsmyanmar.ui.sale_void.VoidViewModel 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.EmvTransactionProcessViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.paylibs.model.TradeData
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import sunmi.sunmiui.utils.LogUtil
@SuppressLint("ContextCastToActivity") @SuppressLint("ContextCastToActivity")
@Composable @Composable
@ -156,7 +160,24 @@ fun AppNavGraph(
}, },
onNavigateAction = { action -> onNavigateAction = { action ->
when (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 } else -> navController.navigate(Routes.Amount.createRoute(action)) { launchSingleTop = true }
} }
}, },
@ -206,9 +227,10 @@ fun AppNavGraph(
composable(Routes.VoidTrace.route) { composable(Routes.VoidTrace.route) {
val voidViewModel: VoidViewModel = hiltViewModel() val voidViewModel: VoidViewModel = hiltViewModel()
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
VoidTraceScreen( VoidTraceScreen(
voidViewModel = voidViewModel, voidViewModel = voidViewModel,
sharedViewModel = sharedViewModel,
onNavigateTranDetail = { trace -> onNavigateTranDetail = { trace ->
navController.navigate(Routes.VoidTranDetail.createRoute(trace)) { navController.navigate(Routes.VoidTranDetail.createRoute(trace)) {
launchSingleTop = true launchSingleTop = true
@ -256,8 +278,18 @@ fun AppNavGraph(
trace = trace, trace = trace,
onBack = { navController.popBackStack() }, onBack = { navController.popBackStack() },
onProceedVoid = { payDetail -> onProceedVoid = { payDetail ->
sharedViewModel.transactionsType.value = TransactionsType.VOID if(sharedViewModel.transactionsType.value == TransactionsType.VOID) {
transProcessViewModel.setTransType(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 sharedViewModel.payDetail.value = payDetail
navController.navigate(Routes.Processing.route) { navController.navigate(Routes.Processing.route) {
popUpTo(Routes.VoidTranDetail.route) { popUpTo(Routes.VoidTranDetail.route) {
@ -358,7 +390,14 @@ fun AppNavGraph(
CardWaitingScreen( CardWaitingScreen(
viewModel = cardWaitingViewModel, viewModel = cardWaitingViewModel,
amount = formatAmountForDisplay(sharedViewModel.amount.value), amount = formatAmountForDisplay(sharedViewModel.amount.value),
onManualEntry = {}, onManualEntry = {
navController.navigate(Routes.ManualEntry.route) {
popUpTo(Routes.CardWaiting.route) {
inclusive = true
}
launchSingleTop = true
}
},
onProcessingCard = { onProcessingCard = {
navController.navigate(Routes.ProcessingCard.route) { navController.navigate(Routes.ProcessingCard.route) {
popUpTo(Routes.CardWaiting.route) { popUpTo(Routes.CardWaiting.route) {
@ -454,8 +493,11 @@ fun AppNavGraph(
composable(Routes.InputRrn.route) { composable(Routes.InputRrn.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity) val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
val rrnViewModel: RRNViewModel = hiltViewModel(activity)
InputRrnRoute( InputRrnRoute(
rrnViewModel = rrnViewModel,
transProcessViewModel = transProcessViewModel,
sharedViewModel = sharedViewModel, sharedViewModel = sharedViewModel,
onBack = { navController.popBackStack() }, onBack = { navController.popBackStack() },
onNavigateProcessing = { 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) { composable(Routes.Processing.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity) val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity) val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)

View File

@ -25,6 +25,9 @@ sealed class Routes(val route: String) {
data object PinPad : Routes("pin_pad") data object PinPad : Routes("pin_pad")
data object InputRrn : Routes("input_rrn") data object InputRrn : Routes("input_rrn")
data object Processing : Routes("processing") data object Processing : Routes("processing")
data object ManualEntry : Routes("manual_entry")
data object TransactionResult : Routes("transaction_result") data object TransactionResult : Routes("transaction_result")
data object PrintReceipt : Routes("print_receipt") data object PrintReceipt : Routes("print_receipt")
data object Version : Routes("version") data object Version : Routes("version")

View File

@ -1,5 +1,9 @@
package com.mob.utsmyanmar.ui.sending_to_host 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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -13,6 +17,10 @@ import com.mob.utsmyanmar.viewmodel.ProcessingTransaction
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import androidx.compose.runtime.collectAsState 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.model.TransactionStatus
import com.mob.utsmyanmar.ui.settlement.SettlementViewModel import com.mob.utsmyanmar.ui.settlement.SettlementViewModel
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@ -35,6 +43,9 @@ fun ProcessingRoute(
val settlementStatus by settlementViewModel.uiState.collectAsStateWithLifecycle() val settlementStatus by settlementViewModel.uiState.collectAsStateWithLifecycle()
var showReversalDialog by rememberSaveable { mutableStateOf(false) }
var reversalMessage by rememberSaveable { mutableStateOf("") }
if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) { if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
settlementViewModel.startSettlementProcess() settlementViewModel.startSettlementProcess()
@ -52,7 +63,6 @@ fun ProcessingRoute(
} }
} else { } else {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// transProcessViewModel.resetTransactionStatus()
transProcessViewModel.startOnlineProcess() transProcessViewModel.startOnlineProcess()
} }
@ -67,14 +77,18 @@ fun ProcessingRoute(
sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value
onNavigateTransactionResult() onNavigateTransactionResult()
} }
TransResultStatus.REVERSAL_SUCCESS -> onNavigateMain() TransResultStatus.REVERSAL_SUCCESS ,
TransResultStatus.REVERSAL_FAIL -> onNavigateMain() TransResultStatus.REVERSAL_FAIL -> {
delay(2500)
onNavigateMain()
}
else -> {} else -> {}
} }
}
}
}
}

View File

@ -9,8 +9,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape 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.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.processing_card.ProcessingCardState
import com.mob.utsmyanmar.ui.theme.Black 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.Primary
import com.mob.utsmyanmar.ui.theme.White import com.mob.utsmyanmar.ui.theme.White
@ -53,7 +62,28 @@ fun ProcessingScreen(state: ProcessingState) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
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) CircularProgressIndicator(color = White)
}
}
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
@ -86,3 +116,10 @@ fun ProcessingScreen(state: ProcessingState) {
} }
} }
} }
@P2Preview
@P3Preview
@Composable
fun PreviewSendingToHostScreen(){
ProcessingScreen(ProcessingState("Processing"))
}

View File

@ -317,7 +317,7 @@ class TransProcessViewModel @Inject constructor(
_transResultStatus.value = _transResultStatus.value =
TransResultStatus.REVERSAL_SUCCESS TransResultStatus.REVERSAL_SUCCESS
_state.value = _state.value.copy("Reversal Success")
payDetail?.let { payDetail?.let {
repository.insertPayDetail(it) repository.insertPayDetail(it)
} }
@ -351,12 +351,14 @@ class TransProcessViewModel @Inject constructor(
_transResultStatus.value = _transResultStatus.value =
TransResultStatus.REVERSAL_FAIL TransResultStatus.REVERSAL_FAIL
_state.value = _state.value.copy("Reversal Failure")
} }
} else { } else {
_transResultStatus.value = _transResultStatus.value =
TransResultStatus.REVERSAL_FAIL TransResultStatus.REVERSAL_FAIL
_state.value = _state.value.copy("Reversal Failure")
} }
} }
@ -364,6 +366,7 @@ class TransProcessViewModel @Inject constructor(
_transResultStatus.value = _transResultStatus.value =
TransResultStatus.REVERSAL_FAIL TransResultStatus.REVERSAL_FAIL
_state.value = _state.value.copy("Reversal Failure")
} }
}) })
} }