diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/amount/AmountRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/amount/AmountRoute.kt index 9366700..6290f41 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/amount/AmountRoute.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/amount/AmountRoute.kt @@ -12,20 +12,18 @@ fun AmountRoute( onBack: () -> Unit, onNavigateCardWaiting: () -> Unit ) { - val canGoBack = !action.equals("Sale", ignoreCase = true) - AmountScreen( onBackClick = onBack, -// onNextClick = { amount -> -// sharedViewModel.amount.value = amount -// sharedViewModel.setAmountExist(true) -// sharedViewModel.setCardDataExist(false) -// sharedViewModel.setTransMenu(null) -// sharedViewModel.transactionsType.value = TransactionsType.SALE -// sharedViewModel.processCode.value = -// ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT -// -// onNavigateCardWaiting() -// } + onChargeClick = { amount -> + sharedViewModel.amount.value = amount + sharedViewModel.setAmountExist(true) + sharedViewModel.setCardDataExist(false) + sharedViewModel.setTransMenu(null) + sharedViewModel.transactionsType.value = TransactionsType.SALE + sharedViewModel.processCode.value = + ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT + + onNavigateCardWaiting() + } ) } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt index a4773fc..4b0b2a8 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingScreen.kt @@ -1,8 +1,9 @@ package com.mob.utsmyanmar.ui.cardwaiting import androidx.activity.compose.BackHandler -import androidx.compose.foundation.Image +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,18 +12,22 @@ 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.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.KeyboardArrowLeft +import androidx.compose.material.icons.rounded.Wifi +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -30,23 +35,22 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.painterResource 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.R -import com.mob.utsmyanmar.ui.theme.Black -import com.mob.utsmyanmar.ui.theme.Primary -import com.mob.utsmyanmar.ui.theme.White +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.theme.Color -@OptIn(ExperimentalMaterial3Api::class) @Composable fun CardWaitingScreen( viewModel: CardWaitingViewModel, + amount: String, onManualEntry: () -> Unit, - onProcessingCard: () -> Unit, - onTimeout: () -> Unit, onBack: () -> Unit, onMain: () -> Unit ) { @@ -56,10 +60,10 @@ fun CardWaitingScreen( viewModel.events.collect { event -> when (event) { CardWaitingEvent.GoManualEntry -> onManualEntry() - CardWaitingEvent.GoProcessingCard -> onProcessingCard() - CardWaitingEvent.GoTimeout -> onTimeout() CardWaitingEvent.GoMain -> onMain() CardWaitingEvent.GoBack -> onBack() + CardWaitingEvent.GoProcessingCard, + CardWaitingEvent.GoTimeout -> Unit } } } @@ -78,160 +82,360 @@ fun CardWaitingScreen( viewModel.onBackPressed() } - Scaffold( - topBar = { - CenterAlignedTopAppBar( - title = { - Text( - text = "CARD CAPTURE", - color = White, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold - ) - }, - navigationIcon = { - if (uiState.canGoBack) { - IconButton(onClick = onBack) { - Icon( - painter = painterResource(R.drawable.ic_left_arrow), - contentDescription = "Back", - tint = White - ) - } - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Primary - ) - ) - }, - containerColor = White - ) { paddingValues -> - Column( + CardWaitingScreenContent( + amount = amount, + uiState = uiState, + onBackClick = viewModel::onBackPressed, + onManualEntryClick = viewModel::onManualEntryClick + ) +} + +@Composable +private fun CardWaitingScreenContent( + amount: String, + uiState: CardWaitingUiState, + onBackClick: () -> Unit, + onManualEntryClick: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.IvoryBeige) + .statusBarsPadding() + .navigationBarsPadding() + .padding(horizontal = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .background(White) + .fillMaxWidth() + .height(54.dp), + contentAlignment = Alignment.Center ) { - - Box( - modifier = Modifier - .fillMaxSize() - .background( - color = Primary, - shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) - ) - .padding(18.dp) - ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + if (uiState.canGoBack) { + IconButton( + onClick = onBackClick, + modifier = Modifier.align(Alignment.CenterStart) ) { - Box( + Icon( + imageVector = Icons.Rounded.KeyboardArrowLeft, + contentDescription = "Back", + tint = Color.LegacyRed + ) + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "AMOUNT TO PAY", + color = Color.Black, + fontSize = 10.sp, + fontWeight = FontWeight.Medium + ) + + Spacer(modifier = Modifier.height(6.dp)) + + Row(verticalAlignment = Alignment.Bottom) { + Text( + text = amount, + color = Color.LegacyRed, + fontSize = 30.sp, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.width(6.dp)) + + Text( + text = "MMK", + color = Color.LegacyRed, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 6.dp) + ) + } + + Spacer(modifier = Modifier.height(32.dp)) + + ContactlessCircle(isLoading = uiState.isLoading) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = when { + uiState.isCardCaptured -> "Card Detected" + uiState.isFallback -> "Swipe Your Card" + else -> "Tap Your Card" + }, + color = Color.LegacyRed, + fontSize = 13.sp, + fontWeight = FontWeight.Bold + ) + + Text( + text = if (uiState.isCardCaptured) { + "Reader captured card data" + } else { + "Hold your card near the reader" + }, + color = Color.Black, + fontSize = 9.sp + ) + + Spacer(modifier = Modifier.height(14.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + HorizontalDivider( + modifier = Modifier.weight(1f), + color = Color.Gray.copy(alpha = 0.4f) + ) + + Text( + text = "OR", + modifier = Modifier.padding(horizontal = 12.dp), + color = Color.Gray, + fontSize = 10.sp + ) + + HorizontalDivider( + modifier = Modifier.weight(1f), + color = Color.Gray.copy(alpha = 0.4f) + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + + InsertCardRow() + + Spacer(modifier = Modifier.height(18.dp)) + + Text( + text = uiState.alertMessage, + color = Color.Black, + fontSize = 11.sp, + textAlign = TextAlign.Center, + lineHeight = 16.sp + ) + + Spacer(modifier = Modifier.height(18.dp)) + + StatusPanel(uiState = uiState) + + Spacer(modifier = Modifier.weight(1f)) + + ManualEntryAction(onManualEntryClick = onManualEntryClick) + + Spacer(modifier = Modifier.height(18.dp)) + + CardLogoRow() + + Spacer(modifier = Modifier.height(34.dp)) + + PoweredByMob() + + Spacer(modifier = Modifier.height(12.dp)) + } +} + +@Composable +private fun ContactlessCircle(isLoading: Boolean) { + Box( + modifier = Modifier.size(190.dp), + contentAlignment = Alignment.Center + ) { + CircleBorder(180, 0.1f) + CircleBorder(150, 0.20f) + + Surface( + modifier = Modifier.size(108.dp), + shape = CircleShape, + color = Color.White, + shadowElevation = 8.dp + ) { + Box(contentAlignment = Alignment.Center) { + if (isLoading) { + CircularProgressIndicator( + color = Color.LegacyRed, + strokeWidth = 3.dp, + modifier = Modifier.size(40.dp) + ) + } else { + Icon( + imageVector = Icons.Rounded.Wifi, + contentDescription = null, + tint = Color.LegacyRed, modifier = Modifier - .fillMaxWidth() - .background( - color = White, - shape = RoundedCornerShape(24.dp) - ) - .padding(horizontal = 22.dp, vertical = 28.dp) - ) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box( - modifier = Modifier - .size(92.dp) - .background( - color = Primary.copy(alpha = 0.08f), - shape = RoundedCornerShape(28.dp) - ), - contentAlignment = Alignment.Center - ) { - Image( - painter = painterResource(R.drawable.buy_ecommerce_finance_payment_pos_shop_svgrepo_com), - contentDescription = "Card reader" - ) - } - - Spacer(modifier = Modifier.height(20.dp)) - - Text( - text = if (uiState.isFallback) { - "Swipe Card" - } else { - "Present Card" - }, - color = Black, - fontSize = 24.sp, - fontWeight = FontWeight.Bold - ) - - Spacer(modifier = Modifier.height(10.dp)) - - Text( - text = uiState.alertMessage, - color = Black, - fontSize = 18.sp, - lineHeight = 26.sp, - textAlign = TextAlign.Center - ) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Row( - modifier = Modifier - .fillMaxWidth() - .background( - color = White.copy(alpha = 0.12f), - shape = RoundedCornerShape(22.dp) - ) - .padding(horizontal = 18.dp, vertical = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = if (uiState.isLoading) { - "Reader status" - } else { - "Ready for card" - }, - color = White, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold - ) - - Text( - text = if (uiState.isLoading) "Initializing" else "Waiting", - color = White, - fontSize = 15.sp - ) - } - - Spacer(modifier = Modifier.weight(1f)) - - Button( - onClick = viewModel::onManualEntryClick, - modifier = Modifier - .fillMaxWidth() - .height(64.dp), - shape = RoundedCornerShape(18.dp), - colors = ButtonDefaults.buttonColors( - containerColor = White, - contentColor = Primary - ) - ) { - Text( - text = "Manual Entry", - fontSize = 20.sp, - fontWeight = FontWeight.Bold - ) - } + .size(72.dp) + .rotate(90f) + ) } } } } } + +@Composable +private fun CircleBorder( + size: Int, + alpha: Float +) { + Surface( + modifier = Modifier + .size(size.dp) + .alpha(alpha), + shape = CircleShape, + border = BorderStroke(1.dp, Color.LegacyRed) + ) {} +} + +@Composable +private fun InsertCardRow() { + Row( + modifier = Modifier.height(44.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_insert_card), + contentDescription = null, + tint = Color.LegacyRed, + modifier = Modifier.size(28.dp) + ) + + Spacer(modifier = Modifier.width(10.dp)) + + Column { + Text( + text = "Insert Your Card", + color = Color.LegacyRed, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "Chip facing up", + color = Color.Black, + fontSize = 9.sp + ) + } + } +} + +@Composable +private fun StatusPanel(uiState: CardWaitingUiState) { + Surface( + modifier = Modifier.fillMaxWidth(), + color = Color.White, + shape = RoundedCornerShape(12.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = when { + uiState.isCardCaptured -> "Reader status" + uiState.isLoading -> "Reader status" + else -> "Ready for card" + }, + color = Color.LegacyRed, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + + Text( + text = when { + uiState.isCardCaptured -> "Captured" + uiState.isLoading -> "Initializing" + else -> "Waiting" + }, + color = Color.Gray, + fontSize = 11.sp + ) + } + } +} + +@Composable +private fun ManualEntryAction(onManualEntryClick: () -> Unit) { + Surface( + modifier = Modifier.fillMaxWidth(), + color = Color.White, + shape = RoundedCornerShape(12.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.White) + .clickable(onClick = onManualEntryClick) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Manual Entry", + color = Color.LegacyRed, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Composable +private fun CardLogoRow() { + Surface( + modifier = Modifier.fillMaxWidth(), + color = Color.White, + shape = RoundedCornerShape(3.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(42.dp) + .padding(horizontal = 14.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("VISA", fontSize = 22.sp, fontWeight = FontWeight.Bold) + Text("MC", fontSize = 18.sp, fontWeight = FontWeight.Bold) + Text("MPU", fontSize = 20.sp, fontWeight = FontWeight.Bold) + Text("UnionPay", fontSize = 12.sp, fontWeight = FontWeight.Bold) + } + } +} + +@Composable +private fun PoweredByMob() { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Powered by", + color = Color.Gray, + fontSize = 8.sp + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = "MOB", + color = Color.Gray, + fontSize = 9.sp, + fontWeight = FontWeight.Bold + ) + } +} + +@P2Preview +@Composable +fun PreviewCardWaitingScreen() { + CardWaitingScreenContent( + amount = "50,000", + uiState = CardWaitingUiState(), + onBackClick = {}, + onManualEntryClick = {} + ) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingUiState.kt b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingUiState.kt index eada451..2dddc5c 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingUiState.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingUiState.kt @@ -4,5 +4,6 @@ data class CardWaitingUiState( val alertMessage: String = "Please insert, tap, or swipe card", val isLoading: Boolean = false, val isFallback: Boolean = false, - val canGoBack: Boolean = true + val canGoBack: Boolean = true, + val isCardCaptured: Boolean = false ) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt index 9ba9709..c8c95c8 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt @@ -78,7 +78,17 @@ class CardWaitingViewModel( _uiState.update { it.copy( alertMessage = "Fallback!\nPlease stripe!", - isFallback = true + isFallback = true, + isCardCaptured = false + ) + } + } else { + _uiState.update { + it.copy( + alertMessage = "Please insert, tap, or swipe card", + isFallback = false, + isLoading = false, + isCardCaptured = false ) } } @@ -122,7 +132,8 @@ class CardWaitingViewModel( _uiState.update { it.copy( alertMessage = "Initializing card reader...", - isLoading = true + isLoading = true, + isCardCaptured = false ) } @@ -136,7 +147,8 @@ class CardWaitingViewModel( } else { "Please insert, tap, or swipe card" }, - isLoading = false + isLoading = false, + isCardCaptured = false ) } setupCardReadProcess(isFallback) @@ -209,8 +221,14 @@ class CardWaitingViewModel( } } - viewModelScope.launch { - _events.send(CardWaitingEvent.GoProcessingCard) + stopCardReading() + + _uiState.update { + it.copy( + alertMessage = "Card detected.\nOnline process disabled.", + isLoading = false, + isCardCaptured = true + ) } } 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 9d3d780..d2a747c 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 @@ -87,16 +87,8 @@ fun AppNavGraph( CardWaitingScreen( viewModel = cardWaitingViewModel, + amount = formatAmountForDisplay(sharedViewModel.amount.value), onManualEntry = {}, - onProcessingCard = { - navController.navigate(Routes.ProcessingCard.route) { - popUpTo(Routes.CardWaiting.route) { - inclusive = true - } - launchSingleTop = true - } - }, - onTimeout = { navController.popBackStack() }, onBack = { navController.popBackStack() }, onMain = { navController.navigate(Routes.Dashboard.route) { @@ -207,3 +199,9 @@ fun AppNavGraph( } } } + +private fun formatAmountForDisplay(amount: String?): String { + val normalizedAmount = amount.orEmpty() + val value = normalizedAmount.toLongOrNull() ?: return normalizedAmount.ifBlank { "0" } + return "%,d".format(value) +} diff --git a/app/src/main/res/drawable/ic_insert_card.xml b/app/src/main/res/drawable/ic_insert_card.xml new file mode 100644 index 0000000..7f40a58 --- /dev/null +++ b/app/src/main/res/drawable/ic_insert_card.xml @@ -0,0 +1,24 @@ + + + + diff --git a/app/src/main/res/drawable/ic_wifi.xml b/app/src/main/res/drawable/ic_wifi.xml new file mode 100644 index 0000000..ed9a35f --- /dev/null +++ b/app/src/main/res/drawable/ic_wifi.xml @@ -0,0 +1,49 @@ + + + + + + +