Compare commits

..

No commits in common. "6f61532741e6a6ed392cfb84e22df138fee945d6" and "c3faee2eb0966d28352f68a612fc1d3ad9b9ab0c" have entirely different histories.

8 changed files with 182 additions and 48 deletions

View File

@ -125,6 +125,4 @@ dependencies {
implementation(project(":ecr")) implementation(project(":ecr"))
implementation(project(":xpay")) implementation(project(":xpay"))
implementation(project(":cmhl")) implementation(project(":cmhl"))
implementation("se.warting.signature:signature-pad:0.2.0")
} }

View File

@ -108,6 +108,11 @@ fun InputAmount(
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Row(
// modifier = Modifier.align(Alignment.CenterHorizontally),
// verticalAlignment = Alignment.Bottom
// ) {
Text( Text(
text = formatAmount(amount.ifEmpty { "0" }), text = formatAmount(amount.ifEmpty { "0" }),
color = Color.LegacyRed, color = Color.LegacyRed,
@ -122,7 +127,12 @@ fun InputAmount(
modifier = Modifier.padding(end = 10.dp, bottom = 6.dp) modifier = Modifier.padding(end = 10.dp, bottom = 6.dp)
) )
} }
// }
Spacer(modifier = Modifier.height(14.dp)) Spacer(modifier = Modifier.height(14.dp))
Text( Text(
text = supportingText, text = supportingText,
color = Color.Gray, color = Color.Gray,
@ -131,6 +141,8 @@ fun InputAmount(
) )
} }
} }
//second container
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -234,15 +246,14 @@ private fun formatAmount(value: String): String {
val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L) val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L)
if (!normalized.contains(".")) { if (!normalized.contains(".")) {
return "$groupedWholePart.00" return groupedWholePart
} }
val decimalPart = normalized.substringAfter(".", "") val decimalPart = normalized.substringAfter(".", "")
return if (normalized.endsWith(".")) { return if (normalized.endsWith(".")) {
"$groupedWholePart.00" "$groupedWholePart."
} else { } else {
val padded = decimalPart.padEnd(2, '0').take(2) "$groupedWholePart.$decimalPart"
"$groupedWholePart.$padded"
} }
} }

View File

@ -26,7 +26,6 @@ import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
import com.mob.utsmyanmar.ui.error.TransactionErrorScreen import com.mob.utsmyanmar.ui.error.TransactionErrorScreen
import com.mob.utsmyanmar.ui.functions.FunctionsScreen import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.history_functions.HistoryRoute import com.mob.utsmyanmar.ui.history_functions.HistoryRoute
import com.mob.utsmyanmar.ui.history_functions.prepareReprintPayDetail
import com.mob.utsmyanmar.ui.input_amount.AmountRoute import com.mob.utsmyanmar.ui.input_amount.AmountRoute
import com.mob.utsmyanmar.ui.password_input.InputPassword import com.mob.utsmyanmar.ui.password_input.InputPassword
import com.mob.utsmyanmar.ui.password_input.PasswordType import com.mob.utsmyanmar.ui.password_input.PasswordType
@ -37,6 +36,7 @@ import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel
import com.mob.utsmyanmar.ui.refund_rrn.InputRrnRoute import com.mob.utsmyanmar.ui.refund_rrn.InputRrnRoute
import com.mob.utsmyanmar.ui.refund_rrn.RRNViewModel import com.mob.utsmyanmar.ui.refund_rrn.RRNViewModel
import com.mob.utsmyanmar.ui.history_functions.prepareReprintPayDetail
import com.mob.utsmyanmar.ui.sale_void.TranDetailPage import com.mob.utsmyanmar.ui.sale_void.TranDetailPage
import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen
import com.mob.utsmyanmar.ui.sale_void.VoidViewModel import com.mob.utsmyanmar.ui.sale_void.VoidViewModel
@ -92,7 +92,7 @@ fun AppNavGraph(
val sharedViewModel: SharedViewModel = hiltViewModel(activity) val sharedViewModel: SharedViewModel = hiltViewModel(activity)
SignaturePad( SignaturePad(
onBack = { navController.popBackStack() }, onBack = { navController.popBackStack() },
onConfirm = { bitmap, signatureBlob -> onConfirm = { _, bitmap, signatureBlob ->
sharedViewModel.signBitmap = bitmap sharedViewModel.signBitmap = bitmap
sharedViewModel.updateSignatureBlob(signatureBlob) sharedViewModel.updateSignatureBlob(signatureBlob)
navController.navigate(Routes.TransactionResult.route) { navController.navigate(Routes.TransactionResult.route) {
@ -179,6 +179,7 @@ fun AppNavGraph(
launchSingleTop = true launchSingleTop = true
} }
} }
"Pre-Auth", "Pre-Auth",
"Cash Adv" -> { "Cash Adv" -> {
navController.navigate(Routes.Amount.createRoute(action)) { navController.navigate(Routes.Amount.createRoute(action)) {
@ -801,14 +802,7 @@ fun AppNavGraph(
} }
private fun formatAmountForDisplay(amount: String?): String { private fun formatAmountForDisplay(amount: String?): String {
val raw = amount.orEmpty().trim() val normalizedAmount = amount.orEmpty()
if (raw.isBlank()) return "0.00" val value = normalizedAmount.toLongOrNull() ?: return normalizedAmount.ifBlank { "0" }
if (raw.contains(".")) { return "%,d".format(value)
val whole = raw.substringBefore(".").ifEmpty { "0" }
val decimal = raw.substringAfter(".", "")
val grouped = "%,d".format(whole.toLongOrNull() ?: 0L)
return "$grouped.${decimal.padEnd(2, '0').take(2)}"
}
val value = raw.toLongOrNull() ?: return raw
return "%,d.00".format(value)
} }

View File

@ -1,10 +1,15 @@
package com.mob.utsmyanmar.ui.signature_pad package com.mob.utsmyanmar.ui.signature_pad
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.compose.foundation.border import android.graphics.Paint
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.Arrangement
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.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
@ -19,31 +24,82 @@ import androidx.compose.material3.OutlinedButton
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.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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color 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.layout.onSizeChanged
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.createBitmap
import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
import se.warting.signaturepad.SignaturePadAdapter
import se.warting.signaturepad.SignaturePadView
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import android.graphics.Canvas as AndroidCanvas
import android.graphics.Path as AndroidPath
import com.mob.utsmyanmar.ui.theme.Color as AppColor import com.mob.utsmyanmar.ui.theme.Color as AppColor
@Composable @Composable
fun SignaturePad( fun SignaturePad(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
signatureColor: Color = Color.Black,
onBack: () -> Unit = {}, onBack: () -> Unit = {},
onConfirm: (Bitmap?, ByteArray?) -> Unit = { _, _ -> } onConfirm: (SignatureMode, Bitmap?, signatureBlob: ByteArray?) -> Unit = { _, _, _ -> }
) { ) {
var signaturePadAdapter: SignaturePadAdapter? = null val paths = remember { mutableStateListOf<List<Offset>>() }
val currentPath = remember { mutableStateListOf<Offset>() }
var selectedMode by remember { mutableStateOf(SignatureMode.Digital) }
var canvasSize by remember { mutableStateOf(Size.Zero) }
var strokeWidth = 6f
val scaledWidth = 300 fun clearSignature() {
paths.clear()
currentPath.clear()
}
fun scaleBitmap(source: Bitmap): Bitmap { fun buildBitmap(): Bitmap? {
val ratio = scaledWidth.toFloat() / source.width if (selectedMode == SignatureMode.Manual) return null
val height = (source.height * ratio).toInt() if (paths.isEmpty() && currentPath.isEmpty()) return null
return Bitmap.createScaledBitmap(source, scaledWidth, height, true) if (canvasSize.width <= 0f || canvasSize.height <= 0f) return null
val bitmap =
createBitmap(canvasSize.width.toInt(), canvasSize.height.toInt(), Bitmap.Config.RGB_565)
val canvas = AndroidCanvas(bitmap)
canvas.drawColor(android.graphics.Color.WHITE)
val paint = Paint().apply {
color = android.graphics.Color.BLACK
style = Paint.Style.STROKE
isAntiAlias = true
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
strokeWidth = 10f
}
fun drawStroke(points: List<Offset>) {
if (points.size < 2) return
val path = AndroidPath().apply {
moveTo(points.first().x, points.first().y)
for (index in 1 until points.size) {
lineTo(points[index].x, points[index].y)
}
}
canvas.drawPath(path, paint)
}
paths.forEach(::drawStroke)
drawStroke(currentPath)
return bitmap
} }
fun bitmapToBlob(bitmap: Bitmap): ByteArray { fun bitmapToBlob(bitmap: Bitmap): ByteArray {
@ -69,20 +125,103 @@ fun SignaturePad(
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
SignaturePadView( Box(
onReady = { signaturePadAdapter = it }, modifier = Modifier
.fillMaxWidth(0.85f)
.height(180.dp)
.align(Alignment.CenterHorizontally)
.background(
color = if (selectedMode == SignatureMode.Digital) {
Color.White
} else {
Color.White.copy(alpha = 0.55f)
},
shape = RoundedCornerShape(16.dp)
)
.padding(12.dp)
.onSizeChanged { size ->
canvasSize = Size(size.width.toFloat(), size.height.toFloat())
}
.pointerInput(selectedMode) {
if (selectedMode == SignatureMode.Manual) return@pointerInput
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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(200.dp) .height(150.dp)
.border(width = 1.dp, color = AppColor.LegacyRed) ) {
) SignatureModeOption(
title = "Manual Sign",
subtitle = "Signature on Paper",
selected = selectedMode == SignatureMode.Manual,
onClick = { selectedMode = SignatureMode.Manual },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(12.dp))
SignatureModeOption(
title = "Digital Sign",
subtitle = "Draw on pad",
selected = selectedMode == SignatureMode.Digital,
onClick = { selectedMode = SignatureMode.Digital },
modifier = Modifier.fillMaxWidth()
)
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
OutlinedButton( OutlinedButton(
onClick = { signaturePadAdapter?.clear() }, onClick = { clearSignature() },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(50.dp) .height(50.dp)
@ -92,10 +231,9 @@ fun SignaturePad(
Button( Button(
onClick = { onClick = {
val rawBitmap = signaturePadAdapter?.getSignatureBitmap() val bitmap = buildBitmap()
val bitmap = rawBitmap?.let { scaleBitmap(it) }
val signatureBlob = bitmap?.let { bitmapToBlob(it) } val signatureBlob = bitmap?.let { bitmapToBlob(it) }
onConfirm(bitmap, signatureBlob) onConfirm(selectedMode, bitmap, signatureBlob)
}, },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)

View File

@ -1189,11 +1189,10 @@ public class PrintReceipt {
private void printSignature(Bitmap signatureBitmap) { private void printSignature(Bitmap signatureBitmap) {
try { try {
sunmiPrinterService.setAlignment(1, null); sunmiPrinterService.setAlignment(1, null);
int targetHeight = 200; if (signatureBitmap.getWidth() > 130) {
if (signatureBitmap.getHeight() > targetHeight) { double size = 1.0 * signatureBitmap.getHeight() * 130 / signatureBitmap.getWidth();
double ratio = 1.0 * targetHeight / signatureBitmap.getHeight(); int newHeight = (int) size;
int newWidth = (int) (signatureBitmap.getWidth() * ratio); signatureBitmap = BitmapUtils.scale(signatureBitmap, 130, newHeight);
signatureBitmap = BitmapUtils.scale(signatureBitmap, newWidth, targetHeight);
} }
sunmiPrinterService.printBitmap(signatureBitmap, null); sunmiPrinterService.printBitmap(signatureBitmap, null);
sunmiPrinterService.printText("\n", null); sunmiPrinterService.printText("\n", null);

View File

@ -954,12 +954,6 @@ public abstract class BaseXPrint {
if (signatureBlob != null && signatureBlob.length > 0) { if (signatureBlob != null && signatureBlob.length > 0) {
Bitmap signatureBitmap = android.graphics.BitmapFactory.decodeByteArray(signatureBlob, 0, signatureBlob.length); Bitmap signatureBitmap = android.graphics.BitmapFactory.decodeByteArray(signatureBlob, 0, signatureBlob.length);
if (signatureBitmap != null) { if (signatureBitmap != null) {
int targetHeight = 200;
if (signatureBitmap.getHeight() > targetHeight) {
double ratio = 1.0 * targetHeight / signatureBitmap.getHeight();
int newWidth = (int) (signatureBitmap.getWidth() * ratio);
signatureBitmap = BitmapUtils.scale(signatureBitmap, newWidth, targetHeight);
}
sunmiPrinterService.printBitmap(signatureBitmap, innerResultCallback); sunmiPrinterService.printBitmap(signatureBitmap, innerResultCallback);
} }
} }