Compare commits

..

4 Commits

Author SHA1 Message Date
moon
72ecfbe4e7 keypad, modify 2026-05-21 11:25:32 +06:30
moon
0f435192ee Merge branch 'dev' of https://hub.utsmyanmar.com/Kyaw_Min_Khant/mob_pos into dev 2026-05-21 11:05:18 +06:30
moon
fe6c5ad15e sign on page 2026-05-21 11:03:12 +06:30
moon
f4fc2fa730 animation added 2026-05-16 21:35:58 +06:30
12 changed files with 793 additions and 167 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@
.cxx
local.properties
/.idea
/.idea

View File

@ -57,5 +57,6 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Backspace
import androidx.compose.material.icons.rounded.KeyboardArrowLeft
import androidx.compose.material3.*
import androidx.compose.runtime.*
@ -28,13 +29,17 @@ fun AmountScreen(
val displayAmount = amount.ifEmpty { "0" }
Column(
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.IvoryBeige)
.padding(horizontal = 20.dp)
.navigationBarsPadding()
.statusBarsPadding()
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Box(
modifier = Modifier
@ -105,12 +110,7 @@ fun AmountScreen(
AmountKeypad(
onNumberClick = { value ->
if (amount.length < 9) {
amount += value
}
},
onDeleteClick = {
amount = amount.dropLast(1)
amount = appendAmountValue(amount, value)
}
)
@ -162,18 +162,37 @@ fun AmountScreen(
Spacer(modifier = Modifier.height(18.dp))
}
Card(
modifier = Modifier
.align(Alignment.TopEnd)//?
.padding(top = 110.dp, end = 20.dp)
.clickable(enabled = amount.isNotEmpty()) {
amount = amount.dropLast(1)
},
shape = RoundedCornerShape(18.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
) {
Icon(
imageVector = Icons.Rounded.Backspace,
contentDescription = "Delete",
tint = if (amount.isNotEmpty()) Color.LegacyRed else Color.Gray,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
)
}
}
}
@Composable
private fun AmountKeypad(
onNumberClick: (String) -> Unit,
onDeleteClick: () -> Unit
onNumberClick: (String) -> Unit
) {
val keys = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf("", "0", "00")
listOf(".", "0", "00")
)
Column(
@ -186,13 +205,6 @@ private fun AmountKeypad(
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
row.forEach { key ->
if (key.isEmpty()) {
Box(
modifier = Modifier
.weight(1f)
.height(66.dp)
)
} else {
KeypadButton(
text = key,
modifier = Modifier.weight(1f),
@ -203,7 +215,6 @@ private fun AmountKeypad(
}
}
}
}
@Composable
private fun KeypadButton(
@ -237,8 +248,46 @@ private fun KeypadButton(
}
private fun formatAmount(value: String): String {
val number = value.toLongOrNull() ?: 0L
return "%,d".format(number)
val normalized = value.ifEmpty { "0" }
val wholePart = normalized.substringBefore(".").ifEmpty { "0" }
val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L)
if (!normalized.contains(".")) {
return groupedWholePart
}
val decimalPart = normalized.substringAfter(".", "")
return if (normalized.endsWith(".")) {
"$groupedWholePart."
} else {
"$groupedWholePart.$decimalPart"
}
}
private fun appendAmountValue(current: String, value: String): String {
if (value == ".") {
if (current.contains(".")) return current
return if (current.isEmpty()) "0." else "$current."
}
val decimalIndex = current.indexOf('.')
return if (decimalIndex >= 0) {
val decimalPart = current.substring(decimalIndex + 1)
val remainingDecimalDigits = 2 - decimalPart.length
if (remainingDecimalDigits <= 0) {
current
} else {
current + value.take(remainingDecimalDigits)
}
} else {
val wholeDigitsCount = current.filter(Char::isDigit).length
val remainingWholeDigits = 9 - wholeDigitsCount
if (remainingWholeDigits <= 0) {
current
} else {
current + value.take(remainingWholeDigits)
}
}
}
@P2Preview

View File

@ -25,7 +25,8 @@ import com.mob.utsmyanmar.ui.theme.Color
@Composable
fun DashboardScreen2(
onNavigateAmount: (String) -> Unit = {}
onNavigateAmount: (String) -> Unit = {},
onNavigateSignOn: () -> Unit = {}
) {
Scaffold(
containerColor = Color.IvoryBeige,
@ -44,7 +45,10 @@ fun DashboardScreen2(
Spacer(modifier = Modifier.height(16.dp))
SummaryCard()
Spacer(modifier = Modifier.height(16.dp))
MenuGrid(onNavigateAmount = onNavigateAmount)
MenuGrid(
onNavigateAmount = onNavigateAmount,
onNavigateSignOn = onNavigateSignOn
)
Spacer(modifier = Modifier.height(18.dp))
// RecentTransactionHeader()
Spacer(modifier = Modifier.height(8.dp))
@ -57,10 +61,8 @@ fun DashboardScreen2(
private fun AdvertisingArea() {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(0.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(8.dp)
) {
Row(
modifier = Modifier.height(170.dp) ) {
@ -164,7 +166,8 @@ private fun SummaryItem(
@Composable
private fun MenuGrid(
onNavigateAmount: (String) -> Unit
onNavigateAmount: (String) -> Unit,
onNavigateSignOn: () -> Unit
) {
Column(
verticalArrangement = Arrangement.spacedBy(10.dp)
@ -181,7 +184,12 @@ private fun MenuGrid(
}
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
MenuCard("Sign On", Icons.Default.Link, Modifier.weight(1f))
MenuCard(
"Sign On",
Icons.Default.Link,
Modifier.weight(1f),
onClick = onNavigateSignOn
)
MenuCard("Settlement", Icons.Default.Wallet, Modifier.weight(1f))
MenuCard("See More", Icons.Default.GridView, Modifier.weight(1f))
}

View File

@ -19,6 +19,9 @@ import com.mob.utsmyanmar.ui.pinpad.PinPadRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel
import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen
import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen
import com.mob.utsmyanmar.ui.sign_on.SignOnRoute
import com.mob.utsmyanmar.ui.sending_to_host.SendingToHostRoute
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel
@ -46,6 +49,62 @@ fun AppNavGraph(
}
launchSingleTop = true
}
},
onNavigateSignOn = {
navController.navigate(Routes.SignOn.route) {
launchSingleTop = true
}
}
)
}
composable(Routes.SignOn.route) {
SignOnRoute(
onBack = { navController.popBackStack() },
onNavigateResult = { isSuccess, message ->
navController.navigate(Routes.SignOnResult.createRoute(isSuccess, message)) {
popUpTo(Routes.SignOn.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
composable(
route = Routes.SignOnResult.route,
arguments = listOf(
navArgument("isSuccess") {
type = NavType.BoolType
},
navArgument("message") {
type = NavType.StringType
}
)
) { backStackEntry ->
val isSuccess = backStackEntry.arguments?.getBoolean("isSuccess") ?: false
val message = backStackEntry.arguments?.getString("message").orEmpty()
SignOnResultScreen(
isSuccess = isSuccess,
message = message,
onBack = { navController.popBackStack() },
onDone = {
navController.navigate(Routes.Dashboard.route) {
popUpTo(Routes.Dashboard.route) {
inclusive = false
}
launchSingleTop = true
}
},
onRetry = {
navController.navigate(Routes.SignOn.route) {
popUpTo(Routes.SignOnResult.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
@ -153,8 +212,8 @@ fun AppNavGraph(
pinPadViewModel = pinPadViewModel,
sharedViewModel = sharedViewModel,
transProcessViewModel = transProcessViewModel,
onNavigateTransactionResult = {
navController.navigate(Routes.TransactionResult.route) {
onNavigateSendingToHost = {
navController.navigate(Routes.SendingToHost.route) {
popUpTo(Routes.PinPad.route) {
inclusive = true
}
@ -165,6 +224,19 @@ fun AppNavGraph(
)
}
composable(Routes.SendingToHost.route) {
SendingToHostRoute(
onNavigateTransactionResult = {
navController.navigate(Routes.TransactionResult.route) {
popUpTo(Routes.SendingToHost.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
composable(Routes.TransactionResult.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)

View File

@ -1,13 +1,22 @@
package com.mob.utsmyanmar.ui.navigation
import android.net.Uri
sealed class Routes(val route: String) {
data object Dashboard : Routes("dashboard")
data object Amount : Routes("amount/{action}") {
fun createRoute(action: String): String = "amount/$action"
}
data object SignOn : Routes("sign_on")
data object SignOnResult : Routes("sign_on_result/{isSuccess}/{message}") {
fun createRoute(isSuccess: Boolean, message: String): String {
return "sign_on_result/$isSuccess/${Uri.encode(message)}"
}
}
data object CardWaiting : Routes("card_waiting")
data object ProcessingCard : Routes("processing_card")
data object PinPad : Routes("pin_pad")
data object SendingToHost : Routes("sending_to_host")
data object TransactionResult : Routes("transaction_result")
data object PrintReceipt : Routes("print_receipt")
}

View File

@ -17,7 +17,7 @@ fun PinPadRoute(
pinPadViewModel: PinPadViewModel,
sharedViewModel: SharedViewModel,
transProcessViewModel: TransProcessViewModel,
onNavigateTransactionResult: () -> Unit,
onNavigateSendingToHost: () -> Unit,
onBack: () -> Unit
) {
val pinText by pinPadViewModel.pinText.collectAsStateWithLifecycle()
@ -37,7 +37,7 @@ fun PinPadRoute(
payDetail?.tradeAnswerCode = Constant.ANSWER_CODE_APPROVED
sharedViewModel.payDetail.value = payDetail
transProcessViewModel.resetTransactionStatus()
onNavigateTransactionResult()
onNavigateSendingToHost()
}
PinPadStatus.ON_CANCEL,

View File

@ -2,6 +2,9 @@ package com.mob.utsmyanmar.ui.print_receipt
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
@ -15,40 +18,47 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
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.theme.Primary
import com.mob.utsmyanmar.ui.theme.White
import com.utsmyanmar.paylibs.print.NewPrintReceipt
import com.mob.utsmyanmar.ui.theme.Color
import com.utsmyanmar.paylibs.print.PrintReceipt
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun PrintReceiptScreen(
onPrint: () -> Unit,
onDone: () -> Unit
) {
BackHandler {
onDone()
val scope = rememberCoroutineScope();
val receiptOffsetY = remember {
Animatable(0f);
}
val animationDuration = 3000;
Scaffold(
topBar = {
AppBar(title = "Receipt")
}
},
containerColor = Color.IvoryBeige
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
@ -57,6 +67,9 @@ fun PrintReceiptScreen(
modifier = Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
.graphicsLayer{
translationY = receiptOffsetY.value
}
)
Spacer(modifier = Modifier.height(16.dp))
@ -87,18 +100,41 @@ fun PrintReceiptScreen(
.weight(1f)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Primary,
contentColor = White,
disabledContainerColor = Primary,
disabledContentColor = White,
containerColor = Color.LegacyRed,
contentColor = Color.White,
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
disabledContentColor = Color.White.copy(alpha = 0.5f),
),
shape = RoundedCornerShape(12.dp),
onClick = {
onPrint()
// onPrint()
try {
NewPrintReceipt.getInstance().testPrint()
PrintReceipt.getInstance().printNow()
scope.launch {
launch {
receiptOffsetY.animateTo(
targetValue = -1500f,
animationSpec = tween(
durationMillis = animationDuration,
easing = FastOutSlowInEasing
)
)
}
delay(5000)
launch {
receiptOffsetY.animateTo(
targetValue = 0f,
animationSpec = tween(
durationMillis = 0,
easing = FastOutSlowInEasing
)
)
}
}
} catch (e: Exception) {
Log.d("PrintReceipt", "error with $e")
}
}
) {
@ -116,6 +152,7 @@ fun PrintReceiptScreen(
}
}
@Composable
fun ReceiptPreview(
modifier: Modifier = Modifier
@ -127,32 +164,26 @@ fun ReceiptPreview(
.background(Color.White)
.border(
width = 1.dp,
color = Color.LightGray,
color = Color.Gray,
shape = RoundedCornerShape(12.dp)
)
.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "MOB POS",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Transaction Receipt",
fontSize = 14.sp,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
ReceiptDivider()
ReceiptRow("Merchant", "MOB UTS Myanmar")
ReceiptRow("Terminal ID", "POS001")
ReceiptRow("Transaction", "SALE")
@ -163,11 +194,8 @@ fun ReceiptPreview(
ReceiptRow("Date", "13/05/2026")
ReceiptRow("Time", "14:30:22")
ReceiptRow("Ref No", "REF123456789")
ReceiptDivider()
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Thank You",
fontSize = 16.sp,
@ -200,7 +228,7 @@ fun ReceiptRow(
text = label,
fontSize = 13.sp,
fontFamily = FontFamily.Monospace,
color = Color.DarkGray
color = Color.Gray
)
Text(

View File

@ -0,0 +1,19 @@
package com.mob.utsmyanmar.ui.sending_to_host
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.delay
private const val MOCK_HOST_DELAY_MS = 2000L
@Composable
fun SendingToHostRoute(
onNavigateTransactionResult: () -> Unit
) {
LaunchedEffect(Unit) {
delay(MOCK_HOST_DELAY_MS)
onNavigateTransactionResult()
}
SendingToHostScreen()
}

View File

@ -0,0 +1,87 @@
package com.mob.utsmyanmar.ui.sending_to_host
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.theme.Black
import com.mob.utsmyanmar.ui.theme.Primary
import com.mob.utsmyanmar.ui.theme.White
@Composable
fun SendingToHostScreen() {
Scaffold(
containerColor = White
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.background(White)
.padding(horizontal = 24.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
color = Primary,
shape = RoundedCornerShape(24.dp)
)
.padding(horizontal = 24.dp, vertical = 32.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
CircularProgressIndicator(color = White)
Spacer(modifier = Modifier.height(20.dp))
Text(
text = "Sending Data",
color = White,
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = "Please wait while we send transaction data to host.",
color = White,
fontSize = 15.sp,
textAlign = TextAlign.Center
)
}
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "Mock host communication in progress.",
color = Black,
fontSize = 14.sp,
textAlign = TextAlign.Center
)
}
}
}

View File

@ -0,0 +1,262 @@
package com.mob.utsmyanmar.ui.sign_on
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.ErrorOutline
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.Color as AppColor
@Composable
fun SignOnRoute(
onBack: () -> Unit,
onNavigateResult: (Boolean, String) -> Unit,
viewModel: SignOnViewModel = viewModel()
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel) {
viewModel.resultEvents.collect { result ->
onNavigateResult(result.isSuccess, result.message)
}
}
SignOnScreen(
state = state,
onBack = onBack,
onStartSignOn = viewModel::startSignOn
)
}
@Composable
fun SignOnScreen(
state: SignOnUiState,
onBack: () -> Unit,
onStartSignOn: () -> Unit
) {
Scaffold(
containerColor = AppColor.IvoryBeige,
topBar = {
AppBar(
title = "Sign On",
icon = Icons.Default.ArrowBack,
onIconClick = onBack
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = AppColor.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = Icons.Default.Sync,
contentDescription = null,
tint = AppColor.LegacyRed,
modifier = Modifier.height(42.dp)
)
Text(
text = "Host Sign On",
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = AppColor.LegacyRed
)
Text(
text = "Run sign on and verify immediately whether the host accepted or rejected the terminal.",
style = MaterialTheme.typography.bodyMedium,
color = AppColor.Gray
)
}
}
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = AppColor.White)
) {
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Current Status",
style = MaterialTheme.typography.titleMedium,
color = AppColor.Black
)
Box(
modifier = Modifier
.fillMaxWidth()
.background(
color = if (state.isLoading) AppColor.GoldenGlow.copy(alpha = 0.18f) else AppColor.SkylineBlue.copy(alpha = 0.12f),
shape = RoundedCornerShape(16.dp)
)
.padding(16.dp)
) {
if (state.isLoading) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
CircularProgressIndicator(color = AppColor.LegacyRed)
Text(text = state.statusText, color = AppColor.Black)
}
} else {
Text(text = state.statusText, color = AppColor.Black)
}
}
Button(
onClick = onStartSignOn,
enabled = !state.isLoading,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = AppColor.CrimsonRed,
disabledContainerColor = AppColor.Gray
)
) {
Text(text = if (state.isLoading) "Processing..." else "Start Sign On")
}
if (state.canRetry) {
OutlinedButton(
onClick = onStartSignOn,
enabled = !state.isLoading,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Retry")
}
}
}
}
}
}
}
@Composable
fun SignOnResultScreen(
isSuccess: Boolean,
message: String,
onBack: () -> Unit,
onDone: () -> Unit,
onRetry: () -> Unit
) {
val accent = if (isSuccess) AppColor.Success else AppColor.LegacyRed
val background = if (isSuccess) AppColor.Success.copy(alpha = 0.12f) else AppColor.CrimsonRed.copy(alpha = 0.12f)
Scaffold(
containerColor = AppColor.IvoryBeige,
topBar = {
AppBar(
title = "Sign On Result",
icon = Icons.Default.ArrowBack,
onIconClick = onBack
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = AppColor.White)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Box(
modifier = Modifier
.background(background, RoundedCornerShape(50))
.padding(18.dp)
) {
Icon(
imageVector = if (isSuccess) Icons.Default.CheckCircle else Icons.Default.ErrorOutline,
contentDescription = null,
tint = accent
)
}
Text(
text = if (isSuccess) "Sign On Success" else "Sign On Failed",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = accent
)
Text(
text = message,
style = MaterialTheme.typography.bodyLarge,
color = Color.Black
)
}
}
Spacer(modifier = Modifier.weight(1f))
if (!isSuccess) {
Button(
onClick = onRetry,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(containerColor = AppColor.CrimsonRed)
) {
Text(text = "Try Again")
}
}
OutlinedButton(
onClick = onDone,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Back To Dashboard")
}
}
}
}

View File

@ -0,0 +1,90 @@
package com.mob.utsmyanmar.ui.sign_on
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.utsmyanmar.paylibs.sign_on.SignOnListener
import com.utsmyanmar.paylibs.sign_on.SignOnProcess
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
data class SignOnUiState(
val isLoading: Boolean = false,
val statusText: String = "Ready to start sign on.",
val canRetry: Boolean = false
)
data class SignOnResult(
val isSuccess: Boolean,
val message: String
)
class SignOnViewModel : ViewModel() {
private val _uiState = MutableStateFlow(SignOnUiState())
val uiState: StateFlow<SignOnUiState> = _uiState.asStateFlow()
private val _resultEvents = MutableSharedFlow<SignOnResult>()
val resultEvents: SharedFlow<SignOnResult> = _resultEvents.asSharedFlow()
fun startSignOn() {
if (_uiState.value.isLoading) return
_uiState.update {
it.copy(
isLoading = true,
statusText = "Signing on to host...",
canRetry = false
)
}
//
// SignOnProcess.getInstance()
// .enqueue()
// .startSignOn(object : SignOnListener {
// override fun onSuccessSignOn() {
// dispatchResult(
// SignOnResult(
// isSuccess = true,
// message = "Sign on completed successfully."
// )
// )
// }
//
// override fun onFailureSignOn(resultCode: Integer?) {
// dispatchResult(
// SignOnResult(
// isSuccess = false,
// message = "Sign on failed. Response code: ${resultCode ?: -1}"
// )
// )
// }
//
// override fun onNetworkFailSignOn(message: String?) {
// dispatchResult(
// SignOnResult(
// isSuccess = false,
// message = message?.takeIf { it.isNotBlank() } ?: "Network error during sign on."
// )
// )
// }
// })
}
private fun dispatchResult(result: SignOnResult) {
viewModelScope.launch {
_uiState.update {
it.copy(
isLoading = false,
statusText = result.message,
canRetry = !result.isSuccess
)
}
_resultEvents.emit(result)
}
}
}