Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
kizzy 2026-06-28 21:25:05 +07:00
commit a6377fb16d
3 changed files with 51 additions and 1132 deletions

View File

@ -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<DashboardMenuItem> = 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<DashboardMenuItem>,
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<DashboardMenuItem>) {
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<TrnxRecord>,
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()
}
}
}

View File

@ -1,13 +1,10 @@
package com.mob.utsmyanmar.ui.navigation
import android.annotation.SuppressLint
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
@ -18,18 +15,10 @@ import com.mob.utsmyanmar.model.ProcessCode
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen2
import com.mob.utsmyanmar.ui.dashboard.DashboardViewModel
import com.mob.utsmyanmar.ui.dashboard.SeeMoreScreen
import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
import com.mob.utsmyanmar.ui.disable.DisableScreen
import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.history_functions.HistoryRoute
import com.mob.utsmyanmar.ui.input_amount.AmountRoute
import com.mob.utsmyanmar.ui.manual_entry.InputManualCardRoute
import com.mob.utsmyanmar.ui.notification.NotificationDetailScreen
import com.mob.utsmyanmar.ui.notification.NotificationListScreen
import com.mob.utsmyanmar.ui.notification.NotificationViewModel
import com.mob.utsmyanmar.ui.password_input.InputPassword
import com.mob.utsmyanmar.ui.password_input.PasswordType
import com.mob.utsmyanmar.ui.pinpad.PinPadRoute
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen
@ -57,7 +46,6 @@ import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.paylibs.model.TradeData
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import sunmi.sunmiui.utils.LogUtil
@SuppressLint("ContextCastToActivity")
@Composable
@ -76,64 +64,22 @@ 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
}
}
onNavigateDisable = {}
)
}
composable(Routes.Dashboard.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val dashboardViewModel: DashboardViewModel = hiltViewModel()
val dashboardUiState by dashboardViewModel.uiState.collectAsStateWithLifecycle()
val deviceInfoViewModel: DeviceInfoViewModel = hiltViewModel()
val deviceInfo by deviceInfoViewModel.uiState.collectAsStateWithLifecycle()
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel()
androidx.compose.runtime.LaunchedEffect(Unit) { deviceInfoViewModel.loadDeviceInfo() }
androidx.compose.runtime.LaunchedEffect(tmsSetupViewModel) {
tmsSetupViewModel.onSyncCompleted.collect {
dashboardViewModel.refreshLastSyncTime()
dashboardViewModel.loadCarouselUrls()
}
}
androidx.compose.runtime.LaunchedEffect(tmsSetupViewModel) {
tmsSetupViewModel.navigateToDisable.collect { message ->
navController.navigate(Routes.Disable.createRoute(message)) {
launchSingleTop = true
}
}
}
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
transProcessViewModel.resetTransactionStatus()
DashboardScreen2(
dashboardUiState = dashboardUiState,
deviceInfo = deviceInfo,
onNavigateAmount = { action ->
if(action == "Sale"){
sharedViewModel.transactionsType.value = TransactionsType.SALE;
@ -143,12 +89,14 @@ fun AppNavGraph(
navController.navigate(Routes.Amount.createRoute(action)) {
launchSingleTop = true
}
},
onNavigateSignOn = {
navController.navigate(Routes.SignOn.route) {
launchSingleTop = true
}
},
onNavigateSettlement = {
navController.navigate(Routes.Settlement.route) {
launchSingleTop = true
@ -158,43 +106,37 @@ fun AppNavGraph(
navController.navigate(Routes.Version.route)
},
onNavigateFunctions = {
navController.navigate(Routes.Password.createRoute(Routes.Functions.route, PasswordType.SETTING)) {
navController.navigate(Routes.Functions.route) {
launchSingleTop = true
}
},
onNavigateHistory = {
navController.navigate(Routes.Password.createRoute(Routes.History.route, PasswordType.SYSTEM)) {
launchSingleTop = true
}
},
onNavigateAction = { action ->
)
}
composable(Routes.SeeMore.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
SeeMoreScreen(
onBack = { navController.popBackStack() },
onNavigateAmount = { action ->
when (action) {
"Void" -> {
sharedViewModel.transactionsType.value = TransactionsType.VOID
navController.navigate(Routes.Password.createRoute(Routes.VoidTrace.route, PasswordType.SYSTEM)) {
navController.navigate(Routes.VoidTrace.route) {
launchSingleTop = true
}
}
"Pre-Auth Void" -> navController.navigate(Routes.Password.createRoute(Routes.Amount.createRoute(action), PasswordType.SYSTEM)) {
launchSingleTop = true
}
"Pre-Auth Complete Void" -> {
sharedViewModel.transactionsType.value = TransactionsType.PRE_AUTH_COMPLETE_VOID
navController.navigate(Routes.Password.createRoute(Routes.VoidTrace.route, PasswordType.SYSTEM)) {
navController.navigate(Routes.VoidTrace.route) {
launchSingleTop = true
}
}
"Refund" -> navController.navigate(Routes.Password.createRoute(Routes.Amount.createRoute(action), PasswordType.SYSTEM)) {
else -> {
navController.navigate(Routes.Amount.createRoute(action)) {
launchSingleTop = true
}
else -> navController.navigate(Routes.Amount.createRoute(action)) { launchSingleTop = true }
}
},
onNavigateNotifications = {
navController.navigate(Routes.NotificationList.route) { launchSingleTop = true }
},
onClickLastSync = {
tmsSetupViewModel.downloadConfigs()
}
}
)
}
@ -207,57 +149,18 @@ fun AppNavGraph(
)
}
composable(
route = Routes.Password.route,
arguments = listOf(
navArgument("destination") { type = NavType.StringType },
navArgument("passwordType") { type = NavType.StringType }
)
) { backStackEntry ->
val destination = Uri.decode(backStackEntry.arguments?.getString("destination").orEmpty())
val passwordType = backStackEntry.arguments?.getString("passwordType").orEmpty()
InputPassword(
passwordType = passwordType,
onBack = { navController.popBackStack() },
onPasswordCorrect = {
navController.navigate(destination) {
popUpTo(Routes.Password.route) { inclusive = true }
launchSingleTop = true
}
}
)
}
composable(Routes.Functions.route) {
FunctionsScreen(
onBack = { navController.popBackStack() }
)
}
composable(Routes.History.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transResultViewModel: TransactionResultViewModel = hiltViewModel(activity)
HistoryRoute(
sharedViewModel = sharedViewModel,
onBack = { navController.popBackStack() },
onNavigatePrintReceipt = {
transResultViewModel.onEvent(
TransactionResultEvent.Start,
sharedViewModel
)
navController.navigate(Routes.PrintReceipt.route) {
launchSingleTop = true
}
}
)
}
composable(Routes.VoidTrace.route) {
val voidViewModel: VoidViewModel = hiltViewModel()
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
VoidTraceScreen(
voidViewModel = voidViewModel,
sharedViewModel = sharedViewModel,
voidViewModel = voidViewModel,
onNavigateTranDetail = { trace ->
navController.navigate(Routes.VoidTranDetail.createRoute(trace)) {
launchSingleTop = true
@ -314,6 +217,7 @@ fun AppNavGraph(
payDetail.transType = TransactionsType.PRE_AUTH_COMPLETE_VOID.name
payDetail.transactionType = TransactionsType.PRE_AUTH_COMPLETE_VOID.value
}
val tradeData = TradeData()
tradeData.payDetail = payDetail
transProcessViewModel.setTradeData(tradeData)
@ -417,14 +321,7 @@ fun AppNavGraph(
CardWaitingScreen(
viewModel = cardWaitingViewModel,
amount = formatAmountForDisplay(sharedViewModel.amount.value),
onManualEntry = {
navController.navigate(Routes.ManualEntry.route) {
popUpTo(Routes.CardWaiting.route) {
inclusive = true
}
launchSingleTop = true
}
},
onManualEntry = {},
onProcessingCard = {
navController.navigate(Routes.ProcessingCard.route) {
popUpTo(Routes.CardWaiting.route) {
@ -520,11 +417,11 @@ fun AppNavGraph(
composable(Routes.InputRrn.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
val rrnViewModel: RRNViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
InputRrnRoute(
rrnViewModel = rrnViewModel,
transProcessViewModel = transProcessViewModel,
rrnViewModel = rrnViewModel,
sharedViewModel = sharedViewModel,
onBack = { navController.popBackStack() },
onNavigateProcessing = {
@ -538,27 +435,6 @@ fun AppNavGraph(
)
}
composable(Routes.ManualEntry.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
val pinPadViewModel: PinPadViewModel = hiltViewModel(activity)
InputManualCardRoute(
transProcessViewModel = transProcessViewModel,
sharedViewModel = sharedViewModel,
pinPadViewModel = pinPadViewModel,
onBack = { navController.popBackStack() },
onNavigatePinPad = {
pinPadViewModel.resetSessionState()
navController.navigate(Routes.PinPad.route) {
popUpTo(Routes.ProcessingCard.route) {
inclusive = true
}
launchSingleTop = true
}
},
)
}
composable(Routes.Processing.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
@ -636,34 +512,6 @@ fun AppNavGraph(
}
)
}
composable(Routes.NotificationList.route) {
val notificationViewModel: NotificationViewModel = hiltViewModel()
NotificationListScreen(
viewModel = notificationViewModel,
onBack = { navController.popBackStack() },
onNavigateDetail = { id ->
navController.navigate(Routes.NotificationDetail.createRoute(id)) {
launchSingleTop = true
}
}
)
}
composable(
route = Routes.NotificationDetail.route,
arguments = listOf(
navArgument("notificationId") { type = NavType.IntType }
)
) { backStackEntry ->
val notificationViewModel: NotificationViewModel = hiltViewModel()
val id = backStackEntry.arguments?.getInt("notificationId") ?: return@composable
NotificationDetailScreen(
viewModel = notificationViewModel,
notificationId = id,
onBack = { navController.popBackStack() }
)
}
}
}

View File

@ -8,12 +8,15 @@ import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import kotlinx.coroutines.delay
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultState
import com.mob.utsmyanmar.viewmodel.ProcessingTransaction
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import androidx.compose.runtime.collectAsState
import com.mob.utsmyanmar.model.TransactionStatus
import com.mob.utsmyanmar.ui.settlement.SettlementViewModel
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import kotlinx.coroutines.channels.Channel
private const val MOCK_HOST_DELAY_MS = 2000L
@ -32,7 +35,6 @@ fun ProcessingRoute(
val settlementStatus by settlementViewModel.uiState.collectAsStateWithLifecycle()
if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) {
LaunchedEffect(Unit) {
settlementViewModel.startSettlementProcess()
@ -50,6 +52,7 @@ fun ProcessingRoute(
}
} else {
LaunchedEffect(Unit) {
// transProcessViewModel.resetTransactionStatus()
transProcessViewModel.startOnlineProcess()
}
@ -64,15 +67,10 @@ fun ProcessingRoute(
sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value
onNavigateTransactionResult()
}
TransResultStatus.REVERSAL_SUCCESS ,
TransResultStatus.REVERSAL_FAIL -> {
delay(2500)
onNavigateMain()
}
TransResultStatus.REVERSAL_SUCCESS -> onNavigateMain()
TransResultStatus.REVERSAL_FAIL -> onNavigateMain()
else -> {}
}
}
}
@ -81,6 +79,7 @@ fun ProcessingRoute(
if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) {
ProcessingScreen(settlementStatus.processingState)
} else {