Compare commits

..

No commits in common. "fc4b8173089480a25a81b7568bd02c78e6399c2b" and "7c0e9c4e66bda46d915e00d1fbbcc7b20f216404" have entirely different histories.

55 changed files with 665 additions and 888 deletions

View File

@ -5,6 +5,9 @@ plugins {
id("com.google.dagger.hilt.android") id("com.google.dagger.hilt.android")
} }
// Set default build variant
project.ext.set("android.defaultPublishConfig", "uatDebug")
android { android {
namespace = "com.mob.utsmyanmar" namespace = "com.mob.utsmyanmar"
compileSdk { compileSdk {
@ -40,8 +43,8 @@ android {
dimension = "env" dimension = "env"
applicationIdSuffix = ".uat" applicationIdSuffix = ".uat"
versionNameSuffix = "-UAT" versionNameSuffix = "-UAT"
versionCode = 12 versionCode = 3
versionName = "3.0" versionName = "2.1"
buildConfigField("String", "ENV", "\"UAT\"") buildConfigField("String", "ENV", "\"UAT\"")
resValue("string", "app_name", "MOB-UAT") resValue("string", "app_name", "MOB-UAT")
} }
@ -59,14 +62,6 @@ android {
"proguard-rules.pro" "proguard-rules.pro"
) )
} }
debug {
isMinifyEnabled = false
isDebuggable = true
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
@ -125,6 +120,4 @@ dependencies {
implementation(project(":ecr")) implementation(project(":ecr"))
implementation(project(":xpay")) implementation(project(":xpay"))
implementation(project(":cmhl")) implementation(project(":cmhl"))
implementation("se.warting.signature:signature-pad:0.2.0")
} }

View File

@ -29,8 +29,6 @@
<application <application
android:name="com.mob.utsmyanmar.MyApplication" android:name="com.mob.utsmyanmar.MyApplication"
android:allowBackup="true" android:allowBackup="true"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@ -2,7 +2,6 @@ package com.mob.utsmyanmar.ui.cardwaiting
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -39,9 +38,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -137,33 +134,33 @@ private fun CardWaitingScreenContent(
Text( Text(
text = "AMOUNT TO PAY", text = "AMOUNT TO PAY",
color = Color.Black, color = Color.Black,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.labelSmall
) )
Spacer(modifier = Modifier.height(6.dp)) Spacer(modifier = Modifier.height(6.dp))
// Row(verticalAlignment = Alignment.Bottom) { Row(verticalAlignment = Alignment.Bottom) {
Text( Text(
text = amount, text = amount,
color = Color.LegacyRed, color = Color.LegacyRed,
style = MaterialTheme.typography.displayLarge style = MaterialTheme.typography.displayMedium
) )
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
Text( Text(
text = "MMK", text = "MMK",
color = Color.LegacyRed, color = Color.LegacyRed,
style = MaterialTheme.typography.displaySmall.copy(fontWeight = FontWeight.Medium), style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(bottom = 6.dp) modifier = Modifier.padding(bottom = 6.dp)
) )
// } }
// Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
ContactlessCircle(isLoading = uiState.isLoading) ContactlessCircle(isLoading = uiState.isLoading)
// Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Text( Text(
text = when { text = when {
@ -209,9 +206,12 @@ private fun CardWaitingScreenContent(
) )
} }
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(12.dp))
InsertCardRow(onMockClick = onMockClick) InsertCardRow(onMockClick = onMockClick)
Spacer(modifier = Modifier.height(10.dp))
Spacer(modifier = Modifier.height(18.dp))
Text( Text(
text = uiState.alertMessage, text = uiState.alertMessage,
color = Color.Black, color = Color.Black,
@ -219,13 +219,23 @@ private fun CardWaitingScreenContent(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
lineHeight = 16.sp lineHeight = 16.sp
) )
Spacer(modifier = Modifier.height(10.dp))
Spacer(modifier = Modifier.height(18.dp))
// StatusPanel(uiState = uiState)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
ManualEntryAction(onManualEntryClick = onManualEntryClick) ManualEntryAction(onManualEntryClick = onManualEntryClick)
Spacer(modifier = Modifier.height(10.dp))
Spacer(modifier = Modifier.height(18.dp))
CardLogoRow() CardLogoRow()
Spacer(modifier = Modifier.height(34.dp)) Spacer(modifier = Modifier.height(34.dp))
PoweredByMob() PoweredByMob()
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
} }
} }
@ -282,7 +292,7 @@ private fun CircleBorder(
} }
@Composable @Composable
private fun InsertCardRow(onMockClick: () -> Unit) { private fun InsertCardRow(onMockClick: ()->Unit) {
Row( Row(
modifier = Modifier.height(44.dp), modifier = Modifier.height(44.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@ -291,8 +301,7 @@ private fun InsertCardRow(onMockClick: () -> Unit) {
painter = painterResource(id = R.drawable.ic_insert_card), painter = painterResource(id = R.drawable.ic_insert_card),
contentDescription = null, contentDescription = null,
tint = Color.LegacyRed, tint = Color.LegacyRed,
modifier = Modifier modifier = Modifier.size(28.dp)
.size(28.dp)
.clickable(onClick = onMockClick) .clickable(onClick = onMockClick)
) )
@ -386,49 +395,13 @@ private fun CardLogoRow() {
.fillMaxWidth() .fillMaxWidth()
.height(42.dp) .height(42.dp)
.padding(horizontal = 14.dp), .padding(horizontal = 14.dp),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
CardLogo( Text("VISA", style = MaterialTheme.typography.titleLarge)
icon = painterResource(R.drawable.logo_visa), Text("MC", style = MaterialTheme.typography.titleMedium)
name = "visa", Text("MPU", style = MaterialTheme.typography.titleLarge)
) Text("UnionPay", style = MaterialTheme.typography.labelMedium)
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)
)
} }
} }
} }

View File

@ -16,13 +16,14 @@ import com.utsmyanmar.ecr.ECRHelper
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import sunmi.sunmiui.utils.LogUtil
class CardWaitingViewModel( class CardWaitingViewModel(
private val cardReadViewModel: CardReaderViewModel, private val cardReadViewModel: CardReaderViewModel,

View File

@ -5,14 +5,14 @@ import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.tooling.preview.Preview 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.Color
import com.mob.utsmyanmar.ui.theme.Primary
import com.mob.utsmyanmar.ui.theme.White import com.mob.utsmyanmar.ui.theme.White
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -35,7 +35,7 @@ fun AppBar(
navigationIcon = { navigationIcon = {
if (icon != null && onIconClick != null) { if (icon != null && onIconClick != null) {
IconButton( IconButton(
onClick = dropUnlessResumed { onIconClick() } onClick = onIconClick
) { ) {
Icon( Icon(
imageVector = icon, imageVector = icon,

View File

@ -104,9 +104,7 @@ fun SummaryCard(
onClickLastSync: () -> Unit, onClickLastSync: () -> Unit,
lastSyncTime: Long?, lastSyncTime: Long?,
totalSaleCount: Int, totalSaleCount: Int,
totalSaleAmount: Long, totalSaleAmount: Long
settlementStatusText: String,
settlementDateTimeText: String
) { ) {
var now by remember { mutableLongStateOf(System.currentTimeMillis()) } var now by remember { mutableLongStateOf(System.currentTimeMillis()) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -147,8 +145,8 @@ fun SummaryCard(
SummaryItem( SummaryItem(
title = "Settlement", title = "Settlement",
value = settlementStatusText, value = "Completed",
subtitle = settlementDateTimeText, subtitle = "Today 11:00 PM",
icon = Icons.Default.Check, icon = Icons.Default.Check,
iconBg = Color.CrimsonRed iconBg = Color.CrimsonRed
) )

View File

@ -1,8 +1,5 @@
package com.mob.utsmyanmar.ui.dashboard 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.Icons
import androidx.compose.material.icons.filled.AccountBalanceWallet import androidx.compose.material.icons.filled.AccountBalanceWallet
import androidx.compose.material.icons.filled.CreditCard import androidx.compose.material.icons.filled.CreditCard
@ -10,6 +7,7 @@ import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.LockOpen import androidx.compose.material.icons.filled.LockOpen
import androidx.compose.material.icons.filled.Replay import androidx.compose.material.icons.filled.Replay
import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -17,7 +15,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.mob.utsmyanmar.R import com.mob.utsmyanmar.R
import com.mob.utsmyanmar.ui.theme.Color import com.mob.utsmyanmar.ui.theme.Color
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
class DashboardMenuItem( class DashboardMenuItem(
val title: String, val title: String,
@ -25,209 +22,121 @@ class DashboardMenuItem(
val onClick: () -> Unit 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( fun buildMenuItems(
onNavigateAmount: (String) -> Unit, onNavigateAmount: (String) -> Unit,
onNavigateSignOn: () -> Unit, onNavigateSignOn: () -> Unit,
onNavigateSettlement: () -> Unit, onNavigateSettlement: () -> Unit,
onNavigateHistory: () -> Unit, onNavigateHistory: () -> Unit,
onNavigateAction: (String) -> Unit onNavigateAction: (String) -> Unit
): List<DashboardMenuItem> = buildList { ): List<DashboardMenuItem> = listOf(
DashboardMenuItem(
val mmqrEnabled = SystemParamsOperation.getInstance().mmqrStatus "Sale",
{
add( Icon(
DashboardMenuItem( painterResource(R.drawable.ic_terminal),
"Sale", contentDescription = null,
{ modifier = Modifier.size(40.dp),
Icon( tint = Color.LegacyRed
painterResource(R.drawable.ic_terminal), )
contentDescription = null, }) { onNavigateAmount("Sale") },
modifier = Modifier.size(40.dp), DashboardMenuItem(
tint = Color.LegacyRed "History",
) {
} Icon(
) { onNavigateAmount("Sale") } painterResource(R.drawable.ic_history),
) contentDescription = null,
modifier = Modifier.size(32.dp),
tint = Color.LegacyRed
if (mmqrEnabled) { )
add( }) { onNavigateHistory() },
DashboardMenuItem( DashboardMenuItem(
"MMQR", "Sign On",
{ {
Image( Icon(
painter = painterResource(R.drawable.ic_mmqr_logo), painterResource(R.drawable.ic_sign_on),
contentDescription = null, contentDescription = null,
modifier = Modifier.height(48.dp) modifier = Modifier.size(32.dp),
) tint = Color.LegacyRed
} )
) { } }) { onNavigateSignOn() },
) DashboardMenuItem(
} "Settlement",
add( {
DashboardMenuItem( Icon(
"History", painterResource(R.drawable.ic_settlement),
{ contentDescription = null,
Icon( modifier = Modifier.size(32.dp),
painterResource(R.drawable.ic_history), tint = Color.LegacyRed
contentDescription = null, )
modifier = Modifier.size(32.dp), }) { onNavigateSettlement() },
tint = Color.LegacyRed DashboardMenuItem(
) "Void",
} {
) { onNavigateHistory() } Icon(
) Icons.Default.Lock,
contentDescription = null,
add( modifier = Modifier.size(32.dp),
DashboardMenuItem( tint = Color.LegacyRed
"Sign On", )
{ }) { onNavigateAction("Void") },
Icon( DashboardMenuItem(
painterResource(R.drawable.ic_sign_on), "Refund",
contentDescription = null, {
modifier = Modifier.size(32.dp), Icon(
tint = Color.LegacyRed Icons.Default.Replay,
) contentDescription = null,
} modifier = Modifier.size(32.dp),
) { onNavigateSignOn() } tint = Color.LegacyRed
) )
}) { onNavigateAction("Refund") },
if (settlementEnabled) { DashboardMenuItem(
add( "Pre-Auth",
DashboardMenuItem( {
"Settlement", Icon(
{ Icons.Default.Lock,
Icon( contentDescription = null,
painterResource(R.drawable.ic_settlement), modifier = Modifier.size(32.dp),
contentDescription = null, tint = Color.LegacyRed
modifier = Modifier.size(32.dp), )
tint = Color.LegacyRed }) { onNavigateAmount("Pre-Auth") },
) DashboardMenuItem(
} "Pre-Auth Void",
) { onNavigateSettlement() } {
) Icon(
} Icons.Default.LockOpen,
contentDescription = null,
if (voidEnabled) { modifier = Modifier.size(32.dp),
add( tint = Color.LegacyRed
DashboardMenuItem( )
"Void", }) { onNavigateAction("Pre-Auth Void") },
{ DashboardMenuItem(
Icon( "Pre-Auth Complete",
Icons.Default.Lock, {
contentDescription = null, Icon(
modifier = Modifier.size(32.dp), Icons.Default.CreditCard,
tint = Color.LegacyRed contentDescription = null,
) modifier = Modifier.size(32.dp),
} tint = Color.LegacyRed
) { onNavigateAction("Void") } )
) }) { onNavigateAction("Pre-Auth Complete") },
} DashboardMenuItem(
"Pre-Auth Comp Void",
if (refundEnabled) { {
add( Icon(
DashboardMenuItem( Icons.Default.SwapHoriz,
"Refund", contentDescription = null,
{ modifier = Modifier.size(32.dp),
Icon( tint = Color.LegacyRed
Icons.Default.Replay, )
contentDescription = null, }) { onNavigateAction("Pre-Auth Complete Void") },
modifier = Modifier.size(32.dp), DashboardMenuItem(
tint = Color.LegacyRed "Cash Out",
) {
} Icon(
) { onNavigateAction("Refund") } Icons.Default.AccountBalanceWallet,
) contentDescription = null,
} modifier = Modifier.size(32.dp),
tint = Color.LegacyRed
if (preAuthEnabled) { )
add( }) { onNavigateAmount("Cash Out") },
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") }
)
}
}

View File

@ -23,9 +23,6 @@ import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.Sync import androidx.compose.material.icons.filled.Sync
import androidx.compose.material3.AlertDialog 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.DrawerValue
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -50,7 +47,6 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.mob.utsmyanmar.R import com.mob.utsmyanmar.R
import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.device_info.DeviceInfoUiState import com.mob.utsmyanmar.ui.device_info.DeviceInfoUiState
@ -180,29 +176,6 @@ 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( ModalNavigationDrawer(
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
@ -421,9 +394,7 @@ fun DashboardScreen2(
onClickLastSync = onClickLastSync, onClickLastSync = onClickLastSync,
lastSyncTime = dashboardUiState.lastSyncTime, lastSyncTime = dashboardUiState.lastSyncTime,
totalSaleCount = dashboardUiState.totalSaleCount, totalSaleCount = dashboardUiState.totalSaleCount,
totalSaleAmount = dashboardUiState.totalSaleAmount, totalSaleAmount = dashboardUiState.totalSaleAmount
settlementStatusText = dashboardUiState.settlementStatusText,
settlementDateTimeText = dashboardUiState.settlementDateTimeText
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(12.dp))
} }

View File

@ -15,9 +15,5 @@ data class DashboardUiState(
val lastSyncTime: Long? = null, val lastSyncTime: Long? = null,
val carouselUrls: List<String> = emptyList(), val carouselUrls: List<String> = emptyList(),
val totalSaleCount: Int = 0, 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..."
) )

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.utsmyanmar.baselib.repo.Repository import com.utsmyanmar.baselib.repo.Repository
import com.utsmyanmar.paylibs.model.PayDetail 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.core_utils.SystemParamsOperation
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -18,7 +17,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal import java.math.BigDecimal
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.Calendar
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -33,23 +31,11 @@ class DashboardViewModel @Inject constructor(
init { init {
viewModelScope.launch { viewModelScope.launch {
repository.getLastTenTransactions().asFlow().collect { payDetails -> repository.getLastTenTransactions().asFlow().collect { payDetails ->
val latestSettlement = payDetails.lastOrNull {
it.transactionType == TransactionsType.SETTLEMENT.value
}
val isCurrentBusinessSettlement = latestSettlement?.isCurrentBusinessSettlement() == true
_uiState.update { _uiState.update {
it.copy( it.copy(
recentTransactions = payDetails.map { pd -> pd.toRecord() }, recentTransactions = payDetails.map { pd -> pd.toRecord() },
totalSaleCount = payDetails.totalSaleCount(), totalSaleCount = payDetails.totalSaleCount(),
totalSaleAmount = payDetails.totalSaleAmount(), totalSaleAmount = payDetails.totalSaleAmount()
settlementStatusText = if (isCurrentBusinessSettlement) {
"Completed"
} else {
"Pending"
},
settlementDateTimeText = latestSettlement
?.let { buildDateTime(it.TradeDate, it.TradeTime) }
?: "--/-- --:--"
) )
} }
} }
@ -72,18 +58,6 @@ class DashboardViewModel @Inject constructor(
.takeIf { it > 0L } .takeIf { it > 0L }
_uiState.update { it.copy(lastSyncTime = time) } _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 { private fun PayDetail.toRecord(): TrnxRecord {
@ -127,7 +101,7 @@ private fun formatTypeLabel(raw: String): String = when {
raw.contains("REFUND", ignoreCase = true) -> "Refund" raw.contains("REFUND", ignoreCase = true) -> "Refund"
raw.contains("SETTLEMENT", ignoreCase = true) -> "Settlement" raw.contains("SETTLEMENT", ignoreCase = true) -> "Settlement"
raw.contains("PRE", ignoreCase = true) -> "Pre-Auth" raw.contains("PRE", ignoreCase = true) -> "Pre-Auth"
raw.contains("CASH", ignoreCase = true) -> "Cash Adv" raw.contains("CASH", ignoreCase = true) -> "Cash Out"
raw.contains("WAVE", ignoreCase = true) -> "Wave Pay" raw.contains("WAVE", ignoreCase = true) -> "Wave Pay"
else -> raw.lowercase().replaceFirstChar { it.uppercaseChar() } else -> raw.lowercase().replaceFirstChar { it.uppercaseChar() }
} }
@ -138,18 +112,3 @@ private fun buildDateTime(date: String?, time: String?): String {
val t = (time ?: "").padStart(6, '0') val t = (time ?: "").padStart(6, '0')
return "${d.take(2)}/${d.drop(2).take(2)} ${t.take(2)}:${t.drop(2).take(2)}" 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)
}

View File

@ -51,7 +51,7 @@ fun SeeMoreScreen(
SeeMoreItem("Pre-Auth Void", Icons.Default.LockOpen), SeeMoreItem("Pre-Auth Void", Icons.Default.LockOpen),
SeeMoreItem("Pre-Auth Complete", Icons.Default.CreditCard), SeeMoreItem("Pre-Auth Complete", Icons.Default.CreditCard),
SeeMoreItem("Pre-Auth Complete Void", Icons.Default.SwapHoriz), SeeMoreItem("Pre-Auth Complete Void", Icons.Default.SwapHoriz),
SeeMoreItem("Cash Adv", Icons.Default.AccountBalanceWallet) SeeMoreItem("Cash Out", Icons.Default.AccountBalanceWallet)
) )
Scaffold( Scaffold(

View File

@ -32,7 +32,7 @@ fun EmvTransactionRoute(
onNavigateTransactionResult: () -> Unit, onNavigateTransactionResult: () -> Unit,
onNavigateInputRrn: () -> Unit, onNavigateInputRrn: () -> Unit,
onNavigateCardWaiting: () -> Unit, onNavigateCardWaiting: () -> Unit,
onNavigateError: (isSuccess:Boolean, messsage:String) -> Unit, onNavigateError: (String) -> Unit,
onNavigateMain: () -> Unit onNavigateMain: () -> Unit
) { ) {
val emvResultStatus by emvViewModel.emvResultStatus.observeAsState() val emvResultStatus by emvViewModel.emvResultStatus.observeAsState()
@ -69,7 +69,7 @@ fun EmvTransactionRoute(
val tradeData = emvViewModel.getTradeData() val tradeData = emvViewModel.getTradeData()
if (tradeData == null) { if (tradeData == null) {
onNavigateError(false,"Please try again") onNavigateError("Please try again")
return return
} }
@ -115,12 +115,12 @@ fun EmvTransactionRoute(
EmvResultStatus.TRY_AGAIN -> { EmvResultStatus.TRY_AGAIN -> {
sharedViewModel.set_errorFragmentMsg("Card Not Supported!") sharedViewModel.set_errorFragmentMsg("Card Not Supported!")
onNavigateError(false,"Card Not Supported!") onNavigateError("Card Not Supported!")
} }
EmvResultStatus.OFFLINE_DECLINE -> { EmvResultStatus.OFFLINE_DECLINE -> {
sharedViewModel.set_errorFragmentMsg("Offline Denial") sharedViewModel.set_errorFragmentMsg("Offline Denial")
onNavigateError(false,"Offline Denial") onNavigateError("Offline Denial")
} }
EmvResultStatus.CONFIRM_CODE_VERIFY -> onNavigateCardWaiting() EmvResultStatus.CONFIRM_CODE_VERIFY -> onNavigateCardWaiting()
@ -131,7 +131,7 @@ fun EmvTransactionRoute(
EmvResultStatus.PIN_MISMATCH -> { EmvResultStatus.PIN_MISMATCH -> {
sharedViewModel.set_errorFragmentMsg("PIN MISMATCH!") sharedViewModel.set_errorFragmentMsg("PIN MISMATCH!")
onNavigateError(false,"PIN MISMATCH!") onNavigateError("PIN MISMATCH!")
} }
EmvResultStatus.PIN_EMPTY -> { EmvResultStatus.PIN_EMPTY -> {
@ -164,27 +164,27 @@ fun EmvTransactionRoute(
EmvResultStatus.REVERSAL_FAIL -> { EmvResultStatus.REVERSAL_FAIL -> {
sharedViewModel.set_errorFragmentMsg("Reversal failed!") sharedViewModel.set_errorFragmentMsg("Reversal failed!")
onNavigateError(false,"Reversal failed!") onNavigateError("Reversal failed!")
} }
EmvResultStatus.REVERSAL_SUCCESS -> { EmvResultStatus.REVERSAL_SUCCESS -> {
sharedViewModel.set_successFragmentMsg("Reversal success!") sharedViewModel.set_successFragmentMsg("Reversal success!")
onNavigateError(true,"Reversal success!") onNavigateMain()
} }
EmvResultStatus.FAIL -> { EmvResultStatus.FAIL -> {
onNavigateError(false,"Transaction failed") onNavigateError("Transaction failed")
} }
EmvResultStatus.NETWORK_ERROR -> { EmvResultStatus.NETWORK_ERROR -> {
onNavigateError(false,"Com Error!") onNavigateError("Com Error!")
} }
EmvResultStatus.ERROR -> { EmvResultStatus.ERROR -> {
loadingMessage = null loadingMessage = null
val mapped = mapEmvError(errorCodeMsg) val mapped = mapEmvError(errorCodeMsg)
sharedViewModel.set_errorFragmentMsg(mapped) sharedViewModel.set_errorFragmentMsg(mapped)
onNavigateError(false,mapped) onNavigateError(mapped)
} }
else -> LogUtil.d(TAG, "Unhandled EmvResult Status : $status") else -> LogUtil.d(TAG, "Unhandled EmvResult Status : $status")

View File

@ -1,139 +0,0 @@
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")
}

View File

@ -74,7 +74,7 @@ private fun String.toTransactionConfig(): TransactionConfig {
processCode = ProcessCode.PRE_AUTH_COMPLETE_VOID + ProcessCode.SMART + ProcessCode.TO_ACCOUNT processCode = ProcessCode.PRE_AUTH_COMPLETE_VOID + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
) )
"cash adv" -> TransactionConfig( "cash out" -> TransactionConfig(
transactionName = "CASH_OUT", transactionName = "CASH_OUT",
transactionType = TransactionsType.CASH_OUT, transactionType = TransactionsType.CASH_OUT,
processCode = ProcessCode.CASH_ADVANCE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT processCode = ProcessCode.CASH_ADVANCE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT

View File

@ -49,23 +49,17 @@ fun InputAmount(
title: String = "Amount", title: String = "Amount",
onBackClick: () -> Unit = {}, onBackClick: () -> Unit = {},
onChargeClick: (String) -> Unit = {} onChargeClick: (String) -> Unit = {}
) { ){
var amount by remember { mutableStateOf("") } var amount by remember { mutableStateOf("") }
val prefixLabel = "MMK" val prefixLabel = "MMK"
val supportingText = "Enter the amount to continue" val supportingText = "Enter the amount to continue"
Scaffold( Scaffold(
topBar = { topBar = { AppBar(title = "Amount", icon = Icons.AutoMirrored.Rounded.ArrowBack, onIconClick = onBackClick) },
AppBar(
title = "Amount",
icon = Icons.AutoMirrored.Rounded.ArrowBack,
onIconClick = onBackClick
)
},
containerColor = Color.IvoryBeige containerColor = Color.IvoryBeige
) { paddingValues -> ) {paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .padding(paddingValues)
@ -78,10 +72,9 @@ fun InputAmount(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.weight(2f), .weight(2f),
) { ){
Card( Card(
modifier = Modifier modifier = Modifier.align(Alignment.CenterEnd)
.align(Alignment.CenterEnd)
.clickable(enabled = true) { .clickable(enabled = true) {
amount = amount.dropLast(1) amount = amount.dropLast(1)
}, },
@ -108,21 +101,29 @@ fun InputAmount(
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text(
text = formatAmount(amount.ifEmpty { "0" }),
color = Color.LegacyRed,
style = MaterialTheme.typography.displayLarge
)
if (prefixLabel.isNotBlank()) { 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)
)
}
Text( Text(
text = prefixLabel, text = formatAmount(amount.ifEmpty { "0" }),
color = Color.LegacyRed, color = Color.LegacyRed,
style = MaterialTheme.typography.displaySmall.copy(fontWeight = FontWeight.Medium), style = MaterialTheme.typography.displayLarge
modifier = Modifier.padding(end = 10.dp, bottom = 6.dp)
) )
} }
Spacer(modifier = Modifier.height(14.dp)) Spacer(modifier = Modifier.height(14.dp))
Text( Text(
text = supportingText, text = supportingText,
color = Color.Gray, color = Color.Gray,
@ -131,12 +132,14 @@ fun InputAmount(
) )
} }
} }
//second container
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(3f), .weight(3f),
verticalArrangement = Arrangement.Bottom verticalArrangement = Arrangement.Bottom
) { ){
NumericKeypad( NumericKeypad(
keys = amountKeys, keys = amountKeys,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -145,14 +148,14 @@ fun InputAmount(
} }
) )
} }
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
//third container //third container
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(0.5f) .weight(0.5f)
) { ){
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
@ -175,8 +178,7 @@ fun InputAmount(
onClick = { onClick = {
if (amount.isNotEmpty()) { if (amount.isNotEmpty()) {
onChargeClick(amount) onChargeClick(amount)
} } },
},
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(56.dp), .height(56.dp),
@ -189,14 +191,14 @@ fun InputAmount(
disabledContentColor = Color.White disabledContentColor = Color.White
) )
) { ) {
Text( Text(
text = "Next", text = "Next",
style = MaterialTheme.typography.titleSmall style = MaterialTheme.typography.titleSmall
) )
} }
} }
} }
} }
} }
} }
@ -234,21 +236,20 @@ private fun formatAmount(value: String): String {
val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L) val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L)
if (!normalized.contains(".")) { if (!normalized.contains(".")) {
return "$groupedWholePart.00" return groupedWholePart
} }
val decimalPart = normalized.substringAfter(".", "") val decimalPart = normalized.substringAfter(".", "")
return if (normalized.endsWith(".")) { return if (normalized.endsWith(".")) {
"$groupedWholePart.00" "$groupedWholePart."
} else { } else {
val padded = decimalPart.padEnd(2, '0').take(2) "$groupedWholePart.$decimalPart"
"$groupedWholePart.$padded"
} }
} }
@P2Preview @P2Preview
@P3Preview @P3Preview
@Composable @Composable
fun PreviewInputAmount() { fun PreviewInputAmount(){
InputAmount(); InputAmount();
} }

View File

@ -39,6 +39,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextRange 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.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
@ -232,7 +233,8 @@ private fun ManualCardEntryScreen(
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyLarge.copy( textStyle = MaterialTheme.typography.bodyLarge.copy(
letterSpacing = 2.sp letterSpacing = 2.sp,
fontFamily = FontFamily.Monospace
) )
) )
@ -261,7 +263,8 @@ private fun ManualCardEntryScreen(
.width(160.dp) .width(160.dp)
.focusRequester(expiryFocusRequester), .focusRequester(expiryFocusRequester),
textStyle = MaterialTheme.typography.bodyLarge.copy( textStyle = MaterialTheme.typography.bodyLarge.copy(
letterSpacing = 2.sp letterSpacing = 2.sp,
fontFamily = FontFamily.Monospace
) )
) )
} }
@ -281,6 +284,7 @@ private fun ManualCardEntryScreen(
Text( Text(
text = "**** **** **** ${pan.takeLast(4)}", text = "**** **** **** ${pan.takeLast(4)}",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.5.sp letterSpacing = 1.5.sp
) )
if (expiry.length == 4) { if (expiry.length == 4) {

View File

@ -91,7 +91,7 @@ fun AppNavGraph(
val sharedViewModel: SharedViewModel = hiltViewModel(activity) val sharedViewModel: SharedViewModel = hiltViewModel(activity)
SignaturePad( SignaturePad(
onBack = { navController.popBackStack() }, onBack = { navController.popBackStack() },
onConfirm = { bitmap, signatureBlob -> onConfirm = { _, bitmap, signatureBlob ->
sharedViewModel.signBitmap = bitmap sharedViewModel.signBitmap = bitmap
sharedViewModel.updateSignatureBlob(signatureBlob) sharedViewModel.updateSignatureBlob(signatureBlob)
navController.navigate(Routes.TransactionResult.route) { navController.navigate(Routes.TransactionResult.route) {
@ -592,9 +592,9 @@ fun AppNavGraph(
launchSingleTop = true launchSingleTop = true
} }
}, },
onNavigateError = { isSuccess,message -> onNavigateError = { message ->
navController.navigate(Routes.TransactionError.createRoute(isSuccess,message)) { navController.navigate(Routes.TransactionError.createRoute(message)) {
popUpTo(Routes.Processing.route) { popUpTo(Routes.ProcessingCard.route) {
inclusive = true inclusive = true
} }
launchSingleTop = true launchSingleTop = true
@ -681,8 +681,8 @@ fun AppNavGraph(
launchSingleTop = true launchSingleTop = true
} }
}, },
onNavigateError = { isSuccess,message -> onNavigateError = { message ->
navController.navigate(Routes.TransactionError.createRoute(isSuccess,message)) { navController.navigate(Routes.TransactionError.createRoute(message)) {
popUpTo(Routes.EmvTransaction.route) { popUpTo(Routes.EmvTransaction.route) {
inclusive = true inclusive = true
} }
@ -764,8 +764,8 @@ fun AppNavGraph(
launchSingleTop = true launchSingleTop = true
} }
}, },
onNavigateError = { isSuccess,message -> onNavigateError = { message ->
navController.navigate(Routes.TransactionError.createRoute(isSuccess,message)) { navController.navigate(Routes.TransactionError.createRoute(message)) {
popUpTo(Routes.Processing.route) { popUpTo(Routes.Processing.route) {
inclusive = true inclusive = true
} }
@ -819,11 +819,9 @@ fun AppNavGraph(
} }
) )
) { backStackEntry -> ) { backStackEntry ->
val isSuccess = backStackEntry.arguments?.getBoolean("isSuccess") ?: false val message = backStackEntry.arguments?.getString("message").orEmpty().trim()
val message = backStackEntry.arguments?.getString("message").orEmpty()
TransactionErrorScreen( TransactionErrorScreen(
isSuccess = isSuccess,
message = message, message = message,
onDone = { onDone = {
navController.navigate(Routes.Dashboard.route) { navController.navigate(Routes.Dashboard.route) {

View File

@ -31,7 +31,10 @@ sealed class Routes(val route: String) {
data object TransactionResult : Routes("transaction_result") data object TransactionResult : Routes("transaction_result")
data object TransactionError : Routes("transaction_error/{message}") {
fun createRoute(message: String): String =
"transaction_error/${Uri.encode(message.ifBlank { " " })}"
}
data object PrintReceipt : Routes("print_receipt") data object PrintReceipt : Routes("print_receipt")
data object Version : Routes("version") data object Version : Routes("version")
data object Functions : Routes("functions") data object Functions : Routes("functions")
@ -47,7 +50,4 @@ sealed class Routes(val route: String) {
data object Disable : Routes("disable/{message}") { data object Disable : Routes("disable/{message}") {
fun createRoute(message: String): String = "disable/${Uri.encode(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)}"
}
} }

View File

@ -2,29 +2,27 @@ package com.mob.utsmyanmar.ui.print_receipt
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing 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.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -32,7 +30,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.graphicsLayer 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@ -43,15 +44,15 @@ import com.mob.utsmyanmar.ui.transaction_result.TransactionResultState
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel
import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.utsmyanmar.paylibs.utils.POSUtil import com.utsmyanmar.paylibs.utils.POSUtil
import com.utsmyanmar.paylibs.utils.PrintStatus
import kotlin.math.abs
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
private enum class ReceiptCopy(val label: String) { private const val PRINT_ANIMATION_DURATION_MS = 2300
MERCHANT("MERCHANT COPY"), private const val RESET_ANIMATION_DURATION_MS = 350
CUSTOMER("CUSTOMER COPY") private const val PRINTED_HOLD_DELAY_MS = 500L
} private const val OFFSCREEN_BUFFER_PX = 120f
@Composable @Composable
fun PrintReceiptScreen( fun PrintReceiptScreen(
@ -63,58 +64,9 @@ fun PrintReceiptScreen(
val state by transactionResultViewModel.state.collectAsStateWithLifecycle() val state by transactionResultViewModel.state.collectAsStateWithLifecycle()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val receiptOffsetY = remember { Animatable(0f) } val receiptOffsetY = remember { Animatable(0f) }
val animationDuration = 5000 var slotHeightPx by remember { mutableFloatStateOf(0f) }
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) } 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( Scaffold(
topBar = { topBar = {
AppBar(title = "Receipt") AppBar(title = "Receipt")
@ -130,16 +82,41 @@ fun PrintReceiptScreen(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
ReceiptPreview( Box(
state = state,
copyLabel = currentCopy.label,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.verticalScroll(rememberScrollState()) .fillMaxWidth()
.graphicsLayer { .onSizeChanged { slotHeightPx = it.height.toFloat() }
translationY = receiptOffsetY.value ) {
} 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()
)
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@ -150,6 +127,7 @@ fun PrintReceiptScreen(
OutlinedButton( OutlinedButton(
onClick = onDone, onClick = onDone,
enabled = !isPrinting,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(50.dp), .height(50.dp),
@ -170,15 +148,50 @@ fun PrintReceiptScreen(
containerColor = Color.LegacyRed, containerColor = Color.LegacyRed,
contentColor = Color.White, contentColor = Color.White,
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f), disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
disabledContentColor = Color.White.copy(alpha = 0.5f), disabledContentColor = Color.White.copy(alpha = 0.7f),
), ),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
onClick = { onClick = {
doPrint(nextCopy) { } 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
}
} }
) { ) {
if (isPrinting) {
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
color = Color.White,
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(8.dp))
}
Text( Text(
text = "Print", text = if (isPrinting) "Printing..." else "Print",
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
} }
@ -188,11 +201,52 @@ 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 @Composable
fun ReceiptPreview( fun ReceiptPreview(
state: TransactionResultState, state: TransactionResultState,
copyLabel: String,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column( Column(
@ -210,18 +264,12 @@ fun ReceiptPreview(
) { ) {
Text( Text(
text = "MOB POS", text = "MOB POS",
style = MaterialTheme.typography.headlineLarge style = MaterialTheme.typography.headlineLarge.copy(fontFamily = FontFamily.Monospace)
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = "Transaction Receipt", text = "Transaction Receipt",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = copyLabel,
style = MaterialTheme.typography.labelLarge,
color = Color.Gray
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ReceiptDivider() ReceiptDivider()
@ -240,14 +288,14 @@ fun ReceiptPreview(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = "Thank You", text = "Thank You",
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium.copy(fontFamily = FontFamily.Monospace)
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = "Please keep this receipt", text = "Please keep this receipt",
style = MaterialTheme.typography.bodySmall style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)
) )
} }
} }
@ -263,16 +311,16 @@ fun ReceiptRow(
.padding(vertical = 6.dp), .padding(vertical = 6.dp),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
color = Color.Gray color = Color.Gray
) )
Text( Text(
text = value, text = value,
style = MaterialTheme.typography.titleSmall style = MaterialTheme.typography.titleSmall.copy(fontFamily = FontFamily.Monospace)
) )
} }
} }
@ -283,6 +331,7 @@ fun ReceiptDivider() {
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 10.dp), .padding(vertical = 10.dp),
text = "--------------------------------", text = "--------------------------------",
fontFamily = FontFamily.Monospace,
color = Color.Gray color = Color.Gray
) )
} }
@ -298,4 +347,4 @@ fun PreviewPrintReceiptScreen() {
onDone = {} onDone = {}
) )
} }
} }

View File

@ -13,7 +13,7 @@ fun ProcessingCardRoute(
onNavigateInputAmount: () -> Unit, onNavigateInputAmount: () -> Unit,
onNavigateProcessing: () -> Unit, onNavigateProcessing: () -> Unit,
onNavigateEmvTransaction: () -> Unit, onNavigateEmvTransaction: () -> Unit,
onNavigateError: (isSuccess: Boolean, message: String) -> Unit, onNavigateError: (String) -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
onShowDecline: (String) -> Unit onShowDecline: (String) -> Unit
) { ) {
@ -31,7 +31,7 @@ fun ProcessingCardRoute(
ProcessingCardUiEvent.NavigateToInputAmount -> onNavigateInputAmount() ProcessingCardUiEvent.NavigateToInputAmount -> onNavigateInputAmount()
ProcessingCardUiEvent.NavigateToProcessing -> onNavigateProcessing() ProcessingCardUiEvent.NavigateToProcessing -> onNavigateProcessing()
ProcessingCardUiEvent.NavigateToEmvTransaction -> onNavigateEmvTransaction() ProcessingCardUiEvent.NavigateToEmvTransaction -> onNavigateEmvTransaction()
is ProcessingCardUiEvent.NavigateToError -> onNavigateError(false,event.message) is ProcessingCardUiEvent.NavigateToError -> onNavigateError(event.message)
is ProcessingCardUiEvent.ShowDeclineAndBack -> { is ProcessingCardUiEvent.ShowDeclineAndBack -> {
onShowDecline(event.message) onShowDecline(event.message)
onBack() onBack()

View File

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.CardTransactionType import com.mob.utsmyanmar.model.CardTransactionType
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.utils.MockCardData
import com.mob.utsmyanmar.utils.MockData import com.mob.utsmyanmar.utils.MockData
import com.mob.utsmyanmar.utils.TransactionUtil import com.mob.utsmyanmar.utils.TransactionUtil
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel import com.mob.utsmyanmar.viewmodel.CardReaderViewModel

View File

@ -25,7 +25,7 @@ fun ProcessingRoute(
settlementViewModel: SettlementViewModel, settlementViewModel: SettlementViewModel,
onNavigateTransactionResult: () -> Unit, onNavigateTransactionResult: () -> Unit,
onNavigateSignature: () -> Unit, onNavigateSignature: () -> Unit,
onNavigateError: (Boolean,String) -> Unit, onNavigateError: (String) -> Unit,
onNavigateMain: ()-> Unit onNavigateMain: ()-> Unit
) { ) {
@ -105,7 +105,7 @@ private fun EmvProcessingContent(
sharedViewModel: SharedViewModel, sharedViewModel: SharedViewModel,
emvTransactionViewModel: EmvTransactionProcessViewModel, emvTransactionViewModel: EmvTransactionProcessViewModel,
onNavigateTransactionResult: () -> Unit, onNavigateTransactionResult: () -> Unit,
onNavigateError: (Boolean,String) -> Unit, onNavigateError: (String) -> Unit,
onNavigateMain: () -> Unit onNavigateMain: () -> Unit
) { ) {
val emvTransStatus by emvTransactionViewModel.getTransStatus().observeAsState() val emvTransStatus by emvTransactionViewModel.getTransStatus().observeAsState()
@ -134,13 +134,13 @@ private fun EmvProcessingContent(
TransResultStatus.REVERSAL_SUCCESS -> { TransResultStatus.REVERSAL_SUCCESS -> {
sharedViewModel.payDetail.value = emvTransactionViewModel.payDetailResult.value sharedViewModel.payDetail.value = emvTransactionViewModel.payDetailResult.value
sharedViewModel.set_successFragmentMsg("Reversal success!") sharedViewModel.set_successFragmentMsg("Reversal success!")
onNavigateError(true,"Reversal success!") onNavigateMain()
} }
TransResultStatus.REVERSAL_FAIL -> { TransResultStatus.REVERSAL_FAIL -> {
sharedViewModel.payDetail.value = emvTransactionViewModel.payDetailResult.value sharedViewModel.payDetail.value = emvTransactionViewModel.payDetailResult.value
sharedViewModel.set_errorFragmentMsg("Reversal failed!") sharedViewModel.set_errorFragmentMsg("Reversal failed!")
onNavigateError(false,"Reversal failed!") onNavigateError("Reversal failed!")
} }
TransResultStatus.ERROR, TransResultStatus.ERROR,
@ -149,7 +149,7 @@ private fun EmvProcessingContent(
?.substringAfter(":") ?.substringAfter(":")
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
?: "Transaction error" ?: "Transaction error"
onNavigateError(false,message) onNavigateError(message)
} }
else -> {} else -> {}

View File

@ -1,10 +1,15 @@
package com.mob.utsmyanmar.ui.signature_pad package com.mob.utsmyanmar.ui.signature_pad
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.compose.foundation.border import android.graphics.Paint
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -19,31 +24,84 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.createBitmap
import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
import se.warting.signaturepad.SignaturePadAdapter
import se.warting.signaturepad.SignaturePadView
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import android.graphics.Canvas as AndroidCanvas
import android.graphics.Path as AndroidPath
import com.mob.utsmyanmar.ui.theme.Color as AppColor import com.mob.utsmyanmar.ui.theme.Color as AppColor
@Composable @Composable
fun SignaturePad( fun SignaturePad(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
signatureColor: Color = Color.Black,
onBack: () -> Unit = {}, onBack: () -> Unit = {},
onConfirm: (Bitmap?, ByteArray?) -> Unit = { _, _ -> } // signatureBlob is null for Digital mode (using stored signature) or when nothing was drawn
onConfirm: (SignatureMode, Bitmap?, signatureBlob: ByteArray?) -> Unit = { _, _, _ -> }
) { ) {
var signaturePadAdapter: SignaturePadAdapter? = null val paths = remember { mutableStateListOf<List<Offset>>() }
val currentPath = remember { mutableStateListOf<Offset>() }
var selectedMode by remember { mutableStateOf(SignatureMode.Manual) }
var canvasSize by remember { mutableStateOf(Size.Zero) }
var strokeWidth = 6f
val scaledWidth = 300 fun clearSignature() {
paths.clear()
currentPath.clear()
}
fun scaleBitmap(source: Bitmap): Bitmap {
val ratio = scaledWidth.toFloat() / source.width fun buildBitmap(): Bitmap? {
val height = (source.height * ratio).toInt() if (selectedMode == SignatureMode.Digital) return null
return Bitmap.createScaledBitmap(source, scaledWidth, height, true) 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<Offset>) {
if (points.size < 2) return
val path = AndroidPath().apply {
moveTo(points.first().x, points.first().y)
for (index in 1 until points.size) {
lineTo(points[index].x, points[index].y)
}
}
canvas.drawPath(path, paint)
}
paths.forEach(::drawStroke)
drawStroke(currentPath)
return bitmap
} }
fun bitmapToBlob(bitmap: Bitmap): ByteArray { fun bitmapToBlob(bitmap: Bitmap): ByteArray {
@ -69,20 +127,95 @@ fun SignaturePad(
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
SignaturePadView( Box(
onReady = { signaturePadAdapter = it }, modifier = Modifier
.fillMaxWidth(0.85f) // 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(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(200.dp) .height(150.dp)
.border(width = 1.dp, color = AppColor.LegacyRed) ) {
) SignatureModeOption(
title = "Manual Sign",
subtitle = "Customer signs on screen",
selected = selectedMode == SignatureMode.Manual,
onClick = { selectedMode = SignatureMode.Manual },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(12.dp))
SignatureModeOption(
title = "Digital Sign",
subtitle = "Use stored signature",
selected = selectedMode == SignatureMode.Digital,
onClick = { selectedMode = SignatureMode.Digital },
modifier = Modifier.fillMaxWidth()
)
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
OutlinedButton( OutlinedButton(
onClick = { signaturePadAdapter?.clear() }, onClick = { clearSignature() },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(50.dp) .height(50.dp)
@ -92,10 +225,13 @@ fun SignaturePad(
Button( Button(
onClick = { onClick = {
val rawBitmap = signaturePadAdapter?.getSignatureBitmap() val bitmap = buildBitmap()
val bitmap = rawBitmap?.let { scaleBitmap(it) } if (selectedMode == SignatureMode.Manual && bitmap == null) {
// nothing drawn yet on Manual mode — block confirm
return@Button
}
val signatureBlob = bitmap?.let { bitmapToBlob(it) } val signatureBlob = bitmap?.let { bitmapToBlob(it) }
onConfirm(bitmap, signatureBlob) onConfirm(selectedMode, bitmap, signatureBlob)
}, },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@ -161,4 +297,4 @@ fun SignaturePadPreview() {
MOBPOSTheme { MOBPOSTheme {
SignaturePad() SignaturePad()
} }
} }

View File

@ -1,96 +1,77 @@
package com.mob.utsmyanmar.ui.theme package com.mob.utsmyanmar.ui.theme
import androidx.compose.ui.text.font.Font
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp 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( val Typography = Typography(
displayLarge = TextStyle( displayLarge = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 32.sp, fontSize = 32.sp,
lineHeight = 40.sp, lineHeight = 40.sp,
), ),
displayMedium = TextStyle( displayMedium = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 30.sp, fontSize = 30.sp,
lineHeight = 38.sp, lineHeight = 38.sp,
), ),
headlineLarge = TextStyle( headlineLarge = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 24.sp, fontSize = 24.sp,
lineHeight = 32.sp, lineHeight = 32.sp,
), ),
headlineMedium = TextStyle( headlineMedium = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 20.sp, fontSize = 20.sp,
lineHeight = 28.sp, lineHeight = 28.sp,
), ),
titleLarge = TextStyle( titleLarge = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
fontSize = 22.sp, fontSize = 22.sp,
lineHeight = 28.sp, lineHeight = 28.sp,
), ),
titleMedium = TextStyle( titleMedium = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
fontSize = 18.sp, fontSize = 18.sp,
lineHeight = 26.sp, lineHeight = 26.sp,
), ),
titleSmall = TextStyle( titleSmall = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 14.sp, fontSize = 14.sp,
lineHeight = 20.sp, lineHeight = 20.sp,
), ),
bodyLarge = TextStyle( bodyLarge = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 16.sp, fontSize = 16.sp,
lineHeight = 24.sp, lineHeight = 24.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
), ),
bodyMedium = TextStyle( bodyMedium = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 14.sp, fontSize = 14.sp,
lineHeight = 20.sp, lineHeight = 20.sp,
), ),
bodySmall = TextStyle( bodySmall = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 12.sp, fontSize = 12.sp,
lineHeight = 16.sp, lineHeight = 16.sp,
), ),
labelLarge = TextStyle( labelLarge = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 14.sp, fontSize = 14.sp,
lineHeight = 20.sp, lineHeight = 20.sp,
), ),
labelMedium = TextStyle( labelMedium = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 12.sp, fontSize = 12.sp,
lineHeight = 16.sp, lineHeight = 16.sp,
), ),
labelSmall = TextStyle( labelSmall = TextStyle(
fontFamily = LibreBaskerville,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 11.sp, fontSize = 11.sp,
lineHeight = 16.sp, lineHeight = 16.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
) )
) )

View File

@ -2,7 +2,6 @@ package com.mob.utsmyanmar.ui.tms_setup
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import androidx.core.content.edit
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.sirius.SiriusResponse import com.mob.utsmyanmar.model.sirius.SiriusResponse
@ -49,7 +48,7 @@ class TmsSetupViewModel @Inject constructor(
private val emvParamOperation: EmvParamOperation, private val emvParamOperation: EmvParamOperation,
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) : ViewModel() { ) : ViewModel() {
private val TAG = TmsSetupViewModel::class.simpleName;
private val _uiState = MutableStateFlow(TmsSetupUiState()) private val _uiState = MutableStateFlow(TmsSetupUiState())
private val _dashboardUiState = MutableStateFlow(DashboardUiState()) private val _dashboardUiState = MutableStateFlow(DashboardUiState())
val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow() val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow()
@ -145,27 +144,19 @@ class TmsSetupViewModel @Inject constructor(
val ops = SystemParamsOperation.getInstance() val ops = SystemParamsOperation.getInstance()
if (!ops.isActive) { if (!ops.isActive) {
LogUtil.d(TAG, "isActive ${ops.isActive}")
val msg = ops.disabledMsg.takeIf { it.isNotEmpty() } val msg = ops.disabledMsg.takeIf { it.isNotEmpty() }
?: "This terminal has been disabled. Please contact your administrator." ?: "This terminal has been disabled. Please contact your administrator."
_uiState.update { _uiState.update { it.copy(isLoading = false) }
it.copy( viewModelScope.launch { _navigateToDisable.emit(msg) }
isLoading = false,
isError = true,
isTerminalDisabled = true,
statusText = "Terminal Disabled",
errorMessage = msg
)
}
return return
} }
val validity = TMSUtil.getInstance().checkParams() val validity = TMSUtil.getInstance().checkParams()
if (validity.status == ValidityStatus.SUCCESS) { if (validity.status == ValidityStatus.SUCCESS) {
context.getSharedPreferences("mobpos_prefs", Context.MODE_PRIVATE) context.getSharedPreferences("mobpos_prefs", Context.MODE_PRIVATE)
.edit { .edit()
putLong("last_sync_time", System.currentTimeMillis()) .putLong("last_sync_time", System.currentTimeMillis())
} .apply()
_uiState.update { it.copy(isLoading = false, statusText = "Ready.") } _uiState.update { it.copy(isLoading = false, statusText = "Ready.") }
viewModelScope.launch { viewModelScope.launch {
_onSyncCompleted.emit(Unit) _onSyncCompleted.emit(Unit)

View File

@ -24,8 +24,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.mob.utsmyanmar.ui.preview.P2Preview import com.mob.utsmyanmar.ui.preview.P2Preview
import com.mob.utsmyanmar.ui.theme.Color 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.model.PayDetail
import com.utsmyanmar.paylibs.print.PrintReceipt import com.utsmyanmar.paylibs.print.PrintReceipt
import com.utsmyanmar.paylibs.utils.POSUtil import com.utsmyanmar.paylibs.utils.POSUtil
@ -84,8 +82,8 @@ fun TransactionResultScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
InfoCard( InfoCard(
date = "${state.payDetail?.transDate?.toFormattedDate()}", date = "26 May 2026",
time = "${state.payDetail?.transTime?.toFormattedTime()}", time = "12:06 PM",
transactionId = state.payDetail?.referNo.toString() transactionId = state.payDetail?.referNo.toString()
) )

View File

@ -6,6 +6,7 @@ import com.mob.utsmyanmar.model.ecr.ECRResultStatus
import com.mob.utsmyanmar.utils.CoreUtils import com.mob.utsmyanmar.utils.CoreUtils
import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.utsmyanmar.baselib.emv.EmvParamOperation import com.utsmyanmar.baselib.emv.EmvParamOperation
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
import com.utsmyanmar.ecr.ECRHelper import com.utsmyanmar.ecr.ECRHelper
import com.utsmyanmar.ecr.ECRProcess import com.utsmyanmar.ecr.ECRProcess
import com.utsmyanmar.ecr.data.model.TransactionsResp import com.utsmyanmar.ecr.data.model.TransactionsResp

View File

@ -1,18 +0,0 @@
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!!)
}

View File

@ -131,7 +131,7 @@ class TMSSetupsImpl : TMSSetups {
} }
//Properties //Properties
for (prop in siriusProperty) { for (prop in siriusProperty.orEmpty()) {
val name = prop.name val name = prop.name
val data = prop.property val data = prop.property
@ -221,7 +221,6 @@ class TMSSetupsImpl : TMSSetups {
"speedup_contactless_enable" -> ops.setSpeedUpContactless(parseBoolean(data)) "speedup_contactless_enable" -> ops.setSpeedUpContactless(parseBoolean(data))
"manual_entry_pin_enable" -> ops.isManualEntryPinEnable = parseBoolean(data) "manual_entry_pin_enable" -> ops.isManualEntryPinEnable = parseBoolean(data)
"cmhl_enabled" -> ops.setCMHLEnable(parseBoolean(data)) "cmhl_enabled" -> ops.setCMHLEnable(parseBoolean(data))
"mmqr_enable" -> ops.setMmqrStatus(parseBoolean(data))
} }
} }
ops.carouselUrls = imgUrls.joinToString(",") ops.carouselUrls = imgUrls.joinToString(",")

View File

@ -4,7 +4,6 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.text.TextUtils import android.text.TextUtils
import com.mob.utsmyanmar.model.TransResultStatus
import com.utsmyanmar.baselib.emv.EmvParamOperation import com.utsmyanmar.baselib.emv.EmvParamOperation
import com.utsmyanmar.baselib.repo.Repository import com.utsmyanmar.baselib.repo.Repository
import com.utsmyanmar.baselib.util.enums.EmvResultStatus import com.utsmyanmar.baselib.util.enums.EmvResultStatus
@ -25,6 +24,7 @@ import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import sunmi.sunmiui.utils.LogUtil import sunmi.sunmiui.utils.LogUtil
import javax.inject.Inject import javax.inject.Inject
import com.mob.utsmyanmar.model.TransResultStatus
@HiltViewModel @HiltViewModel
class EmvTransactionProcessViewModel @Inject constructor( class EmvTransactionProcessViewModel @Inject constructor(

View File

@ -3,7 +3,6 @@ package com.mob.utsmyanmar.viewmodel
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.TransResultStatus import com.mob.utsmyanmar.model.TransResultStatus
import com.mob.utsmyanmar.ui.sending_to_host.ProcessingState import com.mob.utsmyanmar.ui.sending_to_host.ProcessingState
import com.utsmyanmar.baselib.repo.Repository import com.utsmyanmar.baselib.repo.Repository
@ -20,10 +19,8 @@ import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -213,10 +210,7 @@ class TransProcessViewModel @Inject constructor(
TransResultStatus.REVERSAL_PREPARE TransResultStatus.REVERSAL_PREPARE
_state.value = _state.value.copy("Preparing Reversal") _state.value = _state.value.copy("Preparing Reversal")
viewModelScope.launch { callReversal(tradeData)
delay(10_000L)
callReversal(tradeData)
}
} }
override fun onError(message: String) { override fun onError(message: String) {

View File

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="152.4dp"
android:height="108dp"
android:viewportWidth="152.4"
android:viewportHeight="108">
<path
android:pathData="M60.4,25.7h31.5v56.6h-31.5z"
android:fillColor="#FF5F00"/>
<path
android:pathData="M62.4,54c0,-11 5.1,-21.5 13.7,-28.3c-15.6,-12.3 -38.3,-9.6 -50.6,6.1C13.3,47.4 16,70 31.7,82.3c13.1,10.3 31.4,10.3 44.5,0C67.5,75.5 62.4,65 62.4,54z"
android:fillColor="#EB001B"/>
<path
android:pathData="M134.4,54c0,19.9 -16.1,36 -36,36c-8.1,0 -15.9,-2.7 -22.2,-7.7c15.6,-12.3 18.3,-34.9 6,-50.6c-1.8,-2.2 -3.8,-4.3 -6,-6c15.6,-12.3 38.3,-9.6 50.5,6.1C131.7,38.1 134.4,45.9 134.4,54z"
android:fillColor="#F79E1B"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,18 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="750"
android:viewportHeight="471">
<path
android:pathData="M278.2,334.23l33.36,-195.76h53.36l-33.38,195.76H278.2L278.2,334.23z"
android:fillColor="#0E4595"/>
<path
android:pathData="M524.31,142.69c-10.57,-3.97 -27.14,-8.22 -47.82,-8.22c-52.72,0 -89.86,26.55 -90.18,64.6c-0.3,28.13 26.51,43.82 46.75,53.19c20.77,9.6 27.75,15.72 27.65,24.28c-0.13,13.12 -16.59,19.12 -31.92,19.12c-21.35,0 -32.7,-2.97 -50.22,-10.27l-6.88,-3.11l-7.49,43.82c12.46,5.47 35.51,10.2 59.44,10.44c56.09,0 92.5,-26.25 92.92,-66.88c0.2,-22.27 -14.02,-39.22 -44.8,-53.19c-18.65,-9.06 -30.07,-15.1 -29.95,-24.27c0,-8.14 9.67,-16.84 30.56,-16.84c17.45,-0.27 30.09,3.53 39.94,7.5l4.78,2.26L524.31,142.69"
android:fillColor="#0E4595"/>
<path
android:pathData="M661.61,138.46h-41.23c-12.77,0 -22.33,3.49 -27.94,16.23l-79.24,179.4h56.03c0,0 9.16,-24.12 11.23,-29.42c6.12,0 60.56,0.08 68.34,0.08c1.6,6.85 6.49,29.33 6.49,29.33h49.51L661.61,138.46L661.61,138.46zM596.2,264.87c4.41,-11.28 21.26,-54.72 21.26,-54.72c-0.31,0.52 4.38,-11.33 7.07,-18.68l3.61,16.88c0,0 10.22,46.73 12.35,56.53h-44.29V264.87L596.2,264.87z"
android:fillColor="#0E4595"/>
<path
android:pathData="M45.88,138.46L45.2,142.54C66.29,147.64 85.13,155.03 101.62,164.23L148.97,333.91L205.42,333.85L289.43,138.46L232.9,138.46L180.66,271.96L175.1,244.83C174.84,243.99 174.56,243.15 174.27,242.31L156.11,154.99C152.88,142.6 143.51,138.9 131.92,138.46L45.88,138.46z"
android:fillColor="#0e4595"/>
</vector>

View File

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.MOBPOS" parent="android:style/Theme.Material.Light.NoActionBar"> <style name="Theme.MOBPOS" parent="android:style/Theme.Material.Light.NoActionBar"/>
<item name="fontFamily">@font/libre_baskerville_regular</item>
<item name="android:fontFamily">@font/libre_baskerville_regular</item>
</style>
</resources> </resources>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">posuat.myanmarorientalbank.com:5033</domain>
</domain-config>
</network-security-config>

View File

@ -1,12 +1,17 @@
package com.utsmyanmar.ecr package com.utsmyanmar.ecr
import androidx.annotation.Keep
import com.google.gson.Gson import com.google.gson.Gson
import com.utsmyanmar.ecr.data.ResultOf 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.RequestTransMapper
import com.utsmyanmar.ecr.data.mapper.ResponseTransMapper import com.utsmyanmar.ecr.data.mapper.ResponseTransMapper
import com.utsmyanmar.ecr.data.model.RequestTrans 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.Transactions
import com.utsmyanmar.ecr.data.model.TransactionsResp import com.utsmyanmar.ecr.data.model.TransactionsResp
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import java.lang.Exception
//@Keep //@Keep
object ECRProcess { object ECRProcess {

View File

@ -503,7 +503,7 @@ public class ISOSocket {
LogUtil.e(Constant.TAG, "E:" + e.toString()); LogUtil.e(Constant.TAG, "E:" + e.toString());
// closeSocket(); closeSocket();
if (isReversal) { if (isReversal) {
listener.onError(Constant.REVERSAL); listener.onError(Constant.REVERSAL);
// listener.onError(new ReversalException()); // listener.onError(new ReversalException());
@ -519,7 +519,7 @@ public class ISOSocket {
@Override @Override
public void onComplete() { public void onComplete() {
LogUtil.d(Constant.TAG, "onComplete"); LogUtil.d(Constant.TAG, "onComplete");
// closeSocket(); closeSocket();
isReversal = false; isReversal = false;
listener.onComplete(); listener.onComplete();
} }

View File

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

View File

@ -50,7 +50,7 @@ public abstract class BaseXPrint {
protected String currentTime; protected String currentTime;
protected static final String typeface_name = "jetBrainsMonoRegular.ttf"; protected static final String typeface_name = "jbm_semibold.ttf";
protected static final String MPU_AID = "A0000004820000000001"; protected static final String MPU_AID = "A0000004820000000001";
protected static final String HOST_NAME_VM = "VISA/ MASTERCARD"; protected static final String HOST_NAME_VM = "VISA/ MASTERCARD";
@ -954,12 +954,6 @@ public abstract class BaseXPrint {
if (signatureBlob != null && signatureBlob.length > 0) { if (signatureBlob != null && signatureBlob.length > 0) {
Bitmap signatureBitmap = android.graphics.BitmapFactory.decodeByteArray(signatureBlob, 0, signatureBlob.length); Bitmap signatureBitmap = android.graphics.BitmapFactory.decodeByteArray(signatureBlob, 0, signatureBlob.length);
if (signatureBitmap != null) { if (signatureBitmap != null) {
int targetHeight = 200;
if (signatureBitmap.getHeight() > targetHeight) {
double ratio = 1.0 * targetHeight / signatureBitmap.getHeight();
int newWidth = (int) (signatureBitmap.getWidth() * ratio);
signatureBitmap = BitmapUtils.scale(signatureBitmap, newWidth, targetHeight);
}
sunmiPrinterService.printBitmap(signatureBitmap, innerResultCallback); sunmiPrinterService.printBitmap(signatureBitmap, innerResultCallback);
} }
} }

View File

@ -368,7 +368,7 @@ public class TransactionsOperation {
} }
} }
} else if (transactionsType == TransactionsType.PRE_AUTH_VOID) { } else if (transactionsType == TransactionsType.PRE_AUTH_VOID) {
// newPay.setCardType(oldPay.getCardType()); newPay.setCardType(oldPay.getCardType());
newPay.setCustomOrderNo(oldPay.getVoucherNo()); newPay.setCustomOrderNo(oldPay.getVoucherNo());
newPay.setReferNo(oldPay.getReferNo()); newPay.setReferNo(oldPay.getReferNo());
newPay.setCardInfo(oldPay.getCardInfo()); newPay.setCardInfo(oldPay.getCardInfo());

View File

@ -1,16 +1,16 @@
package com.utsmyanmar.paylibs.utils.core_utils; package com.utsmyanmar.paylibs.utils.core_utils;
import static com.utsmyanmar.paylibs.utils.core_utils.SystemParamsSettings.TRANSACTION_MANAGER_KEY;
import android.text.TextUtils; import android.text.TextUtils;
import com.utsmyanmar.paylibs.utils.enums.CurrencyType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import sunmi.sunmiui.utils.LogUtil; 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 class SystemParamsOperation {
public static final String TAG = "SystemParamsOperation"; public static final String TAG = "SystemParamsOperation";
@ -33,6 +33,54 @@ public class SystemParamsOperation {
return instance; 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(){ public boolean isInjectOnce(){
SystemParamsSettings params = getSystemParamsSettings(); SystemParamsSettings params = getSystemParamsSettings();
return params.isInjectOnce(); return params.isInjectOnce();
@ -43,6 +91,13 @@ public class SystemParamsOperation {
params.setInjectOnce(injectOnce); params.setInjectOnce(injectOnce);
saveSystemParamsSettings(params); saveSystemParamsSettings(params);
} }
/**
* Set serial number
*
* @param serialNumber Starting serial number
*/
public void saveSerialNumber(String serialNumber) { public void saveSerialNumber(String serialNumber) {
if (TextUtils.isEmpty(serialNumber)) { if (TextUtils.isEmpty(serialNumber)) {
return; return;
@ -51,6 +106,12 @@ public class SystemParamsOperation {
params.setSerialNum(serialNumber); params.setSerialNum(serialNumber);
saveSystemParamsSettings(params); saveSystemParamsSettings(params);
} }
/**
* Set invoice number
*
* @param invoiceNumber Starting invoice number
*/
public void saveInvoiceNumber(String invoiceNumber) { public void saveInvoiceNumber(String invoiceNumber) {
if (TextUtils.isEmpty(invoiceNumber)) { if (TextUtils.isEmpty(invoiceNumber)) {
return; return;
@ -59,6 +120,13 @@ public class SystemParamsOperation {
params.setInvoiceNum(invoiceNumber); params.setInvoiceNum(invoiceNumber);
saveSystemParamsSettings(params); saveSystemParamsSettings(params);
} }
/**
* Set batch number
*
* @param batchNumber Starting batch number
*/
public void saveBatchNumber(String batchNumber) { public void saveBatchNumber(String batchNumber) {
if (TextUtils.isEmpty(batchNumber)) { if (TextUtils.isEmpty(batchNumber)) {
return; return;
@ -68,6 +136,11 @@ public class SystemParamsOperation {
saveSystemParamsSettings(params); saveSystemParamsSettings(params);
} }
/**
* Serial number increment
*/
private void incrementSerialNum() { private void incrementSerialNum() {
SystemParamsSettings params = getSystemParamsSettings(); SystemParamsSettings params = getSystemParamsSettings();
String currentSerialNum = params.getCurrentSerialNum(); String currentSerialNum = params.getCurrentSerialNum();
@ -99,6 +172,10 @@ public class SystemParamsOperation {
params.setSerialNum(transNumFormat); params.setSerialNum(transNumFormat);
saveSystemParamsSettings(params); saveSystemParamsSettings(params);
} }
/**
* Invoice number increment
*/
private void incrementInvoiceNum() { private void incrementInvoiceNum() {
SystemParamsSettings params = getSystemParamsSettings(); SystemParamsSettings params = getSystemParamsSettings();
String currentInvoiceNum = params.getCurrentInvoiceNum(); String currentInvoiceNum = params.getCurrentInvoiceNum();
@ -875,17 +952,6 @@ public class SystemParamsOperation {
saveSystemParamsSettings(params); 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(){ public boolean isCMHLEnabled(){
SystemParamsSettings params = getSystemParamsSettings(); SystemParamsSettings params = getSystemParamsSettings();
return params.isCmhlEnabled(); return params.isCmhlEnabled();

View File

@ -38,7 +38,7 @@ public class SystemParamsSettings implements Serializable {
private boolean checkExpSwitch = false; private boolean checkExpSwitch = false;
// private String tmsAddress = "https://posmgmt.myanmarorientalbank.com/api/v1"; // private String tmsAddress = "http://posuat.myanmarorientalbank.com:5033";
private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1"; private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1";
// private String tmsAddress = "http://128.199.170.203"; // private String tmsAddress = "http://128.199.170.203";
@ -55,7 +55,6 @@ public class SystemParamsSettings implements Serializable {
private String merchantName = ""; private String merchantName = "";
private String receiptHeader = ""; private String receiptHeader = "";
private String merchantAddress = ""; private String merchantAddress = "";
@ -233,7 +232,6 @@ public class SystemParamsSettings implements Serializable {
private byte[] pik_value; private byte[] pik_value;
private byte[] pik_kcv; private byte[] pik_kcv;
private boolean mmqrEnabled;
protected String getSecHostName() { protected String getSecHostName() {
return secHostName; return secHostName;
@ -903,14 +901,6 @@ public class SystemParamsSettings implements Serializable {
this.emvEnabled = emvEnabled; this.emvEnabled = emvEnabled;
} }
public void setMmqrEnabled(boolean status) {
this.mmqrEnabled = status;
}
public boolean getMmqrEnabled(){
return mmqrEnabled;
}
/* // 流水号起始 /* // 流水号起始
private String serialNum = Configs.getInstance().SERIAL_NUM(); private String serialNum = Configs.getInstance().SERIAL_NUM();
// 批次号起始 // 批次号起始

View File

@ -117,10 +117,7 @@ public class HostUtils {
msgType = MessageType.AUTHORIZATION; msgType = MessageType.AUTHORIZATION;
} }
break; break;
case PRE_AUTH_VOID:
case PRE_AUTH_COMPLETE: case PRE_AUTH_COMPLETE:
if (hostName == HostName.BPC) { if (hostName == HostName.BPC) {
msgType = MessageType.FINANCIAL; msgType = MessageType.FINANCIAL;
@ -145,11 +142,11 @@ public class HostUtils {
break; break;
case VOID: case VOID:
case PRE_AUTH_VOID:
if(isVisaMaster) { if(isVisaMaster) {
msgType = MessageType.REVERSAL; msgType = MessageType.REVERSAL;
break; break;
} }
default: default:
msgType = MessageType.FINANCIAL; msgType = MessageType.FINANCIAL;
break; break;

View File

@ -198,8 +198,7 @@ public class BitmapConfig {
public static final String CZ_REVERSAL="7020078000C08000"; public static final String CZ_REVERSAL="7020078000C08000";
public static final String CZ_BATCH_UPLOAD="703805800CC00010"; public static final String CZ_BATCH_UPLOAD="703805800CC00010";
public static final String CZ_PRE_AUTH="7020058020C09000"; 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="703805802CC08000";
public static final String CZ_PRE_AUTH_COMP_VOID="7038058008C00000"; public static final String CZ_PRE_AUTH_COMP_VOID="7038058008C00000";

View File

@ -361,12 +361,7 @@ public class FieldUtils {
// } else { // } else {
// iterator.remove(); // iterator.remove();
// } // }
if(payDetail.getTransactionType() == REVERSAL.value) { field.setDataStr("000");
field.setDataStr("001");
} else {
field.setDataStr("000");
}
break; break;
case 24: //Network International Identifier NII case 24: //Network International Identifier NII
@ -381,10 +376,10 @@ public class FieldUtils {
08 – mail order/telephone order 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 // 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
// do not remove double check for preauth, it's control for reversal if (payDetail.getTransactionType() == PRE_AUTH_SALE.value) {
if (payDetail.getTransactionType() == PRE_AUTH_SALE.value || payDetail.getTransType().equals(PRE_AUTH_SALE.name)) {
field.setDataStr("06"); field.setDataStr("06");
} else if(payDetail.getTransactionType() == REFUND.value || payDetail.getTransType().equals(REFUND.name) ) { }
else if(payDetail.getTransactionType() == REFUND.value || payDetail.getTransactionType() == REVERSAL.value) {
// field.setDataStr("00"); // MPU // field.setDataStr("00"); // MPU
field.setDataStr("61"); // CZ field.setDataStr("61"); // CZ
} else if(payDetail.getCardType() == -1) { } else if(payDetail.getCardType() == -1) {