update to MVVM
This commit is contained in:
parent
582b08cead
commit
3a63c65e8f
@ -25,15 +25,15 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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.NumericKeypad
|
||||||
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
import com.mob.utsmyanmar.ui.preview.P2Preview
|
import com.mob.utsmyanmar.ui.preview.P2Preview
|
||||||
@ -52,8 +52,6 @@ private val passwords = mapOf(
|
|||||||
PasswordType.SETTLEMENT to "123456"
|
PasswordType.SETTLEMENT to "123456"
|
||||||
)
|
)
|
||||||
|
|
||||||
private const val PASSWORD_LENGTH = 6
|
|
||||||
|
|
||||||
private val passwordKeys = listOf(
|
private val passwordKeys = listOf(
|
||||||
listOf("1", "2", "3"),
|
listOf("1", "2", "3"),
|
||||||
listOf("4", "5", "6"),
|
listOf("4", "5", "6"),
|
||||||
@ -65,11 +63,13 @@ private val passwordKeys = listOf(
|
|||||||
fun InputPassword(
|
fun InputPassword(
|
||||||
passwordType: String,
|
passwordType: String,
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onPasswordCorrect: () -> Unit = {}
|
onPasswordCorrect: () -> Unit = {},
|
||||||
|
viewModel: PasswordInputViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val expectedPassword = passwords[passwordType] ?: passwords[PasswordType.SETTING]!!
|
val expectedPassword = passwords[passwordType] ?: passwords[PasswordType.SETTING]!!
|
||||||
var input by remember { mutableStateOf("") }
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
|
||||||
|
LaunchedEffect(Unit) { viewModel.reset() }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -85,6 +85,7 @@ fun InputPassword(
|
|||||||
) {
|
) {
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Display area
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@ -92,11 +93,8 @@ fun InputPassword(
|
|||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterEnd)
|
.align(Alignment.TopEnd)
|
||||||
.clickable(enabled = input.isNotEmpty()) {
|
.clickable(enabled = uiState.canDelete, onClick = viewModel::onDelete),
|
||||||
input = input.dropLast(1)
|
|
||||||
errorMessage = null
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(18.dp),
|
shape = RoundedCornerShape(18.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
||||||
@ -104,7 +102,7 @@ fun InputPassword(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.Backspace,
|
imageVector = Icons.Rounded.Backspace,
|
||||||
contentDescription = "Delete",
|
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)
|
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -114,23 +112,19 @@ fun InputPassword(
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(text = "Enter Password", color = Color.Gray, fontSize = 18.sp)
|
||||||
text = "Enter Password",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 18.sp
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
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))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = errorMessage ?: "",
|
text = uiState.errorMessage ?: "",
|
||||||
color = Color.LegacyRed,
|
color = Color.LegacyRed,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keypad — mirrors InputAmountScreen weight(3f) section
|
// Keypad — stable lambda: viewModel::onKey never changes between recompositions
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -140,18 +134,13 @@ fun InputPassword(
|
|||||||
NumericKeypad(
|
NumericKeypad(
|
||||||
keys = passwordKeys,
|
keys = passwordKeys,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
onKeyClick = { digit ->
|
onKeyClick = viewModel::onKey
|
||||||
if (input.length < PASSWORD_LENGTH) {
|
|
||||||
input += digit
|
|
||||||
errorMessage = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
// Action buttons — mirrors InputAmountScreen weight(0.5f) section
|
// Action buttons
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -174,16 +163,9 @@ fun InputPassword(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = { viewModel.validate(expectedPassword, onPasswordCorrect) },
|
||||||
if (input == expectedPassword) {
|
|
||||||
onPasswordCorrect()
|
|
||||||
} else {
|
|
||||||
errorMessage = "Incorrect password"
|
|
||||||
input = ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.weight(1f).height(56.dp),
|
modifier = Modifier.weight(1f).height(56.dp),
|
||||||
enabled = input.length == PASSWORD_LENGTH,
|
enabled = uiState.isConfirmEnabled,
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = Color.LegacyRed,
|
containerColor = Color.LegacyRed,
|
||||||
|
|||||||
@ -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()
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user