dashboard Ui changed

This commit is contained in:
moon 2026-06-10 22:52:27 +06:30
parent 36b9d83a40
commit c32c93338e
6 changed files with 228 additions and 40 deletions

View File

@ -3,10 +3,11 @@ package com.mob.utsmyanmar
import android.os.Bundle import android.os.Bundle
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.mob.utsmyanmar.ui.navigation.AppNavGraph import com.mob.utsmyanmar.ui.navigation.AppNavGraph
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
@ -17,7 +18,10 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// installSplashScreen() // installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// enableEdgeToEdge() enableEdgeToEdge()
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.navigationBars())
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
setContent { setContent {
MOBPOSTheme { MOBPOSTheme {

View File

@ -17,6 +17,8 @@ import androidx.compose.foundation.layout.height
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.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.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -33,7 +35,6 @@ import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Replay import androidx.compose.material.icons.filled.Replay
import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material.icons.filled.Sync import androidx.compose.material.icons.filled.Sync
import androidx.compose.material.icons.filled.Undo
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@ -45,8 +46,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -67,6 +66,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -83,6 +83,7 @@ import com.utsmyanmar.paylibs.sign_on.EchoTestProcess
import com.utsmyanmar.paylibs.sign_on.SignOnListener import com.utsmyanmar.paylibs.sign_on.SignOnListener
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
@Composable @Composable
fun DashboardScreen2( fun DashboardScreen2(
@ -92,6 +93,7 @@ fun DashboardScreen2(
onNavigateVersion: () -> Unit = {}, onNavigateVersion: () -> Unit = {},
onNavigateFunctions: () -> Unit = {}, onNavigateFunctions: () -> Unit = {},
onNavigateAction: (String) -> Unit = {}, onNavigateAction: (String) -> Unit = {},
dashboardUiState: DashboardUiState = DashboardUiState(),
deviceInfoViewModel: DeviceInfoViewModel = viewModel() deviceInfoViewModel: DeviceInfoViewModel = viewModel()
) { ) {
val deviceInfo by deviceInfoViewModel.uiState.collectAsState() val deviceInfo by deviceInfoViewModel.uiState.collectAsState()
@ -382,10 +384,10 @@ fun DashboardScreen2(
) { ) {
SummaryCard() SummaryCard()
} }
//bottom section //pager section
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1.5f) .weight(1.3f)
.fillMaxWidth(), .fillMaxWidth(),
) { ) {
MenuPager( MenuPager(
@ -398,37 +400,18 @@ fun DashboardScreen2(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
//transactions section
RecentTransactions(
transactions = dashboardUiState.recentTransactions,
modifier = Modifier
.weight(1.2f)
.fillMaxWidth()
)
} }
} }
} }
} }
@Composable
private fun DrawerItem(
title: String, icon: ImageVector, onClick: () -> Unit
) {
NavigationDrawerItem(
label = {
Text(
text = title, fontWeight = FontWeight.Medium
)
},
selected = false,
onClick = onClick,
icon = {
Icon(
imageVector = icon, contentDescription = title
)
},
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
colors = NavigationDrawerItemDefaults.colors(
unselectedContainerColor = androidx.compose.ui.graphics.Color.Transparent,
unselectedIconColor = Color.LegacyRed,
unselectedTextColor = Color.Black
)
)
}
@Composable @Composable
private fun AdvertisingArea() { private fun AdvertisingArea() {
@ -441,7 +424,7 @@ private fun AdvertisingArea() {
LaunchedEffect(pageState) { LaunchedEffect(pageState) {
while (true) { while (true) {
delay(10000) delay(10000.milliseconds)
val nextPage = (pageState.currentPage + 1) % imageArray.size val nextPage = (pageState.currentPage + 1) % imageArray.size
pageState.animateScrollToPage( pageState.animateScrollToPage(
@ -590,7 +573,7 @@ private fun buildMenuItems(
DashboardMenuItem("History", { Icon(painterResource(R.drawable.ic_history), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { }, 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("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("Settlement", { Icon(painterResource(R.drawable.ic_settlement), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSettlement() },
DashboardMenuItem("Void", { Icon(Icons.Default.Undo, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Void") }, 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("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", { 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 Void", { Icon(Icons.Default.LockOpen, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Void") },
@ -717,16 +700,125 @@ private fun MenuCard(
} }
} }
@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),
)
@P2Preview @P2Preview
@Composable @Composable
fun PreviewDashboardScreen2() { fun PreviewDashboardScreen2() {
DashboardScreen2() DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions))
} }
@P3Preview @P3Preview
@Composable @Composable
fun PreviewDashboardScreen3() { fun PreviewDashboardScreen3() {
DashboardScreen2() DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions))
} }

View File

@ -0,0 +1,15 @@
package com.mob.utsmyanmar.ui.dashboard
data class TrnxRecord(
val pid: Long,
val typeLabel: String,
val amountDisplay: String,
val maskedCard: String,
val dateTime: String,
val isVoided: Boolean,
val isApproved: Boolean
)
data class DashboardUiState(
val recentTransactions: List<TrnxRecord> = emptyList()
)

View File

@ -0,0 +1,71 @@
package com.mob.utsmyanmar.ui.dashboard
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope
import com.utsmyanmar.baselib.repo.Repository
import com.utsmyanmar.paylibs.model.PayDetail
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class DashboardViewModel @Inject constructor(
private val repository: Repository
) : ViewModel() {
private val _uiState = MutableStateFlow(DashboardUiState())
val uiState: StateFlow<DashboardUiState> = _uiState.asStateFlow()
init {
viewModelScope.launch {
repository.getAllTrans().asFlow().collect { payDetails ->
_uiState.update {
it.copy(
recentTransactions = payDetails.take(10).map { pd -> pd.toRecord() }
)
}
}
}
}
}
private fun PayDetail.toRecord(): TrnxRecord {
val type = transType?.takeIf { it.isNotBlank() }?.let { formatTypeLabel(it) } ?: "Transaction"
val amount = "MMK %,d".format(amount)
val card = CardNo?.takeIf { it.length >= 4 }?.let { "**** ${it.takeLast(4)}" } ?: "----"
val dt = buildDateTime(TradeDate, TradeTime)
val approved = tradeAnswerCode == "00" || approvalCode?.isNotBlank() == true
return TrnxRecord(
pid = PID ?: 0L,
typeLabel = type,
amountDisplay = amount,
maskedCard = card,
dateTime = dt,
isVoided = isCanceled,
isApproved = approved
)
}
private fun formatTypeLabel(raw: String): String = when {
raw.contains("SALE", ignoreCase = true) -> "Sale"
raw.contains("VOID", ignoreCase = true) -> "Void"
raw.contains("REFUND", ignoreCase = true) -> "Refund"
raw.contains("SETTLEMENT", ignoreCase = true) -> "Settlement"
raw.contains("PRE", ignoreCase = true) -> "Pre-Auth"
raw.contains("CASH", ignoreCase = true) -> "Cash Out"
raw.contains("WAVE", ignoreCase = true) -> "Wave Pay"
else -> raw.lowercase().replaceFirstChar { it.uppercaseChar() }
}
// TradeDate = "MMDD", TradeTime = "HHmmss" (ISO 8583 fields 13 & 12)
private fun buildDateTime(date: String?, time: String?): String {
val d = date?.padStart(4, '0')?.takeIf { it.length >= 4 } ?: return "--/-- --:--"
val t = (time ?: "").padStart(6, '0')
return "${d.take(2)}/${d.drop(2).take(2)} ${t.take(2)}:${t.drop(2).take(2)}"
}

View File

@ -4,8 +4,10 @@ import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
@ -16,6 +18,7 @@ import com.mob.utsmyanmar.model.ProcessCode
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen2 import com.mob.utsmyanmar.ui.dashboard.DashboardScreen2
import com.mob.utsmyanmar.ui.dashboard.DashboardViewModel
import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
import com.mob.utsmyanmar.ui.functions.FunctionsScreen import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.input_amount.AmountRoute import com.mob.utsmyanmar.ui.input_amount.AmountRoute
@ -74,8 +77,11 @@ fun AppNavGraph(
} }
composable(Routes.Dashboard.route) { composable(Routes.Dashboard.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity); val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val dashboardViewModel: DashboardViewModel = hiltViewModel()
val dashboardUiState by dashboardViewModel.uiState.collectAsStateWithLifecycle()
DashboardScreen2( DashboardScreen2(
dashboardUiState = dashboardUiState,
onNavigateAmount = { action -> onNavigateAmount = { action ->
if(action == "Sale"){ if(action == "Sale"){
sharedViewModel.transactionsType.value = TransactionsType.SALE; sharedViewModel.transactionsType.value = TransactionsType.SALE;

View File

@ -13,7 +13,7 @@ annotation class P2Preview
@Preview( @Preview(
name = "P3", name = "P3",
device = "spec:width=720px,height=1600px,dpi=270", device = "spec:width=427dp,height=949dp,dpi=270",
showBackground = true, showBackground = true,
showSystemUi = true showSystemUi = true
) )