signaturePad

This commit is contained in:
moon 2026-06-30 23:09:38 +06:30
parent 4c869ea6a8
commit 25153bb819
5 changed files with 281 additions and 6 deletions

View File

@ -362,7 +362,10 @@ private fun MenuCard(
}
}
fun LazyListScope.recentTransactionsItems(transactions: List<TrnxRecord>) {
fun LazyListScope.recentTransactionsItems(
transactions: List<TrnxRecord>,
onTransactionClick: (TrnxRecord) -> Unit = {}
) {
item {
Row(
modifier = Modifier
@ -394,19 +397,32 @@ fun LazyListScope.recentTransactionsItems(transactions: List<TrnxRecord>) {
}
} else {
items(items = transactions, key = { it.pid }) { record ->
TrnxRow(record = record)
TrnxRow(
record = record,
onClick = { onTransactionClick(record) }
)
}
}
}
@Composable
private fun TrnxRow(record: TrnxRecord) {
private fun TrnxRow(
record: TrnxRecord,
onClick: (() -> Unit)? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 4.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.White)
.then(
if (onClick != null) {
Modifier.clickable(onClick = onClick)
} else {
Modifier
}
)
.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
@ -427,7 +443,7 @@ private fun TrnxRow(record: TrnxRecord) {
Column(modifier = Modifier.weight(1f)) {
Text(
text =record.typeLabel,
text = record.typeLabel,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold),
color = Color.Black
)

View File

@ -72,6 +72,7 @@ fun DashboardScreen2(
dashboardUiState: DashboardUiState = DashboardUiState(),
deviceInfo: DeviceInfoUiState = DeviceInfoUiState(),
onClickLastSync: () -> Unit = {},
onNavigateRecentTransaction: (TrnxRecord) -> Unit = {},
) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
@ -410,7 +411,10 @@ fun DashboardScreen2(
)
}
item { Spacer(Modifier.height(8.dp)) }
recentTransactionsItems(transactions = dashboardUiState.recentTransactions)
recentTransactionsItems(
transactions = dashboardUiState.recentTransactions,
onTransactionClick = onNavigateRecentTransaction
)
}
}
}

View File

@ -6,6 +6,8 @@ import androidx.activity.ComponentActivity
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -15,7 +17,6 @@ import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.mob.utsmyanmar.model.ProcessCode
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen2
@ -42,6 +43,7 @@ import com.mob.utsmyanmar.ui.settlement.SettlementScreen
import com.mob.utsmyanmar.ui.settlement.SettlementViewModel
import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen
import com.mob.utsmyanmar.ui.sign_on.SignOnRoute
import com.mob.utsmyanmar.ui.signature_pad.SignaturePad
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent
@ -83,11 +85,20 @@ fun AppNavGraph(
)
}
composable(Routes.Signature.route) {
SignaturePad(
onBack = { navController.popBackStack() },
onConfirm = { navController.popBackStack() }
)
}
composable(Routes.Dashboard.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
val transResultViewModel: TransactionResultViewModel = hiltViewModel(activity)
val dashboardViewModel: DashboardViewModel = hiltViewModel(activity)
val dashboardUiState = dashboardViewModel.uiState.collectAsStateWithLifecycle().value
val recentPayDetails by sharedViewModel.getLastThreeTransactions().observeAsState(emptyList())
val deviceInfoViewModel: DeviceInfoViewModel = hiltViewModel(activity)
val deviceInfoUiState = deviceInfoViewModel.uiState.collectAsStateWithLifecycle().value
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel();
@ -109,6 +120,18 @@ fun AppNavGraph(
onClickLastSync = {
tmsSetupViewModel.downloadConfigs()
},
onNavigateRecentTransaction = { record ->
recentPayDetails.firstOrNull { it.PID == record.pid }?.let { payDetail ->
sharedViewModel.payDetail.value = payDetail
transResultViewModel.onEvent(
TransactionResultEvent.Start,
sharedViewModel
)
navController.navigate(Routes.PrintReceipt.route) {
launchSingleTop = true
}
}
},
onNavigateAmount = { action ->
when (action) {
"Pre-Auth",

View File

@ -3,6 +3,7 @@ package com.mob.utsmyanmar.ui.navigation
import android.net.Uri
sealed class Routes(val route: String) {
data object Signature: Routes("signature")
data object TmsSetup : Routes("tms_setup")
data object Dashboard : Routes("dashboard")
data object Amount : Routes("amount/{action}") {

View File

@ -0,0 +1,231 @@
package com.mob.utsmyanmar.ui.signature_pad
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
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.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.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
import com.mob.utsmyanmar.ui.theme.Color as AppColor
@Composable
fun SignaturePad(
modifier: Modifier = Modifier,
signatureColor: Color = Color.Black,
strokeWidth: Float = 8f,
onBack: () -> Unit = {},
onConfirm: () -> Unit = {}
) {
val paths = remember { mutableStateListOf<List<Offset>>() }
val currentPath = remember { mutableStateListOf<Offset>() }
var selectedMode by remember { mutableStateOf(SignatureMode.Manual) }
fun clearSignature() {
paths.clear()
currentPath.clear()
}
Scaffold(
topBar = {
AppBar(
title = "Signature",
onIconClick = onBack
)
},
containerColor = AppColor.IvoryBeige
) { paddingValues ->
Column(
modifier = modifier
.padding(paddingValues)
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(16.dp))
.padding(12.dp)
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { startOffset ->
currentPath.add(startOffset)
},
onDrag = { change, _ ->
change.consume()
currentPath.add(change.position)
},
onDragEnd = {
paths.add(currentPath.toList())
currentPath.clear()
}
)
}
) {
Canvas(modifier = Modifier.fillMaxSize()) {
paths.forEach { points ->
if (points.size > 1) {
val path = Path().apply {
moveTo(points.first().x, points.first().y)
for (i in 1 until points.size) {
lineTo(points[i].x, points[i].y)
}
}
drawPath(
path = path,
color = signatureColor,
style = Stroke(width = strokeWidth)
)
}
}
if (currentPath.size > 1) {
val path = Path().apply {
moveTo(currentPath.first().x, currentPath.first().y)
for (i in 1 until currentPath.size) {
lineTo(currentPath[i].x, currentPath[i].y)
}
}
drawPath(
path = path,
color = signatureColor,
style = Stroke(width = strokeWidth)
)
}
}
}
Column (
modifier = Modifier.fillMaxWidth().height(150.dp),
// horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
SignatureModeOption(
title = "Manual Sign",
subtitle = "Customer signs on screen",
selected = selectedMode == SignatureMode.Manual,
onClick = { selectedMode = SignatureMode.Manual },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(12.dp))
SignatureModeOption(
title = "Digital Sign",
subtitle = "Use stored signature",
selected = selectedMode == SignatureMode.Digital,
onClick = { selectedMode = SignatureMode.Digital },
modifier = Modifier.fillMaxWidth()
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedButton(
onClick = { clearSignature() },
modifier = Modifier
.weight(1f)
.height(50.dp)
) {
Text("Clear")
}
Button(
onClick = onConfirm,
modifier = Modifier
.weight(1f)
.height(50.dp),
colors = ButtonDefaults.buttonColors(
containerColor = AppColor.LegacyRed,
contentColor = Color.White
)
) {
Text("Confirm")
}
}
}
}
}
private enum class SignatureMode {
Manual,
Digital
}
@Composable
private fun SignatureModeOption(
title: String,
subtitle: String,
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Card(
onClick = onClick,
modifier = modifier,
shape = RoundedCornerShape(14.dp),
colors = CardDefaults.cardColors(
containerColor = if (selected) AppColor.LegacyRed.copy(alpha = 0.10f) else Color.White
),
border = androidx.compose.foundation.BorderStroke(
width = 1.dp,
color = if (selected) AppColor.LegacyRed else AppColor.Gray.copy(alpha = 0.35f)
)
) {
Column(
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleSmall,
color = if (selected) AppColor.LegacyRed else Color.Black
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodySmall,
color = AppColor.Gray
)
}
}
}
@Preview(showBackground = true)
@Composable
fun SignaturePadPreview() {
MOBPOSTheme {
SignaturePad()
}
}