From 3a63c65e8f526ae3823ad65ba9e205459d543ba3 Mon Sep 17 00:00:00 2001 From: moon <56061215+MgKyawLay@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:57:14 +0630 Subject: [PATCH] update to MVVM --- .../ui/password_input/PasswordInput.kt | 58 +++++++------------ .../ui/password_input/PasswordInputUiState.kt | 11 ++++ .../password_input/PasswordInputViewModel.kt | 40 +++++++++++++ 3 files changed, 71 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputUiState.kt create mode 100644 app/src/main/java/com/mob/utsmyanmar/ui/password_input/PasswordInputViewModel.kt 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 index 5cc027e..a4ba592 100644 --- 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 @@ -25,15 +25,15 @@ 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.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.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 @@ -52,8 +52,6 @@ private val passwords = mapOf( PasswordType.SETTLEMENT to "123456" ) -private const val PASSWORD_LENGTH = 6 - private val passwordKeys = listOf( listOf("1", "2", "3"), listOf("4", "5", "6"), @@ -65,11 +63,13 @@ private val passwordKeys = listOf( fun InputPassword( passwordType: String, onBack: () -> Unit = {}, - onPasswordCorrect: () -> Unit = {} + onPasswordCorrect: () -> Unit = {}, + viewModel: PasswordInputViewModel = viewModel() ) { val expectedPassword = passwords[passwordType] ?: passwords[PasswordType.SETTING]!! - var input by remember { mutableStateOf("") } - var errorMessage by remember { mutableStateOf(null) } + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { viewModel.reset() } Scaffold( topBar = { @@ -85,6 +85,7 @@ fun InputPassword( ) { Spacer(Modifier.height(8.dp)) + // Display area Box( modifier = Modifier .fillMaxSize() @@ -92,11 +93,8 @@ fun InputPassword( ) { Card( modifier = Modifier - .align(Alignment.CenterEnd) - .clickable(enabled = input.isNotEmpty()) { - input = input.dropLast(1) - errorMessage = null - }, + .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) @@ -104,7 +102,7 @@ fun InputPassword( Icon( imageVector = Icons.Rounded.Backspace, contentDescription = "Delete", - tint = if (input.isNotEmpty()) Color.LegacyRed else Color.Gray, + tint = if (uiState.canDelete) Color.LegacyRed else Color.Gray, modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp) ) } @@ -114,23 +112,19 @@ fun InputPassword( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text( - text = "Enter Password", - color = Color.Gray, - fontSize = 18.sp - ) + Text(text = "Enter Password", color = Color.Gray, fontSize = 18.sp) Spacer(Modifier.height(16.dp)) - PasswordDots(filledCount = input.length, totalCount = PASSWORD_LENGTH) + PasswordDots(filledCount = uiState.input.length, totalCount = PASSWORD_LENGTH) Spacer(Modifier.height(8.dp)) Text( - text = errorMessage ?: "", + text = uiState.errorMessage ?: "", color = Color.LegacyRed, fontSize = 14.sp ) } } - // Keypad — mirrors InputAmountScreen weight(3f) section + // Keypad — stable lambda: viewModel::onKey never changes between recompositions Column( modifier = Modifier .fillMaxWidth() @@ -140,18 +134,13 @@ fun InputPassword( NumericKeypad( keys = passwordKeys, modifier = Modifier.fillMaxSize(), - onKeyClick = { digit -> - if (input.length < PASSWORD_LENGTH) { - input += digit - errorMessage = null - } - } + onKeyClick = viewModel::onKey ) } Spacer(Modifier.height(16.dp)) - // Action buttons — mirrors InputAmountScreen weight(0.5f) section + // Action buttons Box( modifier = Modifier .fillMaxWidth() @@ -174,16 +163,9 @@ fun InputPassword( } Button( - onClick = { - if (input == expectedPassword) { - onPasswordCorrect() - } else { - errorMessage = "Incorrect password" - input = "" - } - }, + onClick = { viewModel.validate(expectedPassword, onPasswordCorrect) }, modifier = Modifier.weight(1f).height(56.dp), - enabled = input.length == PASSWORD_LENGTH, + enabled = uiState.isConfirmEnabled, shape = RoundedCornerShape(8.dp), colors = ButtonDefaults.buttonColors( containerColor = Color.LegacyRed, 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() + } +}