From d011b463457a90d08898a5b3088feda550e04c58 Mon Sep 17 00:00:00 2001 From: moon <56061215+MgKyawLay@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:18:59 +0630 Subject: [PATCH] auto settlement --- .idea/misc.xml | 3 +- app/src/main/AndroidManifest.xml | 5 +- .../java/com/mob/utsmyanmar/MainActivity.kt | 81 +++++++++- .../auto_settlement/AlarmReceiver.kt | 26 +++- .../auto_settlement/AutoSettleService.kt | 141 ++++++++++++++---- .../auto_settlement/SettlementScheduler.kt | 57 +++++-- .../utsmyanmar/ui/navigation/AppNavGraph.kt | 33 +++- .../ui/settlement/SettlementScreen.kt | 8 +- .../TransactionResultEvent.kt | 3 +- .../TransactionResultRoute.kt | 3 + .../TransactionResultScreen.kt | 53 ++++++- .../TransactionResultViewModel.kt | 27 +++- .../mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt | 9 +- .../core_utils/SystemParamsOperation.java | 11 ++ .../core_utils/SystemParamsSettings.java | 4 + 15 files changed, 388 insertions(+), 76 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index b2c751a..991a888 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,7 @@ + - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 68dac2f..fe77493 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,7 +19,10 @@ - + + + + diff --git a/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt b/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt index 65a0c4f..3f54321 100644 --- a/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt +++ b/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt @@ -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()) diff --git a/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AlarmReceiver.kt b/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AlarmReceiver.kt index 88f97c3..52fd02a 100644 --- a/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AlarmReceiver.kt +++ b/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AlarmReceiver.kt @@ -4,23 +4,33 @@ 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") } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AutoSettleService.kt b/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AutoSettleService.kt index 2b9841d..3d96e9b 100644 --- a/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AutoSettleService.kt +++ b/app/src/main/java/com/mob/utsmyanmar/auto_settlement/AutoSettleService.kt @@ -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) } } diff --git a/app/src/main/java/com/mob/utsmyanmar/auto_settlement/SettlementScheduler.kt b/app/src/main/java/com/mob/utsmyanmar/auto_settlement/SettlementScheduler.kt index fc1b38b..2ffddb2 100644 --- a/app/src/main/java/com/mob/utsmyanmar/auto_settlement/SettlementScheduler.kt +++ b/app/src/main/java/com/mob/utsmyanmar/auto_settlement/SettlementScheduler.kt @@ -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? { + 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) } @@ -81,4 +106,4 @@ object SettlementScheduler { alarmManager.cancel(pendingIntent) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt index 11c7491..705fb42 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt @@ -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() + } } ) } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/settlement/SettlementScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/settlement/SettlementScreen.kt index a9ff0d7..f15681d 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/settlement/SettlementScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/settlement/SettlementScreen.kt @@ -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, diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultEvent.kt b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultEvent.kt index d049da0..6dffd6b 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultEvent.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultEvent.kt @@ -5,4 +5,5 @@ sealed interface TransactionResultEvent { data object BackClick: TransactionResultEvent data object RetryPrint: TransactionResultEvent data object PrintLater: TransactionResultEvent -} \ No newline at end of file + data object Continue: TransactionResultEvent +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultRoute.kt index e4f18df..4877cd3 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultRoute.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultRoute.kt @@ -51,6 +51,9 @@ fun TransactionResultRoute( canGoBack = canGoBack, onEvent = { viewModel.onEvent(it, sharedViewModel) + }, + onContinue = { + viewModel.onEvent(TransactionResultEvent.Continue, sharedViewModel) } ) } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt index 5c27ca7..3ab55af 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultScreen.kt @@ -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)) @@ -88,11 +86,55 @@ fun TransactionResultScreen( time = "${state.payDetail?.transTime?.toFormattedTime()}", 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 + ) + } + } +} + @Composable private fun SuccessIcon() { Box( @@ -323,5 +365,6 @@ fun PreviewTransactionResultScreen() { ), canGoBack = true, onEvent = {}, + onContinue = {}, ) } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt index a15506e..7c8b87c 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/transaction_result/TransactionResultViewModel.kt @@ -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 -> { diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt index 6cdf575..8f19ebb 100644 --- a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt +++ b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt @@ -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) { diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java index 7377797..f93cce9 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsOperation.java @@ -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(); diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java index 6562732..ac9677b 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java @@ -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;}