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.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Backspace import androidx.compose.material.icons.automirrored.rounded.Backspace
import androidx.compose.material.icons.rounded.Backspace
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
@ -26,9 +25,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.mob.utsmyanmar.ui.theme.Color 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 package com.mob.utsmyanmar.ui.input_amount
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Backspace
import androidx.compose.material.icons.rounded.Backspace import androidx.compose.material.icons.rounded.Backspace
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@ -30,16 +27,21 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.draw.shadow
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp 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.components.appbar.AppBar
import com.mob.utsmyanmar.ui.preview.P2Preview import com.mob.utsmyanmar.ui.preview.P2Preview
import com.mob.utsmyanmar.ui.preview.P3Preview import com.mob.utsmyanmar.ui.preview.P3Preview
import com.mob.utsmyanmar.ui.theme.Color 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 @Composable
fun InputAmount( fun InputAmount(
@ -140,6 +142,7 @@ fun InputAmount(
verticalArrangement = Arrangement.Bottom verticalArrangement = Arrangement.Bottom
){ ){
NumericKeypad( NumericKeypad(
keys = amountKeys,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
onKeyClick = { value -> onKeyClick = { value ->
amount = appendAmountValue(amount, 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 { private fun appendAmountValue(current: String, value: String): String {
if (value == ".") { 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -33,30 +31,36 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.draw.shadow
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp 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.components.appbar.AppBar
import com.mob.utsmyanmar.ui.preview.P2Preview import com.mob.utsmyanmar.ui.preview.P2Preview
import com.mob.utsmyanmar.ui.preview.P3Preview import com.mob.utsmyanmar.ui.preview.P3Preview
import com.mob.utsmyanmar.ui.theme.Color import com.mob.utsmyanmar.ui.theme.Color
object PasswordType { object PasswordType {
const val SYSTEM = "system_password" const val SYSTEM = "system_password"
const val SETTLEMENT = "settlement_password" const val SETTLEMENT = "settlement_password"
const val SETTING = "setting_password" const val SETTING = "setting_password"
} }
private val passwords = mapOf( private val passwords = mapOf(
PasswordType.SYSTEM to "111111", PasswordType.SYSTEM to "111111",
PasswordType.SETTING to "222222", PasswordType.SETTING to "222222",
PasswordType.SETTLEMENT to "123456", PasswordType.SETTLEMENT to "123456"
) )
private const val PASSWORD_LENGTH = 6 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 @Composable
fun InputPassword( fun InputPassword(
passwordType: String, passwordType: String,
@ -73,154 +77,132 @@ fun InputPassword(
}, },
containerColor = Color.IvoryBeige containerColor = Color.IvoryBeige
) { paddingValues -> ) { 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( Column(
modifier = Modifier modifier = Modifier
.padding(paddingValues)
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 20.dp) .padding(16.dp)
) { ) {
Text( Spacer(Modifier.height(8.dp))
text = "Enter Password",
color = Color.Gray,
fontSize = 11.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(16.dp)) Box(
modifier = Modifier
PasswordDots( .fillMaxSize()
filledCount = input.length, .weight(2f)
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( Card(
onClick = onBack, modifier = Modifier
modifier = Modifier.weight(1f).height(56.dp), .align(Alignment.CenterEnd)
shape = RoundedCornerShape(8.dp), .clickable(enabled = input.isNotEmpty()) {
colors = ButtonDefaults.buttonColors( input = input.dropLast(1)
containerColor = Color.White, errorMessage = null
contentColor = Color.LegacyRed },
) 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( Column(
onClick = onConfirm, modifier = Modifier.fillMaxSize(),
modifier = Modifier.weight(1f).height(56.dp), verticalArrangement = Arrangement.Center,
enabled = input.length == PASSWORD_LENGTH, horizontalAlignment = Alignment.CenterHorizontally
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) 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( Spacer(Modifier.height(16.dp))
modifier = Modifier
.align(Alignment.TopEnd) // Action buttons — mirrors InputAmountScreen weight(0.5f) section
.padding(top = 32.dp, end = 20.dp) Box(
.clickable(enabled = input.isNotEmpty()) { onDelete() }, modifier = Modifier
shape = RoundedCornerShape(18.dp), .fillMaxWidth()
colors = CardDefaults.cardColors(containerColor = Color.White), .weight(0.5f)
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) ) {
) { Row(
Icon( modifier = Modifier.fillMaxWidth(),
imageVector = Icons.Rounded.Backspace, horizontalArrangement = Arrangement.spacedBy(12.dp)
contentDescription = "Delete", ) {
tint = if (input.isNotEmpty()) Color.LegacyRed else Color.Gray, Button(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp) 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 @Composable
private fun PasswordDots( private fun PasswordDots(filledCount: Int, totalCount: Int) {
filledCount: Int,
totalCount: Int,
modifier: Modifier = Modifier
) {
Row( Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@ -229,7 +211,8 @@ private fun PasswordDots(
modifier = Modifier modifier = Modifier
.size(14.dp) .size(14.dp)
.background( .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 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 @P3Preview
@P2Preview @P2Preview
@Composable @Composable