diff --git a/app/src/main/java/com/mob/utsmyanmar/AGENTS.md b/app/src/main/java/com/mob/utsmyanmar/AGENTS.md index c0ef129..537e01e 100644 --- a/app/src/main/java/com/mob/utsmyanmar/AGENTS.md +++ b/app/src/main/java/com/mob/utsmyanmar/AGENTS.md @@ -1,3 +1,9 @@ ## Working agreements -- use Scaffold and AppBar() in every main Screen \ No newline at end of file +- use Scaffold and AppBar() in every main Screen +- use MVVM design pattern +- separate UiState and ViewModel for screen for better performance and smooth + +### Re-usable Screens +- PasswordInput.kt +- InputAmountScreen.kt \ No newline at end of file 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/components/NumericEntryScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/components/NumericEntryScreen.kt index 93e310d..5fc4ee0 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/components/NumericEntryScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/components/NumericEntryScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Backspace -import androidx.compose.material.icons.rounded.Backspace import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card @@ -26,9 +25,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.mob.utsmyanmar.ui.theme.Color @@ -181,62 +178,3 @@ fun NumericEntryScreen( } } } - -@Composable -private fun NumericKeypad( - keys: List>, - onKeyClick: (String) -> Unit -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - keys.forEach { row -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp) - ) { - row.forEach { key -> - KeypadButton( - text = key, - modifier = Modifier.weight(1f), - onClick = { onKeyClick(key) } - ) - } - } - } - } -} - -@Composable -private fun KeypadButton( - text: String, - modifier: Modifier = Modifier, - onClick: () -> Unit -) { - val enabled = text.isNotBlank() - - Box( - modifier = modifier - .height(66.dp) - .shadow( - elevation = 2.dp, - shape = RoundedCornerShape(8.dp), - clip = false - ) - .background( - color = Color.White, - shape = RoundedCornerShape(8.dp) - ) - .clickable(enabled = enabled) { onClick() }, - contentAlignment = Alignment.Center - ) { - Text( - text = text, - color = if (enabled) Color.LegacyRed else Color.White, - fontSize = 24.sp, - fontWeight = FontWeight.Normal, - textAlign = TextAlign.Center - ) - } -} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/components/NumericKeypad.kt b/app/src/main/java/com/mob/utsmyanmar/ui/components/NumericKeypad.kt new file mode 100644 index 0000000..bc1e990 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/components/NumericKeypad.kt @@ -0,0 +1,104 @@ +package com.mob.utsmyanmar.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color as ComposeColor +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.theme.Color + +@Composable +fun NumericKeypad( + modifier: Modifier = Modifier, + onKeyClick: (String) -> Unit, + keys : List>, +) { +// val keys : List> = listOf( +// listOf("1", "2", "3"), +// listOf("4", "5", "6"), +// listOf("7", "8", "9"), +// listOf(".", "0", "00") +// ) + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + keys.forEach { row -> + Row( + modifier = Modifier.fillMaxWidth().weight(1f), + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + row.forEach { key -> + KeypadButton( + text = key, + modifier = Modifier.weight(1f).fillMaxHeight(), + onClick = { onKeyClick(key) } + ) + } + } + } + } +} + +@Composable +fun KeypadButton( + text: String, + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + val enabled = text.isNotBlank() + + Box( + modifier = modifier + .then( + if (enabled) Modifier.shadow(elevation = 2.dp, shape = RoundedCornerShape(8.dp), clip = false) + else Modifier + ) + .background( + color = if (enabled) Color.White else ComposeColor.Transparent, + shape = RoundedCornerShape(8.dp) + ) + .clickable(enabled = enabled) { onClick() }, + contentAlignment = Alignment.Center + ) { + if (enabled) { + Text( + text = text, + color = Color.LegacyRed, + fontSize = 24.sp, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center + ) + } + } +} + +@Preview +@Composable +fun PreviewNumericKeypad(){ + val keys : List> = listOf( + listOf("1", "2", "3"), + listOf("4", "5", "6"), + listOf("7", "8", "9"), + listOf(".", "0", "00") + ) + NumericKeypad( + keys = keys, + onKeyClick = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt b/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt index af064ef..f2b4f1d 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt @@ -1,5 +1,6 @@ package com.mob.utsmyanmar.ui.components.appbar +import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -20,6 +21,7 @@ fun AppBar( title: String, icon: ImageVector? = null, onIconClick: (() -> Unit)? = null, + actions: @Composable RowScope.() -> Unit = {} ) { CenterAlignedTopAppBar( title = { @@ -44,6 +46,8 @@ fun AppBar( } }, + actions = actions, + colors = TopAppBarDefaults.topAppBarColors( containerColor = Color.LegacyRed ) 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 b250bc4..085b117 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 @@ -6,7 +6,20 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +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.offset +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.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 @@ -15,6 +28,42 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.material.icons.filled.AccountBalanceWallet +import androidx.compose.material.icons.filled.BarChart +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.CreditCard +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.LockOpen +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.Replay +import androidx.compose.material.icons.filled.SwapHoriz +import androidx.compose.material.icons.filled.Sync +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.IconButton +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.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.VerticalDivider +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -23,7 +72,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel @@ -39,15 +90,18 @@ import com.utsmyanmar.paylibs.sign_on.SignOnListener import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds @Composable fun DashboardScreen2( onNavigateAmount: (String) -> Unit = {}, onNavigateSignOn: () -> Unit = {}, - onNavigateSeeMore: () -> Unit = {}, onNavigateSettlement: () -> Unit = {}, onNavigateVersion: () -> Unit = {}, onNavigateFunctions: () -> Unit = {}, + onNavigateAction: (String) -> Unit = {}, + onNavigateNotifications: () -> Unit = {}, + dashboardUiState: DashboardUiState = DashboardUiState(), deviceInfoViewModel: DeviceInfoViewModel = viewModel() ) { val deviceInfo by deviceInfoViewModel.uiState.collectAsState() @@ -217,19 +271,58 @@ fun DashboardScreen2( } Spacer(modifier = Modifier.height(10.dp)) + Text( + text = "Connection Settings", + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(horizontal = 16.dp) + ) + + Item( + title = "Echo Test", subTitle = "Test Connection Status", + onClick = { + scope.launch { drawerState.close() } + openHostActionDialog("Echo Test") + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_up_down_arrow), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) + Item( + title = "Log-On", subTitle = "Log on to System", + onClick = { + scope.launch { drawerState.close() } + openHostActionDialog("Log-On") + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_lock), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) + Item( + title = "Log-Off", subTitle = "Log off from System", + onClick = { + scope.launch { drawerState.close() } + openHostActionDialog("Log-Off") + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_cancel_circle), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) - DrawerItem("Log-On", Icons.Default.Dashboard) { - scope.launch { drawerState.close() } - openHostActionDialog("Log-On") - } - DrawerItem("Echo Test", Icons.Default.Sync) { - scope.launch { drawerState.close() } - openHostActionDialog("Echo Test") - } - DrawerItem("Log-Off", Icons.Default.Dashboard) { - scope.launch { drawerState.close() } - openHostActionDialog("Log-Off") - } Text( text = "System Management", fontWeight = FontWeight.Medium, @@ -257,6 +350,38 @@ fun DashboardScreen2( scope.launch { drawerState.close() } onNavigateVersion() } + Item( + title = "Functions", subTitle = "System Function Settings", + onClick = { + scope.launch { drawerState.close() } + onNavigateFunctions() + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_four_boxes), + contentDescription = "icon", + tint = Color.LegacyRed, + modifier = Modifier.size(16.dp) + ) + }, + trailingIcon = {}, + ) + Item( + title = "Version", subTitle = "View App Version Info", + onClick = { + scope.launch { drawerState.close() } + onNavigateVersion() + }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_version), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = {}, + ) + } }) { Scaffold( @@ -264,7 +389,26 @@ fun DashboardScreen2( AppBar( title = "Dashboard", icon = Icons.Default.Menu, - onIconClick = { scope.launch { drawerState.open() } }) + onIconClick = { scope.launch { drawerState.open() } }, + actions = { + IconButton(onClick = onNavigateNotifications) { + Box { + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = "Notifications", + tint = Color.White + ) + Box( + modifier = Modifier + .size(8.dp) + .background(Color.GoldenGlow, CircleShape) + .align(Alignment.TopEnd) + .offset(x = 2.dp, y = (-2).dp) + ) + } + } + } + ) }) { paddingValues -> Column( modifier = Modifier @@ -288,73 +432,34 @@ fun DashboardScreen2( ) { SummaryCard() } - //bottom section + //pager section Box( modifier = Modifier - .weight(1.5f) + .weight(1.3f) .fillMaxWidth(), ) { - MenuGrid( - onNavigateAmount = onNavigateAmount, - onNavigateSignOn = onNavigateSignOn, - onNavigateSeeMore = onNavigateSeeMore, - onNavigateSettlement = onNavigateSettlement + MenuPager( + items = buildMenuItems( + onNavigateAmount = onNavigateAmount, + onNavigateSignOn = onNavigateSignOn, + onNavigateSettlement = onNavigateSettlement, + onNavigateAction = onNavigateAction + ), + modifier = Modifier.fillMaxSize() ) } + //transactions section + RecentTransactions( + transactions = dashboardUiState.recentTransactions, + modifier = Modifier + .weight(1.2f) + .fillMaxWidth() + ) } } } } -@Composable -private fun DrawerItem( - title: String, - icon: ImageVector, - showSwitch: Boolean = false, // New: Flag to enable switch mode - isChecked: Boolean = false, // New: Switch state - onCheckedChange: (Boolean) -> Unit = {}, // New: Switch callback - onClick: () -> Unit -) { - NavigationDrawerItem( - label = { - Text( - text = title, - fontWeight = FontWeight.Medium - ) - }, - selected = false, - // If it's a switch item, clicking the whole row toggles the switch instead of navigating - onClick = { - if (showSwitch) { - onCheckedChange(!isChecked) - } else { - onClick() - } - }, - icon = { - Icon( - imageVector = icon, - contentDescription = title - ) - }, - - badge = { - if (showSwitch) { - Switch( - checked = isChecked, - onCheckedChange = onCheckedChange - ) - } - }, - 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() { @@ -367,7 +472,7 @@ private fun AdvertisingArea() { LaunchedEffect(pageState) { while (true) { - delay(10000) + delay(10000.milliseconds) val nextPage = (pageState.currentPage + 1) % imageArray.size pageState.animateScrollToPage( @@ -498,75 +603,93 @@ private fun IconCircle( } } +private class DashboardMenuItem( + val title: String, + val iconContent: @Composable () -> Unit, + val onClick: () -> Unit +) + @Composable -private fun MenuGrid( +private fun buildMenuItems( onNavigateAmount: (String) -> Unit, onNavigateSignOn: () -> Unit, - onNavigateSeeMore: () -> Unit, - onNavigateSettlement: () -> Unit + onNavigateSettlement: () -> Unit, + onNavigateAction: (String) -> Unit +): List = listOf( + DashboardMenuItem("Sale", { Icon(painterResource(R.drawable.ic_terminal), contentDescription = null, modifier = Modifier.size(40.dp), tint = Color.LegacyRed) }) { onNavigateAmount("Sale") }, + DashboardMenuItem("MMQR", { Image(painter = painterResource(R.drawable.ic_mmqr_logo), contentDescription = null, modifier = Modifier.height(48.dp)) }) { }, + DashboardMenuItem("History", { Icon(painterResource(R.drawable.ic_history), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { }, + DashboardMenuItem("Sign On", { Icon(painterResource(R.drawable.ic_sign_on), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSignOn() }, + DashboardMenuItem("Settlement", { Icon(painterResource(R.drawable.ic_settlement), contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateSettlement() }, + DashboardMenuItem("Void", { Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Void") }, + DashboardMenuItem("Refund", { Icon(Icons.Default.Replay, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Refund") }, + DashboardMenuItem("Pre-Auth", { Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth") }, + DashboardMenuItem("Pre-Auth Void", { Icon(Icons.Default.LockOpen, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Void") }, + DashboardMenuItem("Pre-Auth Complete", { Icon(Icons.Default.CreditCard, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Complete") }, + DashboardMenuItem("Pre-Auth Complete Void", { Icon(Icons.Default.SwapHoriz, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Pre-Auth Complete Void") }, + DashboardMenuItem("Cash Out", { Icon(Icons.Default.AccountBalanceWallet, contentDescription = null, modifier = Modifier.size(32.dp), tint = Color.LegacyRed) }) { onNavigateAction("Cash Out") }, +) + +@Composable +private fun MenuPager( + items: List, + modifier: Modifier = Modifier ) { + val pages = items.chunked(6) + val pagerState = rememberPagerState(pageCount = { pages.size }) + + Column(modifier = modifier) { + HorizontalPager( + state = pagerState, + modifier = Modifier.weight(1f) + ) { pageIndex -> + MenuPage(items = pages[pageIndex]) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + repeat(pages.size) { index -> + val selected = pagerState.currentPage == index + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + .size(if (selected) 8.dp else 6.dp) + .background( + color = if (selected) Color.LegacyRed else Color.Gray.copy(alpha = 0.4f), + shape = CircleShape + ) + ) + } + } + } +} + +@Composable +private fun MenuPage(items: List) { Column( verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(horizontal = 16.dp) ) { Spacer(Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { - MenuCard(title = "Sale", icon = { - Icon( - painterResource(R.drawable.ic_terminal), - contentDescription = "icon", - modifier = Modifier.size(40.dp), - tint = Color.LegacyRed - ) - }, modifier = Modifier.weight(1f), onClick = { onNavigateAmount("Sale") }) - MenuCard(title = "MMQR", icon = { - Image( - painter = painterResource(R.drawable.ic_mmqr_logo), - contentDescription = "mmqr image", - modifier = Modifier.height(48.dp) - ) - }, modifier = Modifier.weight(1f)) - MenuCard("History", icon = { - Icon( - painterResource(R.drawable.ic_history), - contentDescription = "icon", - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }, modifier = Modifier.weight(1f)) - } - - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { - MenuCard( - title = "Sign On", icon = { - Icon( - painterResource(R.drawable.ic_sign_on), - contentDescription = "icon", - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed + 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 ) - }, modifier = Modifier.weight(1f), onClick = onNavigateSignOn - ) - MenuCard( - title = "Settlement", icon = { - Icon( - painterResource(R.drawable.ic_settlement), - contentDescription = "icon", - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }, modifier = Modifier.weight(1f), onClick = onNavigateSettlement - ) - MenuCard( - title = "See More", icon = { - Icon( - painterResource(R.drawable.ic_see_more), - contentDescription = "icon", - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }, modifier = Modifier.weight(1f), onClick = onNavigateSeeMore - ) + } + repeat(3 - rowItems.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } } } } @@ -625,14 +748,191 @@ 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)) } + + +@Preview +@Composable +fun PreviewItem() { + Item( + onClick = {}, + title = "title", + subTitle = "sub-title" + ) +} + + +@Composable +fun Item( + onClick: () -> Unit, + title: String, + subTitle: String, + leadingIcon: (@Composable () -> Unit)? = null, + trailingIcon: (@Composable () -> Unit)? = null, +) { + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + modifier = Modifier.fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + ) { + // Square icon background + leadingIcon?.let { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(40.dp) + .clip(RoundedCornerShape(16.dp)) // change to RectangleShape for sharp corners + .background(Color.LegacyRed.copy(alpha = 0.1f)) + .padding(8.dp) + ) { + it() + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + Column( + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp) + ) { + Text(text = title) + Text( + text = subTitle, + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + } + + trailingIcon?.invoke() + } + } +} \ No newline at end of file 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/functions/FunctionsScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/functions/FunctionsScreen.kt index 3211b17..095401a 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/functions/FunctionsScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/functions/FunctionsScreen.kt @@ -1,13 +1,11 @@ package com.mob.utsmyanmar.ui.functions -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement 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 @@ -17,7 +15,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ChevronRight -import androidx.compose.material.icons.filled.OnDeviceTraining import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ElevatedButton import androidx.compose.material3.Icon @@ -30,15 +27,19 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mob.utsmyanmar.R import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.preview.P2Preview import com.mob.utsmyanmar.ui.preview.P3Preview import com.mob.utsmyanmar.ui.theme.Color -import com.mob.utsmyanmar.R +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation + @Composable fun FunctionsScreen( onBack: () -> Unit = {} ) { + val tmsAddress = SystemParamsOperation.getInstance().tmsAddress + Scaffold( containerColor = Color.IvoryBeige, topBar = { @@ -139,7 +140,7 @@ fun FunctionsScreen( FunctionButton( onClick = {}, title = "TMS Server Url", - subTitle = "Detail for bound hosts", + subTitle = tmsAddress, leadingIcon = { Icon( modifier = Modifier.size(24.dp), @@ -232,4 +233,4 @@ fun PreviewFunctionButton() { title = "title", subTitle = "sub-title" ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt index 84d7b87..66b6b93 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt @@ -1,20 +1,17 @@ package com.mob.utsmyanmar.ui.input_amount -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight 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.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.rounded.Backspace import androidx.compose.material.icons.rounded.Backspace import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -30,16 +27,21 @@ 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.shadow import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.components.NumericKeypad import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.preview.P2Preview import com.mob.utsmyanmar.ui.preview.P3Preview import com.mob.utsmyanmar.ui.theme.Color -import kotlin.collections.List + +private val amountKeys = listOf( + listOf("1", "2", "3"), + listOf("4", "5", "6"), + listOf("7", "8", "9"), + listOf(".", "0", "00") +) @Composable fun InputAmount( @@ -140,6 +142,7 @@ fun InputAmount( verticalArrangement = Arrangement.Bottom ){ NumericKeypad( + keys = amountKeys, modifier = Modifier.fillMaxSize(), onKeyClick = { value -> amount = appendAmountValue(amount, value) @@ -202,69 +205,6 @@ fun InputAmount( } } -@Composable -private fun NumericKeypad( - modifier: Modifier = Modifier, - onKeyClick: (String) -> Unit -) { - val keys : List> = listOf( - listOf("1", "2", "3"), - listOf("4", "5", "6"), - listOf("7", "8", "9"), - listOf(".", "0", "00") - ) - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - keys.forEach { row -> - Row( - modifier = Modifier.fillMaxWidth().weight(1f), - horizontalArrangement = Arrangement.spacedBy(6.dp) - ) { - row.forEach { key -> - KeypadButton( - text = key, - modifier = Modifier.weight(1f).fillMaxHeight(), - onClick = { onKeyClick(key) } - ) - } - } - } - } -} - -@Composable -private fun KeypadButton( - text: String, - modifier: Modifier = Modifier, - onClick: () -> Unit -) { - val enabled = text.isNotBlank() - - Box( - modifier = modifier - .shadow( - elevation = 2.dp, - shape = RoundedCornerShape(8.dp), - clip = false - ) - .background( - color = Color.White, - shape = RoundedCornerShape(8.dp) - ) - .clickable(enabled = enabled) { onClick() }, - contentAlignment = Alignment.Center - ) { - Text( - text = text, - color = if (enabled) Color.LegacyRed else Color.White, - fontSize = 24.sp, - fontWeight = FontWeight.Normal, - textAlign = TextAlign.Center - ) - } -} private fun appendAmountValue(current: String, value: String): String { if (value == ".") { 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 9ab79a9..f576a1e 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 @@ -1,49 +1,56 @@ package com.mob.utsmyanmar.ui.navigation import android.annotation.SuppressLint +import android.net.Uri import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext -import androidx.lifecycle.viewmodel.compose.viewModel 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 import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument import com.mob.utsmyanmar.model.ProcessCode -import com.mob.utsmyanmar.ui.input_amount.AmountRoute 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.SeeMoreScreen +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 +import com.mob.utsmyanmar.ui.password_input.InputPassword +import com.mob.utsmyanmar.ui.password_input.PasswordType import com.mob.utsmyanmar.ui.pinpad.PinPadRoute +import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel +import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel -import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen import com.mob.utsmyanmar.ui.refund_rrn.InputRrnRoute -import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen -import com.mob.utsmyanmar.ui.sign_on.SignOnRoute +import com.mob.utsmyanmar.ui.sale_void.TranDetailPage +import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen +import com.mob.utsmyanmar.ui.sale_void.VoidViewModel import com.mob.utsmyanmar.ui.sending_to_host.ProcessingRoute import com.mob.utsmyanmar.ui.settlement.SettlementScreen -import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute -import com.mob.utsmyanmar.ui.sale_void.TranDetailPage -import com.mob.utsmyanmar.ui.sale_void.VoidViewModel -import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen -import com.mob.utsmyanmar.viewmodel.CardReaderViewModel -import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel -import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel import com.mob.utsmyanmar.ui.settlement.SettlementViewModel -import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent -import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel -import com.mob.utsmyanmar.ui.functions.FunctionsScreen +import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen +import com.mob.utsmyanmar.ui.sign_on.SignOnRoute import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel +import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent +import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute +import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel +import com.mob.utsmyanmar.ui.notification.NotificationDetailScreen +import com.mob.utsmyanmar.ui.notification.NotificationListScreen +import com.mob.utsmyanmar.ui.notification.NotificationViewModel import com.mob.utsmyanmar.ui.version.VersionScreen +import com.mob.utsmyanmar.viewmodel.CardReaderViewModel +import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel -import com.utsmyanmar.ecr.data.TransType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType @SuppressLint("ContextCastToActivity") @@ -73,8 +80,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; @@ -90,11 +100,6 @@ fun AppNavGraph( launchSingleTop = true } }, - onNavigateSeeMore = { - navController.navigate(Routes.SeeMore.route) { - launchSingleTop = true - } - }, onNavigateSettlement = { navController.navigate(Routes.Settlement.route) { launchSingleTop = true @@ -104,30 +109,22 @@ fun AppNavGraph( navController.navigate(Routes.Version.route) }, onNavigateFunctions = { - navController.navigate(Routes.Functions.route) { + navController.navigate(Routes.Password.createRoute(Routes.Functions.route, PasswordType.SETTING)) { launchSingleTop = true } + }, + onNavigateAction = { action -> + when (action) { + "Void" -> navController.navigate(Routes.VoidTrace.route) { launchSingleTop = true } + else -> navController.navigate(Routes.Amount.createRoute(action)) { launchSingleTop = true } + } + }, + onNavigateNotifications = { + navController.navigate(Routes.NotificationList.route) { launchSingleTop = true } } ) } - composable(Routes.SeeMore.route) { - SeeMoreScreen( - onBack = { navController.popBackStack() }, - onNavigateAmount = { action -> - if (action == "Void") { - navController.navigate(Routes.VoidTrace.route) { - launchSingleTop = true - } - } else { - navController.navigate(Routes.Amount.createRoute(action)) { - launchSingleTop = true - } - } - } - ) - } - composable(Routes.Version.route){ val deviceInfoViewModel: DeviceInfoViewModel = hiltViewModel(); VersionScreen( @@ -136,6 +133,27 @@ fun AppNavGraph( ) } + composable( + route = Routes.Password.route, + arguments = listOf( + navArgument("destination") { type = NavType.StringType }, + navArgument("passwordType") { type = NavType.StringType } + ) + ) { backStackEntry -> + val destination = Uri.decode(backStackEntry.arguments?.getString("destination").orEmpty()) + val passwordType = backStackEntry.arguments?.getString("passwordType").orEmpty() + InputPassword( + passwordType = passwordType, + onBack = { navController.popBackStack() }, + onPasswordCorrect = { + navController.navigate(destination) { + popUpTo(Routes.Password.route) { inclusive = true } + launchSingleTop = true + } + } + ) + } + composable(Routes.Functions.route) { FunctionsScreen( onBack = { navController.popBackStack() } @@ -484,6 +502,34 @@ fun AppNavGraph( } ) } + + composable(Routes.NotificationList.route) { + val notificationViewModel: NotificationViewModel = hiltViewModel() + NotificationListScreen( + viewModel = notificationViewModel, + onBack = { navController.popBackStack() }, + onNavigateDetail = { id -> + navController.navigate(Routes.NotificationDetail.createRoute(id)) { + launchSingleTop = true + } + } + ) + } + + composable( + route = Routes.NotificationDetail.route, + arguments = listOf( + navArgument("notificationId") { type = NavType.IntType } + ) + ) { backStackEntry -> + val notificationViewModel: NotificationViewModel = hiltViewModel() + val id = backStackEntry.arguments?.getInt("notificationId") ?: return@composable + NotificationDetailScreen( + viewModel = notificationViewModel, + notificationId = id, + onBack = { navController.popBackStack() } + ) + } } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt index c798df9..253c217 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt @@ -29,4 +29,12 @@ sealed class Routes(val route: String) { data object PrintReceipt : Routes("print_receipt") data object Version : Routes("version") data object Functions : Routes("functions") + data object Password : Routes("password/{destination}/{passwordType}") { + fun createRoute(destination: String, passwordType: String): String = + "password/${Uri.encode(destination)}/$passwordType" + } + data object NotificationList : Routes("notification_list") + data object NotificationDetail : Routes("notification_detail/{notificationId}") { + fun createRoute(id: Int): String = "notification_detail/$id" + } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationDetailScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationDetailScreen.kt new file mode 100644 index 0000000..cdb9634 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationDetailScreen.kt @@ -0,0 +1,116 @@ +package com.mob.utsmyanmar.ui.notification + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color + +@Composable +fun NotificationDetailScreen( + viewModel: NotificationViewModel, + notificationId: Int, + onBack: () -> Unit +) { + val notification = viewModel.getById(notificationId) + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Notification Detail", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + if (notification == null) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center + ) { + Text(text = "Notification not found", color = Color.Gray) + } + } else { + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Box( + modifier = Modifier + .size(56.dp) + .background(Color.LegacyRed.copy(alpha = 0.1f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = null, + tint = Color.LegacyRed, + modifier = Modifier.size(28.dp) + ) + } + + Spacer(Modifier.height(16.dp)) + + Text( + text = notification.title, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + + Spacer(Modifier.height(6.dp)) + + Text( + text = notification.timestamp, + fontSize = 12.sp, + color = Color.Gray + ) + + Spacer(Modifier.height(16.dp)) + + Text( + text = notification.message, + fontSize = 14.sp, + color = Color.Black, + lineHeight = 22.sp + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationListScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationListScreen.kt new file mode 100644 index 0000000..28aa1bc --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationListScreen.kt @@ -0,0 +1,174 @@ +package com.mob.utsmyanmar.ui.notification + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color + +@Composable +fun NotificationListScreen( + viewModel: NotificationViewModel, + onBack: () -> Unit, + onNavigateDetail: (Int) -> Unit +) { + val notifications by viewModel.notifications.collectAsState() + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Notifications", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack, + actions = { + if (notifications.any { !it.isRead }) { + TextButton(onClick = { viewModel.markAllAsRead() }) { + Text(text = "Mark all read", color = Color.White, fontSize = 12.sp) + } + } + } + ) + } + ) { paddingValues -> + if (notifications.isEmpty()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = null, + tint = Color.Gray, + modifier = Modifier.size(64.dp) + ) + Text(text = "No notifications", color = Color.Gray, fontSize = 14.sp) + } + } + } else { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(items = notifications, key = { it.id }) { notification -> + NotificationItem( + notification = notification, + onClick = { + viewModel.markAsRead(notification.id) + onNavigateDetail(notification.id) + } + ) + } + } + } + } +} + +@Composable +private fun NotificationItem( + notification: AppNotification, + onClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(Color.White) + .clickable(onClick = onClick) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(42.dp) + .background( + color = if (notification.isRead) Color.Gray.copy(alpha = 0.12f) + else Color.LegacyRed.copy(alpha = 0.12f), + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = null, + tint = if (notification.isRead) Color.Gray else Color.LegacyRed, + modifier = Modifier.size(22.dp) + ) + } + + Spacer(Modifier.width(12.dp)) + + Column(modifier = Modifier.weight(1f)) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = notification.title, + fontSize = 13.sp, + fontWeight = if (notification.isRead) FontWeight.Normal else FontWeight.SemiBold, + color = if (notification.isRead) Color.Gray else Color.Black, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (!notification.isRead) { + Box( + modifier = Modifier + .size(8.dp) + .background(Color.LegacyRed, CircleShape) + ) + } + } + Text( + text = notification.message, + fontSize = 12.sp, + color = Color.Gray, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = notification.timestamp, + fontSize = 11.sp, + color = Color.Gray.copy(alpha = 0.7f) + ) + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationModel.kt new file mode 100644 index 0000000..4afee4d --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationModel.kt @@ -0,0 +1,9 @@ +package com.mob.utsmyanmar.ui.notification + +data class AppNotification( + val id: Int, + val title: String, + val message: String, + val timestamp: String, + val isRead: Boolean = false +) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationViewModel.kt new file mode 100644 index 0000000..458f131 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/notification/NotificationViewModel.kt @@ -0,0 +1,37 @@ +package com.mob.utsmyanmar.ui.notification + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +@HiltViewModel +class NotificationViewModel @Inject constructor() : ViewModel() { + + private val _notifications = MutableStateFlow(sampleNotifications) + val notifications: StateFlow> = _notifications.asStateFlow() + + val unreadCount: Int get() = _notifications.value.count { !it.isRead } + + fun markAsRead(id: Int) { + _notifications.value = _notifications.value.map { + if (it.id == id) it.copy(isRead = true) else it + } + } + + fun markAllAsRead() { + _notifications.value = _notifications.value.map { it.copy(isRead = true) } + } + + fun getById(id: Int): AppNotification? = _notifications.value.find { it.id == id } +} + +private val sampleNotifications = listOf( + AppNotification(1, "Settlement Reminder", "Your daily settlement is pending. Please settle before end of day.", "Today, 17:30", isRead = false), + AppNotification(2, "TMS Update Available", "A new terminal configuration update is ready. Please restart to apply.", "Today, 09:15", isRead = false), + AppNotification(3, "Transaction Approved", "Sale of MMK 50,000 was approved successfully. Trace #001234.", "Yesterday, 14:22", isRead = true), + AppNotification(4, "Network Warning", "Intermittent network issues detected. Contact your administrator if the problem persists.", "Yesterday, 11:05", isRead = true), + AppNotification(5, "Log-On Required", "Your terminal session has expired. Please log on again to continue.", "06/09, 08:00", isRead = true), +) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInput.kt b/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInput.kt new file mode 100644 index 0000000..a4ba592 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInput.kt @@ -0,0 +1,210 @@ +package com.mob.utsmyanmar.ui.password_input + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Backspace +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.mob.utsmyanmar.ui.components.NumericKeypad +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.preview.P3Preview +import com.mob.utsmyanmar.ui.theme.Color + +object PasswordType { + const val SYSTEM = "system_password" + const val SETTLEMENT = "settlement_password" + const val SETTING = "setting_password" +} + +private val passwords = mapOf( + PasswordType.SYSTEM to "111111", + PasswordType.SETTING to "222222", + PasswordType.SETTLEMENT to "123456" +) + +private val passwordKeys = listOf( + listOf("1", "2", "3"), + listOf("4", "5", "6"), + listOf("7", "8", "9"), + listOf("", "0", "") +) + +@Composable +fun InputPassword( + passwordType: String, + onBack: () -> Unit = {}, + onPasswordCorrect: () -> Unit = {}, + viewModel: PasswordInputViewModel = viewModel() +) { + val expectedPassword = passwords[passwordType] ?: passwords[PasswordType.SETTING]!! + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { viewModel.reset() } + + Scaffold( + topBar = { + AppBar(title = "Password", icon = Icons.Default.ArrowBackIosNew, onIconClick = onBack) + }, + containerColor = Color.IvoryBeige + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(16.dp) + ) { + Spacer(Modifier.height(8.dp)) + + // Display area + Box( + modifier = Modifier + .fillMaxSize() + .weight(2f) + ) { + Card( + modifier = Modifier + .align(Alignment.TopEnd) + .clickable(enabled = uiState.canDelete, onClick = viewModel::onDelete), + shape = RoundedCornerShape(18.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) + ) { + Icon( + imageVector = Icons.Rounded.Backspace, + contentDescription = "Delete", + tint = if (uiState.canDelete) Color.LegacyRed else Color.Gray, + modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp) + ) + } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = "Enter Password", color = Color.Gray, fontSize = 18.sp) + Spacer(Modifier.height(16.dp)) + PasswordDots(filledCount = uiState.input.length, totalCount = PASSWORD_LENGTH) + Spacer(Modifier.height(8.dp)) + Text( + text = uiState.errorMessage ?: "", + color = Color.LegacyRed, + fontSize = 14.sp + ) + } + } + + // Keypad — stable lambda: viewModel::onKey never changes between recompositions + Column( + modifier = Modifier + .fillMaxWidth() + .weight(3f), + verticalArrangement = Arrangement.Bottom + ) { + NumericKeypad( + keys = passwordKeys, + modifier = Modifier.fillMaxSize(), + onKeyClick = viewModel::onKey + ) + } + + Spacer(Modifier.height(16.dp)) + + // Action buttons + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.5f) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = onBack, + modifier = Modifier.weight(1f).height(56.dp), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.LegacyRed + ) + ) { + Text("Cancel") + } + + Button( + onClick = { viewModel.validate(expectedPassword, onPasswordCorrect) }, + modifier = Modifier.weight(1f).height(56.dp), + enabled = uiState.isConfirmEnabled, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.LegacyRed, + contentColor = Color.White, + disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f), + disabledContentColor = Color.White + ) + ) { + Text(text = "Next", fontSize = 14.sp, fontWeight = FontWeight.Medium) + } + } + } + } + } +} + +@Composable +private fun PasswordDots(filledCount: Int, totalCount: Int) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + repeat(totalCount) { index -> + Box( + modifier = Modifier + .size(14.dp) + .background( + color = if (index < filledCount) Color.LegacyRed + else Color.Gray.copy(alpha = 0.3f), + shape = CircleShape + ) + ) + } + } +} + +@P3Preview +@P2Preview +@Composable +fun PreviewInputPassword() { + InputPassword(passwordType = PasswordType.SETTING) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputUiState.kt b/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputUiState.kt new file mode 100644 index 0000000..fc00b5c --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputUiState.kt @@ -0,0 +1,11 @@ +package com.mob.utsmyanmar.ui.password_input + +internal const val PASSWORD_LENGTH = 6 + +data class PasswordInputUiState( + val input: String = "", + val errorMessage: String? = null +) { + val isConfirmEnabled: Boolean get() = input.length == PASSWORD_LENGTH + val canDelete: Boolean get() = input.isNotEmpty() +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputViewModel.kt new file mode 100644 index 0000000..37b36d0 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputViewModel.kt @@ -0,0 +1,40 @@ +package com.mob.utsmyanmar.ui.password_input + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +class PasswordInputViewModel : ViewModel() { + + private val _uiState = MutableStateFlow(PasswordInputUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun onKey(digit: String) { + _uiState.update { state -> + if (state.input.length < PASSWORD_LENGTH) + state.copy(input = state.input + digit, errorMessage = null) + else + state + } + } + + fun onDelete() { + _uiState.update { state -> + state.copy(input = state.input.dropLast(1), errorMessage = null) + } + } + + fun validate(expectedPassword: String, onCorrect: () -> Unit) { + if (_uiState.value.input == expectedPassword) { + onCorrect() + } else { + _uiState.update { it.copy(errorMessage = "Incorrect password", input = "") } + } + } + + fun reset() { + _uiState.value = PasswordInputUiState() + } +} 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 ) diff --git a/app/src/main/res/drawable/ic_cancel_circle.xml b/app/src/main/res/drawable/ic_cancel_circle.xml new file mode 100644 index 0000000..1d7afd1 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel_circle.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_four_boxes.xml b/app/src/main/res/drawable/ic_four_boxes.xml new file mode 100644 index 0000000..a8f1aa6 --- /dev/null +++ b/app/src/main/res/drawable/ic_four_boxes.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml index bb9afa0..6ef732a 100644 --- a/app/src/main/res/drawable/ic_lock.xml +++ b/app/src/main/res/drawable/ic_lock.xml @@ -1,25 +1,9 @@ - + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + android:pathData="M240,880q-33,0 -56.5,-23.5T160,800v-400q0,-33 23.5,-56.5T240,320h40v-80q0,-83 58.5,-141.5T480,40q83,0 141.5,58.5T680,240v80h40q33,0 56.5,23.5T800,400v400q0,33 -23.5,56.5T720,880L240,880ZM240,800h480v-400L240,400v400ZM536.5,656.5Q560,633 560,600t-23.5,-56.5Q513,520 480,520t-56.5,23.5Q400,567 400,600t23.5,56.5Q447,680 480,680t56.5,-23.5ZM360,320h240v-80q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85v80ZM240,800v-400,400Z" + android:fillColor="#e3e3e3"/> diff --git a/app/src/main/res/drawable/ic_up_down_arrow.xml b/app/src/main/res/drawable/ic_up_down_arrow.xml new file mode 100644 index 0000000..87e3175 --- /dev/null +++ b/app/src/main/res/drawable/ic_up_down_arrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_version.xml b/app/src/main/res/drawable/ic_version.xml index 77b3fea..916b16d 100644 --- a/app/src/main/res/drawable/ic_version.xml +++ b/app/src/main/res/drawable/ic_version.xml @@ -1,24 +1,12 @@ - + android:width="800dp" + android:height="800dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M20.245,14.75C21.18,15.364 21.137,16.787 20.117,17.326L12.935,21.122C12.35,21.432 11.65,21.432 11.066,21.122L3.884,17.326C2.863,16.787 2.82,15.364 3.755,14.75L3.818,14.789L3.818,14.789L11.065,18.622C11.65,18.931 12.35,18.931 12.935,18.622L20.116,14.826C20.161,14.802 20.204,14.777 20.245,14.75ZM20.245,10.75C21.139,11.337 21.139,12.665 20.244,13.251L20.117,13.326L12.935,17.122C12.403,17.403 11.777,17.429 11.228,17.199L11.066,17.122L3.884,13.326C2.863,12.787 2.82,11.364 3.755,10.75L3.818,10.789L3.818,10.789L11.065,14.622C11.597,14.903 12.224,14.929 12.773,14.699L12.935,14.622L20.116,10.826C20.161,10.802 20.204,10.777 20.245,10.75ZM12.935,2.878L20.116,6.674C21.182,7.237 21.182,8.763 20.116,9.326L12.935,13.123C12.35,13.432 11.65,13.432 11.065,13.123L3.884,9.326C2.818,8.763 2.818,7.237 3.884,6.674L11.065,2.878C11.65,2.569 12.35,2.569 12.935,2.878Z" + android:strokeWidth="1" + android:fillColor="#09244B" + android:fillType="nonZero" + android:strokeColor="#00000000"/>