responsive keypad

This commit is contained in:
moon 2026-06-10 21:46:17 +06:30
parent d745815f74
commit 582b08cead
4 changed files with 232 additions and 325 deletions

View File

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

View File

@ -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<List<String>>,
) {
// val keys : List<List<String>> = 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<List<String>> = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf(".", "0", "00")
)
NumericKeypad(
keys = keys,
onKeyClick = {}
)
}

View File

@ -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<List<String>> = 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 == ".") {

View File

@ -10,10 +10,8 @@ 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
@ -33,30 +31,36 @@ 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
object PasswordType {
const val SYSTEM = "system_password"
const val SYSTEM = "system_password"
const val SETTLEMENT = "settlement_password"
const val SETTING = "setting_password"
const val SETTING = "setting_password"
}
private val passwords = mapOf(
PasswordType.SYSTEM to "111111",
PasswordType.SETTING to "222222",
PasswordType.SETTLEMENT to "123456",
PasswordType.SETTLEMENT to "123456"
)
private const val PASSWORD_LENGTH = 6
private val passwordKeys = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf("", "0", "")
)
@Composable
fun InputPassword(
passwordType: String,
@ -73,154 +77,132 @@ fun InputPassword(
},
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
.padding(paddingValues)
.fillMaxSize()
.padding(horizontal = 20.dp)
.padding(16.dp)
) {
Text(
text = "Enter Password",
color = Color.Gray,
fontSize = 11.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(Modifier.height(8.dp))
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)
Box(
modifier = Modifier
.fillMaxSize()
.weight(2f)
) {
Button(
onClick = onBack,
modifier = Modifier.weight(1f).height(56.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.LegacyRed
)
Card(
modifier = Modifier
.align(Alignment.CenterEnd)
.clickable(enabled = input.isNotEmpty()) {
input = input.dropLast(1)
errorMessage = null
},
shape = RoundedCornerShape(18.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
) {
Text("Cancel")
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)
)
}
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
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Next", fontSize = 14.sp, fontWeight = FontWeight.Medium)
Text(
text = "Enter Password",
color = Color.Gray,
fontSize = 18.sp
)
Spacer(Modifier.height(16.dp))
PasswordDots(filledCount = input.length, totalCount = PASSWORD_LENGTH)
Spacer(Modifier.height(8.dp))
Text(
text = errorMessage ?: "",
color = Color.LegacyRed,
fontSize = 14.sp
)
}
}
Spacer(modifier = Modifier.height(18.dp))
}
// Keypad — mirrors InputAmountScreen weight(3f) section
Column(
modifier = Modifier
.fillMaxWidth()
.weight(3f),
verticalArrangement = Arrangement.Bottom
) {
NumericKeypad(
keys = passwordKeys,
modifier = Modifier.fillMaxSize(),
onKeyClick = { digit ->
if (input.length < PASSWORD_LENGTH) {
input += digit
errorMessage = null
}
}
)
}
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)
)
Spacer(Modifier.height(16.dp))
// Action buttons — mirrors InputAmountScreen weight(0.5f) section
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 = {
if (input == expectedPassword) {
onPasswordCorrect()
} else {
errorMessage = "Incorrect password"
input = ""
}
},
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)
}
}
}
}
}
}
@Composable
private fun PasswordDots(
filledCount: Int,
totalCount: Int,
modifier: Modifier = Modifier
) {
private fun PasswordDots(filledCount: Int, totalCount: Int) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
@ -229,7 +211,8 @@ private fun PasswordDots(
modifier = Modifier
.size(14.dp)
.background(
color = if (index < filledCount) Color.LegacyRed else Color.Gray.copy(alpha = 0.3f),
color = if (index < filledCount) Color.LegacyRed
else Color.Gray.copy(alpha = 0.3f),
shape = CircleShape
)
)
@ -237,64 +220,6 @@ private fun PasswordDots(
}
}
@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