database add new column

This commit is contained in:
moon 2026-07-01 10:39:26 +06:30
parent 25153bb819
commit f7510c9bad
11 changed files with 185 additions and 65 deletions

View File

@ -55,6 +55,7 @@ import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.paylibs.model.TradeData
import com.utsmyanmar.paylibs.model.enums.TransCVM
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@RequiresApi(Build.VERSION_CODES.N)
@ -86,9 +87,18 @@ fun AppNavGraph(
}
composable(Routes.Signature.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
SignaturePad(
onBack = { navController.popBackStack() },
onConfirm = { navController.popBackStack() }
onConfirm = { _, bitmap ->
sharedViewModel.signBitmap = bitmap
navController.navigate(Routes.TransactionResult.route) {
popUpTo(Routes.Signature.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
@ -663,6 +673,14 @@ fun AppNavGraph(
launchSingleTop = true
}
},
onNavigateSignature = {
navController.navigate(Routes.Signature.route) {
popUpTo(Routes.Processing.route) {
inclusive = true
}
launchSingleTop = true
}
},
onNavigateMain = {
navController.navigate(Routes.Dashboard.route) {
popUpTo(Routes.Dashboard.route) {

View File

@ -2,21 +2,14 @@ package com.mob.utsmyanmar.ui.sending_to_host
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.mob.utsmyanmar.model.TransResultStatus
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import kotlinx.coroutines.delay
import androidx.compose.runtime.getValue
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultState
import com.mob.utsmyanmar.viewmodel.ProcessingTransaction
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import androidx.compose.runtime.collectAsState
import com.mob.utsmyanmar.model.TransactionStatus
import com.mob.utsmyanmar.ui.settlement.SettlementViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import kotlinx.coroutines.channels.Channel
private const val MOCK_HOST_DELAY_MS = 2000L
@ -26,6 +19,7 @@ fun ProcessingRoute(
transProcessViewModel: TransProcessViewModel,
settlementViewModel: SettlementViewModel,
onNavigateTransactionResult: () -> Unit,
onNavigateSignature: () -> Unit,
onNavigateMain: ()-> Unit
) {
@ -60,8 +54,12 @@ fun ProcessingRoute(
when(transStatus) {
TransResultStatus.SUCCESS -> {
sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value
if (requiresSignature(sharedViewModel.payDetail.value)) {
onNavigateSignature()
} else {
onNavigateTransactionResult()
}
}
TransResultStatus.FAIL -> {
sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value
@ -75,12 +73,6 @@ fun ProcessingRoute(
}
}
if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) {
ProcessingScreen(settlementStatus.processingState)
} else {
@ -89,3 +81,8 @@ fun ProcessingRoute(
}
private fun requiresSignature(payDetail: com.utsmyanmar.paylibs.model.PayDetail?): Boolean {
// return payDetail?.transCVM == TransCVM.SIGNATURE && payDetail.isFreeSign == false
return true;
}

View File

@ -1,5 +1,7 @@
package com.mob.utsmyanmar.ui.signature_pad
import android.graphics.Bitmap
import android.graphics.Paint
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
@ -29,32 +31,76 @@ 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.geometry.Size
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.unit.dp
import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
import android.graphics.Canvas as AndroidCanvas
import android.graphics.Path as AndroidPath
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 = {}
onConfirm: (SignatureMode, Bitmap?) -> Unit = { _, _ -> }
) {
val paths = remember { mutableStateListOf<List<Offset>>() }
val currentPath = remember { mutableStateListOf<Offset>() }
var selectedMode by remember { mutableStateOf(SignatureMode.Manual) }
var canvasSize by remember { mutableStateOf(Size.Zero) }
var strokeWidth: Float = 8f;
fun clearSignature() {
paths.clear()
currentPath.clear()
}
fun buildBitmap(): Bitmap? {
if (selectedMode == SignatureMode.Manual) return null
if (paths.isEmpty() && currentPath.isEmpty()) return null
if (canvasSize.width <= 0f || canvasSize.height <= 0f) return null
val bitmap = Bitmap.createBitmap(
canvasSize.width.toInt(),
canvasSize.height.toInt(),
Bitmap.Config.ARGB_8888
)
val canvas = AndroidCanvas(bitmap)
val paint = Paint().apply {
color = android.graphics.Color.BLACK
style = Paint.Style.STROKE
strokeWidth = strokeWidth
isAntiAlias = true
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
}
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
}
Scaffold(
topBar = {
AppBar(
@ -77,6 +123,9 @@ fun SignaturePad(
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(16.dp))
.padding(12.dp)
.onSizeChanged { size ->
canvasSize = Size(size.width.toFloat(), size.height.toFloat())
}
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { startOffset ->
@ -163,7 +212,13 @@ fun SignaturePad(
}
Button(
onClick = onConfirm,
onClick = {
val bitmap = buildBitmap()
if (selectedMode == SignatureMode.Digital && bitmap == null) {
return@Button
}
onConfirm(selectedMode, bitmap)
},
modifier = Modifier
.weight(1f)
.height(50.dp),
@ -179,7 +234,7 @@ fun SignaturePad(
}
}
private enum class SignatureMode {
enum class SignatureMode {
Manual,
Digital
}

View File

@ -316,7 +316,7 @@ class SharedViewModel @Inject constructor(
private fun printReceipt(isMerchantCopy: Boolean) {
PrintXReceipt.getInstance()
.printSmileReceipt(payDetail.value, isMerchantCopy, object : PrintXStatus {
.printSmileReceipt(payDetail.value, isMerchantCopy, signBitmap, object : PrintXStatus {
override fun onSuccess() {
@ -351,7 +351,7 @@ class SharedViewModel @Inject constructor(
}
PrintXReceipt.getInstance()
.printSmileReceipt(getPayDetail(), isFirstPrint, object : PrintXStatus {
.printSmileReceipt(getPayDetail(), isFirstPrint, signBitmap, object : PrintXStatus {
override fun onSuccess() {

View File

@ -25,7 +25,7 @@ import com.utsmyanmar.baselib.db.model.RidEntity;
import com.utsmyanmar.paylibs.model.PayDetail;
@Database(entities = {PayDetail.class, ChipAidEntity.class, PayWaveEntity.class, PayPassEntity.class, QuickPassEntity.class, RidEntity.class, MPUCtlsEntity.class, BinEntity.class},version = 12,exportSchema = false)
@Database(entities = {PayDetail.class, ChipAidEntity.class, PayWaveEntity.class, PayPassEntity.class, QuickPassEntity.class, RidEntity.class, MPUCtlsEntity.class, BinEntity.class}, version = 13, exportSchema = false)
@TypeConverters({SettleDataConverter.class, TransCVMConverter.class, CardSchemeConverter.class})
public abstract class AppDatabase extends RoomDatabase {
public static volatile AppDatabase INSTANCE;

View File

@ -1,10 +1,6 @@
package com.utsmyanmar.baselib.di;
import static com.utsmyanmar.baselib.db.AppDatabase.INSTANCE;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
@ -16,8 +12,6 @@ import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import com.utsmyanmar.baselib.db.AppDatabase;
import com.utsmyanmar.baselib.db.dao.BinDao;
@ -28,8 +22,6 @@ import com.utsmyanmar.baselib.db.dao.PayPassDao;
import com.utsmyanmar.baselib.db.dao.PayWaveDao;
import com.utsmyanmar.baselib.db.dao.QuickPassDao;
import com.utsmyanmar.baselib.db.dao.RidDao;
import com.utsmyanmar.baselib.util.SecureKeyUtil;
import java.io.File;
import java.io.IOException;
@ -39,13 +31,12 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.concurrent.Executors;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.security.cert.CertificateException;
import dagger.Module;
import dagger.Provides;
@ -353,6 +344,14 @@ public class DatabaseModule {
}
};
static final Migration MIGRATION_12_13 = new Migration(12, 13) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE paydetail "
+ " ADD COLUMN signatureBlob BLOB");
}
};
private static byte[] getDatabaseKey(Context context) throws KeyStoreException, CertificateException,
NoSuchAlgorithmException, IOException, NoSuchProviderException,
InvalidAlgorithmParameterException, UnrecoverableEntryException {
@ -398,13 +397,14 @@ public class DatabaseModule {
@Provides
@Singleton
public static AppDatabase provideAppDatabase(Application application) {
return Room.databaseBuilder(application, AppDatabase.class,"yoma")
return Room.databaseBuilder(application, AppDatabase.class, "yoma")
// .fallbackToDestructiveMigration()
.addMigrations(MIGRATION_7_8)
.addMigrations(MIGRATION_8_9)
.addMigrations(MIGRATION_9_10)
.addMigrations(MIGRATION_10_11)
.addMigrations(MIGRATION_11_12)
.addMigrations(MIGRATION_12_13)
.allowMainThreadQueries()
.addCallback(new RoomDatabase.Callback() {
@Override
@ -425,8 +425,6 @@ public class DatabaseModule {
}
private static boolean isDatabaseFileCorruptOrWrongKey(Application application) {
File dbFile = application.getDatabasePath(DATABASE_NAME);
return dbFile.exists() && dbFile.length() > 0;
@ -470,7 +468,7 @@ public class DatabaseModule {
@Provides
@Singleton
public static PayDetailDao providePayDetailDao(AppDatabase appDatabase){
public static PayDetailDao providePayDetailDao(AppDatabase appDatabase) {
return appDatabase.payDetailDao();
}
@ -507,9 +505,13 @@ public class DatabaseModule {
@Provides
@Singleton
public static MPUCtlsDao provideMPUCtlsDao(AppDatabase appDatabase) { return appDatabase.mpuCtlsDao(); }
public static MPUCtlsDao provideMPUCtlsDao(AppDatabase appDatabase) {
return appDatabase.mpuCtlsDao();
}
@Provides
@Singleton
public static BinDao provideBinDao(AppDatabase appDatabase) { return appDatabase.binDao(); }
public static BinDao provideBinDao(AppDatabase appDatabase) {
return appDatabase.binDao();
}
}

View File

@ -74,6 +74,7 @@ public class PayDetail implements Serializable {
public String cardOrgCode = ""; // International credit card company code (domain 63.1)
public String reference = ""; // Remarks
public String eSignHexData = ""; // Electronic signature hexadecimal data
private byte[] signatureBlob; // Signature store as blob type
public int transPlatform; // Supported trading platforms: 0: bank card 1: Alipay 2: WeChat 3: UnionPay wallet
public int transactionType; // 1: Consumption 2: Consumption cancellation; 3: Return 4. Pre-authorization 5. Pre-authorization cancellation 6. Pre-authorization completed 7. Pre-authorization completed cancellation 8. Settlement 9. Sign-in 10. Sign-out 11. Transaction query
@ -1359,4 +1360,11 @@ public class PayDetail implements Serializable {
this.currency = currency;
}
public byte[] getSignatureBlob() {
return signatureBlob;
}
public void setSignatureBlob(byte[] signatureBlob) {
this.signatureBlob = signatureBlob;
}
}

View File

@ -722,8 +722,7 @@ public abstract class BaseXPrint {
sunmiPrinterService.sendRAWData(boldOn(), innerResultCallback);
sunmiPrinterService.setAlignment(1, innerResultCallback);
// default
String cvmText = "NO SIGNATURE REQUIRED";
String cvmText;
switch (payDetail.getTransCVM()) {
case SIGNATURE: cvmText = "SIGN : -----------------------------";
break;
@ -735,9 +734,10 @@ public abstract class BaseXPrint {
cvmText = "NO SIGNATURE REQUIRED";
break;
}
addSignatureBitmap(payDetail);
sunmiPrinterService.printText(cvmText, innerResultCallback);
// if (payDetail.getIsFreeSign()) {
// sunmiPrinterService.printText("NO SIGNATURE REQUIRED", innerResultCallback);
// } else {
@ -948,4 +948,14 @@ public abstract class BaseXPrint {
}
};
private void addSignatureBitmap(PayDetail payDetail) throws RemoteException {
byte[] signatureBlob = payDetail.getSignatureBlob();
if (signatureBlob != null && signatureBlob.length > 0) {
Bitmap signatureBitmap = android.graphics.BitmapFactory.decodeByteArray(signatureBlob, 0, signatureBlob.length);
if (signatureBitmap != null) {
sunmiPrinterService.printBitmap(signatureBitmap, innerResultCallback);
}
}
}
}

View File

@ -2,6 +2,7 @@ package com.utsmyanmar.paylibs.print.printx;
import com.utsmyanmar.paylibs.model.PayDetail;
import com.utsmyanmar.paylibs.utils.enums.HostType;
import android.graphics.Bitmap;
import java.util.List;
@ -9,7 +10,7 @@ public interface PrintX {
void printSimple();
void printSmileReceipt(PayDetail payDetail, boolean isMerchantCopy,PrintXStatus printXStatus);
void printSmileReceipt(PayDetail payDetail, boolean isMerchantCopy, Bitmap signBitmap, PrintXStatus printXStatus);
void printISOReceipt(PayDetail payDetail);

View File

@ -120,7 +120,7 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
@Override
public void printSmileReceipt(PayDetail payDetail, boolean isMerchantCopy,PrintXStatus printXStatus) {
public void printSmileReceipt(PayDetail payDetail, boolean isMerchantCopy, Bitmap signBitmap, PrintXStatus printXStatus) {
try {
this.callbackStatus = printXStatus;
@ -129,9 +129,9 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
} else if (payDetail.getTransactionType() == TransactionsType.SETTLEMENT.value) {
printSmileSettlementReport(payDetail,printXStatus);
} else if (payDetail.getICC55() == null || payDetail.getICC55().isEmpty() || payDetail.getAccountType().equals(CardScheme.MPU_CTLS.name)) {
printSmileMPUReceipt(payDetail, isMerchantCopy);
printSmileMPUReceipt(payDetail, isMerchantCopy, signBitmap);
} else if (!payDetail.getICC55().equals("")) {
printSmileEMVReceipt(payDetail, isMerchantCopy);
printSmileEMVReceipt(payDetail, isMerchantCopy, signBitmap);
}
} catch (Exception e) {
e.printStackTrace();
@ -256,7 +256,7 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
}
}
private void printSmileMPUReceipt(PayDetail payDetail, boolean isMerchantCopy) {
private void printSmileMPUReceipt(PayDetail payDetail, boolean isMerchantCopy, Bitmap signBitmap) {
try {
setHeight(0x12);
@ -268,6 +268,7 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
if (payDetail.getTradeAnswerCode().equals(Constant.ANSWER_CODE_APPROVED) || payDetail.getTradeAnswerCode().equals(Constant.ANSWER_CODE_ACCEPT)) {
printTransFooter(false,payDetail, isMerchantCopy);
printSignatureBlock(payDetail, signBitmap);
} else {
String receiptFooter = SystemParamsOperation.getInstance().getReceiptFooter();
sunmiPrinterService.printText("\n", null);
@ -282,7 +283,7 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
}
}
private void printSmileEMVReceipt(PayDetail payDetail, boolean isMerchantCopy) {
private void printSmileEMVReceipt(PayDetail payDetail, boolean isMerchantCopy, Bitmap signBitmap) {
try {
setHeight(0x12);
printLogo();
@ -292,6 +293,7 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
if (payDetail.getTradeAnswerCode().equals(Constant.ANSWER_CODE_APPROVED) || payDetail.getTradeAnswerCode().equals(Constant.ANSWER_CODE_ACCEPT)) {
printTransFooter(false,payDetail, isMerchantCopy);
printSignatureBlock(payDetail, signBitmap);
} else {
String receiptFooter = SystemParamsOperation.getInstance().getReceiptFooter();
sunmiPrinterService.setFontSize(textSize, null);
@ -307,6 +309,32 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
}
}
private void printSignatureBlock(PayDetail payDetail, Bitmap signBitmap) {
try {
if (payDetail.getTransCVM() != com.utsmyanmar.paylibs.model.enums.TransCVM.SIGNATURE) {
return;
}
if (signBitmap == null) {
return;
}
Bitmap printableBitmap = signBitmap;
if (printableBitmap.getWidth() > 130) {
double size = 1.0 * printableBitmap.getHeight() * 130 / printableBitmap.getWidth();
int newHeight = (int) size;
printableBitmap = BitmapUtils.scale(printableBitmap, 130, newHeight);
}
sunmiPrinterService.lineWrap(1, innerResultCallback);
sunmiPrinterService.setAlignment(1, innerResultCallback);
sunmiPrinterService.printBitmap(printableBitmap, innerResultCallback);
sunmiPrinterService.lineWrap(1, innerResultCallback);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void printTransDetailWave(PayDetail payDetail) {
try {

View File

@ -3,6 +3,7 @@ package com.utsmyanmar.paylibs.print.printx;
import com.utsmyanmar.paylibs.model.PayDetail;
import com.utsmyanmar.paylibs.utils.enums.HostType;
import android.graphics.Bitmap;
import java.util.List;
public class PrintXReceipt {
@ -21,8 +22,8 @@ public class PrintXReceipt {
printX.printSimple();
}
public void printSmileReceipt(PayDetail payDetail,boolean isMerchantCopy,PrintXStatus printXStatus) {
printX.printSmileReceipt(payDetail,isMerchantCopy,printXStatus);
public void printSmileReceipt(PayDetail payDetail,boolean isMerchantCopy, Bitmap signBitmap,PrintXStatus printXStatus) {
printX.printSmileReceipt(payDetail,isMerchantCopy,signBitmap,printXStatus);
}
public void printSmileSettlementReport(PayDetail payDetail,PrintXStatus printXStatus) {