diff --git a/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt b/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt index bba17b2..c4965c0 100644 --- a/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt +++ b/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt @@ -3,10 +3,11 @@ package com.mob.utsmyanmar import android.os.Bundle import android.view.WindowManager import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent 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 com.mob.utsmyanmar.ui.navigation.AppNavGraph import com.mob.utsmyanmar.ui.theme.MOBPOSTheme @@ -17,7 +18,10 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { // installSplashScreen() 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) setContent { MOBPOSTheme { diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt index 5ae5da1..c39f237 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt @@ -17,6 +17,8 @@ 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.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape @@ -33,7 +35,6 @@ import androidx.compose.material.icons.filled.Menu 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.Undo import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -45,8 +46,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.NavigationDrawerItem -import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text 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.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 @@ -83,6 +83,7 @@ import com.utsmyanmar.paylibs.sign_on.EchoTestProcess import com.utsmyanmar.paylibs.sign_on.SignOnListener import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds @Composable fun DashboardScreen2( @@ -92,6 +93,7 @@ fun DashboardScreen2( onNavigateVersion: () -> Unit = {}, onNavigateFunctions: () -> Unit = {}, onNavigateAction: (String) -> Unit = {}, + dashboardUiState: DashboardUiState = DashboardUiState(), deviceInfoViewModel: DeviceInfoViewModel = viewModel() ) { val deviceInfo by deviceInfoViewModel.uiState.collectAsState() @@ -382,10 +384,10 @@ fun DashboardScreen2( ) { SummaryCard() } - //bottom section + //pager section Box( modifier = Modifier - .weight(1.5f) + .weight(1.3f) .fillMaxWidth(), ) { MenuPager( @@ -398,37 +400,18 @@ fun DashboardScreen2( 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 private fun AdvertisingArea() { @@ -441,7 +424,7 @@ private fun AdvertisingArea() { LaunchedEffect(pageState) { while (true) { - delay(10000) + delay(10000.milliseconds) val nextPage = (pageState.currentPage + 1) % imageArray.size 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("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.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("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") }, @@ -717,16 +700,125 @@ private fun MenuCard( } } +@Composable +private fun RecentTransactions( + transactions: List, + modifier: Modifier = Modifier +) { + Column(modifier = modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Recent Transactions", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp + ) + } + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = Color.Gray.copy(alpha = 0.2f) + ) + LazyColumn(modifier = Modifier.fillMaxSize()) { + if (transactions.isEmpty()) { + item { + Text( + text = "No recent transactions", + color = Color.Gray, + fontSize = 13.sp, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp) + ) + } + } else { + items(items = transactions, key = { it.pid }) { record -> + TrnxRow(record = record) + } + } + } + } +} + +@Composable +private fun TrnxRow(record: TrnxRecord) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp) + .clip(RoundedCornerShape(10.dp)) + .background(Color.White) + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(36.dp) + .background(Color.LegacyRed.copy(alpha = 0.1f), CircleShape), + contentAlignment = Alignment.Center + ) { + Text( + text = record.typeLabel.take(1), + color = Color.LegacyRed, + fontWeight = FontWeight.Bold, + fontSize = 15.sp + ) + } + + Spacer(Modifier.width(10.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = if (record.isVoided) "${record.typeLabel} (Voided)" else record.typeLabel, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + color = if (record.isVoided) Color.Gray else Color.Black + ) + Text( + text = record.maskedCard, + fontSize = 11.sp, + color = Color.Gray + ) + } + + Column(horizontalAlignment = Alignment.End) { + Text( + text = record.amountDisplay, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = if (record.isVoided) Color.Gray else Color.LegacyRed + ) + Text( + text = record.dateTime, + fontSize = 10.sp, + color = Color.Gray + ) + } + } +} + +private val previewTransactions = listOf( + TrnxRecord(1L, "Sale", "MMK 10,000", "**** 1234", "06/10 14:32", isVoided = false, isApproved = true), + TrnxRecord(2L, "Void", "MMK 5,500", "**** 5678", "06/10 13:10", isVoided = true, isApproved = false), + TrnxRecord(3L, "Refund", "MMK 2,000", "**** 9012", "06/09 09:45", isVoided = false, isApproved = true), + TrnxRecord(4L, "Sale", "MMK 30,000", "**** 3456", "06/09 08:00", isVoided = false, isApproved = true), + TrnxRecord(5L, "Settlement", "MMK 0", "----", "06/08 18:00", isVoided = false, isApproved = true), +) + @P2Preview @Composable fun PreviewDashboardScreen2() { - DashboardScreen2() + DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions)) } @P3Preview @Composable fun PreviewDashboardScreen3() { - DashboardScreen2() + DashboardScreen2(dashboardUiState = DashboardUiState(previewTransactions)) } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt new file mode 100644 index 0000000..41134f1 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt @@ -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 = emptyList() +) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt new file mode 100644 index 0000000..05b0d45 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt @@ -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 = _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)}" +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt index 8e0a96b..553c204 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt @@ -4,8 +4,10 @@ import android.annotation.SuppressLint import android.net.Uri import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.NavType @@ -16,6 +18,7 @@ import com.mob.utsmyanmar.model.ProcessCode import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel import com.mob.utsmyanmar.ui.dashboard.DashboardScreen2 +import com.mob.utsmyanmar.ui.dashboard.DashboardViewModel import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel import com.mob.utsmyanmar.ui.functions.FunctionsScreen import com.mob.utsmyanmar.ui.input_amount.AmountRoute @@ -74,8 +77,11 @@ fun AppNavGraph( } 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( + dashboardUiState = dashboardUiState, onNavigateAmount = { action -> if(action == "Sale"){ sharedViewModel.transactionsType.value = TransactionsType.SALE; diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt b/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt index a450eaa..a568746 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt @@ -13,7 +13,7 @@ annotation class P2Preview @Preview( name = "P3", - device = "spec:width=720px,height=1600px,dpi=270", + device = "spec:width=427dp,height=949dp,dpi=270", showBackground = true, showSystemUi = true )