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 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.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.annotation.RequiresApi 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.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.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons 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.Menu
import androidx.compose.material.icons.filled.Notifications 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.material.icons.filled.Sync
import androidx.compose.animation.core.tween
import androidx.compose.material3.AlertDialog 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.DrawerValue
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -63,30 +33,21 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.VerticalDivider
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.foundation.Image
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.mob.utsmyanmar.R import com.mob.utsmyanmar.R
import com.mob.utsmyanmar.ui.components.appbar.AppBar 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.EchoTestProcess
import com.utsmyanmar.paylibs.sign_on.SignOnListener import com.utsmyanmar.paylibs.sign_on.SignOnListener
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
@Composable @Composable
@ -116,7 +75,6 @@ fun DashboardScreen2(
deviceInfo: DeviceInfoUiState = DeviceInfoUiState(), deviceInfo: DeviceInfoUiState = DeviceInfoUiState(),
onClickLastSync: () -> Unit = {}, onClickLastSync: () -> Unit = {},
) { ) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val mainHandler = remember { Handler(Looper.getMainLooper()) } val mainHandler = remember { Handler(Looper.getMainLooper()) }
@ -126,12 +84,9 @@ fun DashboardScreen2(
var dialogMessage by remember { mutableStateOf("") } var dialogMessage by remember { mutableStateOf("") }
var reversalEnabled by remember { var reversalEnabled by remember {
mutableStateOf( mutableStateOf(
runCatching { SystemParamsOperation.getInstance().isReversalOn }.getOrDefault( runCatching { SystemParamsOperation.getInstance().isReversalOn }.getOrDefault(false)
false
)
) )
} }
val isOnline by rememberIsOnline() val isOnline by rememberIsOnline()
fun confirmationMessage(action: String) = "Do you want to start ${action.lowercase()}?" fun confirmationMessage(action: String) = "Do you want to start ${action.lowercase()}?"
@ -139,7 +94,6 @@ fun DashboardScreen2(
fun successMessage(action: String) = "$action success." fun successMessage(action: String) = "$action success."
fun failureMessage(action: String, resultCode: Int?) = fun failureMessage(action: String, resultCode: Int?) =
"$action failed. Response code: ${resultCode ?: -1}" "$action failed. Response code: ${resultCode ?: -1}"
fun networkFailureMessage(action: String) = "Network error during $action." fun networkFailureMessage(action: String) = "Network error during $action."
fun openHostActionDialog(action: String) { fun openHostActionDialog(action: String) {
activeHostAction = action activeHostAction = action
@ -151,36 +105,32 @@ fun DashboardScreen2(
if (showHostActionDialog) { if (showHostActionDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { onDismissRequest = {
if (!isHostActionRunning) { if (!isHostActionRunning) showHostActionDialog = false
showHostActionDialog = false },
} title = {
}, title = {
Text( Text(
text = activeHostAction, color = Color.LegacyRed, fontWeight = FontWeight.Bold text = activeHostAction,
color = Color.LegacyRed,
fontWeight = FontWeight.Bold
) )
}, text = { },
Text(text = dialogMessage) text = { Text(text = dialogMessage) },
}, confirmButton = { confirmButton = {
TextButton( TextButton(onClick = {
onClick = {
if (isHostActionRunning) return@TextButton if (isHostActionRunning) return@TextButton
if (dialogMessage != confirmationMessage(activeHostAction)) { if (dialogMessage != confirmationMessage(activeHostAction)) {
showHostActionDialog = false showHostActionDialog = false
dialogMessage = confirmationMessage(activeHostAction) dialogMessage = confirmationMessage(activeHostAction)
return@TextButton return@TextButton
} }
isHostActionRunning = true isHostActionRunning = true
dialogMessage = processingMessage(activeHostAction) dialogMessage = processingMessage(activeHostAction)
val signOnProcess = EchoTestProcess.getInstance() val signOnProcess = EchoTestProcess.getInstance()
val request = when (activeHostAction) { val request = when (activeHostAction) {
"Echo Test" -> signOnProcess.enqueue(false) "Echo Test" -> signOnProcess.enqueue(false)
"Log-Off" -> signOnProcess.enqueueLogOff() "Log-Off" -> signOnProcess.enqueueLogOff()
else -> signOnProcess.enqueueLogOn() else -> signOnProcess.enqueueLogOn()
} }
request.startSignOn(object : SignOnListener { request.startSignOn(object : SignOnListener {
override fun onSuccessSignOn() { override fun onSuccessSignOn() {
mainHandler.post { mainHandler.post {
@ -188,18 +138,12 @@ fun DashboardScreen2(
dialogMessage = successMessage(activeHostAction) dialogMessage = successMessage(activeHostAction)
} }
} }
override fun onFailureSignOn(resultCode: Int?) { override fun onFailureSignOn(resultCode: Int?) {
mainHandler.post {
isHostActionRunning = false
mainHandler.post { mainHandler.post {
isHostActionRunning = false isHostActionRunning = false
dialogMessage = failureMessage(activeHostAction, resultCode) dialogMessage = failureMessage(activeHostAction, resultCode)
} }
} }
}
override fun onNetworkFailSignOn(message: String?) { override fun onNetworkFailSignOn(message: String?) {
mainHandler.post { mainHandler.post {
isHostActionRunning = false isHostActionRunning = false
@ -214,25 +158,28 @@ fun DashboardScreen2(
isHostActionRunning -> "Processing" isHostActionRunning -> "Processing"
dialogMessage == confirmationMessage(activeHostAction) -> "Start" dialogMessage == confirmationMessage(activeHostAction) -> "Start"
else -> "Close" else -> "Close"
}, color = Color.LegacyRed },
color = Color.LegacyRed
) )
} }
}, dismissButton = { },
dismissButton = {
if (!isHostActionRunning) { if (!isHostActionRunning) {
TextButton( TextButton(onClick = {
onClick = {
showHostActionDialog = false showHostActionDialog = false
dialogMessage = confirmationMessage(activeHostAction) dialogMessage = confirmationMessage(activeHostAction)
}) { }) {
Text(text = "Cancel", color = Color.Gray) Text(text = "Cancel", color = Color.Gray)
} }
} }
}, containerColor = Color.White },
containerColor = Color.White
) )
} }
ModalNavigationDrawer( ModalNavigationDrawer(
drawerState = drawerState, drawerContent = { drawerState = drawerState,
drawerContent = {
ModalDrawerSheet( ModalDrawerSheet(
modifier = Modifier.fillMaxWidth(0.78f), modifier = Modifier.fillMaxWidth(0.78f),
drawerContainerColor = Color.White, drawerContainerColor = Color.White,
@ -249,7 +196,8 @@ fun DashboardScreen2(
modifier = Modifier modifier = Modifier
.size(80.dp) .size(80.dp)
.clip(CircleShape) .clip(CircleShape)
.background(Color.White), contentAlignment = Alignment.Center .background(Color.White),
contentAlignment = Alignment.Center
) { ) {
Image( Image(
painter = painterResource(R.drawable.logo_mob), painter = painterResource(R.drawable.logo_mob),
@ -260,7 +208,6 @@ fun DashboardScreen2(
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
} }
Column { Column {
Text( Text(
text = "MOB Merchant", text = "MOB Merchant",
@ -398,12 +345,12 @@ fun DashboardScreen2(
}, },
trailingIcon = {}, trailingIcon = {},
) )
} }
}) { }
//body start ) {
Scaffold( Scaffold(
containerColor = Color.IvoryBeige, topBar = { containerColor = Color.IvoryBeige,
topBar = {
AppBar( AppBar(
title = "Dashboard", title = "Dashboard",
icon = Icons.Default.Menu, icon = Icons.Default.Menu,
@ -427,8 +374,9 @@ fun DashboardScreen2(
} }
} }
) )
}) { paddingValues -> }
LazyColumn( ) { paddingValues ->
androidx.compose.foundation.lazy.LazyColumn(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .padding(paddingValues)
.fillMaxSize() .fillMaxSize()
@ -460,9 +408,7 @@ fun DashboardScreen2(
onNavigateHistory = onNavigateHistory, onNavigateHistory = onNavigateHistory,
onNavigateAction = onNavigateAction onNavigateAction = onNavigateAction
), ),
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
// .height(250.dp)
) )
} }
item { Spacer(Modifier.height(8.dp)) } 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) @RequiresApi(Build.VERSION_CODES.N)
@P3Preview @P3Preview
@P2Preview @P2Preview
@ -1068,91 +425,3 @@ private val previewTransactions = listOf(
fun PreviewDashboardScreen2() { fun PreviewDashboardScreen2() {
DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions)) 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) }
}
}