password screen added

This commit is contained in:
moon 2026-06-10 20:52:51 +06:30
parent 211e633517
commit d745815f74
5 changed files with 353 additions and 19 deletions

View File

@ -1,3 +1,7 @@
## Working agreements ## Working agreements
- use Scaffold and AppBar() in every main Screen - use Scaffold and AppBar() in every main Screen
### Re-usable Screens
- PasswordInput.kt
- InputAmountScreen.kt

View File

@ -93,7 +93,7 @@ fun DashboardScreen2(
deviceInfoViewModel.loadDeviceInfo() deviceInfoViewModel.loadDeviceInfo()
} }
val drawerState = rememberDrawerState(initialValue = DrawerValue.Open) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val mainHandler = remember { Handler(Looper.getMainLooper()) } val mainHandler = remember { Handler(Looper.getMainLooper()) }
var showHostActionDialog by remember { mutableStateOf(false) } var showHostActionDialog by remember { mutableStateOf(false) }

View File

@ -1,49 +1,51 @@
package com.mob.utsmyanmar.ui.navigation package com.mob.utsmyanmar.ui.navigation
import android.annotation.SuppressLint import android.annotation.SuppressLint
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.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.mob.utsmyanmar.model.ProcessCode 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.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.SeeMoreScreen import com.mob.utsmyanmar.ui.dashboard.SeeMoreScreen
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.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.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.ProcessingCardRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel 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.refund_rrn.InputRrnRoute
import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen import com.mob.utsmyanmar.ui.sale_void.TranDetailPage
import com.mob.utsmyanmar.ui.sign_on.SignOnRoute 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.sending_to_host.ProcessingRoute
import com.mob.utsmyanmar.ui.settlement.SettlementScreen 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.settlement.SettlementViewModel
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel import com.mob.utsmyanmar.ui.sign_on.SignOnRoute
import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel 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.version.VersionScreen 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.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.ecr.data.TransType
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@SuppressLint("ContextCastToActivity") @SuppressLint("ContextCastToActivity")
@ -104,7 +106,7 @@ fun AppNavGraph(
navController.navigate(Routes.Version.route) navController.navigate(Routes.Version.route)
}, },
onNavigateFunctions = { onNavigateFunctions = {
navController.navigate(Routes.Functions.route) { navController.navigate(Routes.Password.createRoute(Routes.Functions.route, PasswordType.SETTING)) {
launchSingleTop = true launchSingleTop = true
} }
} }
@ -136,6 +138,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) { composable(Routes.Functions.route) {
FunctionsScreen( FunctionsScreen(
onBack = { navController.popBackStack() } onBack = { navController.popBackStack() }

View File

@ -29,4 +29,8 @@ sealed class Routes(val route: String) {
data object PrintReceipt : Routes("print_receipt") data object PrintReceipt : Routes("print_receipt")
data object Version : Routes("version") data object Version : Routes("version")
data object Functions : Routes("functions") data object Functions : Routes("functions")
data object Password : Routes("password/{destination}/{passwordType}") {
fun createRoute(destination: String, passwordType: String): String =
"password/${Uri.encode(destination)}/$passwordType"
}
} }

View File

@ -0,0 +1,303 @@
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
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.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.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.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 const val PASSWORD_LENGTH = 6
@Composable
fun InputPassword(
passwordType: String,
onBack: () -> Unit = {},
onPasswordCorrect: () -> Unit = {}
) {
val expectedPassword = passwords[passwordType] ?: passwords[PasswordType.SETTING]!!
var input by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf<String?>(null) }
Scaffold(
topBar = {
AppBar(title = "Password", icon = Icons.Default.ArrowBackIosNew, onIconClick = onBack)
},
containerColor = Color.IvoryBeige
) { paddingValues ->
PasswordEntryScreen(
modifier = Modifier.padding(paddingValues),
input = input,
errorMessage = errorMessage,
onBack = onBack,
onConfirm = {
if (input == expectedPassword) {
onPasswordCorrect()
} else {
errorMessage = "Incorrect password"
input = ""
}
},
onKey = { digit ->
if (input.length < PASSWORD_LENGTH) {
input += digit
errorMessage = null
}
},
onDelete = {
input = input.dropLast(1)
errorMessage = null
}
)
}
}
@Composable
private fun PasswordEntryScreen(
input: String,
errorMessage: String?,
onBack: () -> Unit,
onConfirm: () -> Unit,
onKey: (String) -> Unit,
onDelete: () -> Unit,
modifier: Modifier = Modifier
) {
val keys = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf("", "0", "")
)
Box(
modifier = modifier
.fillMaxSize()
.background(Color.White)
.navigationBarsPadding()
.statusBarsPadding()
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Text(
text = "Enter Password",
color = Color.Gray,
fontSize = 11.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(16.dp))
PasswordDots(
filledCount = input.length,
totalCount = PASSWORD_LENGTH,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = errorMessage ?: "",
color = Color.LegacyRed,
fontSize = 11.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(24.dp))
NumericKeypad(keys = keys, onKeyClick = onKey)
Spacer(modifier = Modifier.weight(1f))
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 = onConfirm,
modifier = Modifier.weight(1f).height(56.dp),
enabled = input.length == PASSWORD_LENGTH,
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)
}
}
Spacer(modifier = Modifier.height(18.dp))
}
Card(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(top = 32.dp, end = 20.dp)
.clickable(enabled = input.isNotEmpty()) { 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 (input.isNotEmpty()) Color.LegacyRed else Color.Gray,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
)
}
}
}
@Composable
private fun PasswordDots(
filledCount: Int,
totalCount: Int,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
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
)
)
}
}
}
@Composable
private fun NumericKeypad(
keys: List<List<String>>,
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)
.then(
if (enabled) Modifier.shadow(elevation = 2.dp, shape = RoundedCornerShape(8.dp), clip = false)
else Modifier
)
.background(
color = if (enabled) Color.White else androidx.compose.ui.graphics.Color.Transparent,
shape = RoundedCornerShape(8.dp)
)
.clickable(enabled = enabled) { onClick() },
contentAlignment = Alignment.Center
) {
Text(
text = text,
color = if (enabled) Color.LegacyRed else androidx.compose.ui.graphics.Color.Transparent,
fontSize = 24.sp,
fontWeight = FontWeight.Normal,
textAlign = TextAlign.Center
)
}
}
@P3Preview
@P2Preview
@Composable
fun PreviewInputPassword() {
InputPassword(passwordType = PasswordType.SETTING)
}