diff --git a/.gradle-local/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.lck b/.gradle-local/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.lck deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle-local/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.part b/.gradle-local/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.part deleted file mode 100644 index e69de29..0000000 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 10290a7..a615392 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,9 +5,6 @@ plugins { id("com.google.dagger.hilt.android") } -// Set default build variant -project.ext.set("android.defaultPublishConfig", "uatDebug") - android { namespace = "com.mob.utsmyanmar" compileSdk { @@ -43,8 +40,8 @@ android { dimension = "env" applicationIdSuffix = ".uat" versionNameSuffix = "-UAT" - versionCode = 3 - versionName = "2.1" + versionCode = 12 + versionName = "3.0" buildConfigField("String", "ENV", "\"UAT\"") resValue("string", "app_name", "MOB-UAT") } @@ -62,6 +59,14 @@ android { "proguard-rules.pro" ) } + debug { + isMinifyEnabled = false + isDebuggable = true +// proguardFiles( +// getDefaultProguardFile("proguard-android-optimize.txt"), +// "proguard-rules.pro" +// ) + } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -120,4 +125,6 @@ dependencies { implementation(project(":ecr")) implementation(project(":xpay")) implementation(project(":cmhl")) + + implementation("se.warting.signature:signature-pad:0.2.0") } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5947c89..ce0bac5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,8 @@ Unit) { +private fun InsertCardRow(onMockClick: () -> Unit) { Row( modifier = Modifier.height(44.dp), verticalAlignment = Alignment.CenterVertically @@ -301,7 +291,8 @@ private fun InsertCardRow(onMockClick: ()->Unit) { painter = painterResource(id = R.drawable.ic_insert_card), contentDescription = null, tint = Color.LegacyRed, - modifier = Modifier.size(28.dp) + modifier = Modifier + .size(28.dp) .clickable(onClick = onMockClick) ) @@ -395,13 +386,49 @@ private fun CardLogoRow() { .fillMaxWidth() .height(42.dp) .padding(horizontal = 14.dp), - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically ) { - Text("VISA", style = MaterialTheme.typography.titleLarge) - Text("MC", style = MaterialTheme.typography.titleMedium) - Text("MPU", style = MaterialTheme.typography.titleLarge) - Text("UnionPay", style = MaterialTheme.typography.labelMedium) + CardLogo( + icon = painterResource(R.drawable.logo_visa), + name = "visa", + ) + CardLogo( + icon = painterResource(R.drawable.logo_master), + name = "master", + ) + CardLogo( + icon = painterResource(R.drawable.logo_mpu), + name = "master", + ) + CardLogo( + icon = painterResource(R.drawable.logo_union_pay), + name = "master", + ) + } + } +} + +@Composable +private fun CardLogo( + icon: Painter, + name: String, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(4.dp), + color = Color.White + ) { + Box( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), + contentAlignment = Alignment.Center + ) { + Image( + painter = icon, + contentDescription = name, + modifier = Modifier.height(30.dp) + ) } } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt index bb7c804..fdd28f8 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/cardwaiting/CardWaitingViewModel.kt @@ -16,14 +16,13 @@ import com.utsmyanmar.ecr.ECRHelper import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import sunmi.sunmiui.utils.LogUtil class CardWaitingViewModel( private val cardReadViewModel: CardReaderViewModel, diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt b/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt index eb9e068..232856e 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/components/appbar/AppBar.kt @@ -5,14 +5,14 @@ import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.material3.MaterialTheme import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.compose.dropUnlessResumed import com.mob.utsmyanmar.ui.theme.Color -import com.mob.utsmyanmar.ui.theme.Primary import com.mob.utsmyanmar.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @@ -35,7 +35,7 @@ fun AppBar( navigationIcon = { if (icon != null && onIconClick != null) { IconButton( - onClick = onIconClick + onClick = dropUnlessResumed { onIconClick() } ) { Icon( imageVector = icon, diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardComponents.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardComponents.kt index fcba6ed..45ad090 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardComponents.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardComponents.kt @@ -104,7 +104,9 @@ fun SummaryCard( onClickLastSync: () -> Unit, lastSyncTime: Long?, totalSaleCount: Int, - totalSaleAmount: Long + totalSaleAmount: Long, + settlementStatusText: String, + settlementDateTimeText: String ) { var now by remember { mutableLongStateOf(System.currentTimeMillis()) } LaunchedEffect(Unit) { @@ -145,8 +147,8 @@ fun SummaryCard( SummaryItem( title = "Settlement", - value = "Completed", - subtitle = "Today 11:00 PM", + value = settlementStatusText, + subtitle = settlementDateTimeText, icon = Icons.Default.Check, iconBg = Color.CrimsonRed ) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardMenuItems.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardMenuItems.kt index aa7b437..066884a 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardMenuItems.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardMenuItems.kt @@ -1,5 +1,8 @@ package com.mob.utsmyanmar.ui.dashboard +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountBalanceWallet import androidx.compose.material.icons.filled.CreditCard @@ -7,7 +10,6 @@ import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.LockOpen import androidx.compose.material.icons.filled.Replay import androidx.compose.material.icons.filled.SwapHoriz -import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -15,6 +17,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.mob.utsmyanmar.R import com.mob.utsmyanmar.ui.theme.Color +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation class DashboardMenuItem( val title: String, @@ -22,121 +25,209 @@ class DashboardMenuItem( val onClick: () -> Unit ) +val settlementEnabled = false +val voidEnabled = true +val refundEnabled = true +val preAuthEnabled = true +val preAuthVoidEnabled = true +val preAuthCompleteEnabled = true +val preAuthCompleteVoidEnabled = true +val cashAdvEnabled = true + fun buildMenuItems( onNavigateAmount: (String) -> Unit, onNavigateSignOn: () -> Unit, onNavigateSettlement: () -> Unit, onNavigateHistory: () -> Unit, onNavigateAction: (String) -> Unit -): List = listOf( - DashboardMenuItem( - "Sale", - { - Icon( - painterResource(R.drawable.ic_terminal), - contentDescription = null, - modifier = Modifier.size(40.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAmount("Sale") }, - DashboardMenuItem( - "History", - { - Icon( - painterResource(R.drawable.ic_history), - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateHistory() }, - DashboardMenuItem( - "Sign On", - { - Icon( - painterResource(R.drawable.ic_sign_on), - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateSignOn() }, - DashboardMenuItem( - "Settlement", - { - Icon( - painterResource(R.drawable.ic_settlement), - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateSettlement() }, - DashboardMenuItem( - "Void", - { - Icon( - Icons.Default.Lock, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAction("Void") }, - DashboardMenuItem( - "Refund", - { - Icon( - Icons.Default.Replay, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAction("Refund") }, - DashboardMenuItem( - "Pre-Auth", - { - Icon( - Icons.Default.Lock, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAmount("Pre-Auth") }, - DashboardMenuItem( - "Pre-Auth Void", - { - Icon( - Icons.Default.LockOpen, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAction("Pre-Auth Void") }, - DashboardMenuItem( - "Pre-Auth Complete", - { - Icon( - Icons.Default.CreditCard, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAction("Pre-Auth Complete") }, - DashboardMenuItem( - "Pre-Auth Comp Void", - { - Icon( - Icons.Default.SwapHoriz, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAction("Pre-Auth Complete Void") }, - DashboardMenuItem( - "Cash Out", - { - Icon( - Icons.Default.AccountBalanceWallet, - contentDescription = null, - modifier = Modifier.size(32.dp), - tint = Color.LegacyRed - ) - }) { onNavigateAmount("Cash Out") }, -) +): List = buildList { + + val mmqrEnabled = SystemParamsOperation.getInstance().mmqrStatus + + add( + DashboardMenuItem( + "Sale", + { + Icon( + painterResource(R.drawable.ic_terminal), + contentDescription = null, + modifier = Modifier.size(40.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAmount("Sale") } + ) + + + if (mmqrEnabled) { + add( + DashboardMenuItem( + "MMQR", + { + Image( + painter = painterResource(R.drawable.ic_mmqr_logo), + contentDescription = null, + modifier = Modifier.height(48.dp) + ) + } + ) { } + ) + } + add( + DashboardMenuItem( + "History", + { + Icon( + painterResource(R.drawable.ic_history), + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateHistory() } + ) + + add( + DashboardMenuItem( + "Sign On", + { + Icon( + painterResource(R.drawable.ic_sign_on), + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateSignOn() } + ) + + if (settlementEnabled) { + add( + DashboardMenuItem( + "Settlement", + { + Icon( + painterResource(R.drawable.ic_settlement), + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateSettlement() } + ) + } + + if (voidEnabled) { + add( + DashboardMenuItem( + "Void", + { + Icon( + Icons.Default.Lock, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAction("Void") } + ) + } + + if (refundEnabled) { + add( + DashboardMenuItem( + "Refund", + { + Icon( + Icons.Default.Replay, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAction("Refund") } + ) + } + + if (preAuthEnabled) { + add( + DashboardMenuItem( + "Pre-Auth", + { + Icon( + Icons.Default.Lock, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAmount("Pre-Auth") } + ) + } + + if (preAuthVoidEnabled) { + add( + DashboardMenuItem( + "Pre-Auth Void", + { + Icon( + Icons.Default.LockOpen, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAction("Pre-Auth Void") } + ) + } + + if (preAuthCompleteEnabled) { + add( + DashboardMenuItem( + "Pre-Auth Complete", + { + Icon( + Icons.Default.CreditCard, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAction("Pre-Auth Complete") } + ) + } + + if (preAuthCompleteVoidEnabled) { + add( + DashboardMenuItem( + "Pre-Auth Comp Void", + { + Icon( + Icons.Default.SwapHoriz, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAction("Pre-Auth Complete Void") } + ) + } + + if (cashAdvEnabled) { + add( + DashboardMenuItem( + "Cash Adv", + { + Icon( + Icons.Default.AccountBalanceWallet, + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = Color.LegacyRed + ) + } + ) { onNavigateAmount("Cash Adv") } + ) + } + + +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt index f3b9f9c..87ac2cd 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt @@ -23,6 +23,9 @@ import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Sync import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -47,6 +50,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import com.mob.utsmyanmar.R import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.device_info.DeviceInfoUiState @@ -176,6 +180,29 @@ fun DashboardScreen2( ) } + if (dashboardUiState.isSyncing) { + Dialog(onDismissRequest = {}) { + Card( + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(8.dp) + ) { + Column( + modifier = Modifier.padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator(color = Color.LegacyRed) + Spacer(Modifier.height(16.dp)) + Text( + text = dashboardUiState.syncStatusText, + style = MaterialTheme.typography.bodyMedium, + color = Color.Black + ) + } + } + } + } + ModalNavigationDrawer( drawerState = drawerState, drawerContent = { @@ -394,7 +421,9 @@ fun DashboardScreen2( onClickLastSync = onClickLastSync, lastSyncTime = dashboardUiState.lastSyncTime, totalSaleCount = dashboardUiState.totalSaleCount, - totalSaleAmount = dashboardUiState.totalSaleAmount + totalSaleAmount = dashboardUiState.totalSaleAmount, + settlementStatusText = dashboardUiState.settlementStatusText, + settlementDateTimeText = dashboardUiState.settlementDateTimeText ) Spacer(Modifier.height(12.dp)) } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt index 31205b8..e46cb55 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardUiState.kt @@ -15,5 +15,9 @@ data class DashboardUiState( val lastSyncTime: Long? = null, val carouselUrls: List = emptyList(), val totalSaleCount: Int = 0, - val totalSaleAmount: Long = 0L + val totalSaleAmount: Long = 0L, + val settlementStatusText: String = "Pending", + val settlementDateTimeText: String = "--/-- --:--", + val isSyncing: Boolean = false, + val syncStatusText: String = "Connecting to TMS server..." ) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt index 6cd4935..fe9a6d8 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.utsmyanmar.baselib.repo.Repository import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.system.SystemDateTime import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import dagger.hilt.android.lifecycle.HiltViewModel @@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.math.BigDecimal import java.text.DecimalFormat +import java.util.Calendar import javax.inject.Inject @HiltViewModel @@ -31,11 +33,23 @@ class DashboardViewModel @Inject constructor( init { viewModelScope.launch { repository.getLastTenTransactions().asFlow().collect { payDetails -> + val latestSettlement = payDetails.lastOrNull { + it.transactionType == TransactionsType.SETTLEMENT.value + } + val isCurrentBusinessSettlement = latestSettlement?.isCurrentBusinessSettlement() == true _uiState.update { it.copy( recentTransactions = payDetails.map { pd -> pd.toRecord() }, totalSaleCount = payDetails.totalSaleCount(), - totalSaleAmount = payDetails.totalSaleAmount() + totalSaleAmount = payDetails.totalSaleAmount(), + settlementStatusText = if (isCurrentBusinessSettlement) { + "Completed" + } else { + "Pending" + }, + settlementDateTimeText = latestSettlement + ?.let { buildDateTime(it.TradeDate, it.TradeTime) } + ?: "--/-- --:--" ) } } @@ -58,6 +72,18 @@ class DashboardViewModel @Inject constructor( .takeIf { it > 0L } _uiState.update { it.copy(lastSyncTime = time) } } + + fun startSync() { + _uiState.update { it.copy(isSyncing = true, syncStatusText = "Connecting to TMS server...") } + } + + fun finishSync(message: String) { + _uiState.update { it.copy(syncStatusText = message) } + viewModelScope.launch { + kotlinx.coroutines.delay(1500) + _uiState.update { it.copy(isSyncing = false) } + } + } } private fun PayDetail.toRecord(): TrnxRecord { @@ -101,7 +127,7 @@ private fun formatTypeLabel(raw: String): String = when { raw.contains("REFUND", ignoreCase = true) -> "Refund" raw.contains("SETTLEMENT", ignoreCase = true) -> "Settlement" raw.contains("PRE", ignoreCase = true) -> "Pre-Auth" - raw.contains("CASH", ignoreCase = true) -> "Cash Out" + raw.contains("CASH", ignoreCase = true) -> "Cash Adv" raw.contains("WAVE", ignoreCase = true) -> "Wave Pay" else -> raw.lowercase().replaceFirstChar { it.uppercaseChar() } } @@ -112,3 +138,18 @@ private fun buildDateTime(date: String?, time: String?): String { val t = (time ?: "").padStart(6, '0') return "${d.take(2)}/${d.drop(2).take(2)} ${t.take(2)}:${t.drop(2).take(2)}" } + +private fun PayDetail.isCurrentBusinessSettlement(): Boolean { + if (transactionType != TransactionsType.SETTLEMENT.value) return false + if (TradeDate != SystemDateTime.getMMDD()) return false + + val now = Calendar.getInstance() + val cutoff = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 23) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + return now.before(cutoff) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/SeeMoreScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/SeeMoreScreen.kt index 43637ca..b429823 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/SeeMoreScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/SeeMoreScreen.kt @@ -51,7 +51,7 @@ fun SeeMoreScreen( SeeMoreItem("Pre-Auth Void", Icons.Default.LockOpen), SeeMoreItem("Pre-Auth Complete", Icons.Default.CreditCard), SeeMoreItem("Pre-Auth Complete Void", Icons.Default.SwapHoriz), - SeeMoreItem("Cash Out", Icons.Default.AccountBalanceWallet) + SeeMoreItem("Cash Adv", Icons.Default.AccountBalanceWallet) ) Scaffold( diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/error/TransactionErrorScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/error/TransactionErrorScreen.kt new file mode 100644 index 0000000..bf27286 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/error/TransactionErrorScreen.kt @@ -0,0 +1,139 @@ +package com.mob.utsmyanmar.ui.error + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.theme.Color +import kotlinx.coroutines.delay + +private const val AUTO_DISMISS_DELAY_MS = 3000L + +@Composable +fun TransactionErrorScreen( + isSuccess: Boolean, + message: String, + onDone: () -> Unit +) { + BackHandler(enabled = true) { + onDone() + } + + LaunchedEffect(Unit) { + delay(AUTO_DISMISS_DELAY_MS) + onDone() + } + + TransactionErrorContent(isSuccess= isSuccess, message = message) +} + +@Composable +private fun TransactionErrorContent(isSuccess: Boolean,message: String) { + Scaffold( + containerColor = Color.IvoryBeige + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.IvoryBeige) + .padding(paddingValues) + .statusBarsPadding() + .navigationBarsPadding() + .padding(horizontal = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(72.dp)) + + FailIcon(isSuccess) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "Transaction Failed", + color = Color.LegacyRed, + style = MaterialTheme.typography.headlineLarge + ) + + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = message.ifBlank { "Something went wrong. Please try again." }, + color = Color.Gray, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center + ) + } + } +} + +@Composable +private fun FailIcon(isSuccess: Boolean) { + Box( + modifier = Modifier.size(132.dp), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(132.dp) + .background( + color = if (isSuccess) Color.Success.copy(alpha = 0.12f) else Color.Error.copy(alpha = 0.12f), + shape = CircleShape + ) + ) + + Box( + modifier = Modifier + .size(96.dp) + .background( + color = if (isSuccess) Color.Success else Color.Error, + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + if(isSuccess) { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = "Success", + tint = Color.White, + modifier = Modifier.size(64.dp) + ) + } else { + Icon( + imageVector = Icons.Rounded.Close, + contentDescription = "Failed", + tint = Color.White, + modifier = Modifier.size(64.dp) + ) + } + + } + } +} + +@P2Preview +@Composable +private fun PreviewTransactionErrorScreen() { + TransactionErrorContent(isSuccess = true, message = "Reversal Success") +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/AmountRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/AmountRoute.kt index 3872e30..08be2bd 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/AmountRoute.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/AmountRoute.kt @@ -74,7 +74,7 @@ private fun String.toTransactionConfig(): TransactionConfig { processCode = ProcessCode.PRE_AUTH_COMPLETE_VOID + ProcessCode.SMART + ProcessCode.TO_ACCOUNT ) - "cash out" -> TransactionConfig( + "cash adv" -> TransactionConfig( transactionName = "CASH_OUT", transactionType = TransactionsType.CASH_OUT, processCode = ProcessCode.CASH_ADVANCE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt index 3e76294..8b064f0 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/input_amount/InputAmountScreen.kt @@ -49,17 +49,23 @@ fun InputAmount( title: String = "Amount", onBackClick: () -> Unit = {}, onChargeClick: (String) -> Unit = {} -){ +) { var amount by remember { mutableStateOf("") } val prefixLabel = "MMK" val supportingText = "Enter the amount to continue" Scaffold( - topBar = { AppBar(title = "Amount", icon = Icons.AutoMirrored.Rounded.ArrowBack, onIconClick = onBackClick) }, + topBar = { + AppBar( + title = "Amount", + icon = Icons.AutoMirrored.Rounded.ArrowBack, + onIconClick = onBackClick + ) + }, containerColor = Color.IvoryBeige - ) {paddingValues -> - + ) { paddingValues -> + Column( modifier = Modifier .padding(paddingValues) @@ -72,9 +78,10 @@ fun InputAmount( modifier = Modifier .fillMaxSize() .weight(2f), - ){ + ) { Card( - modifier = Modifier.align(Alignment.CenterEnd) + modifier = Modifier + .align(Alignment.CenterEnd) .clickable(enabled = true) { amount = amount.dropLast(1) }, @@ -101,29 +108,21 @@ fun InputAmount( modifier = Modifier.align(Alignment.CenterHorizontally) ) Spacer(modifier = Modifier.height(8.dp)) + Text( + text = formatAmount(amount.ifEmpty { "0" }), + color = Color.LegacyRed, + style = MaterialTheme.typography.displayLarge + ) - Row( - modifier = Modifier.align(Alignment.CenterHorizontally), - verticalAlignment = Alignment.Bottom - ) { - if (prefixLabel.isNotBlank()) { - Text( - text = prefixLabel, - color = Color.LegacyRed, - style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Medium), - modifier = Modifier.padding(end = 10.dp, bottom = 6.dp) - ) - } - + if (prefixLabel.isNotBlank()) { Text( - text = formatAmount(amount.ifEmpty { "0" }), + text = prefixLabel, color = Color.LegacyRed, - style = MaterialTheme.typography.displayLarge + style = MaterialTheme.typography.displaySmall.copy(fontWeight = FontWeight.Medium), + modifier = Modifier.padding(end = 10.dp, bottom = 6.dp) ) } - Spacer(modifier = Modifier.height(14.dp)) - Text( text = supportingText, color = Color.Gray, @@ -132,14 +131,12 @@ fun InputAmount( ) } } - - //second container Column( modifier = Modifier .fillMaxWidth() .weight(3f), verticalArrangement = Arrangement.Bottom - ){ + ) { NumericKeypad( keys = amountKeys, modifier = Modifier.fillMaxSize(), @@ -148,14 +145,14 @@ fun InputAmount( } ) } - + Spacer(Modifier.height(16.dp)) //third container Box( modifier = Modifier .fillMaxWidth() .weight(0.5f) - ){ + ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) @@ -178,7 +175,8 @@ fun InputAmount( onClick = { if (amount.isNotEmpty()) { onChargeClick(amount) - } }, + } + }, modifier = Modifier .weight(1f) .height(56.dp), @@ -191,14 +189,14 @@ fun InputAmount( disabledContentColor = Color.White ) ) { - Text( - text = "Next", - style = MaterialTheme.typography.titleSmall - ) + Text( + text = "Next", + style = MaterialTheme.typography.titleSmall + ) } } } - + } } } @@ -236,20 +234,21 @@ private fun formatAmount(value: String): String { val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L) if (!normalized.contains(".")) { - return groupedWholePart + return "$groupedWholePart.00" } val decimalPart = normalized.substringAfter(".", "") return if (normalized.endsWith(".")) { - "$groupedWholePart." + "$groupedWholePart.00" } else { - "$groupedWholePart.$decimalPart" + val padded = decimalPart.padEnd(2, '0').take(2) + "$groupedWholePart.$padded" } } @P2Preview @P3Preview @Composable -fun PreviewInputAmount(){ +fun PreviewInputAmount() { InputAmount(); } \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt index 658e93e..7c5af70 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/manual_entry/ManualEntryRoute.kt @@ -39,7 +39,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue @@ -233,8 +232,7 @@ private fun ManualCardEntryScreen( singleLine = true, modifier = Modifier.fillMaxWidth(), textStyle = MaterialTheme.typography.bodyLarge.copy( - letterSpacing = 2.sp, - fontFamily = FontFamily.Monospace + letterSpacing = 2.sp ) ) @@ -263,8 +261,7 @@ private fun ManualCardEntryScreen( .width(160.dp) .focusRequester(expiryFocusRequester), textStyle = MaterialTheme.typography.bodyLarge.copy( - letterSpacing = 2.sp, - fontFamily = FontFamily.Monospace + letterSpacing = 2.sp ) ) } @@ -284,7 +281,6 @@ private fun ManualCardEntryScreen( Text( text = "**** **** **** ${pan.takeLast(4)}", style = MaterialTheme.typography.bodyMedium, - fontFamily = FontFamily.Monospace, letterSpacing = 1.5.sp ) if (expiry.length == 4) { diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt index 438c86a..d5ac4ef 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt @@ -50,4 +50,7 @@ sealed class Routes(val route: String) { data object Disable : Routes("disable/{message}") { fun createRoute(message: String): String = "disable/${Uri.encode(message)}" } + data object TransactionError : Routes("transaction_error/{isSuccess}/{message}") { + fun createRoute(isSuccess: Boolean, message: String): String = "transaction_error/$isSuccess/${Uri.encode(message)}" + } } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/print_receipt/PrintReceiptScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/print_receipt/PrintReceiptScreen.kt index e53d531..723bbfb 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/print_receipt/PrintReceiptScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/print_receipt/PrintReceiptScreen.kt @@ -2,27 +2,29 @@ package com.mob.utsmyanmar.ui.print_receipt import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +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.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CircularProgressIndicator 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.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -30,10 +32,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -44,15 +43,15 @@ import com.mob.utsmyanmar.ui.transaction_result.TransactionResultState import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.utsmyanmar.paylibs.utils.POSUtil - -import kotlin.math.abs +import com.utsmyanmar.paylibs.utils.PrintStatus import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds -private const val PRINT_ANIMATION_DURATION_MS = 2300 -private const val RESET_ANIMATION_DURATION_MS = 350 -private const val PRINTED_HOLD_DELAY_MS = 500L -private const val OFFSCREEN_BUFFER_PX = 120f +private enum class ReceiptCopy(val label: String) { + MERCHANT("MERCHANT COPY"), + CUSTOMER("CUSTOMER COPY") +} @Composable fun PrintReceiptScreen( @@ -64,9 +63,58 @@ fun PrintReceiptScreen( val state by transactionResultViewModel.state.collectAsStateWithLifecycle() val scope = rememberCoroutineScope() val receiptOffsetY = remember { Animatable(0f) } - var slotHeightPx by remember { mutableFloatStateOf(0f) } + val animationDuration = 5000 + val merchant_copy_autoprint = true + val customer_copy_autoprint = false + var currentCopy by remember { mutableStateOf(ReceiptCopy.MERCHANT) } + var nextCopy by remember { mutableStateOf(ReceiptCopy.CUSTOMER) } var isPrinting by remember { mutableStateOf(false) } + suspend fun animatePrint() { + receiptOffsetY.animateTo( + targetValue = -1500f, + animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing) + ) + delay(1000.milliseconds) + receiptOffsetY.animateTo( + targetValue = 0f, + animationSpec = tween(durationMillis = 0, easing = FastOutSlowInEasing) + ) + } + + fun isReprintMode() = sharedViewModel.isReprint.value == true + + fun doPrint(copy: ReceiptCopy, onFinished: () -> Unit) { + val reprint = isReprintMode() + val actualCopy = if (reprint && copy == ReceiptCopy.MERCHANT) ReceiptCopy.CUSTOMER else copy + currentCopy = actualCopy + sharedViewModel.setPrintStatus( + if (actualCopy == ReceiptCopy.MERCHANT) PrintStatus.FIRST_PRINT else PrintStatus.SECOND_PRINT + ) + onPrint() + scope.launch { + isPrinting = true + animatePrint() + isPrinting = false + nextCopy = if (reprint) ReceiptCopy.CUSTOMER + else if (actualCopy == ReceiptCopy.MERCHANT) ReceiptCopy.CUSTOMER else ReceiptCopy.MERCHANT + onFinished() + } + } + + LaunchedEffect(Unit) { + val reprint = isReprintMode() + if (reprint) { + currentCopy = ReceiptCopy.CUSTOMER + nextCopy = ReceiptCopy.CUSTOMER + } + sharedViewModel.isReprint.value = false + when { + merchant_copy_autoprint && !reprint -> doPrint(ReceiptCopy.MERCHANT) { } + customer_copy_autoprint -> doPrint(ReceiptCopy.CUSTOMER) { } + } + } + Scaffold( topBar = { AppBar(title = "Receipt") @@ -82,41 +130,16 @@ fun PrintReceiptScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { - Box( + ReceiptPreview( + state = state, + copyLabel = currentCopy.label, modifier = Modifier .weight(1f) - .fillMaxWidth() - .onSizeChanged { slotHeightPx = it.height.toFloat() } - ) { - Box( - modifier = Modifier - .fillMaxSize() - .clipToBounds() - ) { - ReceiptPreview( - state = state, - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .graphicsLayer { - translationY = receiptOffsetY.value - val travel = slotHeightPx + OFFSCREEN_BUFFER_PX - alpha = if (travel > 0f) { - 1f - (abs(receiptOffsetY.value) / travel).coerceIn(0f, 1f) - } else { - 1f - } - } - ) - } - - PrinterSlot( - isActive = isPrinting, - modifier = Modifier - .align(Alignment.TopCenter) - .fillMaxWidth() - ) - } + .verticalScroll(rememberScrollState()) + .graphicsLayer { + translationY = receiptOffsetY.value + } + ) Spacer(modifier = Modifier.height(16.dp)) @@ -127,7 +150,6 @@ fun PrintReceiptScreen( OutlinedButton( onClick = onDone, - enabled = !isPrinting, modifier = Modifier .weight(1f) .height(50.dp), @@ -148,50 +170,15 @@ fun PrintReceiptScreen( containerColor = Color.LegacyRed, contentColor = Color.White, disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f), - disabledContentColor = Color.White.copy(alpha = 0.7f), + disabledContentColor = Color.White.copy(alpha = 0.5f), ), shape = RoundedCornerShape(12.dp), onClick = { - if (isPrinting) return@Button - - isPrinting = true - onPrint() - - scope.launch { - val targetOffset = -(slotHeightPx + OFFSCREEN_BUFFER_PX) - - receiptOffsetY.animateTo( - targetValue = targetOffset, - animationSpec = tween( - durationMillis = PRINT_ANIMATION_DURATION_MS, - easing = FastOutSlowInEasing - ) - ) - - delay(PRINTED_HOLD_DELAY_MS) - - receiptOffsetY.animateTo( - targetValue = 0f, - animationSpec = tween( - durationMillis = RESET_ANIMATION_DURATION_MS, - easing = FastOutSlowInEasing - ) - ) - - isPrinting = false - } + doPrint(nextCopy) { } } ) { - if (isPrinting) { - CircularProgressIndicator( - modifier = Modifier.size(18.dp), - color = Color.White, - strokeWidth = 2.dp - ) - Spacer(modifier = Modifier.width(8.dp)) - } Text( - text = if (isPrinting) "Printing..." else "Print", + text = "Print", style = MaterialTheme.typography.titleMedium ) } @@ -201,52 +188,11 @@ fun PrintReceiptScreen( } } -/** - * Visual "exit slot" pinned to the top of the receipt preview. The receipt - * appears to feed upward through this slot and out of frame while printing, - * mimicking a physical thermal printer. - */ -@Composable -private fun PrinterSlot( - isActive: Boolean, - modifier: Modifier = Modifier -) { - val infiniteTransition = rememberInfiniteTransition(label = "printerSlotGlow") - val glow by infiniteTransition.animateFloat( - initialValue = 0.35f, - targetValue = 1f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 550, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse - ), - label = "glow" - ) - - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box( - modifier = Modifier - .padding(horizontal = 40.dp) - .fillMaxWidth() - .height(6.dp) - .clip(RoundedCornerShape(50)) - .background( - if (isActive) { - Color.LegacyRed.copy(alpha = glow) - } else { - Color.Gray.copy(alpha = 0.25f) - } - ) - ) - } -} - @Composable fun ReceiptPreview( state: TransactionResultState, + copyLabel: String, modifier: Modifier = Modifier ) { Column( @@ -264,12 +210,18 @@ fun ReceiptPreview( ) { Text( text = "MOB POS", - style = MaterialTheme.typography.headlineLarge.copy(fontFamily = FontFamily.Monospace) + style = MaterialTheme.typography.headlineLarge ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Transaction Receipt", - style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace) + style = MaterialTheme.typography.bodyMedium + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = copyLabel, + style = MaterialTheme.typography.labelLarge, + color = Color.Gray ) Spacer(modifier = Modifier.height(16.dp)) ReceiptDivider() @@ -288,14 +240,14 @@ fun ReceiptPreview( Spacer(modifier = Modifier.height(8.dp)) Text( text = "Thank You", - style = MaterialTheme.typography.titleMedium.copy(fontFamily = FontFamily.Monospace) + style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(4.dp)) Text( text = "Please keep this receipt", - style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace) + style = MaterialTheme.typography.bodySmall ) } } @@ -311,16 +263,16 @@ fun ReceiptRow( .padding(vertical = 6.dp), horizontalArrangement = Arrangement.SpaceBetween ) { - Text( - text = label, - style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace), - color = Color.Gray - ) + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + color = Color.Gray + ) - Text( - text = value, - style = MaterialTheme.typography.titleSmall.copy(fontFamily = FontFamily.Monospace) - ) + Text( + text = value, + style = MaterialTheme.typography.titleSmall + ) } } @@ -331,7 +283,6 @@ fun ReceiptDivider() { .fillMaxWidth() .padding(vertical = 10.dp), text = "--------------------------------", - fontFamily = FontFamily.Monospace, color = Color.Gray ) } @@ -347,4 +298,4 @@ fun PreviewPrintReceiptScreen() { onDone = {} ) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt index 4f91265..36c8d68 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/processing_card/ProcessingCardViewModel.kt @@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.mob.utsmyanmar.model.CardTransactionType import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel -import com.mob.utsmyanmar.utils.MockCardData import com.mob.utsmyanmar.utils.MockData import com.mob.utsmyanmar.utils.TransactionUtil import com.mob.utsmyanmar.viewmodel.CardReaderViewModel diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/signature_pad/SignaturePad.kt b/app/src/main/java/com/mob/utsmyanmar/ui/signature_pad/SignaturePad.kt index ab8695b..1a829a3 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/signature_pad/SignaturePad.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/signature_pad/SignaturePad.kt @@ -1,15 +1,10 @@ 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 +import androidx.compose.foundation.border 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 @@ -24,84 +19,31 @@ 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.Alignment 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 androidx.core.graphics.createBitmap import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.theme.MOBPOSTheme +import se.warting.signaturepad.SignaturePadAdapter +import se.warting.signaturepad.SignaturePadView 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 @Composable fun SignaturePad( modifier: Modifier = Modifier, - signatureColor: Color = Color.Black, onBack: () -> Unit = {}, - // signatureBlob is null for Digital mode (using stored signature) or when nothing was drawn - onConfirm: (SignatureMode, Bitmap?, signatureBlob: ByteArray?) -> Unit = { _, _, _ -> } + onConfirm: (Bitmap?, ByteArray?) -> Unit = { _, _ -> } ) { - val paths = remember { mutableStateListOf>() } - val currentPath = remember { mutableStateListOf() } - var selectedMode by remember { mutableStateOf(SignatureMode.Manual) } - var canvasSize by remember { mutableStateOf(Size.Zero) } - var strokeWidth = 6f + var signaturePadAdapter: SignaturePadAdapter? = null - fun clearSignature() { - paths.clear() - currentPath.clear() - } + val scaledWidth = 300 - - fun buildBitmap(): Bitmap? { - if (selectedMode == SignatureMode.Digital) return null - if (paths.isEmpty() && currentPath.isEmpty()) return null - 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) // fill white background first - - 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) { - 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 scaleBitmap(source: Bitmap): Bitmap { + val ratio = scaledWidth.toFloat() / source.width + val height = (source.height * ratio).toInt() + return Bitmap.createScaledBitmap(source, scaledWidth, height, true) } fun bitmapToBlob(bitmap: Bitmap): ByteArray { @@ -127,95 +69,20 @@ fun SignaturePad( .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Box( - modifier = Modifier - .fillMaxWidth(0.85f) // narrower than full width, adjust as needed - .height(180.dp) // fixed height instead of weight(1f) - .align(Alignment.CenterHorizontally) - .background(Color.White, RoundedCornerShape(16.dp)) - .padding(12.dp) - .onSizeChanged { size -> - canvasSize = Size(size.width.toFloat(), size.height.toFloat()) - } - .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( + SignaturePadView( + onReady = { signaturePadAdapter = it }, modifier = Modifier .fillMaxWidth() - .height(150.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() - ) - } + .height(200.dp) + .border(width = 1.dp, color = AppColor.LegacyRed) + ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { OutlinedButton( - onClick = { clearSignature() }, + onClick = { signaturePadAdapter?.clear() }, modifier = Modifier .weight(1f) .height(50.dp) @@ -225,13 +92,10 @@ fun SignaturePad( Button( onClick = { - val bitmap = buildBitmap() - if (selectedMode == SignatureMode.Manual && bitmap == null) { - // nothing drawn yet on Manual mode — block confirm - return@Button - } + val rawBitmap = signaturePadAdapter?.getSignatureBitmap() + val bitmap = rawBitmap?.let { scaleBitmap(it) } val signatureBlob = bitmap?.let { bitmapToBlob(it) } - onConfirm(selectedMode, bitmap, signatureBlob) + onConfirm(bitmap, signatureBlob) }, modifier = Modifier .weight(1f) @@ -297,4 +161,4 @@ fun SignaturePadPreview() { MOBPOSTheme { SignaturePad() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/theme/Type.kt b/app/src/main/java/com/mob/utsmyanmar/ui/theme/Type.kt index 427512d..f38ac0a 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/theme/Type.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/theme/Type.kt @@ -1,77 +1,96 @@ package com.mob.utsmyanmar.ui.theme +import androidx.compose.ui.text.font.Font import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.R + +private val LibreBaskerville = FontFamily( + Font(R.font.libre_baskerville_regular, FontWeight.Normal) +) val Typography = Typography( displayLarge = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Bold, fontSize = 32.sp, lineHeight = 40.sp, ), displayMedium = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Bold, fontSize = 30.sp, lineHeight = 38.sp, ), headlineLarge = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Bold, fontSize = 24.sp, lineHeight = 32.sp, ), headlineMedium = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Bold, fontSize = 20.sp, lineHeight = 28.sp, ), titleLarge = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.SemiBold, fontSize = 22.sp, lineHeight = 28.sp, ), titleMedium = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.SemiBold, fontSize = 18.sp, lineHeight = 26.sp, ), titleSmall = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, ), bodyLarge = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp ), bodyMedium = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 20.sp, ), bodySmall = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Normal, fontSize = 12.sp, lineHeight = 16.sp, ), labelLarge = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, ), labelMedium = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp, ), labelSmall = TextStyle( + fontFamily = LibreBaskerville, fontWeight = FontWeight.Medium, fontSize = 11.sp, lineHeight = 16.sp, letterSpacing = 0.5.sp ) -) \ No newline at end of file +) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt index 904e98d..907da0a 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt @@ -2,6 +2,7 @@ package com.mob.utsmyanmar.ui.tms_setup import android.annotation.SuppressLint import android.content.Context +import androidx.core.content.edit import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.mob.utsmyanmar.model.sirius.SiriusResponse @@ -48,7 +49,7 @@ class TmsSetupViewModel @Inject constructor( private val emvParamOperation: EmvParamOperation, @ApplicationContext private val context: Context ) : ViewModel() { - + private val TAG = TmsSetupViewModel::class.simpleName; private val _uiState = MutableStateFlow(TmsSetupUiState()) private val _dashboardUiState = MutableStateFlow(DashboardUiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -144,19 +145,27 @@ class TmsSetupViewModel @Inject constructor( val ops = SystemParamsOperation.getInstance() if (!ops.isActive) { + LogUtil.d(TAG, "isActive ${ops.isActive}") val msg = ops.disabledMsg.takeIf { it.isNotEmpty() } ?: "This terminal has been disabled. Please contact your administrator." - _uiState.update { it.copy(isLoading = false) } - viewModelScope.launch { _navigateToDisable.emit(msg) } + _uiState.update { + it.copy( + isLoading = false, + isError = true, + isTerminalDisabled = true, + statusText = "Terminal Disabled", + errorMessage = msg + ) + } return } val validity = TMSUtil.getInstance().checkParams() if (validity.status == ValidityStatus.SUCCESS) { context.getSharedPreferences("mobpos_prefs", Context.MODE_PRIVATE) - .edit() - .putLong("last_sync_time", System.currentTimeMillis()) - .apply() + .edit { + putLong("last_sync_time", System.currentTimeMillis()) + } _uiState.update { it.copy(isLoading = false, statusText = "Ready.") } viewModelScope.launch { _onSyncCompleted.emit(Unit) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt index 9198b0b..5c27ca7 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt @@ -24,6 +24,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.mob.utsmyanmar.ui.preview.P2Preview import com.mob.utsmyanmar.ui.theme.Color +import com.mob.utsmyanmar.utils.toFormattedDate +import com.mob.utsmyanmar.utils.toFormattedTime import com.utsmyanmar.paylibs.model.PayDetail import com.utsmyanmar.paylibs.print.PrintReceipt import com.utsmyanmar.paylibs.utils.POSUtil @@ -82,8 +84,8 @@ fun TransactionResultScreen( Spacer(modifier = Modifier.height(16.dp)) InfoCard( - date = "26 May 2026", - time = "12:06 PM", + date = "${state.payDetail?.transDate?.toFormattedDate()}", + time = "${state.payDetail?.transTime?.toFormattedTime()}", transactionId = state.payDetail?.referNo.toString() ) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt index 3db29eb..9b55dca 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt @@ -6,7 +6,6 @@ import com.mob.utsmyanmar.model.ecr.ECRResultStatus import com.mob.utsmyanmar.utils.CoreUtils import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.utsmyanmar.baselib.emv.EmvParamOperation -import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest import com.utsmyanmar.ecr.ECRHelper import com.utsmyanmar.ecr.ECRProcess import com.utsmyanmar.ecr.data.model.TransactionsResp diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/Extensions.kt b/app/src/main/java/com/mob/utsmyanmar/utils/Extensions.kt new file mode 100644 index 0000000..c1e7b00 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/utils/Extensions.kt @@ -0,0 +1,18 @@ +package com.mob.utsmyanmar.utils + +import java.text.SimpleDateFormat +import java.util.Locale + +fun String.toFormattedDate(): String { + val inputFormat = SimpleDateFormat("dd/MM/yy", Locale.ENGLISH) + val outputFormat = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH) + val date = inputFormat.parse(this) + return outputFormat.format(date!!) +} + +fun String.toFormattedTime(): String { + val inputFormat = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) + val outputFormat = SimpleDateFormat("hh:mm a", Locale.ENGLISH) + val time = inputFormat.parse(this) + return outputFormat.format(time!!) +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt index 2a4bb51..0750b52 100644 --- a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt +++ b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt @@ -131,7 +131,7 @@ class TMSSetupsImpl : TMSSetups { } //Properties - for (prop in siriusProperty.orEmpty()) { + for (prop in siriusProperty) { val name = prop.name val data = prop.property @@ -221,6 +221,7 @@ class TMSSetupsImpl : TMSSetups { "speedup_contactless_enable" -> ops.setSpeedUpContactless(parseBoolean(data)) "manual_entry_pin_enable" -> ops.isManualEntryPinEnable = parseBoolean(data) "cmhl_enabled" -> ops.setCMHLEnable(parseBoolean(data)) + "mmqr_enable" -> ops.setMmqrStatus(parseBoolean(data)) } } ops.carouselUrls = imgUrls.joinToString(",") diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt index 79b3941..737559f 100644 --- a/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/viewmodel/EmvTransactionProcessViewModel.kt @@ -4,6 +4,7 @@ import android.os.Handler import android.os.Looper import android.os.Message import android.text.TextUtils +import com.mob.utsmyanmar.model.TransResultStatus import com.utsmyanmar.baselib.emv.EmvParamOperation import com.utsmyanmar.baselib.repo.Repository import com.utsmyanmar.baselib.util.enums.EmvResultStatus @@ -24,7 +25,6 @@ import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import dagger.hilt.android.lifecycle.HiltViewModel import sunmi.sunmiui.utils.LogUtil import javax.inject.Inject -import com.mob.utsmyanmar.model.TransResultStatus @HiltViewModel class EmvTransactionProcessViewModel @Inject constructor( diff --git a/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt index 6fa433b..c1852df 100644 --- a/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/viewmodel/TransProcessViewModel.kt @@ -3,6 +3,7 @@ package com.mob.utsmyanmar.viewmodel import android.content.Context import android.text.TextUtils import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.mob.utsmyanmar.model.TransResultStatus import com.mob.utsmyanmar.ui.sending_to_host.ProcessingState import com.utsmyanmar.baselib.repo.Repository @@ -19,8 +20,10 @@ import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -210,7 +213,10 @@ class TransProcessViewModel @Inject constructor( TransResultStatus.REVERSAL_PREPARE _state.value = _state.value.copy("Preparing Reversal") - callReversal(tradeData) + viewModelScope.launch { + delay(10_000L) + callReversal(tradeData) + } } override fun onError(message: String) { diff --git a/app/src/main/res/drawable/logo_master.xml b/app/src/main/res/drawable/logo_master.xml new file mode 100644 index 0000000..7fc67b2 --- /dev/null +++ b/app/src/main/res/drawable/logo_master.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/logo_mpu.png b/app/src/main/res/drawable/logo_mpu.png new file mode 100644 index 0000000..04f7a07 Binary files /dev/null and b/app/src/main/res/drawable/logo_mpu.png differ diff --git a/app/src/main/res/drawable/logo_union_pay.xml b/app/src/main/res/drawable/logo_union_pay.xml new file mode 100644 index 0000000..ea4a862 --- /dev/null +++ b/app/src/main/res/drawable/logo_union_pay.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/logo_visa.xml b/app/src/main/res/drawable/logo_visa.xml new file mode 100644 index 0000000..8d5bcec --- /dev/null +++ b/app/src/main/res/drawable/logo_visa.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/font/libre_baskerville_regular.ttf b/app/src/main/res/font/libre_baskerville_regular.ttf new file mode 100644 index 0000000..1f3aece Binary files /dev/null and b/app/src/main/res/font/libre_baskerville_regular.ttf differ diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 7a25a11..5a8dbbc 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,4 +1,7 @@ - \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..f12aa05 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + posuat.myanmarorientalbank.com:5033 + + + diff --git a/ecr/src/main/java/com/utsmyanmar/ecr/ECRProcess.kt b/ecr/src/main/java/com/utsmyanmar/ecr/ECRProcess.kt index ee31084..f7142b6 100644 --- a/ecr/src/main/java/com/utsmyanmar/ecr/ECRProcess.kt +++ b/ecr/src/main/java/com/utsmyanmar/ecr/ECRProcess.kt @@ -1,17 +1,12 @@ package com.utsmyanmar.ecr -import androidx.annotation.Keep import com.google.gson.Gson import com.utsmyanmar.ecr.data.ResultOf -import com.utsmyanmar.ecr.data.TransType import com.utsmyanmar.ecr.data.mapper.RequestTransMapper import com.utsmyanmar.ecr.data.mapper.ResponseTransMapper import com.utsmyanmar.ecr.data.model.RequestTrans -import com.utsmyanmar.ecr.data.model.ResponseTrans import com.utsmyanmar.ecr.data.model.Transactions import com.utsmyanmar.ecr.data.model.TransactionsResp -import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation -import java.lang.Exception //@Keep object ECRProcess { diff --git a/paylibs/src/main/assets/cascadiaCodeRegular.ttf b/paylibs/src/main/assets/cascadiaCodeRegular.ttf new file mode 100644 index 0000000..cd86abc Binary files /dev/null and b/paylibs/src/main/assets/cascadiaCodeRegular.ttf differ diff --git a/paylibs/src/main/assets/gabaritoVariableFont.ttf b/paylibs/src/main/assets/gabaritoVariableFont.ttf new file mode 100644 index 0000000..56bd620 Binary files /dev/null and b/paylibs/src/main/assets/gabaritoVariableFont.ttf differ diff --git a/paylibs/src/main/assets/jetBrainsMonoRegular.ttf b/paylibs/src/main/assets/jetBrainsMonoRegular.ttf new file mode 100644 index 0000000..436c982 Binary files /dev/null and b/paylibs/src/main/assets/jetBrainsMonoRegular.ttf differ diff --git a/paylibs/src/main/assets/libreBaskervilleRegular.ttf b/paylibs/src/main/assets/libreBaskervilleRegular.ttf new file mode 100644 index 0000000..1f3aece Binary files /dev/null and b/paylibs/src/main/assets/libreBaskervilleRegular.ttf differ diff --git a/paylibs/src/main/assets/losevkaCharonMonoRegular.ttf b/paylibs/src/main/assets/losevkaCharonMonoRegular.ttf new file mode 100644 index 0000000..48738cf Binary files /dev/null and b/paylibs/src/main/assets/losevkaCharonMonoRegular.ttf differ diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/network/ISOSocket.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/network/ISOSocket.java index 879082c..f606cbc 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/network/ISOSocket.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/network/ISOSocket.java @@ -503,7 +503,7 @@ public class ISOSocket { LogUtil.e(Constant.TAG, "E:" + e.toString()); - closeSocket(); +// closeSocket(); if (isReversal) { listener.onError(Constant.REVERSAL); // listener.onError(new ReversalException()); @@ -519,7 +519,7 @@ public class ISOSocket { @Override public void onComplete() { LogUtil.d(Constant.TAG, "onComplete"); - closeSocket(); +// closeSocket(); isReversal = false; listener.onComplete(); } diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/print/PrintReceipt.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/print/PrintReceipt.java index 8942235..34a794f 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/print/PrintReceipt.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/print/PrintReceipt.java @@ -1189,10 +1189,11 @@ public class PrintReceipt { private void printSignature(Bitmap signatureBitmap) { try { sunmiPrinterService.setAlignment(1, null); - if (signatureBitmap.getWidth() > 130) { - double size = 1.0 * signatureBitmap.getHeight() * 130 / signatureBitmap.getWidth(); - int newHeight = (int) size; - signatureBitmap = BitmapUtils.scale(signatureBitmap, 130, newHeight); + 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, null); sunmiPrinterService.printText("\n", null); diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java index 692b7aa..da4ab57 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java @@ -50,7 +50,7 @@ public abstract class BaseXPrint { protected String currentTime; - protected static final String typeface_name = "jbm_semibold.ttf"; + protected static final String typeface_name = "jetBrainsMonoRegular.ttf"; protected static final String MPU_AID = "A0000004820000000001"; protected static final String HOST_NAME_VM = "VISA/ MASTERCARD"; @@ -954,6 +954,12 @@ public abstract class BaseXPrint { if (signatureBlob != null && signatureBlob.length > 0) { Bitmap signatureBitmap = android.graphics.BitmapFactory.decodeByteArray(signatureBlob, 0, signatureBlob.length); 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); } } diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/transactions/TransactionsOperation.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/transactions/TransactionsOperation.java index 2bb3bf4..905550a 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/transactions/TransactionsOperation.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/transactions/TransactionsOperation.java @@ -368,7 +368,7 @@ public class TransactionsOperation { } } } else if (transactionsType == TransactionsType.PRE_AUTH_VOID) { - newPay.setCardType(oldPay.getCardType()); +// newPay.setCardType(oldPay.getCardType()); newPay.setCustomOrderNo(oldPay.getVoucherNo()); newPay.setReferNo(oldPay.getReferNo()); newPay.setCardInfo(oldPay.getCardInfo()); diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java index 79371ae..f1bf39a 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java @@ -1,16 +1,16 @@ package com.utsmyanmar.paylibs.utils.core_utils; +import static com.utsmyanmar.paylibs.utils.core_utils.SystemParamsSettings.TRANSACTION_MANAGER_KEY; + import android.text.TextUtils; +import com.utsmyanmar.paylibs.utils.enums.CurrencyType; + import java.util.ArrayList; import java.util.Locale; import sunmi.sunmiui.utils.LogUtil; -import static com.utsmyanmar.paylibs.utils.core_utils.SystemParamsSettings.TRANSACTION_MANAGER_KEY; - -import com.utsmyanmar.paylibs.utils.enums.CurrencyType; - public class SystemParamsOperation { public static final String TAG = "SystemParamsOperation"; @@ -33,54 +33,6 @@ public class SystemParamsOperation { return instance; } - /* public void init(String serialNumStart, - String batchNumStart, - int purchaseOrderStyle, - int correctFrequency, - int transactionNumber, - boolean printNegative, - int nonConnection, - boolean stateSecret, - boolean shieldAuthorizationCard, - int nonConnectionChoice, - boolean eCashVisaExemption) { - SystemParamsSettings bean = getSystemParamsSettings(); - bean.setSerialNum(serialNumStart); - bean.setBatchNumStart(batchNumStart); - bean.setPurchaseOrderStyle(purchaseOrderStyle); - bean.setCorrectFrequency(correctFrequency); - bean.setTransactionNumber(transactionNumber); - bean.setPrintNegative(printNegative); - bean.setNonConnection(nonConnection); - bean.setStateSecret(stateSecret); - bean.setShieldAuthorizationCard(shieldAuthorizationCard); - bean.setNonConnectionChoice(nonConnectionChoice); - bean.setCashVisaExemption(eCashVisaExemption); - saveSystemParamsSettings(bean); - }*/ - - /*public boolean isDownloadParamsOnce() { - SystemParamsSettings params = getSystemParamsSettings(); - return params.isDownloadParamsOnce(); - } - - public void saveDownloadParamsOnce(boolean downloadParamsOnce) { - SystemParamsSettings params = getSystemParamsSettings(); - params.setDownloadParamsOnce(downloadParamsOnce); - saveSystemParamsSettings(params); - } - - public boolean isNeedDownLoadICParams() { - SystemParamsSettings params = getSystemParamsSettings(); - return params.isNeedDownLoadICParams(); - } - - public void saveNeedDownLoadICParams(boolean needDownLoadICParams) { - SystemParamsSettings params = getSystemParamsSettings(); - params.setNeedDownLoadICParams(needDownLoadICParams); - saveSystemParamsSettings(params); - }*/ - public boolean isInjectOnce(){ SystemParamsSettings params = getSystemParamsSettings(); return params.isInjectOnce(); @@ -91,13 +43,6 @@ public class SystemParamsOperation { params.setInjectOnce(injectOnce); saveSystemParamsSettings(params); } - - - /** - * Set serial number - * - * @param serialNumber Starting serial number - */ public void saveSerialNumber(String serialNumber) { if (TextUtils.isEmpty(serialNumber)) { return; @@ -106,12 +51,6 @@ public class SystemParamsOperation { params.setSerialNum(serialNumber); saveSystemParamsSettings(params); } - - /** - * Set invoice number - * - * @param invoiceNumber Starting invoice number - */ public void saveInvoiceNumber(String invoiceNumber) { if (TextUtils.isEmpty(invoiceNumber)) { return; @@ -120,13 +59,6 @@ public class SystemParamsOperation { params.setInvoiceNum(invoiceNumber); saveSystemParamsSettings(params); } - - - /** - * Set batch number - * - * @param batchNumber Starting batch number - */ public void saveBatchNumber(String batchNumber) { if (TextUtils.isEmpty(batchNumber)) { return; @@ -136,11 +68,6 @@ public class SystemParamsOperation { saveSystemParamsSettings(params); } - - - /** - * Serial number increment - */ private void incrementSerialNum() { SystemParamsSettings params = getSystemParamsSettings(); String currentSerialNum = params.getCurrentSerialNum(); @@ -172,10 +99,6 @@ public class SystemParamsOperation { params.setSerialNum(transNumFormat); saveSystemParamsSettings(params); } - - /** - * Invoice number increment - */ private void incrementInvoiceNum() { SystemParamsSettings params = getSystemParamsSettings(); String currentInvoiceNum = params.getCurrentInvoiceNum(); @@ -952,6 +875,17 @@ public class SystemParamsOperation { saveSystemParamsSettings(params); } + public void setMmqrStatus(boolean status) { + SystemParamsSettings params = getSystemParamsSettings(); + params.setMmqrEnabled(status); + saveSystemParamsSettings(params); + } + + public boolean getMmqrStatus(){ + SystemParamsSettings params = getSystemParamsSettings(); + return params.getMmqrEnabled(); + } + public boolean isCMHLEnabled(){ SystemParamsSettings params = getSystemParamsSettings(); return params.isCmhlEnabled(); diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java index aac1055..f896c77 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java @@ -38,7 +38,7 @@ public class SystemParamsSettings implements Serializable { private boolean checkExpSwitch = false; -// private String tmsAddress = "http://posuat.myanmarorientalbank.com:5033"; +// private String tmsAddress = "https://posmgmt.myanmarorientalbank.com/api/v1"; private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1"; // private String tmsAddress = "http://128.199.170.203"; @@ -55,6 +55,7 @@ public class SystemParamsSettings implements Serializable { private String merchantName = ""; + private String receiptHeader = ""; private String merchantAddress = ""; @@ -232,6 +233,7 @@ public class SystemParamsSettings implements Serializable { private byte[] pik_value; private byte[] pik_kcv; + private boolean mmqrEnabled; protected String getSecHostName() { return secHostName; @@ -901,6 +903,14 @@ public class SystemParamsSettings implements Serializable { this.emvEnabled = emvEnabled; } + public void setMmqrEnabled(boolean status) { + this.mmqrEnabled = status; + } + + public boolean getMmqrEnabled(){ + return mmqrEnabled; + } + /* // 流水号起始 private String serialNum = Configs.getInstance().SERIAL_NUM(); // 批次号起始 diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/host_utils/HostUtils.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/host_utils/HostUtils.java index f903392..fb6999c 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/host_utils/HostUtils.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/host_utils/HostUtils.java @@ -117,7 +117,10 @@ public class HostUtils { msgType = MessageType.AUTHORIZATION; } + + break; + case PRE_AUTH_VOID: case PRE_AUTH_COMPLETE: if (hostName == HostName.BPC) { msgType = MessageType.FINANCIAL; @@ -142,11 +145,11 @@ public class HostUtils { break; case VOID: - case PRE_AUTH_VOID: if(isVisaMaster) { msgType = MessageType.REVERSAL; break; } + default: msgType = MessageType.FINANCIAL; break; diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/BitmapConfig.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/BitmapConfig.java index 6930af4..b80308b 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/BitmapConfig.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/BitmapConfig.java @@ -198,7 +198,8 @@ public class BitmapConfig { public static final String CZ_REVERSAL="7020078000C08000"; public static final String CZ_BATCH_UPLOAD="703805800CC00010"; public static final String CZ_PRE_AUTH="7020058020C09000"; - public static final String CZ_PRE_AUTH_VOID="7038058028C08000"; +// public static final String CZ_PRE_AUTH_VOID="7038058028C08000"; + public static final String CZ_PRE_AUTH_VOID="703C058008C08000"; public static final String CZ_PRE_AUTH_COMP="703805802CC08000"; public static final String CZ_PRE_AUTH_COMP_VOID="7038058008C00000"; diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/FieldUtils.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/FieldUtils.java index d84bc0b..efb10f5 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/FieldUtils.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/iso_utils/FieldUtils.java @@ -361,7 +361,12 @@ public class FieldUtils { // } else { // iterator.remove(); // } - field.setDataStr("000"); + if(payDetail.getTransactionType() == REVERSAL.value) { + field.setDataStr("001"); + } else { + field.setDataStr("000"); + } + break; case 24: //Network International Identifier NII @@ -376,10 +381,10 @@ public class FieldUtils { 08 – mail order/telephone order * */ // Service point condition code Fill in 00 for ordinary consumer transactions and mobile phone chip transactions, fill in 08 for order consumer transactions, fill in 64 for installment transactions, fill in 65 for points consumption transactions, fill in 67 for mobile phone card-free reservations - if (payDetail.getTransactionType() == PRE_AUTH_SALE.value) { + // do not remove double check for preauth, it's control for reversal + if (payDetail.getTransactionType() == PRE_AUTH_SALE.value || payDetail.getTransType().equals(PRE_AUTH_SALE.name)) { field.setDataStr("06"); - } - else if(payDetail.getTransactionType() == REFUND.value || payDetail.getTransactionType() == REVERSAL.value) { + } else if(payDetail.getTransactionType() == REFUND.value || payDetail.getTransType().equals(REFUND.name) ) { // field.setDataStr("00"); // MPU field.setDataStr("61"); // CZ } else if(payDetail.getCardType() == -1) {