manual entry added flow only

This commit is contained in:
kizzy 2026-08-18 17:38:07 +07:00
parent 92a24851f1
commit 145b3ea898
3 changed files with 319 additions and 297 deletions

View File

@ -116,6 +116,7 @@ class CardWaitingViewModel(
} }
fun onManualEntryClick() { fun onManualEntryClick() {
stopCardReading()
viewModelScope.launch { viewModelScope.launch {
_events.send(CardWaitingEvent.GoManualEntry) _events.send(CardWaitingEvent.GoManualEntry)
} }

View File

@ -1,8 +1,8 @@
package com.mob.utsmyanmar.ui.manual_entry package com.mob.utsmyanmar.ui.manual_entry
import androidx.compose.animation.AnimatedVisibility import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -10,21 +10,18 @@ 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.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape 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.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.outlined.CreditCard import androidx.compose.material.icons.automirrored.rounded.Backspace
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -35,33 +32,37 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextRange
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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.mob.utsmyanmar.ui.components.NumericKeypad
import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.ui.preview.P2Preview 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.ui.theme.Color
import com.mob.utsmyanmar.utils.TransactionUtil import com.mob.utsmyanmar.utils.TransactionUtil
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.checkxread.model.CardDataX import com.utsmyanmar.checkxread.model.CardDataX
import com.utsmyanmar.checkxread.util.CardTypeX import com.utsmyanmar.checkxread.util.CardTypeX
import com.utsmyanmar.paylibs.model.TradeData
import com.utsmyanmar.paylibs.utils.POSUtil 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 PAN_RAW_MAX = 19
private const val PAN_RAW_MIN = 13
private const val EXPIRY_RAW_MAX = 4 private const val EXPIRY_RAW_MAX = 4
private enum class ManualEntryStep {
CARD_NUMBER,
EXPIRY
}
private val digitKeys = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf("", "0", "")
)
@Composable @Composable
fun InputManualCardRoute( fun InputManualCardRoute(
@ -71,50 +72,67 @@ fun InputManualCardRoute(
onBack: () -> Unit, onBack: () -> Unit,
onNavigatePinPad: () -> Unit onNavigatePinPad: () -> Unit
) { ) {
var step by rememberSaveable { mutableStateOf(ManualEntryStep.CARD_NUMBER) }
var pan by rememberSaveable { mutableStateOf("") } var pan by rememberSaveable { mutableStateOf("") }
var expiry by rememberSaveable { mutableStateOf("") } var expiry by rememberSaveable { mutableStateOf("") }
var panError by rememberSaveable { mutableStateOf<String?>(null) } var errorMessage by rememberSaveable { mutableStateOf<String?>(null) }
var expiryError by rememberSaveable { mutableStateOf<String?>(null) }
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
pan = ""; expiry = "" step = ManualEntryStep.CARD_NUMBER
panError = null; expiryError = null pan = ""
expiry = ""
errorMessage = null
} }
fun validateAndConfirm() { fun goBack() {
panError = null; expiryError = null when (step) {
val rawPan = pan.filter { it.isDigit() } ManualEntryStep.EXPIRY -> {
val rawExp = expiry.filter { it.isDigit() } step = ManualEntryStep.CARD_NUMBER
expiry = ""
errorMessage = null
}
ManualEntryStep.CARD_NUMBER -> onBack()
}
}
if (rawPan.length < 13 || rawPan.length > PAN_RAW_MAX) { BackHandler { goBack() }
panError = "Invalid card number"; return
fun confirmCardNumber() {
errorMessage = null
if (pan.length < PAN_RAW_MIN || pan.length > PAN_RAW_MAX) {
errorMessage = "Invalid card number"
return
} }
if (!luhnCheck(rawPan)) { if (!luhnCheck(pan)) {
panError = "Invalid card number"; return errorMessage = "Invalid card number"
return
} }
if (rawExp.length != EXPIRY_RAW_MAX) { step = ManualEntryStep.EXPIRY
expiryError = "Enter MM/YY"; return errorMessage = null
}
fun confirmExpiry() {
errorMessage = null
if (expiry.length != EXPIRY_RAW_MAX) {
errorMessage = "Enter MM/YY"
return
} }
val month = rawExp.take(2).toIntOrNull() ?: 0 val month = expiry.take(2).toIntOrNull() ?: 0
if (month < 1 || month > 12) { if (month < 1 || month > 12) {
expiryError = "Invalid month"; return errorMessage = "Invalid month"
return
} }
focusManager.clearFocus() val expDate = POSUtil.getInstance().getISOFormatExpDate(expiry)
val mpuCardData = CardDataX().apply {
setPan(pan)
setExp(expDate)
setCardHolderName("MANUAL ENTRY")
}
LogUtil.d("Tag","exp date : $rawExp") val tradeData = TransactionUtil.initMPUTransaction(mpuCardData, CardTypeX.MANUAL)
val cardNo = rawPan.replace("\\s","") sharedViewModel.setCardDataExist(true)
val expDate = sharedViewModel.payDetail.value = tradeData.payDetail
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.setTransType(sharedViewModel.transactionsType.value)
transProcessViewModel.setTradeData(tradeData) transProcessViewModel.setTradeData(tradeData)
pinPadViewModel.setTradeData(tradeData) pinPadViewModel.setTradeData(tradeData)
@ -122,311 +140,287 @@ fun InputManualCardRoute(
} }
ManualCardEntryScreen( ManualCardEntryScreen(
step = step,
pan = pan, pan = pan,
expiry = expiry, expiry = expiry,
panError = panError, errorMessage = errorMessage,
expiryError = expiryError, onBackClick = ::goBack,
onPanChange = { input -> onCancelClick = ::goBack,
val digits = input.filter { it.isDigit() }.take(PAN_RAW_MAX) onConfirmClick = {
pan = digits when (step) {
panError = null ManualEntryStep.CARD_NUMBER -> confirmCardNumber()
ManualEntryStep.EXPIRY -> confirmExpiry()
}
}, },
onExpiryChange = { input -> onKeyClick = { key ->
val digits = input.filter { it.isDigit() }.take(EXPIRY_RAW_MAX) if (key.isBlank()) return@ManualCardEntryScreen
expiry = digits errorMessage = null
expiryError = null when (step) {
ManualEntryStep.CARD_NUMBER -> {
if (pan.length < PAN_RAW_MAX) {
pan += key.filter { it.isDigit() }
}
}
ManualEntryStep.EXPIRY -> {
if (expiry.length < EXPIRY_RAW_MAX) {
expiry += key.filter { it.isDigit() }
}
}
}
}, },
onBackClick = onBack, onDeleteClick = {
onCancelClick = onBack, errorMessage = null
onConfirmClick = ::validateAndConfirm when (step) {
ManualEntryStep.CARD_NUMBER -> {
if (pan.isNotEmpty()) pan = pan.dropLast(1)
}
ManualEntryStep.EXPIRY -> {
if (expiry.isNotEmpty()) expiry = expiry.dropLast(1)
}
}
}
) )
} }
@Composable @Composable
private fun ManualCardEntryScreen( private fun ManualCardEntryScreen(
step: ManualEntryStep,
pan: String, pan: String,
expiry: String, expiry: String,
panError: String?, errorMessage: String?,
expiryError: String?,
onPanChange: (String) -> Unit,
onExpiryChange: (String) -> Unit,
onBackClick: () -> Unit, onBackClick: () -> Unit,
onCancelClick: () -> Unit, onCancelClick: () -> Unit,
onConfirmClick: () -> Unit onConfirmClick: () -> Unit,
onKeyClick: (String) -> Unit,
onDeleteClick: () -> Unit
) { ) {
val panDisplay = remember(pan) { val isCardStep = step == ManualEntryStep.CARD_NUMBER
formatPan(pan) val displayValue = if (isCardStep) formatPanDisplay(pan) else formatExpiryDisplay(expiry)
val prompt = if (isCardStep) "CARD NUMBER" else "EXPIRY DATE"
val supportingText = when {
errorMessage != null -> errorMessage
isCardStep -> "Enter the card number to continue"
else -> "Enter expiry as MM/YY"
} }
val confirmText = if (isCardStep) "Next" else "Confirm"
val expiryDisplay = remember(expiry) { val confirmEnabled = if (isCardStep) {
formatExpiry(expiry) pan.length >= PAN_RAW_MIN
} } else {
val cardScheme = detectCardScheme(pan) expiry.length == EXPIRY_RAW_MAX
val confirmEnabled = pan.length >= 13 && expiry.length == 4
val expiryFocusRequester = remember { FocusRequester() }
LaunchedEffect(pan) {
if (pan.length == PAN_RAW_MAX) expiryFocusRequester.requestFocus()
} }
val canDelete = if (isCardStep) pan.isNotEmpty() else expiry.isNotEmpty()
val cardScheme = remember(pan) { detectCardScheme(pan) }
Scaffold( Scaffold(
topBar = { AppBar(title = "Manual Entry", icon = Icons.AutoMirrored.Rounded.ArrowBack, onIconClick = onBackClick) }, topBar = {
) { innerPadding -> AppBar(
title = "Manual Entry",
icon = Icons.AutoMirrored.Rounded.ArrowBack,
onIconClick = onBackClick
)
},
containerColor = Color.IvoryBeige
) { paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(paddingValues)
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(horizontal = 20.dp)
.padding(horizontal = 20.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Spacer(modifier = Modifier.height(12.dp))
Surface( Text(
shape = MaterialTheme.shapes.medium, text = prompt,
tonalElevation = 1.dp, color = Color.Gray,
modifier = Modifier.fillMaxWidth() style = MaterialTheme.typography.labelSmall,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) { ) {
Column(modifier = Modifier.padding(16.dp)) { Text(
text = displayValue.ifBlank {
if (isCardStep) "---- ---- ---- ----" else "--/--"
},
color = Color.LegacyRed,
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.SemiBold,
letterSpacing = if (isCardStep) 1.sp else 2.sp,
textAlign = TextAlign.Center
),
textAlign = TextAlign.Center,
maxLines = 2,
modifier = Modifier.weight(1f)
)
Row( Spacer(modifier = Modifier.width(8.dp))
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( Card(
value = panDisplay, modifier = Modifier.clickable(enabled = canDelete, onClick = onDeleteClick),
onValueChange = { shape = RoundedCornerShape(18.dp),
onPanChange(it.text) colors = CardDefaults.cardColors(containerColor = Color.White),
}, elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
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
)
)
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
)
)
}
}
AnimatedVisibility(visible = pan.length >= 4) {
Surface(
shape = MaterialTheme.shapes.small,
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.fillMaxWidth()
) { ) {
Row( Icon(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), imageVector = Icons.AutoMirrored.Rounded.Backspace,
horizontalArrangement = Arrangement.SpaceBetween, contentDescription = "Delete",
verticalAlignment = Alignment.CenterVertically tint = if (canDelete) Color.LegacyRed else Color.Gray,
) { modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
Text( )
text = "**** **** **** ${pan.takeLast(4)}",
style = MaterialTheme.typography.bodyMedium,
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)) Spacer(modifier = Modifier.height(10.dp))
Box( Text(
text = supportingText,
color = if (errorMessage == null) Color.Gray else Color.Error,
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
if (!isCardStep && pan.length >= 4) {
Spacer(modifier = Modifier.height(12.dp))
Text(
text = "**** **** **** ${pan.takeLast(4)}" +
(cardScheme?.let { " · $it" } ?: ""),
color = Color.Gray,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
Spacer(modifier = Modifier.height(28.dp))
NumericKeypad(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
){ .height(280.dp),
Row( onKeyClick = onKeyClick,
modifier = Modifier.fillMaxWidth(), keys = digitKeys
horizontalArrangement = Arrangement.spacedBy(12.dp) )
Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Button(
onClick = onCancelClick,
modifier = Modifier
.weight(1f)
.height(56.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.LegacyRed
)
) { ) {
Button( Text(
onClick = onBackClick, text = if (isCardStep) "Cancel" else "Back",
modifier = Modifier style = MaterialTheme.typography.titleSmall
.weight(1f) )
.height(56.dp), }
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.LegacyRed
)
) {
Text("Cancel")
}
Button( Button(
onClick = onConfirmClick, onClick = onConfirmClick,
modifier = Modifier enabled = confirmEnabled,
.weight(1f) modifier = Modifier
.height(56.dp), .weight(1f)
.height(56.dp),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Color.LegacyRed, containerColor = Color.LegacyRed,
contentColor = Color.White, contentColor = Color.White,
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f), disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
disabledContentColor = Color.White disabledContentColor = Color.White
) )
) { ) {
Text( Text(
text = "Confirm", text = confirmText,
style = MaterialTheme.typography.titleSmall style = MaterialTheme.typography.titleSmall
) )
}
} }
} }
Spacer(modifier = Modifier.height(18.dp))
} }
} }
} }
private fun formatPanDisplay(raw: String): String {
if (raw.isEmpty()) return ""
return raw.chunked(4).joinToString(" ")
}
@Composable private fun formatExpiryDisplay(raw: String): String {
private fun SchemeBadge(scheme: String) { return when {
Surface( raw.isEmpty() -> ""
color = MaterialTheme.colorScheme.secondaryContainer, raw.length <= 2 -> raw
shape = MaterialTheme.shapes.small else -> "${raw.take(2)}/${raw.drop(2)}"
) {
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 { private fun detectCardScheme(pan: String): String? = when {
pan.startsWith("4") -> "Visa" 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).toIntOrNull() in 51..55 ||
pan.take(4).toIntOrNull() in 2221..2720
) -> "Mastercard"
pan.length >= 2 && pan.take(2) in listOf("34", "37") -> "Amex" pan.length >= 2 && pan.take(2) in listOf("34", "37") -> "Amex"
pan.startsWith("6") -> "UnionPay" pan.startsWith("62") || pan.startsWith("6") -> "UnionPay / MPU"
else -> null else -> null
} }
private fun luhnCheck(pan: String): Boolean { private fun luhnCheck(pan: String): Boolean {
var sum = 0; var alt = false var sum = 0
var alt = false
for (i in pan.length - 1 downTo 0) { for (i in pan.length - 1 downTo 0) {
var n = pan[i].digitToIntOrNull() ?: return false var n = pan[i].digitToIntOrNull() ?: return false
if (alt) { n *= 2; if (n > 9) n -= 9 } if (alt) {
sum += n; alt = !alt n *= 2
if (n > 9) n -= 9
}
sum += n
alt = !alt
} }
return sum % 10 == 0 return sum % 10 == 0
} }
@P2Preview @P2Preview
@P3Preview
@Composable @Composable
private fun ManualCardEntryScreenPreview() { private fun PreviewManualCardEntryPan() {
MaterialTheme { ManualCardEntryScreen(
ManualCardEntryScreen( step = ManualEntryStep.CARD_NUMBER,
pan = "4111111111111111", pan = "6244310039574344",
expiry = "1228", expiry = "",
panError = null, errorMessage = null,
expiryError = null, onBackClick = {},
onPanChange = {}, onCancelClick = {},
onExpiryChange = {}, onConfirmClick = {},
onBackClick = {}, onKeyClick = {},
onCancelClick = {}, onDeleteClick = {}
onConfirmClick = {} )
)
}
} }
@P2Preview
@Composable
private fun PreviewManualCardEntryExpiry() {
ManualCardEntryScreen(
step = ManualEntryStep.EXPIRY,
pan = "6244310039574344",
expiry = "1228",
errorMessage = null,
onBackClick = {},
onCancelClick = {},
onConfirmClick = {},
onKeyClick = {},
onDeleteClick = {}
)
}

View File

@ -28,6 +28,7 @@ import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.history_functions.HistoryRoute import com.mob.utsmyanmar.ui.history_functions.HistoryRoute
import com.mob.utsmyanmar.ui.history_functions.prepareReprintPayDetail import com.mob.utsmyanmar.ui.history_functions.prepareReprintPayDetail
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.password_input.InputPassword import com.mob.utsmyanmar.ui.password_input.InputPassword
import com.mob.utsmyanmar.ui.password_input.PasswordType import com.mob.utsmyanmar.ui.password_input.PasswordType
import com.mob.utsmyanmar.ui.pinpad.PinPadRoute import com.mob.utsmyanmar.ui.pinpad.PinPadRoute
@ -554,7 +555,11 @@ 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) {
launchSingleTop = true
}
},
onProcessingCard = { onProcessingCard = {
navController.navigate(Routes.ProcessingCard.route) { navController.navigate(Routes.ProcessingCard.route) {
popUpTo(Routes.CardWaiting.route) { popUpTo(Routes.CardWaiting.route) {
@ -575,6 +580,28 @@ 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.ManualEntry.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
composable(Routes.ProcessingCard.route) { composable(Routes.ProcessingCard.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity) val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val cardReaderViewModel: CardReaderViewModel = hiltViewModel(activity) val cardReaderViewModel: CardReaderViewModel = hiltViewModel(activity)