sign on page

This commit is contained in:
moon 2026-05-21 11:03:12 +06:30
parent f4fc2fa730
commit fe6c5ad15e
5 changed files with 432 additions and 4 deletions

View File

@ -25,7 +25,8 @@ import com.mob.utsmyanmar.ui.theme.Color
@Composable @Composable
fun DashboardScreen2( fun DashboardScreen2(
onNavigateAmount: (String) -> Unit = {} onNavigateAmount: (String) -> Unit = {},
onNavigateSignOn: () -> Unit = {}
) { ) {
Scaffold( Scaffold(
containerColor = Color.IvoryBeige, containerColor = Color.IvoryBeige,
@ -42,7 +43,10 @@ fun DashboardScreen2(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
SummaryCard() SummaryCard()
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
MenuGrid(onNavigateAmount = onNavigateAmount) MenuGrid(
onNavigateAmount = onNavigateAmount,
onNavigateSignOn = onNavigateSignOn
)
Spacer(modifier = Modifier.height(18.dp)) Spacer(modifier = Modifier.height(18.dp))
RecentTransactionHeader() RecentTransactionHeader()
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@ -145,7 +149,8 @@ private fun SummaryItem(
@Composable @Composable
private fun MenuGrid( private fun MenuGrid(
onNavigateAmount: (String) -> Unit onNavigateAmount: (String) -> Unit,
onNavigateSignOn: () -> Unit
) { ) {
Column( Column(
verticalArrangement = Arrangement.spacedBy(10.dp) verticalArrangement = Arrangement.spacedBy(10.dp)
@ -162,7 +167,12 @@ private fun MenuGrid(
} }
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { 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("Settlement", Icons.Default.Wallet, Modifier.weight(1f))
MenuCard("See More", Icons.Default.GridView, Modifier.weight(1f)) MenuCard("See More", Icons.Default.GridView, Modifier.weight(1f))
} }

View File

@ -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.ProcessingCardRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel
import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen 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.sending_to_host.SendingToHostRoute
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
@ -47,6 +49,62 @@ fun AppNavGraph(
} }
launchSingleTop = true 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
}
} }
) )
} }

View File

@ -1,10 +1,18 @@
package com.mob.utsmyanmar.ui.navigation package com.mob.utsmyanmar.ui.navigation
import android.net.Uri
sealed class Routes(val route: String) { sealed class Routes(val route: String) {
data object Dashboard : Routes("dashboard") data object Dashboard : Routes("dashboard")
data object Amount : Routes("amount/{action}") { data object Amount : Routes("amount/{action}") {
fun createRoute(action: String): String = "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 CardWaiting : Routes("card_waiting")
data object ProcessingCard : Routes("processing_card") data object ProcessingCard : Routes("processing_card")
data object PinPad : Routes("pin_pad") data object PinPad : Routes("pin_pad")

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)
}
}
}