diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 263ebcc..4a75db1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,9 @@ android { } defaultConfig { - applicationId = "com.mob.ustmm" + applicationId = "com.mob.utsmyanmar" minSdk = 24 + //noinspection OldTargetApi targetSdk = 36 versionCode = 1 versionName = "1.0" @@ -38,6 +39,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } diff --git a/app/src/main/java/com/mob/utsmyanmar/model/sirius/SiriusResponse.kt b/app/src/main/java/com/mob/utsmyanmar/model/sirius/SiriusResponse.kt new file mode 100644 index 0000000..59ee51e --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/model/sirius/SiriusResponse.kt @@ -0,0 +1,14 @@ +package com.mob.utsmyanmar.model.sirius + +import com.utsmyanmar.baselib.network.model.sirius.SiriusHost +import com.utsmyanmar.baselib.network.model.sirius.SiriusMerchant +import com.utsmyanmar.baselib.network.model.sirius.SiriusProperty + +data class SiriusResponse ( + var serial: String, + var ecrKey: String, + var address: String, + var merchant : SiriusMerchant, + var hosts : List, + var properties: List +) \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/model/sirius/TMSUpdate.kt b/app/src/main/java/com/mob/utsmyanmar/model/sirius/TMSUpdate.kt new file mode 100644 index 0000000..f5419f1 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/model/sirius/TMSUpdate.kt @@ -0,0 +1,6 @@ +package com.mob.utsmyanmar.model.sirius + +enum class TMSUpdate { + UPDATE, + CHECK +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/model/sirius/TMSValidity.kt b/app/src/main/java/com/mob/utsmyanmar/model/sirius/TMSValidity.kt new file mode 100644 index 0000000..408a17a --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/model/sirius/TMSValidity.kt @@ -0,0 +1,6 @@ +package com.mob.utsmyanmar.model.sirius + +data class TMSValidity( + var status: ValidityStatus? = null, + var message: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/model/sirius/ValidityStatus.kt b/app/src/main/java/com/mob/utsmyanmar/model/sirius/ValidityStatus.kt new file mode 100644 index 0000000..a4836d3 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/model/sirius/ValidityStatus.kt @@ -0,0 +1,6 @@ +package com.mob.utsmyanmar.model.sirius + +enum class ValidityStatus { + SUCCESS, + FAILURE +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt index 0bb9afe..b250bc4 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt @@ -47,6 +47,7 @@ fun DashboardScreen2( onNavigateSeeMore: () -> Unit = {}, onNavigateSettlement: () -> Unit = {}, onNavigateVersion: () -> Unit = {}, + onNavigateFunctions: () -> Unit = {}, deviceInfoViewModel: DeviceInfoViewModel = viewModel() ) { val deviceInfo by deviceInfoViewModel.uiState.collectAsState() @@ -250,6 +251,7 @@ fun DashboardScreen2( ) DrawerItem("Function", Icons.Default.Dashboard) { scope.launch { drawerState.close() } + onNavigateFunctions() } DrawerItem("Version", Icons.Default.Dashboard) { scope.launch { drawerState.close() } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/functions/FunctionsScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/functions/FunctionsScreen.kt new file mode 100644 index 0000000..3211b17 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/functions/FunctionsScreen.kt @@ -0,0 +1,235 @@ +package com.mob.utsmyanmar.ui.functions + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.OnDeviceTraining +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ElevatedButton +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.preview.P2Preview +import com.mob.utsmyanmar.ui.preview.P3Preview +import com.mob.utsmyanmar.ui.theme.Color +import com.mob.utsmyanmar.R +@Composable +fun FunctionsScreen( + onBack: () -> Unit = {} +) { + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Settings", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp).verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text(text = "General Settings") + FunctionButton( + onClick = {}, + title = "App Version", + subTitle = "1.0-uat", + leadingIcon = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_device_info), + contentDescription = "icon", + tint = Color.LegacyRed + ) + } + ) + + FunctionButton( + onClick = {}, + title = "Host Config", + subTitle = "Detail for bound hosts", + leadingIcon = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_database_config), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "icon", + tint = Color.LegacyRed + ) + } + ) + + Text(text = "System Configuration") + + FunctionButton( + onClick = {}, + title = "Clear Batch", + subTitle = "Detail for bound hosts", + leadingIcon = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_refresh), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "icon", + tint = Color.LegacyRed + ) + } + ) + + FunctionButton( + onClick = {}, + title = "Clear Reversal", + subTitle = "Detail for bound hosts", + leadingIcon = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_clear), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "icon", + tint = Color.LegacyRed + ) + } + ) + + FunctionButton( + onClick = {}, + title = "TMS Server Url", + subTitle = "Detail for bound hosts", + leadingIcon = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_address_global), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + ) + + FunctionButton( + onClick = {}, + title = "Download Config", + subTitle = "Download terminal config from host", + leadingIcon = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_circle_download_arrow), + contentDescription = "icon", + tint = Color.LegacyRed + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "icon", + tint = Color.LegacyRed + ) + } + ) + } + } +} + +@Composable +fun FunctionButton( + onClick: () -> Unit, + title: String, + subTitle: String, + leadingIcon: (@Composable () -> Unit)? = null, + trailingIcon: (@Composable () -> Unit)? = null, +) { + ElevatedButton( + onClick = onClick, + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + modifier = Modifier.fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + ) { + leadingIcon?.invoke() + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp)) { + Text(text = title) + Text( + text = subTitle, + style = MaterialTheme.typography.bodySmall, + color = Color.Gray + ) + } + + trailingIcon?.invoke() + } + } +} + + +@P3Preview +@P2Preview +@Composable +fun PreviewFunctionsScreen() { + FunctionsScreen() +} + +@Preview +@Composable +fun PreviewFunctionButton() { + FunctionButton( + onClick = {}, + title = "title", + subTitle = "sub-title" + ) +} \ 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 0c3e462..9ab79a9 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 @@ -37,6 +37,9 @@ import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel import com.mob.utsmyanmar.ui.settlement.SettlementViewModel import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel +import com.mob.utsmyanmar.ui.functions.FunctionsScreen +import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute +import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel import com.mob.utsmyanmar.ui.version.VersionScreen import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel @@ -52,8 +55,23 @@ fun AppNavGraph( NavHost( navController = navController, - startDestination = Routes.Dashboard.route + startDestination = Routes.TmsSetup.route ) { + composable(Routes.TmsSetup.route) { + val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel() + TmsSetupRoute( + viewModel = tmsSetupViewModel, + onNavigateDashboard = { + navController.navigate(Routes.Dashboard.route) { + popUpTo(Routes.TmsSetup.route) { + inclusive = true + } + launchSingleTop = true + } + } + ) + } + composable(Routes.Dashboard.route) { val sharedViewModel: SharedViewModel = hiltViewModel(activity); DashboardScreen2( @@ -62,7 +80,7 @@ fun AppNavGraph( sharedViewModel.transactionsType.value = TransactionsType.SALE; sharedViewModel.processCode.value = ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT; } - + navController.navigate(Routes.Amount.createRoute(action)) { launchSingleTop = true } @@ -83,7 +101,12 @@ fun AppNavGraph( } }, onNavigateVersion = { - navController.navigate(Routes.Version.route); + navController.navigate(Routes.Version.route) + }, + onNavigateFunctions = { + navController.navigate(Routes.Functions.route) { + launchSingleTop = true + } } ) } @@ -113,6 +136,12 @@ fun AppNavGraph( ) } + composable(Routes.Functions.route) { + FunctionsScreen( + onBack = { navController.popBackStack() } + ) + } + composable(Routes.VoidTrace.route) { val voidViewModel: VoidViewModel = hiltViewModel() diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt index 3799537..c798df9 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt @@ -3,6 +3,7 @@ package com.mob.utsmyanmar.ui.navigation import android.net.Uri sealed class Routes(val route: String) { + data object TmsSetup : Routes("tms_setup") data object Dashboard : Routes("dashboard") data object Amount : Routes("amount/{action}") { fun createRoute(action: String): String = "amount/${Uri.encode(action)}" @@ -27,4 +28,5 @@ sealed class Routes(val route: String) { data object TransactionResult : Routes("transaction_result") data object PrintReceipt : Routes("print_receipt") data object Version : Routes("version") + data object Functions : Routes("functions") } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt new file mode 100644 index 0000000..f2c9f9f --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupScreen.kt @@ -0,0 +1,173 @@ +package com.mob.utsmyanmar.ui.tms_setup + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CloudDownload +import androidx.compose.material.icons.filled.ErrorOutline +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.theme.Color as AppColor + +@Composable +fun TmsSetupRoute( + viewModel: TmsSetupViewModel, + onNavigateDashboard: () -> Unit +) { + val state by viewModel.uiState.collectAsState() + + LaunchedEffect(viewModel) { + viewModel.navigateToDashboard.collect { onNavigateDashboard() } + } + + TmsSetupScreen( + state = state, + onRetry = viewModel::downloadConfigs, + onSkip = viewModel::skipDownload + ) +} + +@Composable +fun TmsSetupScreen( + state: TmsSetupUiState, + onRetry: () -> Unit, + onSkip: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxSize() + .background(AppColor.IvoryBeige), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + Icon( + imageVector = if (state.isError) Icons.Default.ErrorOutline else Icons.Default.CloudDownload, + contentDescription = null, + tint = if (state.isError) AppColor.Error else AppColor.LegacyRed, + modifier = Modifier.size(72.dp) + ) + + Text( + text = if (state.isError) "Configuration Error" else "Setting Up Terminal", + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = AppColor.LegacyRed, + textAlign = TextAlign.Center + ) + + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = AppColor.White), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (state.isLoading) { + CircularProgressIndicator( + color = AppColor.CrimsonRed, + modifier = Modifier.size(40.dp) + ) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + color = AppColor.CrimsonRed, + trackColor = AppColor.GoldenGlow.copy(alpha = 0.3f), + strokeCap = StrokeCap.Round + ) + } + + Text( + text = state.statusText, + fontSize = 14.sp, + color = AppColor.Black, + textAlign = TextAlign.Center + ) + + if (state.isError) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + AppColor.Error.copy(alpha = 0.08f), + RoundedCornerShape(12.dp) + ) + .padding(12.dp) + ) { + Text( + text = state.errorMessage, + fontSize = 13.sp, + color = AppColor.Error, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + } + } + } + + if (state.isError) { + Button( + onClick = onRetry, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors(containerColor = AppColor.CrimsonRed) + ) { + Icon( + imageVector = Icons.Default.Refresh, + contentDescription = null, + modifier = Modifier + .padding(end = 8.dp) + .size(18.dp) + ) + Text(text = "Retry", fontSize = 16.sp) + } + + OutlinedButton( + onClick = onSkip, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.outlinedButtonColors(contentColor = AppColor.LegacyRed) + ) { + Text(text = "Skip", fontSize = 16.sp) + } + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt new file mode 100644 index 0000000..886252b --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/tms_setup/TmsSetupViewModel.kt @@ -0,0 +1,165 @@ +package com.mob.utsmyanmar.ui.tms_setup + +import android.annotation.SuppressLint +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mob.utsmyanmar.model.sirius.SiriusResponse +import com.mob.utsmyanmar.model.sirius.TMSUpdate +import com.mob.utsmyanmar.model.sirius.ValidityStatus +import com.mob.utsmyanmar.utils.tms.TMSSetupsImpl +import com.mob.utsmyanmar.utils.tms.TMSUtil +import com.utsmyanmar.baselib.BaseApplication +import com.utsmyanmar.baselib.emv.EmvParamOperation +import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest +import com.utsmyanmar.baselib.repo.Repository +import dagger.hilt.android.lifecycle.HiltViewModel +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import sunmi.sunmiui.utils.LogUtil +import javax.inject.Inject + +data class TmsSetupUiState( + val isLoading: Boolean = false, + val statusText: String = "Initializing...", + val isError: Boolean = false, + val errorMessage: String = "" +) + +@HiltViewModel +class TmsSetupViewModel @Inject constructor( + private val repository: Repository, + private val emvParamOperation: EmvParamOperation +) : ViewModel() { + + private val _uiState = MutableStateFlow(TmsSetupUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _navigateToDashboard = MutableSharedFlow() + val navigateToDashboard: SharedFlow = _navigateToDashboard.asSharedFlow() + + private val disposables = CompositeDisposable() + private val tmsSetups = TMSSetupsImpl() + + init { + viewModelScope.launch { + waitForHardware() + downloadConfigs() + } + } + + fun downloadConfigs() { + _uiState.update { + it.copy(isLoading = true, isError = false, statusText = "Connecting to TMS server...") + } + + val disposable = repository.getParams(buildRequest()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { response -> + _uiState.update { it.copy(statusText = "Applying configuration...") } + + val appResponse = SiriusResponse( + serial = response.serial.orEmpty(), + ecrKey = response.ecrKey.orEmpty(), + address = response.address.orEmpty(), + merchant = response.merchant, + hosts = response.hosts, + properties = response.properties + ) + + tmsSetups.initParams(appResponse, TMSUpdate.UPDATE, emvParamOperation) + onConfigApplied() + }, + { error -> + _uiState.update { + it.copy( + isLoading = false, + isError = true, + statusText = "Download failed", + errorMessage = formatNetworkError(error) + ) + } + } + ) + + disposables.add(disposable) + } + + private suspend fun waitForHardware() { + _uiState.update { it.copy(isLoading = true, statusText = "Starting hardware...") } + var elapsed = 0 + while (BaseApplication.basicOptV2 == null && elapsed < 10_000) { + delay(500) + elapsed += 500 + } + } + + private fun onConfigApplied() { + val validity = TMSUtil.getInstance().checkParams() + if (validity.status == ValidityStatus.SUCCESS) { + _uiState.update { it.copy(isLoading = false, statusText = "Ready.") } + viewModelScope.launch { _navigateToDashboard.emit(Unit) } + } else { + _uiState.update { + it.copy( + isLoading = false, + isError = true, + statusText = "Configuration incomplete", + errorMessage = validity.message ?: "Invalid TMS configuration" + ) + } + } + } + + fun skipDownload() { + viewModelScope.launch { _navigateToDashboard.emit(Unit) } + } + + private fun formatNetworkError(error: Throwable): String { + return when (error) { + is javax.net.ssl.SSLHandshakeException -> + "SSL handshake failed: ${error.message ?: "Certificate or protocol mismatch"}" + is javax.net.ssl.SSLException -> + "SSL/TLS error: ${error.message ?: "Secure connection could not be established"}" + is java.security.cert.CertificateException -> + "Server certificate error: ${error.message ?: "Certificate is invalid or untrusted"}" + is java.net.UnknownHostException -> + "Host not found: ${error.message ?: "Check server URL and network connection"}" + is java.net.ConnectException -> + "Connection refused: ${error.message ?: "Server is unreachable"}" + is java.net.SocketTimeoutException -> + "Connection timed out: ${error.message ?: "Server did not respond in time"}" + is retrofit2.HttpException -> + "HTTP ${error.code()} ${error.message()}" + else -> + error.message ?: "Unknown network error" + } + } + + @SuppressLint("MissingPermission") + private fun buildRequest(): SiriusRequest { + return try { + val tranTime: Long = System.currentTimeMillis() + TMSUtil.getInstance().generateRequestParams("...", tranTime) + } catch (e: Exception) { + LogUtil.e("TmsSetupViewModel", e.message) + SiriusRequest() + } + } + + override fun onCleared() { + super.onCleared() + disposables.clear() + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/tms/Connectivity.kt b/app/src/main/java/com/mob/utsmyanmar/utils/tms/Connectivity.kt new file mode 100644 index 0000000..d965834 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/utils/tms/Connectivity.kt @@ -0,0 +1,89 @@ +package com.mob.utsmyanmar.utils.tms + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import android.telephony.TelephonyManager +import androidx.annotation.RequiresPermission + +object Connectivity { + + fun isConnected(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val network = cm.activeNetwork ?: return false + val capabilities = cm.getNetworkCapabilities(network) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } + + fun isConnectedWifi(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + } + + fun isConnectedMobile(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + } + + fun isConnectedFast(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false + return when { + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> { + // Downstream bandwidth in Kbps; 2000 Kbps = ~2 Mbps threshold for "fast" + capabilities.linkDownstreamBandwidthKbps >= 2000 + } + else -> false + } + } + + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + fun getNetworkType(context: Context): String { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return "None") ?: return "None" + + if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return when { + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "WiFi" + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "Ethernet" + else -> "Unknown" + } + } + + val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + val networkType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tm.dataNetworkType + } else { + @Suppress("DEPRECATION") + tm.networkType + } + + return when (networkType) { + TelephonyManager.NETWORK_TYPE_GPRS, + TelephonyManager.NETWORK_TYPE_EDGE, + TelephonyManager.NETWORK_TYPE_CDMA, + TelephonyManager.NETWORK_TYPE_1xRTT, + TelephonyManager.NETWORK_TYPE_IDEN -> "2G" + + TelephonyManager.NETWORK_TYPE_UMTS, + TelephonyManager.NETWORK_TYPE_EVDO_0, + TelephonyManager.NETWORK_TYPE_EVDO_A, + TelephonyManager.NETWORK_TYPE_EVDO_B, + TelephonyManager.NETWORK_TYPE_HSDPA, + TelephonyManager.NETWORK_TYPE_HSUPA, + TelephonyManager.NETWORK_TYPE_HSPA, + TelephonyManager.NETWORK_TYPE_EHRPD, + TelephonyManager.NETWORK_TYPE_HSPAP -> "3G" + + TelephonyManager.NETWORK_TYPE_LTE -> "4G" + TelephonyManager.NETWORK_TYPE_NR -> "5G" + else -> "Unknown" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetups.kt b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetups.kt new file mode 100644 index 0000000..e32784f --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetups.kt @@ -0,0 +1,25 @@ +package com.mob.utsmyanmar.utils.tms + +import com.mob.utsmyanmar.model.sirius.SiriusResponse +import com.mob.utsmyanmar.model.sirius.TMSUpdate +import com.utsmyanmar.baselib.emv.EmvParamOperation + +interface TMSSetups { + + fun initParams( + siriusResponse: SiriusResponse, + tmsUpdate: TMSUpdate, + emvParamOperation: EmvParamOperation + ) + + fun initParams(json: String) + + fun convertToArray(string: String): ArrayList + + fun getPayHardwareVersion(): String + + fun getRomVersion(): String + + fun generateFinalVersion(): String + +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt new file mode 100644 index 0000000..e340302 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSSetupsImpl.kt @@ -0,0 +1,274 @@ +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.utsmyanmar.baselib.BaseApplication +import com.utsmyanmar.baselib.emv.EmvParamOperation +import com.utsmyanmar.baselib.network.model.sirius.SiriusHost +import com.utsmyanmar.baselib.network.model.sirius.SiriusMerchant +import com.utsmyanmar.baselib.network.model.sirius.SiriusProperty +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.enums.CurrencyType +import sunmi.sunmiui.utils.LogUtil + +class TMSSetupsImpl : TMSSetups { + + companion object { + private const val TAG = "TMSSetupsImpl" + private const val UPI = "UPI" + private const val JCB = "JCB" + private const val VISA = "VISA" + private const val MASTERCARD = "MASTERCARD" + } + + private fun currencyTextToCurrencyType(currencyText: String): CurrencyType { + return try { + CurrencyType.valueOf(currencyText) + } catch (e: Exception) { + CurrencyType.MMK + } + } + + private fun currencyTextToCode(currencyText: String): String { + return when (currencyText) { + "USD" -> "804" + "CNY" -> "156" + "THB" -> "764" + "RUB" -> "643" + else -> "104" + } + } + + private fun currencyCodeToText(currencyCode: String): String { + return when (currencyCode) { + "804" -> "USD" + "156" -> "CNY" + "764" -> "THB" + "643" -> "RUB" + else -> "MMK" + } + } + + private fun init() { + SystemParamsOperation.getInstance().apply { + hostName = "" + terminalId = "" + merchantId = "" + ipAddress = "" + secIpAddress = "" + + secHostName = "" + secHostTerminalId = "" + secHostMerchantId = "" + secHostIpAddress = "" + secHostSecIpAddress = "" + } + } + + override fun initParams( + siriusResponse: SiriusResponse, + tmsUpdate: TMSUpdate, + emvParamOperation: EmvParamOperation + ) { + val siriusMerchant: SiriusMerchant = siriusResponse.merchant + val siriusHosts: List = siriusResponse.hosts + val siriusProperty: List = siriusResponse.properties + val imgUrls = mutableListOf() + val ops = SystemParamsOperation.getInstance() + + if (tmsUpdate == TMSUpdate.UPDATE) ops.isNeedSettlement = false + + siriusResponse.address.takeIf { it.isEmpty() }?.let { ops.merchantAddress = "" } + + init() + + siriusMerchant.let { m -> + ops.merchantName = m.name + ops.merchantAddress = m.address + ops.merchantPhoneNo = m.phone + } + + siriusResponse.address.takeIf { it.isNotEmpty() }?.let { ops.merchantAddress = it } + //host + if (siriusHosts.isNotEmpty()) { + for (host in siriusHosts) { + val isMMQR = listOf(host.name, host.description).any { + it.lowercase().run { contains("mmqr") } + } + + if (isMMQR) { + ops.secHostName = host.name + ops.secHostTerminalId = host.tid + ops.secHostMerchantId = host.mid + + host.secondaryIP.trim().let { ip -> + ops.secHostIpAddress = if (ip.contains(":")) "$ip/" else "" + } + host.currency.takeIf { it.isNotEmpty() }?.let { + ops.secHostCurrency = currencyTextToCode(it) + } + if (host.tid.isEmpty()) ops.secHostTerminalId = "" + if (host.mid.isEmpty()) ops.secHostTerminalId = "" + } else { + ops.hostName = host.name + ops.terminalId = host.tid + ops.merchantId = host.mid + + host.primaryIP.trim().let { ip -> + ops.ipAddress = if (ip.contains(":")) ip else "" + } + host.secondaryIP.trim().let { ip -> + ops.secIpAddress = if (ip.contains(":")) ip else "" + } + host.currency.takeIf { it.isNotEmpty() }?.let { + ops.currencyType = currencyTextToCurrencyType(it) + } + if (host.tid.isEmpty()) ops.terminalId = "" + if (host.mid.isEmpty()) ops.merchantId = "" + } + } + } + + //Properties + for (prop in siriusProperty) { + val name = prop.name + val data = prop.property + + when (name) { + //image + "carousel_img_1", + "carousel_img_2", + "carousel_img_3", + "carousel_img_4", + "carousel_img_5", + "carousel_img_6" -> imgUrls.add(data) + //host + "host_timeout", + "host_connect_timeout" -> ops.hostResponseTimeout = data + "host_read_timeout" -> ops.hostReadTimeout = data + "reversal_delay" -> ops.reversalDelay = data + "key_index" -> ops.tmkIndex = data + "receipt_footer" -> ops.receiptFooter = data + "time_out" -> ops.setTmsTimeout(data) + "manual_update" -> ops.manualUpdate = parseBoolean(data) + "emv_enable" -> ops.setEmvEnable(parseBoolean(data)) + "hostport" -> ops.portAddress = data.toInt() + "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 } + "settlement_password" -> ops.settlementPassword = data.take(6).ifEmpty { data } + "setting_password" -> ops.settingPassword = data.take(6).ifEmpty { data } + "terminal_enable" -> ops.isActive = parseBoolean(data) + "terminal_enable_msg" -> ops.disabledMsg = data + "ssl_enable" -> ops.setSslSwitchStatus(parseBoolean(data)) + "wave_pay_inquiry_status_enable" -> ops.wavePayInquiryStatus = parseBoolean(data) + "tips_adjustment_enable" -> ops.tipsAdjustmentStatus = parseBoolean(data) + "wave_enable" -> ops.wavePayStatus = parseBoolean(data) + "print_iso_enable" -> ops.printISOStatus = parseBoolean(data) + "receipt_header" -> ops.receiptHeader = data + "random_pin_pad_enable" -> ops.isRandomPinPad = parseBoolean(data) + "clear_batch_time" -> ops.clearBatchTime = data + "alert_sound_enable" -> ops.isAlertSound = parseBoolean(data) + "auto_print_enable" -> ops.isAutoPrintCustomerCopy = parseBoolean(data) + "ecr_enable" -> ops.ecrStatus = parseBoolean(data) + "manual_entry_enable" -> ops.setManualEntyrStatus(parseBoolean(data)) + "mmqr_interval_waiting_time" -> ops.waveIntervalTime = data + "full_void_preauth_enable" -> ops.fullVoidPreauthStatus = parseBoolean(data) + "partial_void_preauth_enable" -> ops.partialVoidPreauthStatus = parseBoolean(data) + "clear_batch_day" -> ops.clearBatchDay = data + "qr_min_amount" -> ops.minAmount = data + "qr_max_amount" -> ops.maxAmount = data + "mmqr_auth_token" -> ops.authToken = data + "mmqr_grant_type" -> ops.grantType = data + "mmqr_token_host_address" -> ops.tokenHostAddress = "${data.trim()}/" + "mmpay_enable" -> ops.isMMPayEnabled = parseBoolean(data) + "fallback_enable" -> ops.fallbackEnabled = parseBoolean(data) + "magstripe_enable" -> ops.isMagStripeEnabled = parseBoolean(data) + "nfc_enable" -> ops.isNfcEnabled = parseBoolean(data) + "cvv_bypass_enable" -> ops.cvvBypassStatus = parseBoolean(data) + "upi_chip_cvm" -> emvParamOperation.updateChipCVM(UPI, data.toLong()) + "upi_contactless_cvm" -> { + val limit = data.toLong() + ops.upiCvmLimit = limit + emvParamOperation.updateUpiCVM(limit) + } + + "upi_currency_code" -> emvParamOperation.updateUpiCurrencyCode(data) + "jcb_chip_cvm" -> emvParamOperation.updateChipCVM(JCB, data.toLong()) + "visa_chip_cvm" -> emvParamOperation.updateChipCVM(VISA, data.toLong()) + "visa_contactless_cvm" -> emvParamOperation.updatePayWaveCVM(data.toLong()) + "visa_currency_code" -> emvParamOperation.updatePayWaveCurrencyCode(data) + "master_chip_cvm" -> emvParamOperation.updateChipCVM(MASTERCARD, data.toLong()) + "master_contactless_cvm" -> emvParamOperation.updatePayPassCVM(data.toLong()) + "master_currency_code" -> emvParamOperation.updatePayPassCurrencyCode(data) + "terminal_capability" -> { + if (data.isNotEmpty()) ops.setTerminalCapability(data) + } + + "upi_ttq" -> emvParamOperation.updateQuickPassTTQ(data) + "visa_ttq" -> emvParamOperation.updatePayWaveTTQ(data) + "master_ttq" -> emvParamOperation.updatePayPassTTQ(data) + "upi_tc_enabled" -> ops.setUpiTCEnabled(parseBoolean(data)) + "debug_feature_enable" -> ops.setDebugFeatureEnabled(parseBoolean(data)) + "master_terminal_capability" -> { + if (data.isNotEmpty()) emvParamOperation.updatePayPassTerminalCapability(data) + } + + "speedup_contactless_enable" -> ops.setSpeedUpContactless(parseBoolean(data)) + "manual_entry_pin_enable" -> ops.isManualEntryPinEnable = parseBoolean(data) + "cmhl_enabled" -> ops.setCMHLEnable(parseBoolean(data)) + } + } + ops.carouselUrls = imgUrls.joinToString(",") + } + + override fun initParams(json: String) { + val response = Gson().fromJson(json, SiriusResponse::class.java) + response.properties.forEach { prop -> + LogUtil.d(TAG, "name : ${prop.name}") + LogUtil.d(TAG, "value: ${prop.property}") + } + } + + override fun convertToArray(string: String): ArrayList { + if (string.isEmpty()) return ArrayList() + return ArrayList(string.split(",").filter { it.isNotEmpty() }) + } + + override fun getPayHardwareVersion(): String { + return try { + BaseApplication.getInstance().applicationContext.packageManager + .getPackageInfo("com.sunmi.pay.hardware_v3", 0) + .versionName ?: "?" + } catch (e: Exception) { + "?" + } + } + + override fun getRomVersion(): String { + return try { + android.os.Build.VERSION.RELEASE + } catch (e: Exception) { + "UNKNOWN" + } + } + + override fun generateFinalVersion(): String { + val phv = getPayHardwareVersion() + val rv = getRomVersion() + val sv = try { + BaseApplication.getInstance().applicationContext.packageManager + .getPackageInfo(BaseApplication.getInstance().packageName, 0) + .versionName ?: "?" + } catch (e: Exception) { + "?" + } + return "PHV$phv-RV$rv-SV$sv" + } + private fun parseBoolean(data: String): Boolean = + data.toIntOrNull()?.let { it == 1 } ?: data.toBoolean(); +} \ No newline at end of file diff --git a/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSUtil.kt b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSUtil.kt new file mode 100644 index 0000000..79b8d7c --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/utils/tms/TMSUtil.kt @@ -0,0 +1,169 @@ +package com.mob.utsmyanmar.utils.tms + +import android.Manifest +import android.content.Context +import android.os.BatteryManager +import android.os.Build +import androidx.annotation.RequiresPermission +import com.mob.utsmyanmar.BuildConfig +import com.mob.utsmyanmar.model.sirius.TMSValidity +import com.mob.utsmyanmar.model.sirius.ValidityStatus +import com.mob.utsmyanmar.viewmodel.SharedViewModel +import com.sunmi.pay.hardware.aidl.AidlConstants +import com.utsmyanmar.baselib.BaseApplication +import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import sunmi.sunmiui.utils.LogUtil + +class TMSUtil private constructor() { + private val tmsSetups: TMSSetups = TMSSetupsImpl(); + + companion object { + private val TAG = TMSUtil::class.java.simpleName + + @Volatile + private var app: TMSUtil? = null + + fun getInstance(): TMSUtil = app ?: synchronized(this) { + app ?: TMSUtil().also { app = it } + } + } + + // --- Param Init --- + fun initParams(json: String) { + tmsSetups.initParams(json) + } + + fun convertToArray(string: String): ArrayList = tmsSetups.convertToArray(string) + + fun getPayHardwareVersion(): String = tmsSetups.getPayHardwareVersion() + + fun getRomVersion(): String = tmsSetups.getRomVersion() + + fun generateFinalVersion(): String = tmsSetups.generateFinalVersion() + + //---shared view model--- + fun loadDownloadParameters(sharedViewModel: SharedViewModel) { + sharedViewModel.setManualEntryStatus(SystemParamsOperation.getInstance().manualEntryStatus) + } + + //---system params--- + fun getSystemParams(name: String): String = runCatching { + BaseApplication.basicOptV2.getSysParam(name) + }.getOrDefault("") + + fun getSerialNumber(): String = runCatching { + BaseApplication.basicOptV2.getSysParam("SN") + }.getOrDefault("") + + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + fun generateRequestParams(lastTransName: String, lastTransTime: Long): SiriusRequest = + SiriusRequest().apply { + serial = getSerialNumber() + appPackage = BuildConfig.APPLICATION_ID + androidVersion = Build.VERSION.RELEASE + firmwareVersion = getSystemParams(AidlConstants.SysParam.FIRMWARE_VERSION) + applicationVersion = BuildConfig.VERSION_NAME + currentNetwork = getNetworkType() + lastTransaction = lastTransName + lastTranTime = lastTransTime + latitude = 0.000000 + longitude = 0.000000 + value = "YourValueHere" + } + + //---logging-- + fun retrieveParameters() { + val ops = SystemParamsOperation.getInstance() + LogUtil.d(TAG, "TID: ${ops.terminalId}") + LogUtil.d(TAG, "MID: ${ops.merchantId}") + LogUtil.d(TAG, "Merchant Name: ${ops.merchantName}") + LogUtil.d(TAG, "Merchant Address: ${ops.merchantAddress}") + LogUtil.d(TAG, "Host Timeout: ${ops.hostResponseTimeout}") + LogUtil.d(TAG, "TMS Timeout: ${ops.tmsTimeOut}") + LogUtil.d(TAG, "Key Index: ${ops.tmkIndex}") + LogUtil.d(TAG, "Receipt Footer: ${ops.receiptFooter}") + LogUtil.d(TAG, "Manual Update: ${ops.manualUpdate}") + LogUtil.d(TAG, "Master Enabled: ${ops.isEmvEnabled}") + } + + //---checks--- + fun checkParams(): TMSValidity { + val ops = SystemParamsOperation.getInstance() + val tid = ops.terminalId + val mid = ops.merchantId + val hostIp = ops.ipAddress + val secIp = ops.secIpAddress + val keyIndex = ops.tmkIndex + + return when { + tid.length == 8 && mid.length == 15 && hostIp.isNotEmpty() && secIp.isNotEmpty() && keyIndex.isNotEmpty() -> TMSValidity( + ValidityStatus.SUCCESS, "Success" + ) + + tid.length != 8 -> TMSValidity(ValidityStatus.FAILURE, "Tid is invalid") + mid.length != 15 -> TMSValidity(ValidityStatus.FAILURE, "Mid is invalid") + hostIp.isEmpty() -> TMSValidity(ValidityStatus.FAILURE, "host ip is empty") + secIp.isEmpty() -> TMSValidity(ValidityStatus.FAILURE, "sec ip is empty") + + else -> TMSValidity(ValidityStatus.FAILURE, "KeyIndex is invalid") + } + } + + fun checkSecHostParams(): TMSValidity { + val ops = SystemParamsOperation.getInstance() + val tid = ops.secHostTerminalId + val mid = ops.secHostMerchantId + val hostIp = ops.secHostIpAddress + val secIp = ops.secHostSecIpAddress + val keyIndex = ops.tmkIndex + + return when { + tid.length == 8 && mid.length == 15 && hostIp.isNotEmpty() && secIp.isNotEmpty() && keyIndex.isNotEmpty() + -> TMSValidity(ValidityStatus.SUCCESS, "Success") + + tid.length != 8 -> TMSValidity(ValidityStatus.FAILURE, "MMQR Tid is invalid!") + mid.length != 15 -> TMSValidity(ValidityStatus.FAILURE, "MMQR Mid is invalid!") + hostIp.isEmpty() -> TMSValidity(ValidityStatus.FAILURE, "MMQR Pri-Ip is invalid!") + else -> TMSValidity(ValidityStatus.FAILURE, "MMQR Sec-Ip is invalid!") + } + } + + //---helpers--- + private fun getTransactionStatus(code: String): String = when (code) { + "00" -> "Transaction Approved" + else -> "Transaction Failed, reason : $code" + } + + private fun getBoolean(data: String): Boolean = data == "1" + + private fun getBatteryLevel(context: Context): Int { + val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager + return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) + } + + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + private fun getNetworkType(): String { + val ctx = BaseApplication.getInstance().applicationContext + + return when { + Connectivity.isConnectedWifi(ctx) -> { + LogUtil.d(TAG, "Connected to Wifi") + "WIFI" + } + + Connectivity.isConnectedMobile(ctx) -> { + val type = try { + Connectivity.getNetworkType(ctx) + } catch (e: SecurityException) { + LogUtil.d(TAG, "READ_PHONE_STATE permission not granted: ${e.message}") + "MOBILE" + } + LogUtil.d(TAG, "Connected to Mobile Network: $type") + type + } + + else -> "No Internet Connection" + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_address_global.xml b/app/src/main/res/drawable/ic_address_global.xml new file mode 100644 index 0000000..d335b02 --- /dev/null +++ b/app/src/main/res/drawable/ic_address_global.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_circle_download_arrow.xml b/app/src/main/res/drawable/ic_circle_download_arrow.xml new file mode 100644 index 0000000..dfe40fa --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_download_arrow.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 0000000..327d968 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_database_config.xml b/app/src/main/res/drawable/ic_database_config.xml new file mode 100644 index 0000000..5ff7b01 --- /dev/null +++ b/app/src/main/res/drawable/ic_database_config.xml @@ -0,0 +1,52 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_device_info.xml b/app/src/main/res/drawable/ic_device_info.xml new file mode 100644 index 0000000..aad74f6 --- /dev/null +++ b/app/src/main/res/drawable/ic_device_info.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..147ea32 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/di/NetworkModule.java b/baselib/src/main/java/com/utsmyanmar/baselib/di/NetworkModule.java index 33de1b3..5503517 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/di/NetworkModule.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/di/NetworkModule.java @@ -1,7 +1,5 @@ package com.utsmyanmar.baselib.di; -import android.text.TextUtils; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.utsmyanmar.baselib.BuildConfig; @@ -11,8 +9,6 @@ import com.utsmyanmar.baselib.network.WaveTokenApiService; import com.utsmyanmar.baselib.network.interceptor.HostSelectionInterceptor; import com.utsmyanmar.baselib.network.interceptor.QRAuthInterceptor; import com.utsmyanmar.baselib.network.interceptor.SiriusInterceptor; -import com.utsmyanmar.baselib.network.interceptor.WaveAuthInterceptor; -import com.utsmyanmar.paylibs.Constant; import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; import java.lang.annotation.Retention; @@ -38,7 +34,6 @@ import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; -import sunmi.sunmiui.utils.LogUtil; @Module @InstallIn(SingletonComponent.class) @@ -355,7 +350,7 @@ public class NetworkModule { tmsAddress = getTMSUrlFromNative(); } - String baseUrl = tmsAddress.trim() + "/api/v1/"; + String baseUrl = tmsAddress.trim() + "/"; final Gson gson = diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/network/interceptor/SiriusInterceptor.java b/baselib/src/main/java/com/utsmyanmar/baselib/network/interceptor/SiriusInterceptor.java index bdbe5ce..6983f03 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/network/interceptor/SiriusInterceptor.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/network/interceptor/SiriusInterceptor.java @@ -3,12 +3,10 @@ package com.utsmyanmar.baselib.network.interceptor; import androidx.annotation.NonNull; import com.utsmyanmar.baselib.util.TerminalUtil; -import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; import java.io.IOException; import java.security.NoSuchAlgorithmException; -import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; @@ -18,13 +16,6 @@ public class SiriusInterceptor implements Interceptor { private static final String TAG = SiriusInterceptor.class.getSimpleName(); - public static native String getHiddenFromNative(); - - static { - System.loadLibrary("native-lib"); - } - - @NonNull @Override public Response intercept(@NonNull Chain chain) throws IOException { @@ -35,9 +26,9 @@ public class SiriusInterceptor implements Interceptor { String hashed = ""; String nonce = TerminalUtil.getInstance().generateRandomNumbers(); try { - hashed = TerminalUtil.getInstance().generateHashedString(nonce); + hashed = TerminalUtil.getInstance().generateHashedString(nonce).toLowerCase(); -// LogUtil.d(TAG,"hashed :"+ hashed); + LogUtil.d(TAG,"hashed :"+ hashed); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } @@ -48,8 +39,13 @@ public class SiriusInterceptor implements Interceptor { .addHeader("request-id", hashed) .addHeader("request-code",nonce) .build(); + + LogUtil.d(TAG, "URL: " + newRequest.url()); + LogUtil.d(TAG, "Method: " + newRequest.method()); + LogUtil.d(TAG, "Headers: " + newRequest.headers()); + LogUtil.d(TAG, "request-id: " + hashed); + LogUtil.d(TAG, "request-code: " + nonce); + return chain.proceed(newRequest); - - } } diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusMerchant.java b/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusMerchant.java index c06dd85..e268ee8 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusMerchant.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusMerchant.java @@ -2,7 +2,7 @@ package com.utsmyanmar.baselib.network.model.sirius; public class SiriusMerchant { - private int id; + private String id; private String name; @@ -16,7 +16,7 @@ public class SiriusMerchant { private String mobile; - public SiriusMerchant(int id, String name, String description, String address,String address2,String phone,String mobile) { + public SiriusMerchant(String id, String name, String description, String address,String address2,String phone,String mobile) { this.id = id; this.name = name; this.description = description; @@ -50,7 +50,7 @@ public class SiriusMerchant { return mobile; } - public void setId(int id) { + public void setId(String id) { this.id = id; } @@ -66,7 +66,7 @@ public class SiriusMerchant { this.address = address; } - public int getId() { + public String getId() { return id; } diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusProperty.java b/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusProperty.java index f7e681c..aed66a0 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusProperty.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusProperty.java @@ -2,12 +2,12 @@ package com.utsmyanmar.baselib.network.model.sirius; public class SiriusProperty { - private int id; - private int terId; + private String id; + private String terId; - private int appId; + private String appId; - private int configId; + private String configId; private String name; @@ -17,7 +17,7 @@ public class SiriusProperty { private String property; - public SiriusProperty(int id, int terId, int appId, int configId, String name, String description, String type, String property) { + public SiriusProperty(String id, String terId, String appId, String configId, String name, String description, String type, String property) { this.id = id; this.terId = terId; this.appId = appId; @@ -28,19 +28,19 @@ public class SiriusProperty { this.property = property; } - public void setId(int id) { + public void setId(String id) { this.id = id; } - public void setTerId(int terId) { + public void setTerId(String terId) { this.terId = terId; } - public void setAppId(int appId) { + public void setAppId(String appId) { this.appId = appId; } - public void setConfigId(int configId) { + public void setConfigId(String configId) { this.configId = configId; } @@ -60,19 +60,19 @@ public class SiriusProperty { this.property = property; } - public int getId() { + public String getId() { return id; } - public int getTerId() { + public String getTerId() { return terId; } - public int getAppId() { + public String getAppId() { return appId; } - public int getConfigId() { + public String getConfigId() { return configId; } diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusRequest.java b/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusRequest.java index 46f162d..0b5f7de 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusRequest.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/network/model/sirius/SiriusRequest.java @@ -15,10 +15,30 @@ public class SiriusRequest { private String lastTransaction; - private String lastTranTime; + private Long lastTranTime; - public SiriusRequest() {} - public SiriusRequest(String serial, String appPackage, String androidVersion, String firmwareVersion, String applicationVersion, String currentNetwork, String lastTransaction, String lastTranTime) { + private String value; + + private double latitude; + + private double longitude; + + public SiriusRequest() { + } + + public SiriusRequest( + String serial, + String appPackage, + String androidVersion, + String firmwareVersion, + String applicationVersion, + String currentNetwork, + String lastTransaction, + Long lastTranTime, + String value, + double latitude, + double longitude + ) { this.serial = serial; this.appPackage = appPackage; this.androidVersion = androidVersion; @@ -27,6 +47,9 @@ public class SiriusRequest { this.currentNetwork = currentNetwork; this.lastTransaction = lastTransaction; this.lastTranTime = lastTranTime; + this.value = value; + this.latitude = latitude; + this.longitude = longitude; } public void setSerial(String serial) { @@ -57,7 +80,7 @@ public class SiriusRequest { this.lastTransaction = lastTransaction; } - public void setLastTranTime(String lastTranTime) { + public void setLastTranTime(Long lastTranTime) { this.lastTranTime = lastTranTime; } @@ -89,7 +112,31 @@ public class SiriusRequest { return lastTransaction; } - public String getLastTranTime() { + public Long getLastTranTime() { return lastTranTime; } + + public String getValue(){ + return this.value; + } + + public void setValue(String value){ + this.value = value; + } + + public double getLatitude(){ + return this.latitude; + } + + public void setLatitude(double latitude){ + this.latitude = latitude; + } + + public double getLongitude(){ + return this.longitude; + } + + public void setLongitude(double longitude){ + this.longitude = longitude; + } } diff --git a/baselib/src/main/java/com/utsmyanmar/baselib/util/TerminalUtilsImpl.java b/baselib/src/main/java/com/utsmyanmar/baselib/util/TerminalUtilsImpl.java index e497b5f..30ebe52 100644 --- a/baselib/src/main/java/com/utsmyanmar/baselib/util/TerminalUtilsImpl.java +++ b/baselib/src/main/java/com/utsmyanmar/baselib/util/TerminalUtilsImpl.java @@ -1,9 +1,6 @@ package com.utsmyanmar.baselib.util; -import android.os.RemoteException; - import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2; -import com.utsmyanmar.baselib.BaseApplication; import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil; import java.nio.charset.StandardCharsets; @@ -12,53 +9,28 @@ import java.security.NoSuchAlgorithmException; import java.util.Random; public class TerminalUtilsImpl implements TerminalUtils{ - - long number = 2485718; - - long sub = 1251151; - - public static native String getHiddenFromNative(); - - public static native String getEncryptedFromNative(); - - - static { - System.loadLibrary("native-lib"); - } - @Override public String generateHashString(String random,BasicOptV2 basicOptV2) throws NoSuchAlgorithmException { - String sn = getSerialNumber(basicOptV2); - String snPN = BaseApplication.getInstance().getPackageName(); - String nonce = random; - String text = sn + snPN + nonce; -// LogUtil.d(TAG,"Plain Text: "+text); + String sn = "P30224BSJ0276"; + String snPN = "com.mob.utsmyanmar"; + String text = sn + snPN + random; MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8)); - return ByteUtil.bytes2HexStr(hash); } @Override public String generateRandom() { Random rnd = new Random(); - StringBuilder sb = new StringBuilder((1000000 + rnd.nextInt(9000000))); + StringBuilder sb = new StringBuilder(); + sb.append(1000000 + rnd.nextInt(9000000)); return sb.toString(); } @Override public String getSerialNumber(BasicOptV2 basicOptV2){ - String data = ""; - try { - data = basicOptV2.getSysParam(getEncryptedFromNative()); - } catch (RemoteException e) { - e.printStackTrace(); - } - - return data; - - + return "abcd"; } 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 45aed40..63b5f00 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/utils/core_utils/SystemParamsSettings.java @@ -38,7 +38,7 @@ public class SystemParamsSettings implements Serializable { private boolean checkExpSwitch = false; - private String tmsAddress = "https://tms.smile-mm.com"; + private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1"; // private String tmsAddress = "http://128.199.170.203"; private String terminalCapability = "E0E8C8";