From fe6c5ad15eaa21ffb40fa64884c4aca64baef0d5 Mon Sep 17 00:00:00 2001 From: moon <56061215+MgKyawLay@users.noreply.github.com> Date: Thu, 21 May 2026 11:03:12 +0630 Subject: [PATCH] sign on page --- .../ui/dashboard/DashboardScreen2.kt | 18 +- .../utsmyanmar/ui/navigation/AppNavGraph.kt | 58 ++++ .../mob/utsmyanmar/ui/navigation/Routes.kt | 8 + .../mob/utsmyanmar/ui/sign_on/SignOnScreen.kt | 262 ++++++++++++++++++ .../utsmyanmar/ui/sign_on/SignOnViewModel.kt | 90 ++++++ 5 files changed, 432 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnScreen.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnViewModel.kt diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt index 5060d22..f7fcbcd 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt @@ -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, @@ -42,7 +43,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)) @@ -145,7 +149,8 @@ private fun SummaryItem( @Composable private fun MenuGrid( - onNavigateAmount: (String) -> Unit + onNavigateAmount: (String) -> Unit, + onNavigateSignOn: () -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(10.dp) @@ -162,7 +167,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)) } 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 85a0dff..78c5d54 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 @@ -19,6 +19,8 @@ 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 @@ -47,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 + } } ) } 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 7a69b9f..c9d64de 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 @@ -1,10 +1,18 @@ 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") diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnScreen.kt new file mode 100644 index 0000000..cc205cc --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnScreen.kt @@ -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") + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnViewModel.kt new file mode 100644 index 0000000..1b2bbe8 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnViewModel.kt @@ -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 = _uiState.asStateFlow() + + private val _resultEvents = MutableSharedFlow() + val resultEvents: SharedFlow = _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) + } + } +}