system maintainment alert
This commit is contained in:
parent
0234c05e63
commit
1f3beeb165
@ -71,7 +71,7 @@ fun buildMenuItems(
|
||||
modifier = Modifier.height(48.dp)
|
||||
)
|
||||
}
|
||||
) { }
|
||||
) { onNavigateAction("MMQR") }
|
||||
)
|
||||
}
|
||||
add(
|
||||
|
||||
@ -5,10 +5,21 @@ import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mob.utsmyanmar.ui.theme.Color as AppColor
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@ -74,11 +85,14 @@ fun AppNavGraph(
|
||||
val activity = LocalContext.current as ComponentActivity
|
||||
val currentBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = currentBackStackEntry?.destination?.route
|
||||
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel(activity)
|
||||
val isSyncBlockedGlobal by tmsSetupViewModel.isSyncBlocked.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(autoSettlementRequested, currentRoute) {
|
||||
LaunchedEffect(autoSettlementRequested, currentRoute, isSyncBlockedGlobal) {
|
||||
if (!autoSettlementRequested ||
|
||||
currentRoute == null ||
|
||||
currentRoute == Routes.TmsSetup.route
|
||||
currentRoute == Routes.TmsSetup.route ||
|
||||
isSyncBlockedGlobal
|
||||
) return@LaunchedEffect
|
||||
if (currentRoute == Routes.Settlement.route) {
|
||||
return@LaunchedEffect
|
||||
@ -98,12 +112,11 @@ fun AppNavGraph(
|
||||
startDestination = Routes.TmsSetup.route
|
||||
) {
|
||||
composable(Routes.TmsSetup.route) {
|
||||
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel()
|
||||
TmsSetupRoute(
|
||||
viewModel = tmsSetupViewModel,
|
||||
onNavigateDashboard = {
|
||||
navController.navigate(
|
||||
if (autoSettlementRequested) Routes.Settlement.route else Routes.Dashboard.route
|
||||
if (autoSettlementRequested && !isSyncBlockedGlobal) Routes.Settlement.route else Routes.Dashboard.route
|
||||
) {
|
||||
popUpTo(Routes.TmsSetup.route) {
|
||||
inclusive = true
|
||||
@ -150,6 +163,9 @@ fun AppNavGraph(
|
||||
}
|
||||
|
||||
val tmsSetupUiState by tmsSetupViewModel.uiState.collectAsStateWithLifecycle()
|
||||
val isSyncBlocked by tmsSetupViewModel.isSyncBlocked.collectAsStateWithLifecycle()
|
||||
val syncBlockedMessage by tmsSetupViewModel.syncBlockedMessage.collectAsStateWithLifecycle()
|
||||
var showBlockedSaleAlert by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(tmsSetupViewModel) {
|
||||
tmsSetupViewModel.onSyncCompleted.collect {
|
||||
@ -201,6 +217,9 @@ fun AppNavGraph(
|
||||
}
|
||||
},
|
||||
onNavigateAmount = { action ->
|
||||
if (isSyncBlocked) {
|
||||
showBlockedSaleAlert = true
|
||||
} else {
|
||||
when (action) {
|
||||
"Pre-Auth",
|
||||
"Cash Adv",
|
||||
@ -209,12 +228,6 @@ fun AppNavGraph(
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
"Pre-Auth",
|
||||
"Cash Adv" -> {
|
||||
navController.navigate(Routes.Amount.createRoute(action)) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
navController.navigate(
|
||||
@ -227,8 +240,12 @@ fun AppNavGraph(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onNavigateAction = { action ->
|
||||
if (isSyncBlocked) {
|
||||
showBlockedSaleAlert = true
|
||||
} else {
|
||||
when (action) {
|
||||
"Void" -> {
|
||||
sharedViewModel.transactionsType.value = TransactionsType.VOID
|
||||
@ -265,6 +282,9 @@ fun AppNavGraph(
|
||||
}
|
||||
}
|
||||
|
||||
"MMQR" -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
navController.navigate(
|
||||
Routes.Password.createRoute(
|
||||
@ -276,14 +296,22 @@ fun AppNavGraph(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onNavigateSignOn = {
|
||||
if (isSyncBlocked) {
|
||||
showBlockedSaleAlert = true
|
||||
} else {
|
||||
navController.navigate(Routes.SignOn.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onNavigateSettlement = {
|
||||
if (isSyncBlocked) {
|
||||
showBlockedSaleAlert = true
|
||||
} else {
|
||||
navController.navigate(
|
||||
Routes.Password.createRoute(
|
||||
destination = Routes.Settlement.route,
|
||||
@ -292,6 +320,7 @@ fun AppNavGraph(
|
||||
) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
},
|
||||
onNavigateVersion = {
|
||||
navController.navigate(
|
||||
@ -317,13 +346,54 @@ fun AppNavGraph(
|
||||
navController.navigate(Routes.History.route)
|
||||
}
|
||||
)
|
||||
|
||||
if (showBlockedSaleAlert) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showBlockedSaleAlert = false },
|
||||
title = {
|
||||
Text(
|
||||
text = "Maintenance Alert",
|
||||
color = AppColor.LegacyRed,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = syncBlockedMessage.ifBlank {
|
||||
"Terminal sync is blocked due to an active maintenance window."
|
||||
},
|
||||
color = AppColor.Black,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showBlockedSaleAlert = false }) {
|
||||
Text(
|
||||
text = "OK",
|
||||
color = AppColor.LegacyRed,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
},
|
||||
containerColor = AppColor.White,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composable(Routes.SeeMore.route) {
|
||||
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||
val isSyncBlocked by tmsSetupViewModel.isSyncBlocked.collectAsStateWithLifecycle()
|
||||
val syncBlockedMessage by tmsSetupViewModel.syncBlockedMessage.collectAsStateWithLifecycle()
|
||||
var showBlockedSaleAlert by remember { mutableStateOf(false) }
|
||||
|
||||
SeeMoreScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
onNavigateAmount = { action ->
|
||||
if (isSyncBlocked) {
|
||||
showBlockedSaleAlert = true
|
||||
} else {
|
||||
when (action) {
|
||||
"Void" -> {
|
||||
sharedViewModel.transactionsType.value = TransactionsType.VOID
|
||||
@ -371,7 +441,42 @@ fun AppNavGraph(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (showBlockedSaleAlert) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showBlockedSaleAlert = false },
|
||||
title = {
|
||||
Text(
|
||||
text = "Maintenance Alert",
|
||||
color = AppColor.LegacyRed,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = syncBlockedMessage.ifBlank {
|
||||
"Terminal sync is blocked due to an active maintenance window."
|
||||
},
|
||||
color = AppColor.Black,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showBlockedSaleAlert = false }) {
|
||||
Text(
|
||||
text = "OK",
|
||||
color = AppColor.LegacyRed,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
},
|
||||
containerColor = AppColor.White,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composable(Routes.Version.route){
|
||||
|
||||
@ -32,6 +32,9 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
import sunmi.sunmiui.utils.LogUtil
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@ -40,7 +43,9 @@ data class TmsSetupUiState(
|
||||
val statusText: String = "Initializing...",
|
||||
val isError: Boolean = false,
|
||||
val errorMessage: String = "",
|
||||
val isTerminalDisabled: Boolean = false
|
||||
val isTerminalDisabled: Boolean = false,
|
||||
val isSyncBlocked: Boolean = false,
|
||||
val syncBlockedMessage: String = ""
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
@ -49,8 +54,30 @@ class TmsSetupViewModel @Inject constructor(
|
||||
private val emvParamOperation: EmvParamOperation,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
companion object {
|
||||
const val PREF_KEY_SYNC_BLOCKED = "is_sync_blocked"
|
||||
const val PREF_KEY_SYNC_BLOCKED_MSG = "sync_blocked_message"
|
||||
}
|
||||
|
||||
private val TAG = TmsSetupViewModel::class.simpleName
|
||||
private val _uiState = MutableStateFlow(TmsSetupUiState())
|
||||
private val prefs = context.getSharedPreferences("mobpos_prefs", Context.MODE_PRIVATE)
|
||||
|
||||
private val _isSyncBlocked = MutableStateFlow(
|
||||
prefs.getBoolean(PREF_KEY_SYNC_BLOCKED, false)
|
||||
)
|
||||
val isSyncBlocked: StateFlow<Boolean> = _isSyncBlocked.asStateFlow()
|
||||
|
||||
private val _syncBlockedMessage = MutableStateFlow(
|
||||
prefs.getString(PREF_KEY_SYNC_BLOCKED_MSG, "").orEmpty()
|
||||
)
|
||||
val syncBlockedMessage: StateFlow<String> = _syncBlockedMessage.asStateFlow()
|
||||
|
||||
private val _uiState = MutableStateFlow(
|
||||
TmsSetupUiState(
|
||||
isSyncBlocked = prefs.getBoolean(PREF_KEY_SYNC_BLOCKED, false),
|
||||
syncBlockedMessage = prefs.getString(PREF_KEY_SYNC_BLOCKED_MSG, "").orEmpty()
|
||||
)
|
||||
)
|
||||
private val _dashboardUiState = MutableStateFlow(DashboardUiState())
|
||||
val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow()
|
||||
|
||||
@ -100,7 +127,16 @@ class TmsSetupViewModel @Inject constructor(
|
||||
.doFinally { requestInProgress = false }
|
||||
.subscribe(
|
||||
{ response ->
|
||||
prefs.edit {
|
||||
putBoolean(PREF_KEY_SYNC_BLOCKED, false)
|
||||
remove(PREF_KEY_SYNC_BLOCKED_MSG)
|
||||
}
|
||||
_isSyncBlocked.value = false
|
||||
_syncBlockedMessage.value = ""
|
||||
|
||||
_uiState.update { it.copy(
|
||||
isSyncBlocked = false,
|
||||
syncBlockedMessage = "",
|
||||
statusText = "Applying configuration...")
|
||||
}
|
||||
|
||||
@ -117,24 +153,59 @@ class TmsSetupViewModel @Inject constructor(
|
||||
onConfigApplied(onSuccess)
|
||||
},
|
||||
{ error ->
|
||||
val errorMessage = if (error is retrofit2.HttpException) {
|
||||
var isSyncBlocked = false
|
||||
var errorMessage = error.message.toString()
|
||||
|
||||
if (error is retrofit2.HttpException) {
|
||||
val httpCode = error.code()
|
||||
try {
|
||||
val body = error.response()?.errorBody()?.string()
|
||||
if (!body.isNullOrBlank()) {
|
||||
JSONObject(body).optString("message", "Unknown server error")
|
||||
val json = JSONObject(body)
|
||||
val code = json.optString("code")
|
||||
val statusCode = json.optInt("statusCode", httpCode)
|
||||
val msg = json.optString("message", "")
|
||||
if (msg.isNotBlank()) {
|
||||
errorMessage = msg
|
||||
}
|
||||
|
||||
if (httpCode == 423 || statusCode == 423 ||
|
||||
code.equals("TERMINAL_SYNC_BLOCKED", ignoreCase = true) ||
|
||||
(msg.contains("blocked", ignoreCase = true) && msg.contains("maintenance", ignoreCase = true))
|
||||
) {
|
||||
isSyncBlocked = true
|
||||
errorMessage = buildAlertDetailsMessage(json, msg.ifBlank { errorMessage })
|
||||
}
|
||||
} else {
|
||||
"Empty error response (${error.code()})"
|
||||
if (httpCode == 423) {
|
||||
isSyncBlocked = true
|
||||
}
|
||||
errorMessage = "Empty error response ($httpCode)"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"Server error: ${error.code()}"
|
||||
if (httpCode == 423) {
|
||||
isSyncBlocked = true
|
||||
}
|
||||
} else {
|
||||
error.message.toString()
|
||||
errorMessage = "Server error: $httpCode"
|
||||
}
|
||||
}
|
||||
|
||||
if (isSyncBlocked) {
|
||||
LogUtil.i(TAG, "Terminal sync is blocked due to maintenance window ($errorMessage).")
|
||||
prefs.edit {
|
||||
putBoolean(PREF_KEY_SYNC_BLOCKED, true)
|
||||
putString(PREF_KEY_SYNC_BLOCKED_MSG, errorMessage)
|
||||
}
|
||||
_isSyncBlocked.value = true
|
||||
_syncBlockedMessage.value = errorMessage
|
||||
}
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isError = true,
|
||||
isLoading = false,
|
||||
isSyncBlocked = isSyncBlocked,
|
||||
syncBlockedMessage = if (isSyncBlocked) errorMessage else "",
|
||||
statusText = "Download failed!",
|
||||
errorMessage = errorMessage
|
||||
)
|
||||
@ -181,12 +252,20 @@ class TmsSetupViewModel @Inject constructor(
|
||||
|
||||
val validity = TMSUtil.getInstance().checkParams()
|
||||
if (validity.status == ValidityStatus.SUCCESS) {
|
||||
context.getSharedPreferences("mobpos_prefs", Context.MODE_PRIVATE)
|
||||
.edit {
|
||||
prefs.edit {
|
||||
putLong("last_sync_time", System.currentTimeMillis())
|
||||
putBoolean(PREF_KEY_SYNC_BLOCKED, false)
|
||||
remove(PREF_KEY_SYNC_BLOCKED_MSG)
|
||||
}
|
||||
_isSyncBlocked.value = false
|
||||
_syncBlockedMessage.value = ""
|
||||
onSuccess()
|
||||
_uiState.update { it.copy(isLoading = false, statusText = "Ready.") }
|
||||
_uiState.update { it.copy(
|
||||
isLoading = false,
|
||||
isSyncBlocked = false,
|
||||
syncBlockedMessage = "",
|
||||
statusText = "Ready."
|
||||
) }
|
||||
viewModelScope.launch {
|
||||
_onSyncCompleted.emit(Unit)
|
||||
_navigateToDashboard.emit(Unit)
|
||||
@ -222,6 +301,65 @@ class TmsSetupViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAlertDetailsMessage(json: JSONObject, defaultMessage: String): String {
|
||||
val builder = StringBuilder()
|
||||
val msg = json.optString("message", defaultMessage).trim()
|
||||
if (msg.isNotEmpty()) {
|
||||
builder.append(msg)
|
||||
}
|
||||
|
||||
val alertsArray = json.optJSONArray("alerts")
|
||||
if (alertsArray != null && alertsArray.length() > 0) {
|
||||
for (i in 0 until alertsArray.length()) {
|
||||
val alert = alertsArray.optJSONObject(i) ?: continue
|
||||
if (builder.isNotEmpty()) {
|
||||
builder.append("\n\n")
|
||||
}
|
||||
val title = alert.optString("title").trim()
|
||||
val desc = alert.optString("description").trim()
|
||||
val type = alert.optString("type").trim()
|
||||
val startAt = alert.optString("startAt").trim()
|
||||
val endAt = alert.optString("endAt").trim()
|
||||
|
||||
val lines = mutableListOf<String>()
|
||||
if (title.isNotEmpty()) lines.add("Title: $title")
|
||||
if (desc.isNotEmpty()) lines.add("Description: $desc")
|
||||
if (type.isNotEmpty()) lines.add("Type: $type")
|
||||
if (startAt.isNotEmpty()) lines.add("Start At: ${formatDateTime(startAt)}")
|
||||
if (endAt.isNotEmpty()) lines.add("End At: ${formatDateTime(endAt)}")
|
||||
|
||||
builder.append(lines.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString().ifBlank { defaultMessage }
|
||||
}
|
||||
|
||||
private fun formatDateTime(isoString: String): String {
|
||||
if (isoString.isBlank()) return ""
|
||||
val patterns = arrayOf(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
||||
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS",
|
||||
"yyyy-MM-dd'T'HH:mm:ss"
|
||||
)
|
||||
for (pattern in patterns) {
|
||||
try {
|
||||
val parser = SimpleDateFormat(pattern, Locale.US).apply {
|
||||
if (pattern.endsWith("'Z'")) {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}
|
||||
}
|
||||
val date = parser.parse(isoString)
|
||||
if (date != null) {
|
||||
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
|
||||
return formatter.format(date)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
return isoString
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
disposables.clear()
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">posuat.myanmarorientalbank.com:5033</domain>
|
||||
<domain includeSubdomains="true">192.168.10.104</domain>
|
||||
</domain-config>
|
||||
|
||||
</network-security-config>
|
||||
|
||||
@ -38,9 +38,10 @@ public class SystemParamsSettings implements Serializable {
|
||||
|
||||
private boolean checkExpSwitch = false;
|
||||
|
||||
private String tmsAddress = "https://posmgmt.myanmarorientalbank.com/api/v1";
|
||||
// private String tmsAddress = "https://posmgmt.myanmarorientalbank.com/api/v1";
|
||||
// private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1";
|
||||
// private String tmsAddress = "http://128.199.170.203";
|
||||
private String tmsAddress = "http://192.168.10.104:3333/api/v1";
|
||||
|
||||
private String terminalCapability = "E0E8C8";
|
||||
private String masterTerminalCapability = "E008C8";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user