Compare commits
2 Commits
5bb07df9e3
...
752693c8f3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
752693c8f3 | ||
|
|
d011b46345 |
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@ -11,10 +11,9 @@
|
||||
|
||||
### version 3.4
|
||||
* disable display always on
|
||||
|
||||
* auto-settlement
|
||||
* show expiry date as "`**`/`**`"
|
||||
* align the customer name, in paper slip
|
||||
* settlement fixed in both UI and system
|
||||
* amount, date, time fixed in Void Preview
|
||||
* show reversal status in trans records
|
||||
* show reversal status in trans records
|
||||
@ -19,7 +19,10 @@
|
||||
<uses-permission android:name="com.sunmi.perm.SECURITY" />
|
||||
<uses-permission android:name="com.sunmi.perm.CONTACTLESS_CARD" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
|
||||
@ -1,41 +1,108 @@
|
||||
package com.mob.utsmyanmar
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlarmManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.mob.utsmyanmar.auto_settlement.SettlementScheduler
|
||||
import com.mob.utsmyanmar.ui.navigation.AppNavGraph
|
||||
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
|
||||
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
private var autoSettlementRequested by mutableStateOf(false)
|
||||
private var exactAlarmSettingsOpened = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
SettlementScheduler.schedule(this)
|
||||
// installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
autoSettlementRequested = intent?.getBooleanExtra("AUTO_SETTLEMENT", false) == true
|
||||
requestNotificationPermission()
|
||||
scheduleSettlementAlarm()
|
||||
enableEdgeToEdge()
|
||||
hideSystemBars()
|
||||
// window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
setContent {
|
||||
MOBPOSTheme {
|
||||
val navController = rememberNavController()
|
||||
AppNavGraph(navController = navController)
|
||||
AppNavGraph(
|
||||
navController = navController,
|
||||
autoSettlementRequested = autoSettlementRequested,
|
||||
onAutoSettlementHandled = { autoSettlementRequested = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
if (intent.getBooleanExtra("AUTO_SETTLEMENT", false)) {
|
||||
autoSettlementRequested = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
scheduleSettlementAlarm()
|
||||
}
|
||||
|
||||
private fun requestNotificationPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestPermissions(
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleSettlementAlarm() {
|
||||
if (!SystemParamsOperation.getInstance().isAutoSettlement()) {
|
||||
SettlementScheduler.schedule(this)
|
||||
return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val alarmManager = getSystemService(AlarmManager::class.java)
|
||||
if (!alarmManager.canScheduleExactAlarms()) {
|
||||
if (!exactAlarmSettingsOpened) {
|
||||
exactAlarmSettingsOpened = true
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
|
||||
Uri.parse("package:$packageName")
|
||||
)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
exactAlarmSettingsOpened = false
|
||||
SettlementScheduler.schedule(this)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val NOTIFICATION_PERMISSION_REQUEST_CODE = 1003
|
||||
}
|
||||
|
||||
private fun hideSystemBars() {
|
||||
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
|
||||
@ -4,22 +4,32 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import sunmi.sunmiui.utils.LogUtil
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AlarmReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val serviceIntent = Intent(context, AutoSettleService::class.java)
|
||||
val appContext = context ?: return
|
||||
|
||||
// Exact alarms are one-shot, so keep the next daily alarm alive before
|
||||
// starting the foreground service.
|
||||
if (!SettlementScheduler.schedule(appContext)) {
|
||||
LogUtil.e(AlarmReceiver::class.java.simpleName, "Unable to reschedule settlement alarm")
|
||||
}
|
||||
|
||||
val systemParams = SystemParamsOperation.getInstance()
|
||||
if (!systemParams.isAutoSettlement() || !systemParams.settlementStatus) {
|
||||
LogUtil.d(AlarmReceiver::class.java.simpleName, "Auto settlement is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
val serviceIntent = Intent(appContext, AutoSettleService::class.java)
|
||||
|
||||
LogUtil.d(AlarmReceiver::class.java.simpleName, "Alert service")
|
||||
|
||||
if (context != null) {
|
||||
ContextCompat.startForegroundService(
|
||||
context,
|
||||
serviceIntent
|
||||
)
|
||||
}
|
||||
ContextCompat.startForegroundService(appContext, serviceIntent)
|
||||
|
||||
LogUtil.d(AlarmReceiver::class.java.simpleName, "Foreground service")
|
||||
}
|
||||
|
||||
@ -3,13 +3,16 @@ package com.mob.utsmyanmar.auto_settlement
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
import android.media.Ringtone
|
||||
import android.media.RingtoneManager
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.os.Looper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.mob.utsmyanmar.MainActivity
|
||||
import com.mob.utsmyanmar.R
|
||||
@ -21,8 +24,24 @@ import sunmi.sunmiui.utils.LogUtil
|
||||
class AutoSettleService : Service() {
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "auto_settlement_channel"
|
||||
const val NOTIFICATION_ID = 1001
|
||||
private const val FOREGROUND_CHANNEL_ID = "auto_settlement_foreground_channel"
|
||||
private const val ALERT_CHANNEL_ID = "auto_settlement_alert_channel"
|
||||
private const val NOTIFICATION_ID = 1001
|
||||
private const val ALERT_NOTIFICATION_ID = 1002
|
||||
private const val ALERT_SOUND_DURATION_MS = 3_000L
|
||||
}
|
||||
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
private var alertRingtone: Ringtone? = null
|
||||
private val stopServiceRunnable = Runnable {
|
||||
alertRingtone?.stop()
|
||||
alertRingtone = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
stopForeground(true)
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
@ -31,59 +50,129 @@ class AutoSettleService : Service() {
|
||||
startForegroundNotification()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (!SystemParamsOperation.getInstance().settlementStatus) {
|
||||
val systemParams = SystemParamsOperation.getInstance()
|
||||
if (!systemParams.isAutoSettlement() || !systemParams.settlementStatus) {
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
showSettlementAlert()
|
||||
playAlertSound()
|
||||
launchSettlement()
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
mainHandler.removeCallbacks(stopServiceRunnable)
|
||||
mainHandler.postDelayed(stopServiceRunnable, ALERT_SOUND_DURATION_MS)
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onDestroy() {
|
||||
mainHandler.removeCallbacks(stopServiceRunnable)
|
||||
alertRingtone?.stop()
|
||||
alertRingtone = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
private fun startForegroundNotification() {
|
||||
LogUtil.d(AutoSettleService::class.java.simpleName, "foreground noti called")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
FOREGROUND_CHANNEL_ID,
|
||||
"AUTO SETTLEMENT",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
channel.description = "Auto Settlement notification"
|
||||
channel.enableVibration(true)
|
||||
channel.setSound(
|
||||
RingtoneManager.getDefaultUri(
|
||||
RingtoneManager.TYPE_NOTIFICATION
|
||||
), AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
channel.description = "Auto Settlement service status"
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
manager?.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
val notification = NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.logo_mob)
|
||||
.setContentTitle("Auto Settlement")
|
||||
.setContentText("Processing...")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setContentText("Preparing settlement reminder...")
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun launchSettlement() {
|
||||
val intent = Intent(this, MainActivity::class.java).apply {
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
)
|
||||
putExtra("AUTO_SETTLEMENT", true)
|
||||
private fun showSettlementAlert() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
ALERT_CHANNEL_ID,
|
||||
"AUTO SETTLEMENT ALERT",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = "Daily settlement reminder"
|
||||
enableVibration(true)
|
||||
setSound(
|
||||
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
|
||||
AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
|
||||
}
|
||||
startActivity(intent)
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
ALERT_NOTIFICATION_ID,
|
||||
settlementIntent(),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(this, ALERT_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.logo_mob)
|
||||
.setContentTitle("Settlement Reminder")
|
||||
.setContentText("Daily settlement is due. Tap to open settlement.")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
||||
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
|
||||
.setVibrate(longArrayOf(0, 250, 150, 250))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
|
||||
getSystemService(NotificationManager::class.java)
|
||||
?.notify(ALERT_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun playAlertSound() {
|
||||
alertRingtone?.stop()
|
||||
alertRingtone = RingtoneManager.getRingtone(
|
||||
this,
|
||||
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||
)?.apply {
|
||||
audioAttributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||
.build()
|
||||
play()
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchSettlement() {
|
||||
runCatching {
|
||||
startActivity(settlementIntent())
|
||||
}.onFailure {
|
||||
LogUtil.e(
|
||||
AutoSettleService::class.java.simpleName,
|
||||
"Unable to open settlement screen: ${it.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun settlementIntent(): Intent = Intent(this, MainActivity::class.java).apply {
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
)
|
||||
putExtra("AUTO_SETTLEMENT", true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,26 +1,42 @@
|
||||
package com.mob.utsmyanmar.auto_settlement
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.annotation.RequiresPermission
|
||||
import android.os.Build
|
||||
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||
import java.util.Calendar
|
||||
|
||||
object SettlementScheduler {
|
||||
private const val FALLBACK_ALERT_TIME = "01:10:00"
|
||||
|
||||
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
|
||||
fun schedule(context: Context) {
|
||||
fun schedule(context: Context): Boolean {
|
||||
if (!SystemParamsOperation.getInstance().isAutoSettlement()) {
|
||||
cancel(context)
|
||||
return false
|
||||
}
|
||||
|
||||
val alarmManager =
|
||||
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
!alarmManager.canScheduleExactAlarms()
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
val configuredTime = parseTime(
|
||||
// SystemParamsOperation.getInstance().clearBatchTime
|
||||
"23:05:00"
|
||||
) ?: parseTime(FALLBACK_ALERT_TIME)!!
|
||||
|
||||
val calendar = Calendar.getInstance().apply {
|
||||
|
||||
// Auto settlement time
|
||||
set(Calendar.HOUR_OF_DAY, 1)
|
||||
set(Calendar.MINUTE, 10)
|
||||
set(Calendar.SECOND, 0)
|
||||
|
||||
set(Calendar.HOUR_OF_DAY, configuredTime.first)
|
||||
set(Calendar.MINUTE, configuredTime.second)
|
||||
set(Calendar.SECOND, configuredTime.third)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
|
||||
if (before(Calendar.getInstance())) {
|
||||
add(Calendar.DAY_OF_MONTH, 1)
|
||||
@ -28,11 +44,6 @@ object SettlementScheduler {
|
||||
}
|
||||
|
||||
|
||||
val alarmManager =
|
||||
context.getSystemService(Context.ALARM_SERVICE)
|
||||
as AlarmManager
|
||||
|
||||
|
||||
val intent = Intent(
|
||||
context,
|
||||
AlarmReceiver::class.java
|
||||
@ -53,6 +64,20 @@ object SettlementScheduler {
|
||||
calendar.timeInMillis,
|
||||
pendingIntent
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun parseTime(value: String): Triple<Int, Int, Int>? {
|
||||
val parts = value.trim().split(":")
|
||||
if (parts.size !in 2..3) return null
|
||||
|
||||
val hour = parts[0].toIntOrNull() ?: return null
|
||||
val minute = parts[1].toIntOrNull() ?: return null
|
||||
val second = if (parts.size == 3) parts[2].toIntOrNull() ?: return null else 0
|
||||
|
||||
if (hour !in 0..23 || minute !in 0..59 || second !in 0..59) return null
|
||||
return Triple(hour, minute, second)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.navArgument
|
||||
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
|
||||
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
|
||||
@ -65,9 +66,31 @@ import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||
@SuppressLint("ContextCastToActivity")
|
||||
@Composable
|
||||
fun AppNavGraph(
|
||||
navController: NavHostController
|
||||
navController: NavHostController,
|
||||
autoSettlementRequested: Boolean = false,
|
||||
onAutoSettlementHandled: () -> Unit = {}
|
||||
) {
|
||||
val activity = LocalContext.current as ComponentActivity
|
||||
val currentBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = currentBackStackEntry?.destination?.route
|
||||
|
||||
LaunchedEffect(autoSettlementRequested, currentRoute) {
|
||||
if (!autoSettlementRequested ||
|
||||
currentRoute == null ||
|
||||
currentRoute == Routes.TmsSetup.route
|
||||
) return@LaunchedEffect
|
||||
if (currentRoute == Routes.Settlement.route) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
if (currentRoute == Routes.Password.route) {
|
||||
onAutoSettlementHandled()
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
navController.navigate(Routes.Settlement.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
@ -78,7 +101,9 @@ fun AppNavGraph(
|
||||
TmsSetupRoute(
|
||||
viewModel = tmsSetupViewModel,
|
||||
onNavigateDashboard = {
|
||||
navController.navigate(Routes.Dashboard.route) {
|
||||
navController.navigate(
|
||||
if (autoSettlementRequested) Routes.Settlement.route else Routes.Dashboard.route
|
||||
) {
|
||||
popUpTo(Routes.TmsSetup.route) {
|
||||
inclusive = true
|
||||
}
|
||||
@ -412,6 +437,7 @@ fun AppNavGraph(
|
||||
sharedViewModel = sharedViewModel,
|
||||
settlementViewMode = settlementViewMode,
|
||||
onBack = { navController.popBackStack(Routes.Dashboard.route,false) },
|
||||
autoStartSettlement = autoSettlementRequested,
|
||||
onStartSettlement = {
|
||||
sharedViewModel.transactionsType.value = TransactionsType.SETTLEMENT
|
||||
navController.navigate(Routes.Processing.route) {
|
||||
@ -420,6 +446,9 @@ fun AppNavGraph(
|
||||
}
|
||||
launchSingleTop = true
|
||||
}
|
||||
if (autoSettlementRequested) {
|
||||
onAutoSettlementHandled()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -717,10 +746,15 @@ fun AppNavGraph(
|
||||
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
|
||||
val settlementViewModel: SettlementViewModel = hiltViewModel(activity)
|
||||
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel(activity)
|
||||
val dashboardViewModel: DashboardViewModel = hiltViewModel(activity)
|
||||
ProcessingRoute(
|
||||
sharedViewModel = sharedViewModel,
|
||||
transProcessViewModel = transProcessViewModel,
|
||||
settlementViewModel = settlementViewModel,
|
||||
onTransactionFinished = {
|
||||
tmsSetupViewModel.downloadConfigs(dashboardViewModel::refreshLastSyncTime)
|
||||
},
|
||||
onNavigateTransactionResult = {
|
||||
navController.navigate(Routes.TransactionResult.route) {
|
||||
popUpTo(Routes.Processing.route) {
|
||||
@ -779,14 +813,10 @@ fun AppNavGraph(
|
||||
composable(Routes.TransactionResult.route) {
|
||||
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||
val transResultViewModel: TransactionResultViewModel = hiltViewModel(activity)
|
||||
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel(activity)
|
||||
val dashboardViewModel: DashboardViewModel = hiltViewModel(activity)
|
||||
|
||||
TransactionResultRoute(
|
||||
viewModel = transResultViewModel,
|
||||
sharedViewModel = sharedViewModel,
|
||||
tmsSetupViewModel = tmsSetupViewModel,
|
||||
dashboardViewModel = dashboardViewModel,
|
||||
onNavigateMain = {
|
||||
navController.navigate(Routes.Dashboard.route) {
|
||||
popUpTo(Routes.Dashboard.route) {
|
||||
|
||||
@ -21,7 +21,8 @@ fun ProcessingRoute(
|
||||
settlementViewModel: SettlementViewModel,
|
||||
onNavigateTransactionResult: () -> Unit,
|
||||
onNavigateSignature: () -> Unit,
|
||||
onNavigateError: (Boolean, String) -> Unit
|
||||
onNavigateError: (Boolean, String) -> Unit,
|
||||
onTransactionFinished: () -> Unit
|
||||
) {
|
||||
|
||||
val transStatus by transProcessViewModel.transResultStatus.collectAsStateWithLifecycle()
|
||||
@ -39,6 +40,7 @@ fun ProcessingRoute(
|
||||
when(settlementStatus.status) {
|
||||
TransactionStatus.ON_ERROR,
|
||||
TransactionStatus.ON_SUCCESS -> {
|
||||
onTransactionFinished()
|
||||
sharedViewModel.payDetail.value = settlementViewModel.getPayDetail()
|
||||
onNavigateTransactionResult()
|
||||
}
|
||||
@ -54,6 +56,7 @@ fun ProcessingRoute(
|
||||
LaunchedEffect(transStatus) {
|
||||
when(transStatus) {
|
||||
TransResultStatus.SUCCESS -> {
|
||||
onTransactionFinished()
|
||||
sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value
|
||||
if (requiresSignature(sharedViewModel.transactionsType.value, sharedViewModel.payDetail.value)) {
|
||||
onNavigateSignature()
|
||||
@ -63,18 +66,22 @@ fun ProcessingRoute(
|
||||
}
|
||||
|
||||
TransResultStatus.FAIL -> {
|
||||
onTransactionFinished()
|
||||
sharedViewModel.payDetail.value = transProcessViewModel.payDetailResult.value
|
||||
onNavigateTransactionResult()
|
||||
}
|
||||
TransResultStatus.REVERSAL_SUCCESS -> {
|
||||
onTransactionFinished()
|
||||
onNavigateError(true,"Reversal success")
|
||||
}
|
||||
|
||||
TransResultStatus.REVERSAL_FAIL -> {
|
||||
onTransactionFinished()
|
||||
onNavigateError(false,"Reversal failed")
|
||||
}
|
||||
|
||||
TransResultStatus.ERROR -> {
|
||||
onTransactionFinished()
|
||||
onNavigateError(false,
|
||||
transProcessViewModel.errorMessage.value?.takeIf { it.isNotBlank() }
|
||||
?: "Something went wrong. Please try again."
|
||||
|
||||
@ -38,11 +38,17 @@ fun SettlementScreen(
|
||||
sharedViewModel: SharedViewModel,
|
||||
settlementViewMode: SettlementViewModel,
|
||||
onBack: () -> Unit,
|
||||
onStartSettlement: () -> Unit
|
||||
onStartSettlement: () -> Unit,
|
||||
autoStartSettlement: Boolean = false
|
||||
) {
|
||||
val records by settlementViewMode.getSettlementPOS().observeAsState(emptyList())
|
||||
val totalAmount = records.sumOf { it.amount }
|
||||
|
||||
LaunchedEffect(autoStartSettlement, records) {
|
||||
if (autoStartSettlement && records.isNotEmpty()) {
|
||||
onStartSettlement()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = Color.IvoryBeige,
|
||||
|
||||
@ -74,7 +74,7 @@ class TmsSetupViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadConfigs() {
|
||||
fun downloadConfigs(onSuccess: () -> Unit = {}) {
|
||||
_uiState.update {
|
||||
it.copy(isLoading = true, isError = false, statusText = "Connecting to TMS server...")
|
||||
}
|
||||
@ -99,7 +99,7 @@ class TmsSetupViewModel @Inject constructor(
|
||||
)
|
||||
|
||||
tmsSetups.initParams(appResponse, TMSUpdate.UPDATE, emvParamOperation)
|
||||
onConfigApplied()
|
||||
onConfigApplied(onSuccess)
|
||||
},
|
||||
{ error ->
|
||||
val errorMessage = if (error is retrofit2.HttpException) {
|
||||
@ -141,7 +141,7 @@ class TmsSetupViewModel @Inject constructor(
|
||||
|
||||
}
|
||||
|
||||
private fun onConfigApplied() {
|
||||
private fun onConfigApplied(onSuccess: () -> Unit) {
|
||||
val ops = SystemParamsOperation.getInstance()
|
||||
|
||||
if (!ops.isActive) {
|
||||
@ -166,6 +166,7 @@ class TmsSetupViewModel @Inject constructor(
|
||||
.edit {
|
||||
putLong("last_sync_time", System.currentTimeMillis())
|
||||
}
|
||||
onSuccess()
|
||||
_uiState.update { it.copy(isLoading = false, statusText = "Ready.") }
|
||||
viewModelScope.launch {
|
||||
_onSyncCompleted.emit(Unit)
|
||||
|
||||
@ -5,4 +5,5 @@ sealed interface TransactionResultEvent {
|
||||
data object BackClick: TransactionResultEvent
|
||||
data object RetryPrint: TransactionResultEvent
|
||||
data object PrintLater: TransactionResultEvent
|
||||
data object Continue: TransactionResultEvent
|
||||
}
|
||||
@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.mob.utsmyanmar.ui.dashboard.DashboardViewModel
|
||||
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel
|
||||
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||
|
||||
@ -18,9 +16,7 @@ fun TransactionResultRoute(
|
||||
onNavigatePrintReceipt: () -> Unit,
|
||||
onShowError: (String) -> Unit,
|
||||
onShowSuccess: (String) -> Unit,
|
||||
onShowPrinterDialog: (String) -> Unit,
|
||||
tmsSetupViewModel: TmsSetupViewModel,
|
||||
dashboardViewModel: DashboardViewModel,
|
||||
onShowPrinterDialog: (String) -> Unit
|
||||
) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val canGoBack = sharedViewModel.transactionsType.value != TransactionsType.SALE
|
||||
@ -30,8 +26,6 @@ fun TransactionResultRoute(
|
||||
TransactionResultEvent.Start,
|
||||
sharedViewModel
|
||||
)
|
||||
dashboardViewModel.refreshLastSyncTime()
|
||||
tmsSetupViewModel.downloadConfigs()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
@ -51,6 +45,9 @@ fun TransactionResultRoute(
|
||||
canGoBack = canGoBack,
|
||||
onEvent = {
|
||||
viewModel.onEvent(it, sharedViewModel)
|
||||
},
|
||||
onContinue = {
|
||||
viewModel.onEvent(TransactionResultEvent.Continue, sharedViewModel)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ fun TransactionResultScreen(
|
||||
state: TransactionResultState,
|
||||
canGoBack: Boolean,
|
||||
onEvent: (TransactionResultEvent) -> Unit,
|
||||
onContinue: () -> Unit,
|
||||
) {
|
||||
BackHandler(enabled = canGoBack) {
|
||||
onEvent(TransactionResultEvent.BackClick)
|
||||
@ -70,11 +71,8 @@ fun TransactionResultScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Text(
|
||||
text = state.message.ifBlank { "Your payment has been processed successfully." },
|
||||
color = Color.Gray,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center
|
||||
ReasonCard(
|
||||
reason = state.message.ifBlank { "Your payment has been processed successfully." }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
@ -89,6 +87,50 @@ fun TransactionResultScreen(
|
||||
transactionId = state.payDetail?.referNo.toString()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Button(
|
||||
onClick = onContinue,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.LegacyRed,
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Text("Continue")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReasonCard(reason: String) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 18.dp, vertical = 14.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Reason",
|
||||
color = Color.Gray,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = reason,
|
||||
color = Color.Black,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -323,5 +365,6 @@ fun PreviewTransactionResultScreen() {
|
||||
),
|
||||
canGoBack = true,
|
||||
onEvent = {},
|
||||
onContinue = {},
|
||||
)
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import com.utsmyanmar.paylibs.model.PayDetail
|
||||
import com.utsmyanmar.paylibs.print.PaperRollStatusCallback
|
||||
import com.utsmyanmar.paylibs.print.PrintHelper
|
||||
import com.utsmyanmar.paylibs.utils.PrintStatus
|
||||
import com.utsmyanmar.paylibs.utils.POSUtil
|
||||
import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil
|
||||
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||
@ -24,6 +25,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Job
|
||||
import sunmi.sunmiui.utils.LogUtil
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -44,6 +46,7 @@ class TransactionResultViewModel @Inject constructor(
|
||||
val uiEvent = _uiEvent.receiveAsFlow()
|
||||
|
||||
private var started = false
|
||||
private var resultTimeoutJob: Job? = null
|
||||
|
||||
fun onEvent(
|
||||
event: TransactionResultEvent,
|
||||
@ -52,11 +55,16 @@ class TransactionResultViewModel @Inject constructor(
|
||||
when (event) {
|
||||
TransactionResultEvent.Start -> {
|
||||
started = false
|
||||
resultTimeoutJob?.cancel()
|
||||
start(sharedViewModel)
|
||||
}
|
||||
TransactionResultEvent.BackClick -> isCardInside()
|
||||
TransactionResultEvent.RetryPrint -> startPrintProcess(sharedViewModel, false)
|
||||
TransactionResultEvent.PrintLater -> isCardInside()
|
||||
TransactionResultEvent.Continue -> {
|
||||
resultTimeoutJob?.cancel()
|
||||
navigateMain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +107,7 @@ class TransactionResultViewModel @Inject constructor(
|
||||
|
||||
val message = when {
|
||||
isSuccess -> "Transaction approved"
|
||||
payDetail?.tradeAnswerCode?.isNotBlank() == true -> payDetail.tradeAnswerCode
|
||||
payDetail != null -> failureReason(payDetail)
|
||||
else -> "Transaction failed"
|
||||
}
|
||||
|
||||
@ -109,8 +117,20 @@ class TransactionResultViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun failureReason(payDetail: PayDetail): String {
|
||||
val responseCode = payDetail.tradeAnswerCode.orEmpty().trim()
|
||||
val resultDescription = payDetail.tradeResultDes.orEmpty().trim()
|
||||
|
||||
if (resultDescription.isNotBlank() && !resultDescription.equals(responseCode, ignoreCase = true)) {
|
||||
return resultDescription
|
||||
}
|
||||
if (responseCode.isBlank()) return "Transaction failed"
|
||||
|
||||
return POSUtil.getInstance().getResponse(responseCode)
|
||||
}
|
||||
|
||||
private fun startResultTimeout(sharedViewModel: SharedViewModel) {
|
||||
viewModelScope.launch {
|
||||
resultTimeoutJob = viewModelScope.launch {
|
||||
delay(RESULT_TIMEOUT)
|
||||
|
||||
val payDetail = sharedViewModel.payDetail.value
|
||||
@ -128,7 +148,6 @@ class TransactionResultViewModel @Inject constructor(
|
||||
if (SystemParamsOperation.getInstance().isPrintErrorSlip) {
|
||||
startPrintProcess(sharedViewModel, false)
|
||||
}
|
||||
isCardInside()
|
||||
}
|
||||
|
||||
transactionType == TransactionsType.SETTLEMENT -> {
|
||||
@ -138,12 +157,10 @@ class TransactionResultViewModel @Inject constructor(
|
||||
"Configs are updated"
|
||||
)
|
||||
)
|
||||
navigateMain()
|
||||
}
|
||||
|
||||
isWavePayNonSuccessTransaction(transactionType, payDetail) -> {
|
||||
startPrintProcess(sharedViewModel, false)
|
||||
navigateMain()
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
@ -3,6 +3,7 @@ package com.mob.utsmyanmar.utils.tms
|
||||
import com.google.gson.Gson
|
||||
import com.mob.utsmyanmar.model.sirius.SiriusResponse
|
||||
import com.mob.utsmyanmar.model.sirius.TMSUpdate
|
||||
import com.mob.utsmyanmar.auto_settlement.SettlementScheduler
|
||||
import com.utsmyanmar.baselib.BaseApplication
|
||||
import com.utsmyanmar.baselib.emv.EmvParamOperation
|
||||
import com.utsmyanmar.baselib.network.model.sirius.SiriusHost
|
||||
@ -157,9 +158,10 @@ class TMSSetupsImpl : TMSSetups {
|
||||
"pre_auth_enable" -> ops.preAuthStatus = parseBoolean(data)
|
||||
"void_enable" -> ops.voidStatus = parseBoolean(data)
|
||||
"cash_advance_enable" -> ops.cashAdvanceStatus = parseBoolean(data)
|
||||
"refund_enable" -> ops.refundStatus = parseBoolean(data)
|
||||
"settlement_enable" -> ops.settlementStatus = parseBoolean(data)
|
||||
"system_password" -> ops.systemPassword = data.take(6).ifEmpty { data }
|
||||
"refund_enable" -> ops.refundStatus = parseBoolean(data)
|
||||
"settlement_enable" -> ops.settlementStatus = parseBoolean(data)
|
||||
"auto_settlement" -> ops.setAutoSettlement(parseBoolean(data))
|
||||
"system_password" -> ops.systemPassword = data.take(6).ifEmpty { data }
|
||||
"settlement_password" -> ops.settlementPassword = data.take(6).ifEmpty { data }
|
||||
"setting_password" -> ops.settingPassword = data.take(6).ifEmpty { data }
|
||||
"terminal_enable" -> ops.isActive = parseBoolean(data)
|
||||
@ -226,6 +228,7 @@ class TMSSetupsImpl : TMSSetups {
|
||||
}
|
||||
}
|
||||
ops.carouselUrls = imgUrls.joinToString(",")
|
||||
SettlementScheduler.schedule(BaseApplication.getInstance().applicationContext)
|
||||
}
|
||||
|
||||
override fun initParams(json: String) {
|
||||
|
||||
@ -409,6 +409,17 @@ public class SystemParamsOperation {
|
||||
return params.getSettlementStatus();
|
||||
}
|
||||
|
||||
public boolean isAutoSettlement() {
|
||||
SystemParamsSettings params = getSystemParamsSettings();
|
||||
return params.isAutoSettlement();
|
||||
}
|
||||
|
||||
public void setAutoSettlement(boolean enabled) {
|
||||
SystemParamsSettings params = getSystemParamsSettings();
|
||||
params.setAutoSettlement(enabled);
|
||||
saveSystemParamsSettings(params);
|
||||
}
|
||||
|
||||
public String getCarouselUrls() {
|
||||
SystemParamsSettings params = getSystemParamsSettings();
|
||||
return params.getCarouselUrls();
|
||||
|
||||
@ -220,6 +220,8 @@ public class SystemParamsSettings implements Serializable {
|
||||
|
||||
private boolean settlementStatus = true;
|
||||
|
||||
private boolean autoSettlement = false;
|
||||
|
||||
private boolean isNeedSettlement = false;
|
||||
|
||||
private boolean manualEntryStatus = true;
|
||||
@ -566,6 +568,8 @@ public class SystemParamsSettings implements Serializable {
|
||||
protected boolean getSettlementStatus() { return settlementStatus; }
|
||||
|
||||
protected void setSettlementStatus(boolean status) { this.settlementStatus = status; }
|
||||
protected boolean isAutoSettlement() { return autoSettlement; }
|
||||
protected void setAutoSettlement(boolean enabled) { this.autoSettlement = enabled; }
|
||||
protected void setUserRole(int role){ this.user_role = role;}
|
||||
|
||||
protected int getUserRole(){ return user_role;}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user