From 211b092c2d092c32e87f705aa08b27a44652f423 Mon Sep 17 00:00:00 2001 From: moon <56061215+MgKyawLay@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:31:54 +0630 Subject: [PATCH] last sync animation --- .../utsmyanmar/ui/dashboard/DashboardRoute.kt | 16 - .../ui/dashboard/DashboardScreen.kt | 1061 ++++++++++++++--- .../ui/dashboard/DashboardScreen2.kt | 928 -------------- .../utsmyanmar/ui/disable/DisableScreen.kt | 66 + .../ui/disable/DisableScreenUiState.kt | 2 + .../ui/disable/DisableScreenViewModel.kt | 2 + .../utsmyanmar/ui/navigation/AppNavGraph.kt | 29 +- .../mob/utsmyanmar/ui/navigation/Routes.kt | 3 + .../utsmyanmar/ui/tms_setup/TmsSetupScreen.kt | 29 +- .../ui/tms_setup/TmsSetupViewModel.kt | 74 +- .../main/res/drawable/ic_alert_triangle.xml | 14 + .../utsmyanmar/baselib/TerminalKeyUtil.java | 7 +- 12 files changed, 1074 insertions(+), 1157 deletions(-) delete mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardRoute.kt delete mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreen.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenUiState.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenViewModel.kt create mode 100644 app/src/main/res/drawable/ic_alert_triangle.xml diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardRoute.kt deleted file mode 100644 index 3623e0c..0000000 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardRoute.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.mob.utsmyanmar.ui.dashboard - -import androidx.compose.runtime.Composable - -@Composable -fun DashboardRoute( - onNavigateAmount: (String) -> Unit, - settlementEnabled: Boolean, - wavePayEnabled: Boolean, -) { - DashboardScreen( - settlementEnabled = settlementEnabled, - wavePayEnabled = wavePayEnabled, - onNavigateAmount = onNavigateAmount - ) -} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt index 37bd47f..be5b139 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt @@ -1,238 +1,959 @@ package com.mob.utsmyanmar.ui.dashboard -import android.util.Log +import android.os.Handler +import android.os.Looper +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountBalanceWallet +import androidx.compose.material.icons.filled.BarChart +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.CreditCard +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.LockOpen import androidx.compose.material.icons.filled.Menu -import androidx.compose.material3.* +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.Replay +import androidx.compose.material.icons.filled.SwapHoriz +import androidx.compose.material.icons.filled.Sync +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.VerticalDivider +import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow 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.SquareButton -import com.mob.utsmyanmar.ui.theme.MOBPOSTheme -import com.mob.utsmyanmar.ui.theme.* -import kotlinx.coroutines.launch +import coil3.compose.AsyncImage import com.mob.utsmyanmar.R import com.mob.utsmyanmar.ui.components.appbar.AppBar -import com.utsmyanmar.paylibs.print.NewPrintReceipt +import com.mob.utsmyanmar.ui.device_info.DeviceInfoUiState +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.preview.P3Preview +import com.mob.utsmyanmar.ui.theme.Color +import com.utsmyanmar.paylibs.sign_on.EchoTestProcess +import com.utsmyanmar.paylibs.sign_on.SignOnListener +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun DashboardScreen( - settlementEnabled: Boolean, - wavePayEnabled: Boolean, - onNavigateAmount: (String) -> Unit +fun DashboardScreen2( + onNavigateAmount: (String) -> Unit = {}, + onNavigateSignOn: () -> Unit = {}, + onNavigateSettlement: () -> Unit = {}, + onNavigateVersion: () -> Unit = {}, + onNavigateFunctions: () -> Unit = {}, + onNavigateAction: (String) -> Unit = {}, + onNavigateNotifications: () -> Unit = {}, + dashboardUiState: DashboardUiState = DashboardUiState(), + deviceInfo: DeviceInfoUiState = DeviceInfoUiState() ) { + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val scope = rememberCoroutineScope() + val mainHandler = remember { Handler(Looper.getMainLooper()) } + var showHostActionDialog by remember { mutableStateOf(false) } + var activeHostAction by remember { mutableStateOf("Log-On") } + var isHostActionRunning by remember { mutableStateOf(false) } + var dialogMessage by remember { mutableStateOf("") } + var reversalEnabled by remember { mutableStateOf(runCatching { SystemParamsOperation.getInstance().isReversalOn }.getOrDefault(false)) } + + val isOnline = true + + fun confirmationMessage(action: String) = "Do you want to start ${action.lowercase()}?" + fun processingMessage(action: String) = "Sending ${action.lowercase()} request to host..." + fun successMessage(action: String) = "$action success." + fun failureMessage(action: String, resultCode: Int?) = + "$action failed. Response code: ${resultCode ?: -1}" + + fun networkFailureMessage(action: String) = "Network error during $action." + fun openHostActionDialog(action: String) { + activeHostAction = action + isHostActionRunning = false + dialogMessage = confirmationMessage(action) + showHostActionDialog = true + } + + if (showHostActionDialog) { + AlertDialog( + onDismissRequest = { + if (!isHostActionRunning) { + showHostActionDialog = false + } + }, title = { + Text( + text = activeHostAction, color = Color.LegacyRed, fontWeight = FontWeight.Bold + ) + }, text = { + Text(text = dialogMessage) + }, confirmButton = { + TextButton( + onClick = { + if (isHostActionRunning) return@TextButton + + if (dialogMessage != confirmationMessage(activeHostAction)) { + showHostActionDialog = false + dialogMessage = confirmationMessage(activeHostAction) + return@TextButton + } + + isHostActionRunning = true + dialogMessage = processingMessage(activeHostAction) + + val signOnProcess = EchoTestProcess.getInstance() + val request = when (activeHostAction) { + "Echo Test" -> signOnProcess.enqueue(false) + "Log-Off" -> signOnProcess.enqueueLogOff() + else -> signOnProcess.enqueueLogOn() + } + + request.startSignOn(object : SignOnListener { + override fun onSuccessSignOn() { + mainHandler.post { + isHostActionRunning = false + dialogMessage = successMessage(activeHostAction) + } + } + + override fun onFailureSignOn(resultCode: Int?) { + mainHandler.post { + isHostActionRunning = false + mainHandler.post { + isHostActionRunning = false + dialogMessage = failureMessage(activeHostAction, resultCode) + } + } + } + + + override fun onNetworkFailSignOn(message: String?) { + mainHandler.post { + isHostActionRunning = false + dialogMessage = message?.takeIf { it.isNotBlank() } + ?: networkFailureMessage(activeHostAction) + } + } + }) + }) { + Text( + text = when { + isHostActionRunning -> "Processing" + dialogMessage == confirmationMessage(activeHostAction) -> "Start" + else -> "Close" + }, color = Color.LegacyRed + ) + } + }, dismissButton = { + if (!isHostActionRunning) { + TextButton( + onClick = { + showHostActionDialog = false + dialogMessage = confirmationMessage(activeHostAction) + }) { + Text(text = "Cancel", color = Color.Gray) + } + } + }, containerColor = Color.White + ) + } ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { + drawerState = drawerState, drawerContent = { ModalDrawerSheet( - modifier = Modifier.fillMaxWidth(0.8f), - drawerContainerColor = White, - drawerShape = RoundedCornerShape(topEnd = 20.dp, bottomEnd = 20.dp) + modifier = Modifier.fillMaxWidth(0.78f), + drawerContainerColor = Color.White, + drawerShape = RoundedCornerShape(topEnd = 24.dp, bottomEnd = 24.dp) ) { - Text( - text = "MOBPOS", - modifier = Modifier.padding(start = 24.dp, top = 28.dp, bottom = 8.dp), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold - ) - Text( - text = "Dashboard", + Column( modifier = Modifier .fillMaxWidth() - .clickable { - scope.launch { drawerState.close() } + .background(Color.IvoryBeige) + .padding(horizontal = 20.dp, vertical = 28.dp) + ) { + Row { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(Color.White), contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(R.drawable.logo_mob), + contentDescription = "mob logo", + modifier = Modifier + .size(100.dp) + .padding(16.dp), + contentScale = ContentScale.Fit + ) } - .padding(horizontal = 24.dp, vertical = 14.dp), - fontSize = 16.sp, - fontWeight = FontWeight.Medium - ) + + Column { + Text( + text = "MOB Merchant", + color = Color.CrimsonRed, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "S/N:${deviceInfo.serialNumber}", + color = Color.Black, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = if (isOnline) "Online" else "Offline", + color = if (isOnline) Color.Success else Color.Error, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + } + } + } + + Spacer(modifier = Modifier.height(10.dp)) Text( - text = "Transactions", - modifier = Modifier - .fillMaxWidth() - .clickable { - scope.launch { drawerState.close() } - } - .padding(horizontal = 24.dp, vertical = 14.dp), - fontSize = 16.sp + text = "Connection Settings", + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(horizontal = 16.dp) ) + + Item( + title = "Echo Test", subTitle = "Test Connection Status", + onClick = { + scope.launch { drawerState.close() } + openHostActionDialog("Echo Test") + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_up_down_arrow), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) + Item( + title = "Log-On", subTitle = "Log on to System", + onClick = { + scope.launch { drawerState.close() } + openHostActionDialog("Log-On") + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_lock), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) + Item( + title = "Log-Off", subTitle = "Log off from System", + onClick = { + scope.launch { drawerState.close() } + openHostActionDialog("Log-Off") + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_cancel_circle), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) + Item( + title = "Reversal", subTitle = "Enable / Disable Reversal", + onClick = { + reversalEnabled = !reversalEnabled + runCatching { SystemParamsOperation.getInstance().setReversalFlag(reversalEnabled) } + }, + leadingIcon = { + Icon( + Icons.Default.Sync, + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = { + Switch( + checked = reversalEnabled, + onCheckedChange = { isChecked -> + reversalEnabled = isChecked + runCatching { SystemParamsOperation.getInstance().setReversalFlag(isChecked) } + } + ) + }, + ) + Text( - text = "History", - modifier = Modifier - .fillMaxWidth() - .clickable { - scope.launch { drawerState.close() } - } - .padding(horizontal = 24.dp, vertical = 14.dp), - fontSize = 16.sp + text = "System Management", + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(horizontal = 16.dp) ) - Text( - text = "Settlement", - modifier = Modifier - .fillMaxWidth() - .clickable(enabled = settlementEnabled) { - scope.launch { drawerState.close() } - } - .padding(horizontal = 24.dp, vertical = 14.dp), - color = if (settlementEnabled) Black else White, - fontSize = 16.sp + + Item( + title = "Functions", subTitle = "System Function Settings", + onClick = { + scope.launch { drawerState.close() } + onNavigateFunctions() + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_four_boxes), + contentDescription = "icon", + tint = Color.LegacyRed, + modifier = Modifier.size(16.dp) + ) + }, + trailingIcon = {}, ) - Text( - text = "WavePay", - modifier = Modifier - .fillMaxWidth() - .clickable(enabled = wavePayEnabled) { - scope.launch { drawerState.close() } - } - .padding(horizontal = 24.dp, vertical = 14.dp), - color = if (wavePayEnabled) Black else White, - fontSize = 16.sp + Item( + title = "Version", subTitle = "View App Version Info", + onClick = { + scope.launch { drawerState.close() } + onNavigateVersion() + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_version), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, ) + } - } - ) { + }) { +//body start Scaffold( - topBar = { + containerColor = Color.IvoryBeige, topBar = { AppBar( title = "Dashboard", icon = Icons.Default.Menu, - onIconClick = { - scope.launch { - drawerState.open() + onIconClick = { scope.launch { drawerState.open() } }, + actions = { + IconButton(onClick = onNavigateNotifications) { + Box { + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = "Notifications", + tint = Color.White + ) + Box( + modifier = Modifier + .size(8.dp) + .background(Color.GoldenGlow, CircleShape) + .align(Alignment.TopEnd) + .offset(x = 2.dp, y = (-2).dp) + ) + } } } ) - } - ) { paddingValues -> + }) { paddingValues -> Column( modifier = Modifier - .fillMaxSize() .padding(paddingValues) - .background(White) + .fillMaxSize() ) { + //top section Box( modifier = Modifier - .fillMaxWidth() - .padding(20.dp), + .weight(1f) + .fillMaxWidth(), + ) { + AdvertisingArea() + } + //center section + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), contentAlignment = Alignment.Center ) { - Image( - painter = painterResource(R.drawable.logo_mob), - contentDescription = "Mob logo" - ) + SummaryCard() } - + //pager section Box( modifier = Modifier - .fillMaxSize() - .background( - color = Primary, - shape = RoundedCornerShape( - topStart = 16.dp, - topEnd = 16.dp, - bottomStart = 0.dp, - bottomEnd = 0.dp - ) - ) + .weight(1.3f) + .fillMaxWidth(), ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Button( - onClick = { try { - NewPrintReceipt.getInstance().testPrint() - println("printing...") - }catch (e: Exception){ - println("printing error $e") - } } - ) { - Text(text = "test") - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - SquareButton( - title = "Sale", - icon = R.drawable.ic_sale, - modifier = Modifier.weight(1f), - containerColor = White, - contentColor = Primary, - iconTint = Primary, - border = null, - onClick = { onNavigateAmount("Sale") }, - ) - SquareButton( - title = "Sign On", - icon = R.drawable.ic_menu, - modifier = Modifier.weight(1f), - containerColor = White, - contentColor = Primary, - iconTint = Primary, - border = null, - onClick = { onNavigateAmount("Sign On") } - ) - } + MenuPager( + items = buildMenuItems( + onNavigateAmount = onNavigateAmount, + onNavigateSignOn = onNavigateSignOn, + onNavigateSettlement = onNavigateSettlement, + onNavigateAction = onNavigateAction + ), + modifier = Modifier.fillMaxSize() + ) + } + //transactions section +// RecentTransactions( +// transactions = dashboardUiState.recentTransactions, +// modifier = Modifier +// .weight(1.2f) +// .fillMaxWidth() +// ) + } + } + } +} - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - SquareButton( - title = "Settlement", - icon = R.drawable.ic_menu, - modifier = Modifier.weight(1f), - enabled = settlementEnabled, - containerColor = White, - contentColor = Primary, - iconTint = Primary, - border = null, - onClick = {} - ) - SquareButton( - title = "Others", - icon = R.drawable.ic_menu, - modifier = Modifier.weight(1f), - containerColor = White, - contentColor = Primary, - iconTint = Primary, - border = null, - onClick = {} - ) - } +@Composable +private fun AdvertisingArea() { + + val imageArray = listOf( + "https://i.ytimg.com/vi/eRUVxGRp1Ms/maxresdefault.jpg", + "https://i.ytimg.com/vi/AwvmgTPd7qw/maxresdefault.jpg", + "https://mma.prnewswire.com/media/2080956/SUNMI_3rd_generation_products_T3_PRO_series_V3_MIX.jpg?p=facebook" + ) + val pageState = rememberPagerState(pageCount = { imageArray.size }) + + LaunchedEffect(pageState) { + while (true) { + delay(10000.milliseconds) + val nextPage = (pageState.currentPage + 1) % imageArray.size + + pageState.animateScrollToPage( + page = nextPage, animationSpec = tween(durationMillis = 700) + ) + } + } + + Card( + modifier = Modifier.fillMaxSize(), + shape = RoundedCornerShape(0.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + ) { + HorizontalPager( + state = pageState, modifier = Modifier.fillMaxSize() + ) { page -> + AsyncImage( + model = imageArray[page], + contentDescription = "ads images", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } +} + +@Composable +private fun SummaryCard() { + var isRotating by remember { mutableStateOf(false) } + val rotation by animateFloatAsState( + targetValue = if(isRotating) 360f else 0f, + animationSpec = tween(durationMillis = 3000, easing = LinearEasing), + label = "sync rotating", + finishedListener = {isRotating = false} + ) + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Column(modifier = Modifier.weight(1f)) { + SummaryItem( + title = "Sales Today", + value = "MMK 2,000,000", + subtitle = "47 Transactions", + icon = Icons.Default.BarChart, + iconBg = Color.CrimsonRed + ) + + HorizontalDivider( + color = Color.Gray, + modifier = Modifier.padding(vertical = 6.dp, horizontal = 6.dp) + ) + + SummaryItem( + title = "Settlement", + value = "Completed", + subtitle = "Today 11:00 PM", + icon = Icons.Default.Check, + iconBg = Color.CrimsonRed + ) + } + + VerticalDivider(color = Color.Gray, modifier = Modifier.height(160.dp)) + + Column( + modifier = Modifier + .weight(0.75f) + .padding(start = 18.dp), + horizontalAlignment = Alignment.Start + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Last Sync", fontSize = 12.sp + ) + Box( + modifier = Modifier + .size(38.dp) + .clip(CircleShape) + .background(Color.CrimsonRed.copy(alpha = 0.1f)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ){ + if(!isRotating) isRotating = true + }, + contentAlignment = Alignment.Center + ){ + Icon( + imageVector = Icons.Default.Sync, + contentDescription = null, + tint = Color.LegacyRed, + modifier = Modifier.size(22.dp).rotate(rotation) + ) } } + + Spacer(Modifier.height(6.dp)) + + Text( + text = "5 mins ago", + fontSize = 18.sp, + color = Color.LegacyRed, + fontWeight = FontWeight.SemiBold + ) + } + } + } +} + +@Composable +private fun SummaryItem( + title: String, + value: String, + subtitle: String, + icon: ImageVector, + iconBg: androidx.compose.ui.graphics.Color +) { + Row( + verticalAlignment = Alignment.Top, modifier = Modifier.padding(6.dp) + ) { + Column(modifier = Modifier.weight(1f)) { + Text(title, fontSize = 12.sp) + Text( + value, fontSize = 14.sp, color = Color.LegacyRed, fontWeight = FontWeight.Bold + ) + Text(subtitle, fontSize = 12.sp) + } + IconCircle(icon, iconBg) + } +} + +@Composable +private fun IconCircle( + icon: ImageVector, color: androidx.compose.ui.graphics.Color +) { + Box( + modifier = Modifier + .size(38.dp) + .clip(CircleShape) + .background(color.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = color, + modifier = Modifier.size(22.dp) + ) + } +} + +private class DashboardMenuItem( + val title: String, + val iconContent: @Composable () -> Unit, + val onClick: () -> Unit +) + +@Composable +private fun buildMenuItems( + onNavigateAmount: (String) -> Unit, + onNavigateSignOn: () -> Unit, + onNavigateSettlement: () -> Unit, + onNavigateAction: (String) -> Unit +): List = listOf( + DashboardMenuItem("Sale", { Icon(painterResource(R.drawable.ic_terminal), contentDescription = null, modifier = Modifier.size(40.dp), tint = Color.LegacyRed) }) { onNavigateAmount("Sale") }, + DashboardMenuItem("MMQR", { Image(painter = painterResource(R.drawable.ic_mmqr_logo), contentDescription = null, modifier = Modifier.height(48.dp)) }) { }, + DashboardMenuItem("History", { Icon(painterResource(R.drawable.ic_history), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { }, + DashboardMenuItem("Sign On", { Icon(painterResource(R.drawable.ic_sign_on), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSignOn() }, + DashboardMenuItem("Settlement", { Icon(painterResource(R.drawable.ic_settlement), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSettlement() }, + DashboardMenuItem("Void", { Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Void") }, + DashboardMenuItem("Refund", { Icon(Icons.Default.Replay, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Refund") }, + DashboardMenuItem("Pre-Auth", { Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth") }, + DashboardMenuItem("Pre-Auth Void", { Icon(Icons.Default.LockOpen, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Void") }, + DashboardMenuItem("Pre-Auth Complete", { Icon(Icons.Default.CreditCard, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Complete") }, + DashboardMenuItem("Pre-Auth Complete Void", { Icon(Icons.Default.SwapHoriz, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Complete Void") }, + DashboardMenuItem("Cash Out", { Icon(Icons.Default.AccountBalanceWallet, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Cash Out") }, +) + +@Composable +private fun MenuPager( + items: List, + modifier: Modifier = Modifier +) { + val pages = items.chunked(6) + val pagerState = rememberPagerState(pageCount = { pages.size }) + + Column(modifier = modifier) { + HorizontalPager( + state = pagerState, + modifier = Modifier.weight(1f) + ) { pageIndex -> + MenuPage(items = pages[pageIndex]) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + repeat(pages.size) { index -> + val selected = pagerState.currentPage == index + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + .size(if (selected) 8.dp else 6.dp) + .background( + color = if (selected) Color.LegacyRed else Color.Gray.copy(alpha = 0.4f), + shape = CircleShape + ) + ) + } + } + } +} + +@Composable +private fun MenuPage(items: List) { + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier.padding(horizontal = 16.dp) + ) { + Spacer(Modifier.height(8.dp)) + items.chunked(3).forEach { rowItems -> + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + rowItems.forEach { item -> + MenuCard( + title = item.title, + icon = item.iconContent, + modifier = Modifier.weight(1f), + onClick = item.onClick + ) + } + repeat(3 - rowItems.size) { + Spacer(modifier = Modifier.weight(1f)) + } } } } } -@Preview(showBackground = true, showSystemUi = true) @Composable -private fun DashboardScreenPreview() { - MOBPOSTheme { - DashboardScreen( - settlementEnabled = true, - wavePayEnabled = true, - onNavigateAmount = {} - ) +private fun MenuCard( + title: String, + icon: @Composable () -> Unit, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null +) { + Card( + modifier = modifier + .height(100.dp) + .then( + if (onClick != null) { + Modifier.clickable(onClick = onClick) + } else { + Modifier + } + ), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(5.dp) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(6.dp) + ) { + Column { + icon() + Spacer(modifier = Modifier.weight(1f)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + fontSize = 12.sp, + color = Color.Black, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.weight(1f) + ) + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = Color.LegacyRed, + modifier = Modifier.size(28.dp) + ) + } + } + } } } + +@Composable +private fun RecentTransactions( + transactions: List, + modifier: Modifier = Modifier +) { + Column(modifier = modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Recent Transactions", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp + ) + } + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = Color.Gray.copy(alpha = 0.2f) + ) + LazyColumn(modifier = Modifier.fillMaxSize()) { + if (transactions.isEmpty()) { + item { + Text( + text = "No recent transactions", + color = Color.Gray, + fontSize = 13.sp, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp) + ) + } + } else { + items(items = transactions, key = { it.pid }) { record -> + TrnxRow(record = record) + } + } + } + } +} + +@Composable +private fun TrnxRow(record: TrnxRecord) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp) + .clip(RoundedCornerShape(10.dp)) + .background(Color.White) + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(36.dp) + .background(Color.LegacyRed.copy(alpha = 0.1f), CircleShape), + contentAlignment = Alignment.Center + ) { + Text( + text = record.typeLabel.take(1), + color = Color.LegacyRed, + fontWeight = FontWeight.Bold, + fontSize = 15.sp + ) + } + + Spacer(Modifier.width(10.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = if (record.isVoided) "${record.typeLabel} (Voided)" else record.typeLabel, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + color = if (record.isVoided) Color.Gray else Color.Black + ) + Text( + text = record.maskedCard, + fontSize = 11.sp, + color = Color.Gray + ) + } + + Column(horizontalAlignment = Alignment.End) { + Text( + text = record.amountDisplay, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = if (record.isVoided) Color.Gray else Color.LegacyRed + ) + Text( + text = record.dateTime, + fontSize = 10.sp, + color = Color.Gray + ) + } + } +} + +private val previewTransactions = listOf( + TrnxRecord(1L, "Sale", "MMK 10,000", "**** 1234", "06/10 14:32", isVoided = false, isApproved = true), + TrnxRecord(2L, "Void", "MMK 5,500", "**** 5678", "06/10 13:10", isVoided = true, isApproved = false), + TrnxRecord(3L, "Refund", "MMK 2,000", "**** 9012", "06/09 09:45", isVoided = false, isApproved = true), + TrnxRecord(4L, "Sale", "MMK 30,000", "**** 3456", "06/09 08:00", isVoided = false, isApproved = true), + TrnxRecord(5L, "Settlement", "MMK 0", "----", "06/08 18:00", isVoided = false, isApproved = true), +) + +@P3Preview +@P2Preview +@Composable +fun PreviewDashboardScreen2() { + DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions)) +} + +@Preview +@Composable +fun PreviewItem() { + Item( + onClick = {}, + title = "title", + subTitle = "sub-title" + ) +} + + +@Composable +fun Item( + onClick: () -> Unit, + title: String, + subTitle: String, + leadingIcon: (@Composable () -> Unit)? = null, + trailingIcon: (@Composable () -> Unit)? = null, +) { + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + modifier = Modifier.fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + ) { + // Square icon background + leadingIcon?.let { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(40.dp) + .clip(RoundedCornerShape(16.dp)) // change to RectangleShape for sharp corners + .background(Color.LegacyRed.copy(alpha = 0.1f)) + .padding(8.dp) + ) { + it() + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + Column( + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp) + ) { + Text(text = title) + Text( + text = subTitle, + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + } + + trailingIcon?.invoke() + } + } +} \ No newline at end of file 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 deleted file mode 100644 index fa30d44..0000000 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt +++ /dev/null @@ -1,928 +0,0 @@ -package com.mob.utsmyanmar.ui.dashboard - -import android.os.Handler -import android.os.Looper -import androidx.compose.animation.core.tween -import androidx.compose.foundation.Image -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 -import androidx.compose.foundation.layout.Row -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.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AccountBalanceWallet -import androidx.compose.material.icons.filled.BarChart -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.ChevronRight -import androidx.compose.material.icons.filled.CreditCard -import androidx.compose.material.icons.filled.Lock -import androidx.compose.material.icons.filled.LockOpen -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material.icons.filled.Notifications -import androidx.compose.material.icons.filled.Replay -import androidx.compose.material.icons.filled.SwapHoriz -import androidx.compose.material.icons.filled.Sync -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.DrawerValue -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.VerticalDivider -import androidx.compose.material3.rememberDrawerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import coil3.compose.AsyncImage -import com.mob.utsmyanmar.R -import com.mob.utsmyanmar.ui.components.appbar.AppBar -import com.mob.utsmyanmar.ui.device_info.DeviceInfoUiState -import com.mob.utsmyanmar.ui.preview.P2Preview -import com.mob.utsmyanmar.ui.preview.P3Preview -import com.mob.utsmyanmar.ui.theme.Color -import com.utsmyanmar.paylibs.sign_on.EchoTestProcess -import com.utsmyanmar.paylibs.sign_on.SignOnListener -import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.milliseconds - -@Composable -fun DashboardScreen2( - onNavigateAmount: (String) -> Unit = {}, - onNavigateSignOn: () -> Unit = {}, - onNavigateSettlement: () -> Unit = {}, - onNavigateVersion: () -> Unit = {}, - onNavigateFunctions: () -> Unit = {}, - onNavigateAction: (String) -> Unit = {}, - onNavigateNotifications: () -> Unit = {}, - dashboardUiState: DashboardUiState = DashboardUiState(), - deviceInfo: DeviceInfoUiState = DeviceInfoUiState() -) { - - val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) - val scope = rememberCoroutineScope() - val mainHandler = remember { Handler(Looper.getMainLooper()) } - var showHostActionDialog by remember { mutableStateOf(false) } - var activeHostAction by remember { mutableStateOf("Log-On") } - var isHostActionRunning by remember { mutableStateOf(false) } - var dialogMessage by remember { mutableStateOf("") } - var reversalEnabled by remember { mutableStateOf(runCatching { SystemParamsOperation.getInstance().isReversalOn }.getOrDefault(false)) } - - val isOnline = true - - fun confirmationMessage(action: String) = "Do you want to start ${action.lowercase()}?" - fun processingMessage(action: String) = "Sending ${action.lowercase()} request to host..." - fun successMessage(action: String) = "$action success." - fun failureMessage(action: String, resultCode: Int?) = - "$action failed. Response code: ${resultCode ?: -1}" - - fun networkFailureMessage(action: String) = "Network error during $action." - fun openHostActionDialog(action: String) { - activeHostAction = action - isHostActionRunning = false - dialogMessage = confirmationMessage(action) - showHostActionDialog = true - } - - if (showHostActionDialog) { - AlertDialog( - onDismissRequest = { - if (!isHostActionRunning) { - showHostActionDialog = false - } - }, title = { - Text( - text = activeHostAction, color = Color.LegacyRed, fontWeight = FontWeight.Bold - ) - }, text = { - Text(text = dialogMessage) - }, confirmButton = { - TextButton( - onClick = { - if (isHostActionRunning) return@TextButton - - if (dialogMessage != confirmationMessage(activeHostAction)) { - showHostActionDialog = false - dialogMessage = confirmationMessage(activeHostAction) - return@TextButton - } - - isHostActionRunning = true - dialogMessage = processingMessage(activeHostAction) - - val signOnProcess = EchoTestProcess.getInstance() - val request = when (activeHostAction) { - "Echo Test" -> signOnProcess.enqueue(false) - "Log-Off" -> signOnProcess.enqueueLogOff() - else -> signOnProcess.enqueueLogOn() - } - - request.startSignOn(object : SignOnListener { - override fun onSuccessSignOn() { - mainHandler.post { - isHostActionRunning = false - dialogMessage = successMessage(activeHostAction) - } - } - - override fun onFailureSignOn(resultCode: Int?) { - mainHandler.post { - isHostActionRunning = false - mainHandler.post { - isHostActionRunning = false - dialogMessage = failureMessage(activeHostAction, resultCode) - } - } - } - - - override fun onNetworkFailSignOn(message: String?) { - mainHandler.post { - isHostActionRunning = false - dialogMessage = message?.takeIf { it.isNotBlank() } - ?: networkFailureMessage(activeHostAction) - } - } - }) - }) { - Text( - text = when { - isHostActionRunning -> "Processing" - dialogMessage == confirmationMessage(activeHostAction) -> "Start" - else -> "Close" - }, color = Color.LegacyRed - ) - } - }, dismissButton = { - if (!isHostActionRunning) { - TextButton( - onClick = { - showHostActionDialog = false - dialogMessage = confirmationMessage(activeHostAction) - }) { - Text(text = "Cancel", color = Color.Gray) - } - } - }, containerColor = Color.White - ) - } - - ModalNavigationDrawer( - drawerState = drawerState, drawerContent = { - ModalDrawerSheet( - modifier = Modifier.fillMaxWidth(0.78f), - drawerContainerColor = Color.White, - drawerShape = RoundedCornerShape(topEnd = 24.dp, bottomEnd = 24.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .background(Color.IvoryBeige) - .padding(horizontal = 20.dp, vertical = 28.dp) - ) { - Row { - Box( - modifier = Modifier - .size(80.dp) - .clip(CircleShape) - .background(Color.White), contentAlignment = Alignment.Center - ) { - Image( - painter = painterResource(R.drawable.logo_mob), - contentDescription = "mob logo", - modifier = Modifier - .size(100.dp) - .padding(16.dp), - contentScale = ContentScale.Fit - ) - } - - Column { - Text( - text = "MOB Merchant", - color = Color.CrimsonRed, - fontSize = 12.sp, - fontWeight = FontWeight.Bold - ) - Text( - text = "S/N:${deviceInfo.serialNumber}", - color = Color.Black, - fontSize = 12.sp, - fontWeight = FontWeight.Bold - ) - Text( - text = if (isOnline) "Online" else "Offline", - color = if (isOnline) Color.Success else Color.Error, - fontSize = 12.sp, - fontWeight = FontWeight.Bold - ) - } - } - } - - Spacer(modifier = Modifier.height(10.dp)) - Text( - text = "Connection Settings", - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(horizontal = 16.dp) - ) - - Item( - title = "Echo Test", subTitle = "Test Connection Status", - onClick = { - scope.launch { drawerState.close() } - openHostActionDialog("Echo Test") - }, - leadingIcon = { - Icon( - painterResource(R.drawable.ic_up_down_arrow), - contentDescription = "icon", - tint = Color.LegacyRed - ) - }, - trailingIcon = {}, - ) - Item( - title = "Log-On", subTitle = "Log on to System", - onClick = { - scope.launch { drawerState.close() } - openHostActionDialog("Log-On") - }, - leadingIcon = { - Icon( - painterResource(R.drawable.ic_lock), - contentDescription = "icon", - tint = Color.LegacyRed - ) - }, - trailingIcon = {}, - ) - Item( - title = "Log-Off", subTitle = "Log off from System", - onClick = { - scope.launch { drawerState.close() } - openHostActionDialog("Log-Off") - }, - leadingIcon = { - Icon( - painterResource(R.drawable.ic_cancel_circle), - contentDescription = "icon", - tint = Color.LegacyRed - ) - }, - trailingIcon = {}, - ) - Item( - title = "Reversal", subTitle = "Enable / Disable Reversal", - onClick = { - reversalEnabled = !reversalEnabled - runCatching { SystemParamsOperation.getInstance().setReversalFlag(reversalEnabled) } - }, - leadingIcon = { - Icon( - Icons.Default.Sync, - contentDescription = "icon", - tint = Color.LegacyRed - ) - }, - trailingIcon = { - Switch( - checked = reversalEnabled, - onCheckedChange = { isChecked -> - reversalEnabled = isChecked - runCatching { SystemParamsOperation.getInstance().setReversalFlag(isChecked) } - } - ) - }, - ) - - Text( - text = "System Management", - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(horizontal = 16.dp) - ) - - Item( - title = "Functions", subTitle = "System Function Settings", - onClick = { - scope.launch { drawerState.close() } - onNavigateFunctions() - }, - leadingIcon = { - Icon( - painterResource(R.drawable.ic_four_boxes), - contentDescription = "icon", - tint = Color.LegacyRed, - modifier = Modifier.size(16.dp) - ) - }, - trailingIcon = {}, - ) - Item( - title = "Version", subTitle = "View App Version Info", - onClick = { - scope.launch { drawerState.close() } - onNavigateVersion() - }, - leadingIcon = { - Icon( - painterResource(R.drawable.ic_version), - contentDescription = "icon", - tint = Color.LegacyRed - ) - }, - trailingIcon = {}, - ) - - } - }) { -//body start - Scaffold( - containerColor = Color.IvoryBeige, topBar = { - AppBar( - title = "Dashboard", - icon = Icons.Default.Menu, - onIconClick = { scope.launch { drawerState.open() } }, - actions = { - IconButton(onClick = onNavigateNotifications) { - Box { - Icon( - imageVector = Icons.Default.Notifications, - contentDescription = "Notifications", - tint = Color.White - ) - Box( - modifier = Modifier - .size(8.dp) - .background(Color.GoldenGlow, CircleShape) - .align(Alignment.TopEnd) - .offset(x = 2.dp, y = (-2).dp) - ) - } - } - } - ) - }) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - ) { - //top section - Box( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - ) { - AdvertisingArea() - } - //center section - Box( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - contentAlignment = Alignment.Center - ) { - SummaryCard() - } - //pager section - Box( - modifier = Modifier - .weight(1.3f) - .fillMaxWidth(), - ) { - MenuPager( - items = buildMenuItems( - onNavigateAmount = onNavigateAmount, - onNavigateSignOn = onNavigateSignOn, - onNavigateSettlement = onNavigateSettlement, - onNavigateAction = onNavigateAction - ), - modifier = Modifier.fillMaxSize() - ) - } - //transactions section -// RecentTransactions( -// transactions = dashboardUiState.recentTransactions, -// modifier = Modifier -// .weight(1.2f) -// .fillMaxWidth() -// ) - } - } - } -} - -@Composable -private fun AdvertisingArea() { - - val imageArray = listOf( - "https://i.ytimg.com/vi/eRUVxGRp1Ms/maxresdefault.jpg", - "https://i.ytimg.com/vi/AwvmgTPd7qw/maxresdefault.jpg", - "https://mma.prnewswire.com/media/2080956/SUNMI_3rd_generation_products_T3_PRO_series_V3_MIX.jpg?p=facebook" - ) - val pageState = rememberPagerState(pageCount = { imageArray.size }) - - LaunchedEffect(pageState) { - while (true) { - delay(10000.milliseconds) - val nextPage = (pageState.currentPage + 1) % imageArray.size - - pageState.animateScrollToPage( - page = nextPage, animationSpec = tween(durationMillis = 700) - ) - } - } - - Card( - modifier = Modifier.fillMaxSize(), - shape = RoundedCornerShape(0.dp), - colors = CardDefaults.cardColors(containerColor = Color.White), - ) { - HorizontalPager( - state = pageState, modifier = Modifier.fillMaxSize() - ) { page -> - AsyncImage( - model = imageArray[page], - contentDescription = "ads images", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop - ) - } - } -} - -@Composable -private fun SummaryCard() { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors(containerColor = Color.White), - elevation = CardDefaults.cardElevation(4.dp) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Column(modifier = Modifier.weight(1f)) { - SummaryItem( - title = "Sales Today", - value = "MMK 2,000,000", - subtitle = "47 Transactions", - icon = Icons.Default.BarChart, - iconBg = Color.CrimsonRed - ) - - HorizontalDivider( - color = Color.Gray, - modifier = Modifier.padding(vertical = 6.dp, horizontal = 6.dp) - ) - - SummaryItem( - title = "Settlement", - value = "Completed", - subtitle = "Today 11:00 PM", - icon = Icons.Default.Check, - iconBg = Color.CrimsonRed - ) - } - - VerticalDivider(color = Color.Gray, modifier = Modifier.height(160.dp)) - - Column( - modifier = Modifier - .weight(0.75f) - .padding(start = 18.dp), - horizontalAlignment = Alignment.Start - ) { - Row(verticalAlignment = Alignment.Bottom) { - Text( - text = "Last Sync", fontSize = 12.sp - ) - IconCircle(Icons.Default.Sync, Color.CrimsonRed) - } - - Spacer(Modifier.height(6.dp)) - - Text( - text = "5 mins ago", - fontSize = 18.sp, - color = Color.LegacyRed, - fontWeight = FontWeight.SemiBold - ) - } - } - } -} - -@Composable -private fun SummaryItem( - title: String, - value: String, - subtitle: String, - icon: ImageVector, - iconBg: androidx.compose.ui.graphics.Color -) { - Row( - verticalAlignment = Alignment.Top, modifier = Modifier.padding(6.dp) - ) { - Column(modifier = Modifier.weight(1f)) { - Text(title, fontSize = 12.sp) - Text( - value, fontSize = 14.sp, color = Color.LegacyRed, fontWeight = FontWeight.Bold - ) - Text(subtitle, fontSize = 12.sp) - } - IconCircle(icon, iconBg) - } -} - -@Composable -private fun IconCircle( - icon: ImageVector, color: androidx.compose.ui.graphics.Color -) { - Box( - modifier = Modifier - .size(38.dp) - .clip(CircleShape) - .background(color.copy(alpha = 0.1f)), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = color, - modifier = Modifier.size(22.dp) - ) - } -} - -private class DashboardMenuItem( - val title: String, - val iconContent: @Composable () -> Unit, - val onClick: () -> Unit -) - -@Composable -private fun buildMenuItems( - onNavigateAmount: (String) -> Unit, - onNavigateSignOn: () -> Unit, - onNavigateSettlement: () -> Unit, - onNavigateAction: (String) -> Unit -): List = listOf( - DashboardMenuItem("Sale", { Icon(painterResource(R.drawable.ic_terminal), contentDescription = null, modifier = Modifier.size(40.dp), tint = Color.LegacyRed) }) { onNavigateAmount("Sale") }, - DashboardMenuItem("MMQR", { Image(painter = painterResource(R.drawable.ic_mmqr_logo), contentDescription = null, modifier = Modifier.height(48.dp)) }) { }, - DashboardMenuItem("History", { Icon(painterResource(R.drawable.ic_history), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { }, - DashboardMenuItem("Sign On", { Icon(painterResource(R.drawable.ic_sign_on), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSignOn() }, - DashboardMenuItem("Settlement", { Icon(painterResource(R.drawable.ic_settlement), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSettlement() }, - DashboardMenuItem("Void", { Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Void") }, - DashboardMenuItem("Refund", { Icon(Icons.Default.Replay, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Refund") }, - DashboardMenuItem("Pre-Auth", { Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth") }, - DashboardMenuItem("Pre-Auth Void", { Icon(Icons.Default.LockOpen, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Void") }, - DashboardMenuItem("Pre-Auth Complete", { Icon(Icons.Default.CreditCard, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Complete") }, - DashboardMenuItem("Pre-Auth Complete Void", { Icon(Icons.Default.SwapHoriz, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Complete Void") }, - DashboardMenuItem("Cash Out", { Icon(Icons.Default.AccountBalanceWallet, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Cash Out") }, -) - -@Composable -private fun MenuPager( - items: List, - modifier: Modifier = Modifier -) { - val pages = items.chunked(6) - val pagerState = rememberPagerState(pageCount = { pages.size }) - - Column(modifier = modifier) { - HorizontalPager( - state = pagerState, - modifier = Modifier.weight(1f) - ) { pageIndex -> - MenuPage(items = pages[pageIndex]) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - repeat(pages.size) { index -> - val selected = pagerState.currentPage == index - Box( - modifier = Modifier - .padding(horizontal = 4.dp) - .size(if (selected) 8.dp else 6.dp) - .background( - color = if (selected) Color.LegacyRed else Color.Gray.copy(alpha = 0.4f), - shape = CircleShape - ) - ) - } - } - } -} - -@Composable -private fun MenuPage(items: List) { - Column( - verticalArrangement = Arrangement.spacedBy(10.dp), - modifier = Modifier.padding(horizontal = 16.dp) - ) { - Spacer(Modifier.height(8.dp)) - items.chunked(3).forEach { rowItems -> - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { - rowItems.forEach { item -> - MenuCard( - title = item.title, - icon = item.iconContent, - modifier = Modifier.weight(1f), - onClick = item.onClick - ) - } - repeat(3 - rowItems.size) { - Spacer(modifier = Modifier.weight(1f)) - } - } - } - } -} - -@Composable -private fun MenuCard( - title: String, - icon: @Composable () -> Unit, - modifier: Modifier = Modifier, - onClick: (() -> Unit)? = null -) { - Card( - modifier = modifier - .height(100.dp) - .then( - if (onClick != null) { - Modifier.clickable(onClick = onClick) - } else { - Modifier - } - ), - shape = RoundedCornerShape(14.dp), - colors = CardDefaults.cardColors(containerColor = Color.White), - elevation = CardDefaults.cardElevation(5.dp) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(6.dp) - ) { - Column { - icon() - Spacer(modifier = Modifier.weight(1f)) - - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = title, - fontSize = 12.sp, - color = Color.Black, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - modifier = Modifier.weight(1f) - ) - Icon( - imageVector = Icons.Default.ChevronRight, - contentDescription = null, - tint = Color.LegacyRed, - modifier = Modifier.size(28.dp) - ) - } - } - } - } -} - -@Composable -private fun RecentTransactions( - transactions: List, - modifier: Modifier = Modifier -) { - Column(modifier = modifier) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Recent Transactions", - fontWeight = FontWeight.SemiBold, - fontSize = 14.sp - ) - } - HorizontalDivider( - modifier = Modifier.padding(horizontal = 16.dp), - color = Color.Gray.copy(alpha = 0.2f) - ) - LazyColumn(modifier = Modifier.fillMaxSize()) { - if (transactions.isEmpty()) { - item { - Text( - text = "No recent transactions", - color = Color.Gray, - fontSize = 13.sp, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 24.dp) - ) - } - } else { - items(items = transactions, key = { it.pid }) { record -> - TrnxRow(record = record) - } - } - } - } -} - -@Composable -private fun TrnxRow(record: TrnxRecord) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 4.dp) - .clip(RoundedCornerShape(10.dp)) - .background(Color.White) - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(36.dp) - .background(Color.LegacyRed.copy(alpha = 0.1f), CircleShape), - contentAlignment = Alignment.Center - ) { - Text( - text = record.typeLabel.take(1), - color = Color.LegacyRed, - fontWeight = FontWeight.Bold, - fontSize = 15.sp - ) - } - - Spacer(Modifier.width(10.dp)) - - Column(modifier = Modifier.weight(1f)) { - Text( - text = if (record.isVoided) "${record.typeLabel} (Voided)" else record.typeLabel, - fontSize = 13.sp, - fontWeight = FontWeight.SemiBold, - color = if (record.isVoided) Color.Gray else Color.Black - ) - Text( - text = record.maskedCard, - fontSize = 11.sp, - color = Color.Gray - ) - } - - Column(horizontalAlignment = Alignment.End) { - Text( - text = record.amountDisplay, - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - color = if (record.isVoided) Color.Gray else Color.LegacyRed - ) - Text( - text = record.dateTime, - fontSize = 10.sp, - color = Color.Gray - ) - } - } -} - -private val previewTransactions = listOf( - TrnxRecord(1L, "Sale", "MMK 10,000", "**** 1234", "06/10 14:32", isVoided = false, isApproved = true), - TrnxRecord(2L, "Void", "MMK 5,500", "**** 5678", "06/10 13:10", isVoided = true, isApproved = false), - TrnxRecord(3L, "Refund", "MMK 2,000", "**** 9012", "06/09 09:45", isVoided = false, isApproved = true), - TrnxRecord(4L, "Sale", "MMK 30,000", "**** 3456", "06/09 08:00", isVoided = false, isApproved = true), - TrnxRecord(5L, "Settlement", "MMK 0", "----", "06/08 18:00", isVoided = false, isApproved = true), -) - -@P3Preview -@P2Preview -@Composable -fun PreviewDashboardScreen2() { - DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions)) -} - -@Preview -@Composable -fun PreviewItem() { - Item( - onClick = {}, - title = "title", - subTitle = "sub-title" - ) -} - - -@Composable -fun Item( - onClick: () -> Unit, - title: String, - subTitle: String, - leadingIcon: (@Composable () -> Unit)? = null, - trailingIcon: (@Composable () -> Unit)? = null, -) { - Button( - onClick = onClick, - colors = ButtonDefaults.buttonColors( - containerColor = Color.White, - contentColor = Color.Black - ), - modifier = Modifier.fillMaxWidth() - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .fillMaxWidth() - ) { - // Square icon background - leadingIcon?.let { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .size(40.dp) - .clip(RoundedCornerShape(16.dp)) // change to RectangleShape for sharp corners - .background(Color.LegacyRed.copy(alpha = 0.1f)) - .padding(8.dp) - ) { - it() - } - } - - Spacer(modifier = Modifier.width(12.dp)) - - Column( - modifier = Modifier - .weight(1f) - .padding(horizontal = 8.dp) - ) { - Text(text = title) - Text( - text = subTitle, - style = MaterialTheme.typography.bodySmall, - color = Color.Gray - ) - } - - trailingIcon?.invoke() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreen.kt new file mode 100644 index 0000000..affff12 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreen.kt @@ -0,0 +1,66 @@ +package com.mob.utsmyanmar.ui.disable + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +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.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.Color + +@Composable +fun DisableScreen( + message: String, + onRetry: () -> Unit +) { + Scaffold( + containerColor = Color.IvoryBeige + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(R.drawable.ic_alert_triangle), + contentDescription = null, + tint = Color.LegacyRed, + modifier = Modifier.size(100.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Terminal Disabled", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.LegacyRed + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = message, + fontSize = 14.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 32.dp) + ) + Spacer(modifier = Modifier.height(24.dp)) + Button(onClick = onRetry) { + Text(text = "Retry") + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenUiState.kt b/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenUiState.kt new file mode 100644 index 0000000..c412192 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenUiState.kt @@ -0,0 +1,2 @@ +package com.mob.utsmyanmar.ui.disable + diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenViewModel.kt new file mode 100644 index 0000000..c412192 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/disable/DisableScreenViewModel.kt @@ -0,0 +1,2 @@ +package com.mob.utsmyanmar.ui.disable + 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 b9e580f..c9620f2 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 @@ -41,6 +41,7 @@ import com.mob.utsmyanmar.ui.settlement.SettlementScreen import com.mob.utsmyanmar.ui.settlement.SettlementViewModel import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen import com.mob.utsmyanmar.ui.sign_on.SignOnRoute +import com.mob.utsmyanmar.ui.disable.DisableScreen import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent @@ -70,9 +71,31 @@ fun AppNavGraph( viewModel = tmsSetupViewModel, onNavigateDashboard = { navController.navigate(Routes.Dashboard.route) { - popUpTo(Routes.TmsSetup.route) { - inclusive = true - } + popUpTo(Routes.TmsSetup.route) { inclusive = true } + launchSingleTop = true + } + }, + onNavigateDisable = { message -> + navController.navigate(Routes.Disable.createRoute(message)) { + popUpTo(Routes.TmsSetup.route) { inclusive = true } + launchSingleTop = true + } + } + ) + } + + composable( + route = Routes.Disable.route, + arguments = listOf( + navArgument("message") { type = NavType.StringType } + ) + ) { backStackEntry -> + val message = Uri.decode(backStackEntry.arguments?.getString("message").orEmpty()) + DisableScreen( + message = message, + onRetry = { + navController.navigate(Routes.TmsSetup.route) { + popUpTo(Routes.Disable.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 253c217..65b2ea2 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 @@ -37,4 +37,7 @@ sealed class Routes(val route: String) { data object NotificationDetail : Routes("notification_detail/{notificationId}") { fun createRoute(id: Int): String = "notification_detail/$id" } + data object Disable : Routes("disable/{message}") { + fun createRoute(message: String): String = "disable/${Uri.encode(message)}" + } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt index f2c9f9f..74ac64d 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt @@ -40,7 +40,8 @@ import com.mob.utsmyanmar.ui.theme.Color as AppColor @Composable fun TmsSetupRoute( viewModel: TmsSetupViewModel, - onNavigateDashboard: () -> Unit + onNavigateDashboard: () -> Unit, + onNavigateDisable: (String) -> Unit ) { val state by viewModel.uiState.collectAsState() @@ -48,6 +49,10 @@ fun TmsSetupRoute( viewModel.navigateToDashboard.collect { onNavigateDashboard() } } + LaunchedEffect(viewModel) { + viewModel.navigateToDisable.collect { message -> onNavigateDisable(message) } + } + TmsSetupScreen( state = state, onRetry = viewModel::downloadConfigs, @@ -82,7 +87,11 @@ fun TmsSetupScreen( ) Text( - text = if (state.isError) "Configuration Error" else "Setting Up Terminal", + text = when { + state.isTerminalDisabled -> "Terminal Disabled" + state.isError -> "Configuration Error" + else -> "Setting Up Terminal" + }, fontSize = 22.sp, fontWeight = FontWeight.Bold, color = AppColor.LegacyRed, @@ -159,13 +168,15 @@ fun TmsSetupScreen( Text(text = "Retry", fontSize = 16.sp) } - OutlinedButton( - onClick = onSkip, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = ButtonDefaults.outlinedButtonColors(contentColor = AppColor.LegacyRed) - ) { - Text(text = "Skip", fontSize = 16.sp) + if (!state.isTerminalDisabled) { + OutlinedButton( + onClick = onSkip, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.outlinedButtonColors(contentColor = AppColor.LegacyRed) + ) { + Text(text = "Skip", fontSize = 16.sp) + } } } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt index 886252b..c463169 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt @@ -12,6 +12,7 @@ import com.utsmyanmar.baselib.BaseApplication import com.utsmyanmar.baselib.emv.EmvParamOperation import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest import com.utsmyanmar.baselib.repo.Repository +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -25,14 +26,17 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import org.json.JSONObject import sunmi.sunmiui.utils.LogUtil import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds data class TmsSetupUiState( val isLoading: Boolean = false, val statusText: String = "Initializing...", val isError: Boolean = false, - val errorMessage: String = "" + val errorMessage: String = "", + val isTerminalDisabled: Boolean = false ) @HiltViewModel @@ -47,6 +51,9 @@ class TmsSetupViewModel @Inject constructor( private val _navigateToDashboard = MutableSharedFlow() val navigateToDashboard: SharedFlow = _navigateToDashboard.asSharedFlow() + private val _navigateToDisable = MutableSharedFlow() + val navigateToDisable: SharedFlow = _navigateToDisable.asSharedFlow() + private val disposables = CompositeDisposable() private val tmsSetups = TMSSetupsImpl() @@ -82,12 +89,19 @@ class TmsSetupViewModel @Inject constructor( onConfigApplied() }, { error -> + val errorMessage = if(error is retrofit2.HttpException){ +// error.response()?.errorBody()?.toString() ?: error.message.toString() + val body = error.response()?.errorBody()?.toString() + JSONObject(body ?: "").getString("message") + }else{ + error.message.toString() + } _uiState.update { it.copy( - isLoading = false, isError = true, - statusText = "Download failed", - errorMessage = formatNetworkError(error) + isLoading = false, + statusText = "Download failed!", + errorMessage = errorMessage ) } } @@ -100,12 +114,22 @@ class TmsSetupViewModel @Inject constructor( _uiState.update { it.copy(isLoading = true, statusText = "Starting hardware...") } var elapsed = 0 while (BaseApplication.basicOptV2 == null && elapsed < 10_000) { - delay(500) + delay(500.milliseconds) elapsed += 500 } } private fun onConfigApplied() { + val ops = SystemParamsOperation.getInstance() + + if (!ops.isActive) { + val msg = ops.disabledMsg.takeIf { it.isNotEmpty() } + ?: "This terminal has been disabled. Please contact your administrator." + _uiState.update { it.copy(isLoading = false) } + viewModelScope.launch { _navigateToDisable.emit(msg) } + return + } + val validity = TMSUtil.getInstance().checkParams() if (validity.status == ValidityStatus.SUCCESS) { _uiState.update { it.copy(isLoading = false, statusText = "Ready.") } @@ -126,26 +150,26 @@ class TmsSetupViewModel @Inject constructor( viewModelScope.launch { _navigateToDashboard.emit(Unit) } } - private fun formatNetworkError(error: Throwable): String { - return when (error) { - is javax.net.ssl.SSLHandshakeException -> - "SSL handshake failed: ${error.message ?: "Certificate or protocol mismatch"}" - is javax.net.ssl.SSLException -> - "SSL/TLS error: ${error.message ?: "Secure connection could not be established"}" - is java.security.cert.CertificateException -> - "Server certificate error: ${error.message ?: "Certificate is invalid or untrusted"}" - is java.net.UnknownHostException -> - "Host not found: ${error.message ?: "Check server URL and network connection"}" - is java.net.ConnectException -> - "Connection refused: ${error.message ?: "Server is unreachable"}" - is java.net.SocketTimeoutException -> - "Connection timed out: ${error.message ?: "Server did not respond in time"}" - is retrofit2.HttpException -> - "HTTP ${error.code()} ${error.message()}" - else -> - error.message ?: "Unknown network error" - } - } +// private fun formatNetworkError(error: Throwable): String { +// return when (error) { +// is javax.net.ssl.SSLHandshakeException -> +// "SSL handshake failed: ${error.message ?: "Certificate or protocol mismatch"}" +// is javax.net.ssl.SSLException -> +// "SSL/TLS error: ${error.message ?: "Secure connection could not be established"}" +// is java.security.cert.CertificateException -> +// "Server certificate error: ${error.message ?: "Certificate is invalid or untrusted"}" +// is java.net.UnknownHostException -> +// "Host not found: ${error.message ?: "Check server URL and network connection"}" +// is java.net.ConnectException -> +// "Connection refused: ${error.message ?: "Server is unreachable"}" +// is java.net.SocketTimeoutException -> +// "Connection timed out: ${error.message ?: "Server did not respond in time"}" +// is retrofit2.HttpException -> +// "HTTP ${error.code()} ${error.message()}" +// else -> +// error.message ?: "Unknown network error" +// } +// } @SuppressLint("MissingPermission") private fun buildRequest(): SiriusRequest { diff --git a/app/src/main/res/drawable/ic_alert_triangle.xml b/app/src/main/res/drawable/ic_alert_triangle.xml new file mode 100644 index 0000000..a00eabc --- /dev/null +++ b/app/src/main/res/drawable/ic_alert_triangle.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/TerminalKeyUtil.java b/baselib/src/main/java/com/utsmyanmar/baselib/TerminalKeyUtil.java index 3dbf2a7..1240fe8 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/TerminalKeyUtil.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/TerminalKeyUtil.java @@ -3,15 +3,10 @@ package com.utsmyanmar.baselib; import android.content.ContentValues; import android.os.RemoteException; -import com.sunmi.pay.hardware.aidl.AidlConstants; import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2; import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2; -import com.utsmyanmar.paylibs.Constant; import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil; import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; -import com.utsmyanmar.paylibs.utils.secure.TriDes; - -import java.security.GeneralSecurityException; import sunmi.sunmiui.utils.LogUtil; @@ -28,7 +23,7 @@ public final class TerminalKeyUtil { SecurityOptV2 mSecurityOptV2 = BaseApplication.getInstance().mSecurityOptV2; byte[] cvByte = ByteUtil.hexStr2Bytes("B7B520"); - byte[] dataByte = ByteUtil.hexStr2Bytes("e121249099a677e8b7d4f6a9d49fe8d1".toUpperCase()); + byte[] dataByte = ByteUtil.hexStr2Bytes("05FFB893726A5F1E59692D24E72E36AC".toUpperCase()); byte[] makBytes = ByteUtil.hexStr2Bytes("250738083EC15BD3BA67D66B8A7AA13B"); // byte[] makCvBytes = ByteUtil.hexStr2Bytes("204E449B97");