Compare commits

...

2 Commits

Author SHA1 Message Date
moon
73195ebc62 seperate code for Dashboard 2026-06-30 14:48:11 +06:30
moon
2f53577f08 Item Label fixed 2026-06-30 14:41:59 +06:30
4 changed files with 783 additions and 799 deletions

View File

@ -0,0 +1,531 @@
package com.mob.utsmyanmar.ui.dashboard
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListScope
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.BarChart
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.unit.dp
import coil3.compose.AsyncImage
import com.mob.utsmyanmar.R
import com.mob.utsmyanmar.ui.theme.Color
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.milliseconds
@Composable
fun AdvertisingArea(
modifier: Modifier = Modifier,
carouselUrls: List<String> = emptyList(),
) {
val imageModels: List<Any> = carouselUrls.ifEmpty {
listOf(R.drawable.sunmi1, R.drawable.sunmi2, R.drawable.sunmi3)
}
val pageState = rememberPagerState(pageCount = { imageModels.size })
LaunchedEffect(pageState) {
while (true) {
delay(10000.milliseconds)
val nextPage = (pageState.currentPage + 1) % imageModels.size
pageState.animateScrollToPage(
page = nextPage, animationSpec = tween(durationMillis = 700)
)
}
}
Card(
modifier = modifier,
shape = RoundedCornerShape(0.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
) {
HorizontalPager(
state = pageState, modifier = Modifier.fillMaxSize()
) { page ->
AsyncImage(
model = imageModels[page],
contentDescription = "ads images",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
}
}
@Composable
fun SummaryCard(
onClickLastSync: () -> Unit,
lastSyncTime: Long?,
totalSaleCount: Int,
totalSaleAmount: Long
) {
var now by remember { mutableLongStateOf(System.currentTimeMillis()) }
LaunchedEffect(Unit) {
while (true) {
delay(30_000.milliseconds)
now = System.currentTimeMillis()
}
}
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 = "Total Sale",
value = "MMK ${formatAmount(totalSaleAmount)}",
subtitle = "$totalSaleCount 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", style = MaterialTheme.typography.bodySmall
)
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
onClickLastSync()
},
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Default.Sync,
contentDescription = null,
tint = Color.CrimsonRed,
modifier = Modifier
.size(22.dp)
.rotate(rotation)
)
}
}
Spacer(Modifier.height(6.dp))
Text(
text = formatRelativeTime(lastSyncTime, now),
style = MaterialTheme.typography.titleMedium,
color = Color.LegacyRed
)
}
}
}
}
@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, style = MaterialTheme.typography.bodySmall)
Text(
value, style = MaterialTheme.typography.titleSmall, color = Color.LegacyRed
)
Text(subtitle, style = MaterialTheme.typography.bodySmall)
}
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)
)
}
}
@Composable
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.fillMaxWidth()
) { 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)
) {
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,
style = MaterialTheme.typography.bodySmall,
color = Color.Black,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
modifier = Modifier.weight(1f)
)
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = null,
tint = Color.LegacyRed,
modifier = Modifier.size(28.dp)
)
}
}
}
}
}
fun LazyListScope.recentTransactionsItems(transactions: List<TrnxRecord>) {
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Recent Transactions",
style = MaterialTheme.typography.titleSmall
)
}
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp),
color = Color.Gray.copy(alpha = 0.2f)
)
}
if (transactions.isEmpty()) {
item {
Text(
text = "No recent transactions",
color = Color.Gray,
style = MaterialTheme.typography.bodyMedium,
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,
style = MaterialTheme.typography.bodyLarge
)
}
Spacer(Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text =record.typeLabel,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
color = Color.Black
)
Text(
text = record.maskedCard,
style = MaterialTheme.typography.labelSmall,
color = Color.Gray
)
}
Column(horizontalAlignment = Alignment.End) {
Text(
text = record.amountDisplay,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold),
color = if (record.isVoided) Color.Gray else Color.LegacyRed
)
Text(
text = record.dateTime,
style = MaterialTheme.typography.labelSmall,
color = Color.Gray
)
}
}
}
@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()
) {
leadingIcon?.let {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(16.dp))
.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()
}
}
}
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
),
)

View File

@ -0,0 +1,142 @@
package com.mob.utsmyanmar.ui.dashboard
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountBalanceWallet
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.Replay
import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.mob.utsmyanmar.R
import com.mob.utsmyanmar.ui.theme.Color
class DashboardMenuItem(
val title: String,
val iconContent: @Composable () -> Unit,
val onClick: () -> Unit
)
fun buildMenuItems(
onNavigateAmount: (String) -> Unit,
onNavigateSignOn: () -> Unit,
onNavigateSettlement: () -> Unit,
onNavigateHistory: () -> 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(
"History",
{
Icon(
painterResource(R.drawable.ic_history),
contentDescription = null,
modifier = Modifier.size(32.dp),
tint = Color.LegacyRed
)
}) { onNavigateHistory() },
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 Comp 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") },
)

View File

@ -1,21 +1,10 @@
package com.mob.utsmyanmar.ui.dashboard
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.annotation.RequiresApi
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.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
@ -26,34 +15,15 @@ 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.LazyListScope
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.animation.core.tween
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
@ -63,30 +33,21 @@ 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.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
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.platform.LocalContext
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.foundation.Image
import coil3.compose.AsyncImage
import com.mob.utsmyanmar.R
import com.mob.utsmyanmar.ui.components.appbar.AppBar
@ -97,9 +58,7 @@ 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
@RequiresApi(Build.VERSION_CODES.N)
@Composable
@ -116,7 +75,6 @@ fun DashboardScreen2(
deviceInfo: DeviceInfoUiState = DeviceInfoUiState(),
onClickLastSync: () -> Unit = {},
) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
val mainHandler = remember { Handler(Looper.getMainLooper()) }
@ -126,12 +84,9 @@ fun DashboardScreen2(
var dialogMessage by remember { mutableStateOf("") }
var reversalEnabled by remember {
mutableStateOf(
runCatching { SystemParamsOperation.getInstance().isReversalOn }.getOrDefault(
false
)
runCatching { SystemParamsOperation.getInstance().isReversalOn }.getOrDefault(false)
)
}
val isOnline by rememberIsOnline()
fun confirmationMessage(action: String) = "Do you want to start ${action.lowercase()}?"
@ -139,7 +94,6 @@ fun DashboardScreen2(
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
@ -151,36 +105,32 @@ fun DashboardScreen2(
if (showHostActionDialog) {
AlertDialog(
onDismissRequest = {
if (!isHostActionRunning) {
showHostActionDialog = false
}
}, title = {
if (!isHostActionRunning) showHostActionDialog = false
},
title = {
Text(
text = activeHostAction, color = Color.LegacyRed, fontWeight = FontWeight.Bold
text = activeHostAction,
color = Color.LegacyRed,
fontWeight = FontWeight.Bold
)
}, text = {
Text(text = dialogMessage)
}, confirmButton = {
TextButton(
onClick = {
},
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 {
@ -188,18 +138,12 @@ fun DashboardScreen2(
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
@ -214,25 +158,28 @@ fun DashboardScreen2(
isHostActionRunning -> "Processing"
dialogMessage == confirmationMessage(activeHostAction) -> "Start"
else -> "Close"
}, color = Color.LegacyRed
},
color = Color.LegacyRed
)
}
}, dismissButton = {
},
dismissButton = {
if (!isHostActionRunning) {
TextButton(
onClick = {
TextButton(onClick = {
showHostActionDialog = false
dialogMessage = confirmationMessage(activeHostAction)
}) {
Text(text = "Cancel", color = Color.Gray)
}
}
}, containerColor = Color.White
},
containerColor = Color.White
)
}
ModalNavigationDrawer(
drawerState = drawerState, drawerContent = {
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
modifier = Modifier.fillMaxWidth(0.78f),
drawerContainerColor = Color.White,
@ -249,7 +196,8 @@ fun DashboardScreen2(
modifier = Modifier
.size(80.dp)
.clip(CircleShape)
.background(Color.White), contentAlignment = Alignment.Center
.background(Color.White),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.logo_mob),
@ -260,7 +208,6 @@ fun DashboardScreen2(
contentScale = ContentScale.Fit
)
}
Column {
Text(
text = "MOB Merchant",
@ -398,12 +345,12 @@ fun DashboardScreen2(
},
trailingIcon = {},
)
}
}) {
//body start
}
) {
Scaffold(
containerColor = Color.IvoryBeige, topBar = {
containerColor = Color.IvoryBeige,
topBar = {
AppBar(
title = "Dashboard",
icon = Icons.Default.Menu,
@ -427,8 +374,9 @@ fun DashboardScreen2(
}
}
)
}) { paddingValues ->
LazyColumn(
}
) { paddingValues ->
androidx.compose.foundation.lazy.LazyColumn(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
@ -460,9 +408,7 @@ fun DashboardScreen2(
onNavigateHistory = onNavigateHistory,
onNavigateAction = onNavigateAction
),
modifier = Modifier
.fillMaxWidth()
// .height(250.dp)
modifier = Modifier.fillMaxWidth()
)
}
item { Spacer(Modifier.height(8.dp)) }
@ -472,595 +418,6 @@ fun DashboardScreen2(
}
}
@Composable
private fun AdvertisingArea(
modifier: Modifier = Modifier,
carouselUrls: List<String> = emptyList(),
) {
val imageModels: List<Any> = carouselUrls.ifEmpty {
listOf(R.drawable.sunmi1, R.drawable.sunmi2, R.drawable.sunmi3)
}
val pageState = rememberPagerState(pageCount = { imageModels.size })
LaunchedEffect(pageState) {
while (true) {
delay(10000.milliseconds)
val nextPage = (pageState.currentPage + 1) % imageModels.size
pageState.animateScrollToPage(
page = nextPage, animationSpec = tween(durationMillis = 700)
)
}
}
Card(
modifier = modifier,
shape = RoundedCornerShape(0.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
) {
HorizontalPager(
state = pageState, modifier = Modifier.fillMaxSize()
) { page ->
AsyncImage(
model = imageModels[page],
contentDescription = "ads images",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
}
}
private fun formatRelativeTime(timestamp: Long?, now: Long): String {
if (timestamp == null) return "Never"
val diff = now - timestamp
return when {
diff < 60_000L -> "Just now"
diff < 3_600_000L -> "${diff / 60_000L} mins ago"
diff < 86_400_000L -> "${diff / 3_600_000L} hours ago"
else -> "${diff / 86_400_000L} days ago"
}
}
@Composable
private fun SummaryCard(
onClickLastSync: () -> Unit,
lastSyncTime: Long?,
totalSaleCount: Int,
totalSaleAmount: Long
) {
var now by remember { mutableLongStateOf(System.currentTimeMillis()) }
LaunchedEffect(Unit) {
while (true) {
delay(30_000.milliseconds)
now = System.currentTimeMillis()
}
}
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 = "Total Sale",
value = "MMK ${formatAmount(totalSaleAmount)}",
subtitle = "$totalSaleCount 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", style = MaterialTheme.typography.bodySmall
)
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
onClickLastSync()
},
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Default.Sync,
contentDescription = null,
tint = Color.CrimsonRed,
modifier = Modifier
.size(22.dp)
.rotate(rotation)
)
}
}
Spacer(Modifier.height(6.dp))
Text(
text = formatRelativeTime(lastSyncTime, now),
style = MaterialTheme.typography.titleMedium,
color = Color.LegacyRed
)
}
}
}
}
@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, style = MaterialTheme.typography.bodySmall)
Text(
value, style = MaterialTheme.typography.titleSmall, color = Color.LegacyRed
)
Text(subtitle, style = MaterialTheme.typography.bodySmall)
}
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,
onNavigateHistory: () -> 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
)
}) { onNavigateHistory() },
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
.fillMaxWidth()
// .height(236.dp)
) { 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)
) {
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,
style = MaterialTheme.typography.bodySmall,
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)
)
}
}
}
}
}
private fun LazyListScope.recentTransactionsItems(transactions: List<TrnxRecord>) {
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Recent Transactions",
style = MaterialTheme.typography.titleSmall
)
}
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp),
color = Color.Gray.copy(alpha = 0.2f)
)
}
if (transactions.isEmpty()) {
item {
Text(
text = "No recent transactions",
color = Color.Gray,
style = MaterialTheme.typography.bodyMedium,
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,
style = MaterialTheme.typography.bodyLarge
)
}
Spacer(Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text =record.typeLabel,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
color = Color.Black
)
Text(
text = record.maskedCard,
style = MaterialTheme.typography.labelSmall,
color = Color.Gray
)
}
Column(horizontalAlignment = Alignment.End) {
Text(
text = record.amountDisplay,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold),
color = if (record.isVoided) Color.Gray else Color.LegacyRed
)
Text(
text = record.dateTime,
style = MaterialTheme.typography.labelSmall,
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
),
)
@RequiresApi(Build.VERSION_CODES.N)
@P3Preview
@P2Preview
@ -1068,91 +425,3 @@ private val previewTransactions = listOf(
fun PreviewDashboardScreen2() {
DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions))
}
@Preview
@Composable
fun PreviewItem() {
Item(
onClick = {},
title = "title",
subTitle = "sub-title"
)
}
@RequiresApi(Build.VERSION_CODES.N)
@Composable
private fun rememberIsOnline() = run {
val context = LocalContext.current
val cm = remember(context) {
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
produceState(
initialValue = cm.activeNetwork
?.let { cm.getNetworkCapabilities(it)?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) }
?: false
) {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { value = true }
override fun onLost(network: Network) { value = false }
}
cm.registerDefaultNetworkCallback(callback)
awaitDispose { cm.unregisterNetworkCallback(callback) }
}
}
@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

@ -0,0 +1,42 @@
package com.mob.utsmyanmar.ui.dashboard
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
fun formatRelativeTime(timestamp: Long?, now: Long): String {
if (timestamp == null) return "Never"
val diff = now - timestamp
return when {
diff < 60_000L -> "Just now"
diff < 3_600_000L -> "${diff / 60_000L} mins ago"
diff < 86_400_000L -> "${diff / 3_600_000L} hours ago"
else -> "${diff / 86_400_000L} days ago"
}
}
@Composable
fun rememberIsOnline(): State<Boolean> = run {
val context = LocalContext.current
val cm = remember(context) {
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
produceState(
initialValue = cm.activeNetwork
?.let { cm.getNetworkCapabilities(it)?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) }
?: false
) {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { value = true }
override fun onLost(network: Network) { value = false }
}
cm.registerDefaultNetworkCallback(callback)
awaitDispose { cm.unregisterNetworkCallback(callback) }
}
}