update to MVVM

This commit is contained in:
moon 2026-06-10 21:57:14 +06:30
parent 582b08cead
commit 3a63c65e8f
3 changed files with 71 additions and 38 deletions

View File

@ -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<String?>(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,

View File

@ -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()
}

View File

@ -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<PasswordInputUiState> = _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()
}
}