Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40ee7c600f | ||
|
|
8671d459cf | ||
| 000c48bf6b | |||
|
|
f95208aa9d | ||
|
|
4aebd825d2 | ||
| f80c7c5d1b | |||
|
|
5e2cfa3821 | ||
|
|
0ed4113366 | ||
|
|
6ddf0dda6c | ||
|
|
ba24c60058 | ||
|
|
feb6ea449f | ||
|
|
030eb6f836 | ||
|
|
af917520b6 | ||
|
|
767456b29a | ||
|
|
5c6c8f78c5 | ||
|
|
0f0bd49709 | ||
|
|
dbc5424aab | ||
|
|
675e398aaf | ||
|
|
57dcd13c34 | ||
|
|
72ecfbe4e7 | ||
|
|
0f435192ee | ||
|
|
fe6c5ad15e | ||
| 10f5bcf355 | |||
| e985b37aa2 | |||
|
|
f4fc2fa730 | ||
|
|
ff2858d484 | ||
|
|
9d9ef95140 | ||
|
|
79ab7c0bae | ||
|
|
283d2d64ce | ||
|
|
fb4c832476 | ||
|
|
5bbda074a8 | ||
|
|
ee2f9df351 | ||
|
|
35600bda29 | ||
|
|
44c0fa20af | ||
|
|
04549321fb | ||
|
|
42bd592c3e | ||
|
|
5904b4f69a | ||
|
|
bdd252cda9 | ||
|
|
9602d7cf81 | ||
|
|
0abf7bc6c9 | ||
|
|
bcd5634941 | ||
|
|
b5e2ec8e01 | ||
|
|
d44163b601 | ||
|
|
37a1a2e38d | ||
|
|
d3a6ddbc37 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,3 +14,6 @@
|
|||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
/.idea
|
/.idea
|
||||||
|
/.idea
|
||||||
|
/sunmiui-lib
|
||||||
|
/paysdk-lib
|
||||||
|
|||||||
@ -57,5 +57,6 @@
|
|||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
96
CLAUDE.md
Normal file
96
CLAUDE.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Build debug APK
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# Build release APK
|
||||||
|
./gradlew assembleRelease
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
|
# Run instrumented tests (requires connected device/emulator)
|
||||||
|
./gradlew connectedAndroidTest
|
||||||
|
|
||||||
|
# Run a single unit test class
|
||||||
|
./gradlew :app:test --tests "com.mob.utsmyammer.ExampleUnitTest"
|
||||||
|
|
||||||
|
# Clean build
|
||||||
|
./gradlew clean assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
**MOBPOS** is a mobile Point-of-Sale system for Myanmar, targeting Sunmi hardware devices (card readers, PIN pads, thermal printers). It processes EMV card transactions (sale, void, refund, settlement) and QR payments (MMQR, Wave Pay).
|
||||||
|
|
||||||
|
### Single-Activity Compose App
|
||||||
|
|
||||||
|
`MainActivity` hosts the entire app. Navigation is handled by Compose Navigation (`AppNavGraph.kt`) with typed routes defined in a sealed `Routes` object.
|
||||||
|
|
||||||
|
### MVVM with Shared State
|
||||||
|
|
||||||
|
- Each screen has a dedicated `ViewModel` (Hilt-injected)
|
||||||
|
- `SharedViewModel` in `viewmodel/` is the central state holder for cross-screen transaction data (card info, amount, transaction type, approval codes)
|
||||||
|
- Hardware interaction is abstracted into `CardReaderViewModel` and `EmvTransactionProcessViewModel`
|
||||||
|
- State is a mix of `LiveData` (older screens) and `StateFlow` (newer screens like card reading)
|
||||||
|
|
||||||
|
### Screen Structure Pattern
|
||||||
|
|
||||||
|
Every feature follows: `ui/<feature>/` containing:
|
||||||
|
- `*Screen.kt` — Composable UI
|
||||||
|
- `*ViewModel.kt` — business logic and state
|
||||||
|
|
||||||
|
### Key Transaction Flow
|
||||||
|
|
||||||
|
Dashboard → InputAmount → CardWaiting → ProcessingCard → SendingToHost → TransactionResult → PrintReceipt
|
||||||
|
|
||||||
|
### Local Modules
|
||||||
|
|
||||||
|
The app depends on several local library modules (in `settings.gradle.kts`):
|
||||||
|
- `:baselib` — Room DB, Retrofit, Hilt base setup
|
||||||
|
- `:paylibs` / `:paysdk-lib` — Payment SDK wrappers
|
||||||
|
- `:ecr` / `:ecr-service-lib` — Electronic Cash Register integration
|
||||||
|
- `:mpulib` / `:mpu-lib` — Mini POS Unit hardware
|
||||||
|
- `:sunmiui-lib` — Sunmi device UI components
|
||||||
|
- `:qrgen-lib` — QR code generation
|
||||||
|
|
||||||
|
### Key Dependencies
|
||||||
|
|
||||||
|
- **UI:** Jetpack Compose + Material 3, Compose Navigation
|
||||||
|
- **DI:** Hilt 2.59.2 with KSP
|
||||||
|
- **Async:** RxJava 3 + RxAndroid (primary async pattern)
|
||||||
|
- **Network:** Retrofit 3 + OkHttp 4 + GSON
|
||||||
|
- **DB:** Room 2.4.3 with RxJava 3 support
|
||||||
|
- **Hardware:** Sunmi SDK (card reader, PIN pad, printer APIs)
|
||||||
|
|
||||||
|
### Package Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
com.mob.utsmyammer/
|
||||||
|
├── config/ # Constants, SunmiPayManager
|
||||||
|
├── model/ # Data models (CardTransactionType, ProcessCode, TransactionStatus)
|
||||||
|
│ └── ecr/ # ECR-specific models
|
||||||
|
├── ui/ # All Compose screens (one subdir per feature)
|
||||||
|
│ ├── components/ # Shared reusables (NumericEntryScreen, SquareButton, AppBar)
|
||||||
|
│ ├── navigation/ # AppNavGraph, Routes
|
||||||
|
│ └── theme/ # Compose theme and colors
|
||||||
|
├── utils/ # Utility functions
|
||||||
|
└── viewmodel/ # Shared ViewModels (SharedViewModel, CardReaderViewModel, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hardware Integration Notes
|
||||||
|
|
||||||
|
Sunmi device permissions are declared in `AndroidManifest.xml` (LED, MSR, ICC, PINPAD, PRINTER). When modifying card reading or PIN entry flows, be aware that `CardReaderViewModel` and the Sunmi ECR service handle hardware callbacks via RxJava subjects. Hardware callbacks arrive on background threads — always observe on the main thread before updating UI state.
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
- `compileSdk 36`, `minSdk 24`, `targetSdk 36`
|
||||||
|
- Java 11 toolchain, Kotlin 2.2.10
|
||||||
|
- Compose BOM 2026.02.01
|
||||||
|
- ProGuard/minification is **disabled** for all build types
|
||||||
|
- Application ID: `com.mob.ustmm`
|
||||||
@ -1,6 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
id("com.google.dagger.hilt.android")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -44,10 +46,13 @@ dependencies {
|
|||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.compose.foundation)
|
implementation(libs.androidx.compose.foundation)
|
||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
|
implementation(libs.androidx.compose.runtime)
|
||||||
|
implementation(libs.androidx.compose.runtime.livedata)
|
||||||
implementation(libs.androidx.compose.ui)
|
implementation(libs.androidx.compose.ui)
|
||||||
implementation(libs.androidx.compose.ui.graphics)
|
implementation(libs.androidx.compose.ui.graphics)
|
||||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.hilt.navigation.compose)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
@ -57,4 +62,29 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
}
|
implementation(libs.hilt.android.v2592)
|
||||||
|
ksp(libs.hilt.android.compiler)
|
||||||
|
implementation(libs.rxjava)
|
||||||
|
implementation(libs.rxandroid)
|
||||||
|
implementation(libs.retrofit)
|
||||||
|
implementation(libs.converter.gson)
|
||||||
|
// Core icons (usually included with material library)
|
||||||
|
implementation( libs.androidx.compose.material.icons.core)
|
||||||
|
// Extended icons (full set of icons)
|
||||||
|
implementation(libs.androidx.compose.material.icons.extended)
|
||||||
|
// splash screen
|
||||||
|
implementation(libs.androidx.core.splashscreen)
|
||||||
|
//image lib
|
||||||
|
implementation("io.coil-kt.coil3:coil-compose:3.4.0")
|
||||||
|
implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0")
|
||||||
|
// local libs
|
||||||
|
implementation(project(":baselib"))
|
||||||
|
implementation(project(":mpulib"))
|
||||||
|
implementation(project(":paylibs"))
|
||||||
|
implementation(project(":paysdk-lib"))
|
||||||
|
implementation(project(":qrgen-lib"))
|
||||||
|
implementation(project(":sunmiui-lib"))
|
||||||
|
implementation(project(":ecr"))
|
||||||
|
implementation(project(":xpay"))
|
||||||
|
implementation(project(":cmhl"))
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,32 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.telephony"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="com.sunmi.perm.LED" />
|
||||||
|
<uses-permission android:name="com.sunmi.perm.MSR" />
|
||||||
|
<uses-permission android:name="com.sunmi.perm.ICC" />
|
||||||
|
<uses-permission android:name="com.sunmi.perm.PINPAD" />
|
||||||
|
<uses-permission android:name="com.sunmi.perm.SECURITY" />
|
||||||
|
<uses-permission android:name="com.sunmi.perm.CONTACTLESS_CARD" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="com.mob.utsmyanmar.MyApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@ -12,16 +37,14 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.MOBPOS">
|
android:theme="@style/Theme.MOBPOS">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name="com.mob.utsmyanmar.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name">
|
||||||
android:theme="@style/Theme.MOBPOS">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
3
app/src/main/java/com/mob/utsmyanmar/AGENTS.md
Normal file
3
app/src/main/java/com/mob/utsmyanmar/AGENTS.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## Working agreements
|
||||||
|
|
||||||
|
- use Scaffold and AppBar() in every main Screen
|
||||||
@ -1,17 +1,24 @@
|
|||||||
package com.mob.utsmyanmar
|
package com.mob.utsmyanmar
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.WindowManager
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.mob.utsmyanmar.ui.navigation.AppNavGraph
|
import com.mob.utsmyanmar.ui.navigation.AppNavGraph
|
||||||
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
|
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
// enableEdgeToEdge()
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
setContent {
|
setContent {
|
||||||
MOBPOSTheme {
|
MOBPOSTheme {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|||||||
18
app/src/main/java/com/mob/utsmyanmar/MyApplication.kt
Normal file
18
app/src/main/java/com/mob/utsmyanmar/MyApplication.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.mob.utsmyanmar
|
||||||
|
|
||||||
|
|
||||||
|
import com.mob.utsmyanmar.utils.AppContextHolder
|
||||||
|
import com.utsmyanmar.baselib.BaseApplication
|
||||||
|
import com.utsmyanmar.paylibs.network.ISOSocket
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class MyApplication : BaseApplication() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
AppContextHolder.init(this)
|
||||||
|
ISOSocket.getInstance().initContext(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
app/src/main/java/com/mob/utsmyanmar/config/Constants.kt
Normal file
13
app/src/main/java/com/mob/utsmyanmar/config/Constants.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.mob.utsmyanmar.config
|
||||||
|
|
||||||
|
import com.mob.utsmyanmar.R
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val WALLET = "WALLET"
|
||||||
|
const val MPU_CARD_SCHEME = "MPU"
|
||||||
|
const val TIMEOUT = 30
|
||||||
|
const val PROCESSING_TIMEOUT = 180
|
||||||
|
const val PIN_PAD_TIMEOUT = 60
|
||||||
|
const val REVERSAL = "REVERSAL"
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.mob.utsmyanmar.config
|
||||||
|
|
||||||
|
import com.sunmi.pay.hardware.aidl.security.SecurityOpt
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.readcard.ReadCardOptV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2
|
||||||
|
import com.utsmyanmar.baselib.BaseApplication
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SunmiPayManager @Inject constructor() {
|
||||||
|
private val app: BaseApplication?
|
||||||
|
get() = BaseApplication.getInstance()
|
||||||
|
|
||||||
|
val pinPadOptV2: PinPadOptV2?
|
||||||
|
get() = app?.mPinPadOptV2
|
||||||
|
|
||||||
|
val securityOptV2: SecurityOptV2?
|
||||||
|
get() = app?.mSecurityOptV2
|
||||||
|
|
||||||
|
val readCardOptV2: ReadCardOptV2?
|
||||||
|
get() = app?.mReadCardOptV2
|
||||||
|
|
||||||
|
val emvOptV2: EMVOptV2?
|
||||||
|
get() = app?.mEMVOptV2
|
||||||
|
|
||||||
|
val basicOptV2: BasicOptV2?
|
||||||
|
get() = BaseApplication.basicOptV2
|
||||||
|
|
||||||
|
val securityOpt: SecurityOpt?
|
||||||
|
get() = app?.securityOpt
|
||||||
|
|
||||||
|
fun isReady(): Boolean {
|
||||||
|
return pinPadOptV2 != null &&
|
||||||
|
securityOptV2 != null &&
|
||||||
|
readCardOptV2 != null &&
|
||||||
|
emvOptV2 != null &&
|
||||||
|
basicOptV2 != null
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.mob.utsmyanmar.model
|
||||||
|
|
||||||
|
enum class CardTransactionType {
|
||||||
|
MPU, EMV, MAG, FALLBACK
|
||||||
|
}
|
||||||
12
app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt
Normal file
12
app/src/main/java/com/mob/utsmyanmar/model/PinPadStatus.kt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.mob.utsmyanmar.model
|
||||||
|
|
||||||
|
enum class PinPadStatus {
|
||||||
|
ON_CONFIRM,
|
||||||
|
ON_CANCEL ,
|
||||||
|
ON_EMPTY,
|
||||||
|
ON_ERROR,
|
||||||
|
ON_TIMEOUT,
|
||||||
|
ON_NEXT_SCREEN,
|
||||||
|
ON_CARD_REMOVED,
|
||||||
|
ON_ERROR_DUKPT,
|
||||||
|
}
|
||||||
39
app/src/main/java/com/mob/utsmyanmar/model/ProcessCode.kt
Normal file
39
app/src/main/java/com/mob/utsmyanmar/model/ProcessCode.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package com.mob.utsmyanmar.model
|
||||||
|
|
||||||
|
object ProcessCode {
|
||||||
|
const val BALANCE_INQUIRY = "31"
|
||||||
|
|
||||||
|
const val SALE_PURCHASE = "00"
|
||||||
|
|
||||||
|
const val SALE_VOID = "02"
|
||||||
|
|
||||||
|
const val PRE_AUTH_SALE = "30"
|
||||||
|
|
||||||
|
const val PRE_AUTH_VOID = "30"
|
||||||
|
|
||||||
|
const val PRE_AUTH_COMPLETE = "30"
|
||||||
|
|
||||||
|
const val PRE_AUTH_COMPLETE_VOID = "30"
|
||||||
|
|
||||||
|
const val CASH_ADVANCE = "01" // 01-MPU 17-TTIP
|
||||||
|
|
||||||
|
const val FUND_TRANSFER = "61"
|
||||||
|
|
||||||
|
const val PIN_CHANGE = "70"
|
||||||
|
|
||||||
|
const val SETTLEMENT = "92"
|
||||||
|
|
||||||
|
const val SIGN_ON = "95"
|
||||||
|
|
||||||
|
const val CASH_DEPOSIT = "21"
|
||||||
|
|
||||||
|
const val REFUND = "20"
|
||||||
|
|
||||||
|
const val SMART = "00"
|
||||||
|
|
||||||
|
const val SAVING = "10"
|
||||||
|
|
||||||
|
const val CURRENT = "20"
|
||||||
|
|
||||||
|
const val TO_ACCOUNT = "00"
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package com.mob.utsmyanmar.model
|
||||||
|
|
||||||
|
enum class SettlementType {
|
||||||
|
NORMAL,
|
||||||
|
CUT_OVER
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.mob.utsmyanmar.model
|
||||||
|
|
||||||
|
enum class TransResultStatus {
|
||||||
|
CLICK_CONFIRM,
|
||||||
|
SUCCESS,
|
||||||
|
FAIL,
|
||||||
|
OFFLINE_SUCCESS,
|
||||||
|
OFFLINE_FAILURE,
|
||||||
|
SECONDARY,
|
||||||
|
REVERSAL_PROCESS,
|
||||||
|
REVERSAL_PREPARE,
|
||||||
|
REVERSAL_SECONDARY,
|
||||||
|
REVERSAL_THIRD,
|
||||||
|
REVERSAL_FAIL,
|
||||||
|
REVERSAL_SUCCESS,
|
||||||
|
BEFORE_REVERSAL,
|
||||||
|
PIN_PAD_CANCEL,
|
||||||
|
PIN_PAD_CONFIRM,
|
||||||
|
PIN_PAD_ERROR,
|
||||||
|
PIN_MISMATCH,
|
||||||
|
PIN_MISMATCH_END,
|
||||||
|
REMOVED_CARD,
|
||||||
|
EMV_ERROR,
|
||||||
|
ERROR,
|
||||||
|
RETRY_AGAIN,
|
||||||
|
NEXT_SCREEN,
|
||||||
|
EMPTY_PIN,
|
||||||
|
NETWORK_ERROR
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.mob.utsmyanmar.model
|
||||||
|
|
||||||
|
enum class TransactionStatus {
|
||||||
|
ON_SUCCESS,
|
||||||
|
ON_REVERSAL,
|
||||||
|
ON_BATCH_UPLOAD,
|
||||||
|
ON_FAIL,
|
||||||
|
ON_ERROR,
|
||||||
|
ON_SECONDARY,
|
||||||
|
ON_DONE
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.mob.utsmyanmar.model.ecr
|
||||||
|
|
||||||
|
enum class ECRResultStatus {
|
||||||
|
USER_CANCEL,
|
||||||
|
TIME_OUT,
|
||||||
|
RESPONSE_RECEIVED
|
||||||
|
}
|
||||||
@ -1,362 +0,0 @@
|
|||||||
package com.mob.utsmyanmar.ui.amount
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
|
|
||||||
import com.mob.utsmyanmar.ui.theme.Primary
|
|
||||||
import com.mob.utsmyanmar.ui.theme.White
|
|
||||||
import com.mob.utsmyanmar.R
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun AmountScreen(
|
|
||||||
action: String,
|
|
||||||
onBackClick: () -> Unit,
|
|
||||||
onCancelClick: () -> Unit = {},
|
|
||||||
onNextClick: (String) -> Unit = {}
|
|
||||||
) {
|
|
||||||
|
|
||||||
var amount by remember {
|
|
||||||
mutableStateOf("0")
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
CenterAlignedTopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = action.uppercase(),
|
|
||||||
color = White,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onBackClick) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_left_arrow),
|
|
||||||
contentDescription = "Back",
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
tint = White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = Primary
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
containerColor = White
|
|
||||||
) { paddingValues ->
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues)
|
|
||||||
.background(White)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
AmountBox(
|
|
||||||
amount = formatTypedAmount(amount),
|
|
||||||
height = 134.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
|
|
||||||
Keypad(
|
|
||||||
buttonHeight = 74.dp,
|
|
||||||
buttonSpacing = 12.dp,
|
|
||||||
onKeyClick = { key ->
|
|
||||||
amount = handleAmountInput(amount, key)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(18.dp))
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = onCancelClick,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(68.dp),
|
|
||||||
shape = RoundedCornerShape(18.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = White,
|
|
||||||
contentColor = Primary
|
|
||||||
),
|
|
||||||
border = ButtonDefaults.outlinedButtonBorder
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Cancel",
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onNextClick(normalizeAmount(amount))
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(68.dp),
|
|
||||||
shape = RoundedCornerShape(18.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Primary,
|
|
||||||
contentColor = White
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Next",
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AmountBox(
|
|
||||||
amount: String,
|
|
||||||
height: Dp
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(height)
|
|
||||||
.background(
|
|
||||||
color = Primary,
|
|
||||||
shape = RoundedCornerShape(20.dp)
|
|
||||||
)
|
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
contentAlignment = Alignment.CenterEnd
|
|
||||||
) {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = amount,
|
|
||||||
color = White,
|
|
||||||
fontSize = 30.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(14.dp))
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(
|
|
||||||
color = White,
|
|
||||||
shape = RoundedCornerShape(10.dp)
|
|
||||||
)
|
|
||||||
.padding(
|
|
||||||
horizontal = 16.dp,
|
|
||||||
vertical = 6.dp
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "MMK",
|
|
||||||
color = Primary,
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun Keypad(
|
|
||||||
buttonHeight: Dp,
|
|
||||||
buttonSpacing: Dp,
|
|
||||||
onKeyClick: (String) -> Unit
|
|
||||||
) {
|
|
||||||
|
|
||||||
val keys = listOf(
|
|
||||||
listOf("1", "2", "3"),
|
|
||||||
listOf("4", "5", "6"),
|
|
||||||
listOf("7", "8", "9"),
|
|
||||||
listOf(".", "0", "DEL")
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(buttonSpacing)
|
|
||||||
) {
|
|
||||||
|
|
||||||
keys.forEach { row ->
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
row.forEach { key ->
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onKeyClick(key)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(buttonHeight),
|
|
||||||
shape = RoundedCornerShape(18.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Primary,
|
|
||||||
contentColor = White
|
|
||||||
),
|
|
||||||
contentPadding = PaddingValues(0.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
if (key == "DEL") {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "⌫",
|
|
||||||
fontSize = 26.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = key,
|
|
||||||
fontSize = 22.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAmountInput(
|
|
||||||
current: String,
|
|
||||||
key: String
|
|
||||||
): String {
|
|
||||||
|
|
||||||
return when (key.uppercase()) {
|
|
||||||
|
|
||||||
"DEL" -> {
|
|
||||||
current
|
|
||||||
.dropLast(1)
|
|
||||||
.ifEmpty { "0" }
|
|
||||||
}
|
|
||||||
|
|
||||||
"." -> {
|
|
||||||
if (current.contains(".")) {
|
|
||||||
current
|
|
||||||
} else {
|
|
||||||
"${current.ifEmpty { "0" }}."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
in "0".."9" -> {
|
|
||||||
val sanitizedCurrent = if (current == "0") "" else current
|
|
||||||
|
|
||||||
if (sanitizedCurrent.contains(".")) {
|
|
||||||
val decimal = sanitizedCurrent.substringAfter(".")
|
|
||||||
|
|
||||||
if (decimal.length >= 2) {
|
|
||||||
current
|
|
||||||
} else {
|
|
||||||
sanitizedCurrent + key
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val nextValue = sanitizedCurrent + key
|
|
||||||
nextValue.trimStart('0').ifEmpty { "0" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun normalizeAmount(value: String): String {
|
|
||||||
if (value.isBlank()) return "0.00"
|
|
||||||
|
|
||||||
val normalized = if (value.endsWith(".")) {
|
|
||||||
value.dropLast(1)
|
|
||||||
} else {
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
normalized.isBlank() -> "0.00"
|
|
||||||
normalized.contains(".") -> {
|
|
||||||
val whole = normalized.substringBefore(".").ifEmpty { "0" }
|
|
||||||
val decimal = normalized.substringAfter(".")
|
|
||||||
|
|
||||||
when (decimal.length) {
|
|
||||||
0 -> "$whole.00"
|
|
||||||
1 -> "$whole.${decimal}0"
|
|
||||||
else -> "$whole.${decimal.take(2)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> "${normalized}.00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatTypedAmount(value: String): String {
|
|
||||||
if (!value.contains(".")) return "$value.00"
|
|
||||||
|
|
||||||
val whole = value.substringBefore(".").ifEmpty { "0" }
|
|
||||||
val decimal = value.substringAfter(".")
|
|
||||||
|
|
||||||
return when (decimal.length) {
|
|
||||||
0 -> "$whole."
|
|
||||||
1 -> "$whole.${decimal}0"
|
|
||||||
else -> "$whole.${decimal.take(2)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
private fun AmountScreenPreview() {
|
|
||||||
|
|
||||||
MOBPOSTheme {
|
|
||||||
|
|
||||||
AmountScreen(
|
|
||||||
action = "Amount",
|
|
||||||
onBackClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.cardwaiting
|
||||||
|
|
||||||
|
sealed interface CardWaitingEvent {
|
||||||
|
data object GoManualEntry : CardWaitingEvent
|
||||||
|
data object GoProcessingCard : CardWaitingEvent
|
||||||
|
data object GoTimeout : CardWaitingEvent
|
||||||
|
data object GoMain : CardWaitingEvent
|
||||||
|
data object GoBack : CardWaitingEvent
|
||||||
|
}
|
||||||
@ -0,0 +1,442 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.cardwaiting
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowLeft
|
||||||
|
import androidx.compose.material.icons.rounded.Wifi
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
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.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
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.R
|
||||||
|
import com.mob.utsmyanmar.ui.preview.P2Preview
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CardWaitingScreen(
|
||||||
|
viewModel: CardWaitingViewModel,
|
||||||
|
amount: String,
|
||||||
|
onManualEntry: () -> Unit,
|
||||||
|
onProcessingCard: () -> Unit,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onMain: () -> Unit
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.events.collect { event ->
|
||||||
|
when (event) {
|
||||||
|
CardWaitingEvent.GoManualEntry -> onManualEntry()
|
||||||
|
CardWaitingEvent.GoProcessingCard -> onProcessingCard()
|
||||||
|
CardWaitingEvent.GoMain -> onMain()
|
||||||
|
CardWaitingEvent.GoBack -> onBack()
|
||||||
|
CardWaitingEvent.GoTimeout -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.onScreenResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(viewModel) {
|
||||||
|
onDispose {
|
||||||
|
viewModel.onScreenPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(enabled = uiState.canGoBack) {
|
||||||
|
viewModel.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
CardWaitingScreenContent(
|
||||||
|
amount = amount,
|
||||||
|
uiState = uiState,
|
||||||
|
onBackClick = viewModel::onBackPressed,
|
||||||
|
onManualEntryClick = viewModel::onManualEntryClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CardWaitingScreenContent(
|
||||||
|
amount: String,
|
||||||
|
uiState: CardWaitingUiState,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
onManualEntryClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.IvoryBeige)
|
||||||
|
.statusBarsPadding()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(horizontal = 20.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(54.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (uiState.canGoBack) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onBackClick,
|
||||||
|
modifier = Modifier.align(Alignment.CenterStart)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowLeft,
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "AMOUNT TO PAY",
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
|
Text(
|
||||||
|
text = amount,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 30.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "MMK",
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 6.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
ContactlessCircle(isLoading = uiState.isLoading)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = when {
|
||||||
|
uiState.isCardCaptured -> "Card Detected"
|
||||||
|
uiState.isFallback -> "Swipe Your Card"
|
||||||
|
else -> "Tap Your Card"
|
||||||
|
},
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = if (uiState.isCardCaptured) {
|
||||||
|
"Reader captured card data"
|
||||||
|
} else {
|
||||||
|
"Hold your card near the reader"
|
||||||
|
},
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 9.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(14.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = Color.Gray.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "OR",
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 10.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = Color.Gray.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
InsertCardRow()
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = uiState.alertMessage,
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
lineHeight = 16.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
|
||||||
|
// StatusPanel(uiState = uiState)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
ManualEntryAction(onManualEntryClick = onManualEntryClick)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
|
||||||
|
CardLogoRow()
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(34.dp))
|
||||||
|
|
||||||
|
PoweredByMob()
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContactlessCircle(isLoading: Boolean) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(190.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircleBorder(180, 0.1f)
|
||||||
|
CircleBorder(150, 0.20f)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.size(108.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = Color.White,
|
||||||
|
shadowElevation = 8.dp
|
||||||
|
) {
|
||||||
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
strokeWidth = 3.dp,
|
||||||
|
modifier = Modifier.size(40.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Wifi,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.LegacyRed,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(72.dp)
|
||||||
|
.rotate(90f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CircleBorder(
|
||||||
|
size: Int,
|
||||||
|
alpha: Float
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(size.dp)
|
||||||
|
.alpha(alpha),
|
||||||
|
shape = CircleShape,
|
||||||
|
border = BorderStroke(1.dp, Color.LegacyRed)
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InsertCardRow() {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.height(44.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_insert_card),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.LegacyRed,
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = "Insert Your Card",
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Chip facing up",
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 9.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StatusPanel(uiState: CardWaitingUiState) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = when {
|
||||||
|
uiState.isCardCaptured -> "Reader status"
|
||||||
|
uiState.isLoading -> "Reader status"
|
||||||
|
else -> "Ready for card"
|
||||||
|
},
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = when {
|
||||||
|
uiState.isCardCaptured -> "Captured"
|
||||||
|
uiState.isLoading -> "Initializing"
|
||||||
|
else -> "Waiting"
|
||||||
|
},
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ManualEntryAction(onManualEntryClick: () -> Unit) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color.White)
|
||||||
|
.clickable(onClick = onManualEntryClick)
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Manual Entry",
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CardLogoRow() {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(3.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(42.dp)
|
||||||
|
.padding(horizontal = 14.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text("VISA", fontSize = 22.sp, fontWeight = FontWeight.Bold)
|
||||||
|
Text("MC", fontSize = 18.sp, fontWeight = FontWeight.Bold)
|
||||||
|
Text("MPU", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||||
|
Text("UnionPay", fontSize = 12.sp, fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PoweredByMob() {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = "Powered by",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 8.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "MOB",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 9.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@P2Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewCardWaitingScreen() {
|
||||||
|
CardWaitingScreenContent(
|
||||||
|
amount = "50,000",
|
||||||
|
uiState = CardWaitingUiState(),
|
||||||
|
onBackClick = {},
|
||||||
|
onManualEntryClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.cardwaiting
|
||||||
|
|
||||||
|
data class CardWaitingUiState(
|
||||||
|
val alertMessage: String = "Please insert, tap, or swipe card",
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val isFallback: Boolean = false,
|
||||||
|
val canGoBack: Boolean = true,
|
||||||
|
val isCardCaptured: Boolean = false
|
||||||
|
)
|
||||||
@ -0,0 +1,319 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.cardwaiting
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.mob.utsmyanmar.model.CardTransactionType
|
||||||
|
import com.mob.utsmyanmar.model.ecr.ECRResultStatus
|
||||||
|
import com.mob.utsmyanmar.utils.CoreUtils
|
||||||
|
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.sunmi.pay.hardware.aidl.AidlConstants
|
||||||
|
import com.utsmyanmar.checkxread.checkcard.CheckCardResultX
|
||||||
|
import com.utsmyanmar.checkxread.util.CardTypeX
|
||||||
|
import com.utsmyanmar.ecr.ECRHelper
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CardWaitingViewModel(
|
||||||
|
private val cardReadViewModel: CardReaderViewModel,
|
||||||
|
private val sharedViewModel: SharedViewModel
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun provideFactory(
|
||||||
|
cardReadViewModel: CardReaderViewModel,
|
||||||
|
sharedViewModel: SharedViewModel
|
||||||
|
): ViewModelProvider.Factory {
|
||||||
|
return object : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return CardWaitingViewModel(
|
||||||
|
cardReadViewModel = cardReadViewModel,
|
||||||
|
sharedViewModel = sharedViewModel
|
||||||
|
) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(CardWaitingUiState())
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _events = Channel<CardWaitingEvent>()
|
||||||
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
|
private var retryCounter = 0
|
||||||
|
private var fallbackCounter = 0
|
||||||
|
private var fallbackEnabled = false
|
||||||
|
private var readerInitJob: Job? = null
|
||||||
|
|
||||||
|
fun onScreenResume() {
|
||||||
|
retryCounter = 0
|
||||||
|
fallbackCounter = SystemParamsOperation.getInstance().fallbackCounter
|
||||||
|
fallbackEnabled = SystemParamsOperation.getInstance().fallbackEnabled
|
||||||
|
val isSaleTransaction = sharedViewModel.transactionsType.value == TransactionsType.SALE
|
||||||
|
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(canGoBack = !isSaleTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedViewModel.transactionsType.value == TransactionsType.REFUND) {
|
||||||
|
sharedViewModel.enableCardStatusIcon(false, false, false, false)
|
||||||
|
} else {
|
||||||
|
sharedViewModel.enableCardStatusIcon(true, true, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isFallback = sharedViewModel.getIsFallback().value == true
|
||||||
|
if (isFallback) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Fallback!\nPlease stripe!",
|
||||||
|
isFallback = true,
|
||||||
|
isCardCaptured = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Please insert, tap, or swipe card",
|
||||||
|
isFallback = false,
|
||||||
|
isLoading = false,
|
||||||
|
isCardCaptured = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startCardReadWhenReady(isFallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onScreenPause() {
|
||||||
|
readerInitJob?.cancel()
|
||||||
|
stopCardReading()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackPressed() {
|
||||||
|
if (sharedViewModel.isEcr.value == true) {
|
||||||
|
sharedViewModel.isEcr.postValue(false)
|
||||||
|
CoreUtils.getInstance(sharedViewModel).responseRejectMsg("Transaction cancelled")
|
||||||
|
sharedViewModel.isEcrFinished.postValue(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCardReading()
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
_events.send(CardWaitingEvent.GoBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onManualEntryClick() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_events.send(CardWaitingEvent.GoManualEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCardReadWhenReady(isFallback: Boolean) {
|
||||||
|
readerInitJob?.cancel()
|
||||||
|
readerInitJob = viewModelScope.launch {
|
||||||
|
if (cardReadViewModel.isReaderReady()) {
|
||||||
|
setupCardReadProcess(isFallback)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Initializing card reader...",
|
||||||
|
isLoading = true,
|
||||||
|
isCardCaptured = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat(10) {
|
||||||
|
delay(300)
|
||||||
|
if (cardReadViewModel.isReaderReady()) {
|
||||||
|
_uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
alertMessage = if (isFallback) {
|
||||||
|
"Fallback!\nPlease stripe!"
|
||||||
|
} else {
|
||||||
|
"Please insert, tap, or swipe card"
|
||||||
|
},
|
||||||
|
isLoading = false,
|
||||||
|
isCardCaptured = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setupCardReadProcess(isFallback)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Card reader unavailable.\nPlease wait and try again.",
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCardReadProcess(isFallback: Boolean) {
|
||||||
|
initCheckCard(isFallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initCheckCard(isFallback: Boolean) {
|
||||||
|
var allType =
|
||||||
|
AidlConstants.CardType.NFC.value or
|
||||||
|
AidlConstants.CardType.IC.value or
|
||||||
|
AidlConstants.CardType.MAGNETIC.value
|
||||||
|
|
||||||
|
if (isFallback) {
|
||||||
|
allType = AidlConstants.CardType.MAGNETIC.value
|
||||||
|
} else if (
|
||||||
|
SystemParamsOperation.getInstance().isMagStripeEnabled &&
|
||||||
|
!SystemParamsOperation.getInstance().isNfcEnabled
|
||||||
|
) {
|
||||||
|
allType =
|
||||||
|
AidlConstants.CardType.IC.value or
|
||||||
|
AidlConstants.CardType.MAGNETIC.value
|
||||||
|
}
|
||||||
|
|
||||||
|
cardReadViewModel.startCheckXProcess(
|
||||||
|
allType,
|
||||||
|
65,
|
||||||
|
object : CheckCardResultX {
|
||||||
|
override fun onSuccess(cardType: CardTypeX, isMPU: Boolean) {
|
||||||
|
Log.d("CardWaitingViewModel", "on success cardType=$cardType and isMpu=$isMPU")
|
||||||
|
when {
|
||||||
|
!isFallback && cardType == CardTypeX.MAG -> {
|
||||||
|
if (SystemParamsOperation.getInstance().isMagStripeEnabled) {
|
||||||
|
cardReadViewModel.setCardTransactionType(CardTransactionType.MAG)
|
||||||
|
} else {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(alertMessage = "Mag stripe not allowed")
|
||||||
|
}
|
||||||
|
setupCardReadProcess(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFallback && cardType == CardTypeX.MAG -> {
|
||||||
|
sharedViewModel.setEmvTrans(false)
|
||||||
|
cardReadViewModel.setCardTransactionType(CardTransactionType.FALLBACK)
|
||||||
|
}
|
||||||
|
|
||||||
|
cardType == CardTypeX.IC || cardType == CardTypeX.NFC -> {
|
||||||
|
if (isMPU) {
|
||||||
|
sharedViewModel.setEmvTrans(false)
|
||||||
|
cardReadViewModel.setCardTransactionType(CardTransactionType.MPU)
|
||||||
|
} else {
|
||||||
|
cardReadViewModel.setCardData(cardType.value)
|
||||||
|
cardReadViewModel.setCardTransactionType(CardTransactionType.EMV)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Card detected.",
|
||||||
|
isLoading = false,
|
||||||
|
isCardCaptured = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_events.send(CardWaitingEvent.GoProcessingCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(code: Int, message: String) {
|
||||||
|
ecrActionCancel("Transaction cancelled")
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = message,
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_events.send(CardWaitingEvent.GoMain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCommError() {
|
||||||
|
if (fallbackEnabled && retryCounter < fallbackCounter) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Card not detected!\nRemain Attempt - ${fallbackCounter - retryCounter}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
retryCounter++
|
||||||
|
setupCardReadProcess(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryCounter == fallbackCounter) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(alertMessage = "Fallback!\nPlease stripe!")
|
||||||
|
}
|
||||||
|
setupCardReadProcess(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ecrActionCancel("Transaction cancelled")
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
alertMessage = "Chip not detected!",
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_events.send(CardWaitingEvent.GoMain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ecrActionCancel(msg: String) {
|
||||||
|
if (sharedViewModel.isEcr.value != true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedViewModel.isEcr.postValue(false)
|
||||||
|
sharedViewModel.isEcrFinished.postValue(true)
|
||||||
|
|
||||||
|
if (SystemParamsOperation.getInstance().isCMHLEnabled) {
|
||||||
|
ECRHelper.send(
|
||||||
|
CoreUtils.getInstance(sharedViewModel)
|
||||||
|
.generateCMHLResponse(ECRResultStatus.USER_CANCEL)
|
||||||
|
)
|
||||||
|
|
||||||
|
CoreUtils.getInstance(sharedViewModel).responseACKCMHL()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreUtils.getInstance(sharedViewModel).responseRejectMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopCardReading() {
|
||||||
|
sharedViewModel.setIsFallback(false)
|
||||||
|
cardReadViewModel.cancelCheckCard()
|
||||||
|
cardReadViewModel.resetOneTimeFlag()
|
||||||
|
cardReadViewModel.cancelCheckXProcess()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
readerInitJob?.cancel()
|
||||||
|
stopCardReading()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,242 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.rounded.Backspace
|
||||||
|
import androidx.compose.material.icons.rounded.Backspace
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NumericEntryScreen(
|
||||||
|
title: String,
|
||||||
|
prompt: String,
|
||||||
|
displayValue: String,
|
||||||
|
supportingText: String,
|
||||||
|
confirmText: String,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
onCancelClick: () -> Unit,
|
||||||
|
onConfirmClick: () -> Unit,
|
||||||
|
onKeyClick: (String) -> Unit,
|
||||||
|
onDeleteClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
prefixLabel: String? = null,
|
||||||
|
errorMessage: String? = null,
|
||||||
|
confirmEnabled: Boolean = true,
|
||||||
|
canDelete: Boolean = true,
|
||||||
|
keys: List<List<String>> = listOf(
|
||||||
|
listOf("1", "2", "3"),
|
||||||
|
listOf("4", "5", "6"),
|
||||||
|
listOf("7", "8", "9"),
|
||||||
|
listOf(".", "0", "00")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.IvoryBeige)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.statusBarsPadding()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 20.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = prompt,
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
|
if (!prefixLabel.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = prefixLabel,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
modifier = Modifier.padding(end = 10.dp, bottom = 6.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = displayValue,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 32.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = supportingText,
|
||||||
|
color = if (errorMessage == null) Color.Gray else Color.Error,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(80.dp))
|
||||||
|
|
||||||
|
NumericKeypad(
|
||||||
|
keys = keys,
|
||||||
|
onKeyClick = onKeyClick
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onCancelClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.White,
|
||||||
|
contentColor = Color.LegacyRed
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onConfirmClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
enabled = confirmEnabled,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.LegacyRed,
|
||||||
|
contentColor = Color.White,
|
||||||
|
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
|
||||||
|
disabledContentColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = confirmText,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(top = 25.dp, end = 20.dp)
|
||||||
|
.clickable(enabled = canDelete) {
|
||||||
|
onDeleteClick()
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Rounded.Backspace,
|
||||||
|
contentDescription = "Delete",
|
||||||
|
tint = if (canDelete) Color.LegacyRed else Color.Gray,
|
||||||
|
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NumericKeypad(
|
||||||
|
keys: List<List<String>>,
|
||||||
|
onKeyClick: (String) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
keys.forEach { row ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
row.forEach { key ->
|
||||||
|
KeypadButton(
|
||||||
|
text = key,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = { onKeyClick(key) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun KeypadButton(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val enabled = text.isNotBlank()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.height(66.dp)
|
||||||
|
.shadow(
|
||||||
|
elevation = 2.dp,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
clip = false
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.clickable(enabled = enabled) { onClick() },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = if (enabled) Color.LegacyRed else Color.White,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.components.appbar
|
||||||
|
|
||||||
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Primary
|
||||||
|
import com.mob.utsmyanmar.ui.theme.White
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppBar(
|
||||||
|
title: String,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
onIconClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = White,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
navigationIcon = {
|
||||||
|
if (icon != null && onIconClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onIconClick
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = "App bar icon",
|
||||||
|
tint = White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.LegacyRed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewAppBar(){
|
||||||
|
AppBar(
|
||||||
|
title = "Title"
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.dashboard
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DashboardRoute(
|
||||||
|
onNavigateAmount: (String) -> Unit,
|
||||||
|
settlementEnabled: Boolean,
|
||||||
|
wavePayEnabled: Boolean,
|
||||||
|
) {
|
||||||
|
DashboardScreen(
|
||||||
|
settlementEnabled = settlementEnabled,
|
||||||
|
wavePayEnabled = wavePayEnabled,
|
||||||
|
onNavigateAmount = onNavigateAmount
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,10 +1,13 @@
|
|||||||
package com.mob.utsmyanmar.ui.dashboard
|
package com.mob.utsmyanmar.ui.dashboard
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@ -20,23 +23,19 @@ import com.mob.utsmyanmar.ui.theme.MOBPOSTheme
|
|||||||
import com.mob.utsmyanmar.ui.theme.*
|
import com.mob.utsmyanmar.ui.theme.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.mob.utsmyanmar.R
|
import com.mob.utsmyanmar.R
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.utsmyanmar.paylibs.print.NewPrintReceipt
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreen(
|
fun DashboardScreen(
|
||||||
settlementEnabled: Boolean,
|
settlementEnabled: Boolean,
|
||||||
wavePayEnabled: Boolean,
|
wavePayEnabled: Boolean,
|
||||||
onAmountClick: (String) -> Unit,
|
onNavigateAmount: (String) -> Unit
|
||||||
onTransactionClick: () -> Unit,
|
|
||||||
onSettlementClick: () -> Unit,
|
|
||||||
onHistoryClick: () -> Unit,
|
|
||||||
onCardClick: () -> Unit,
|
|
||||||
onWavePayClick: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
@ -68,7 +67,6 @@ fun DashboardScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
scope.launch { drawerState.close() }
|
scope.launch { drawerState.close() }
|
||||||
onTransactionClick()
|
|
||||||
}
|
}
|
||||||
.padding(horizontal = 24.dp, vertical = 14.dp),
|
.padding(horizontal = 24.dp, vertical = 14.dp),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
@ -79,7 +77,6 @@ fun DashboardScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
scope.launch { drawerState.close() }
|
scope.launch { drawerState.close() }
|
||||||
onHistoryClick()
|
|
||||||
}
|
}
|
||||||
.padding(horizontal = 24.dp, vertical = 14.dp),
|
.padding(horizontal = 24.dp, vertical = 14.dp),
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
@ -90,7 +87,6 @@ fun DashboardScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(enabled = settlementEnabled) {
|
.clickable(enabled = settlementEnabled) {
|
||||||
scope.launch { drawerState.close() }
|
scope.launch { drawerState.close() }
|
||||||
onSettlementClick()
|
|
||||||
}
|
}
|
||||||
.padding(horizontal = 24.dp, vertical = 14.dp),
|
.padding(horizontal = 24.dp, vertical = 14.dp),
|
||||||
color = if (settlementEnabled) Black else White,
|
color = if (settlementEnabled) Black else White,
|
||||||
@ -102,7 +98,6 @@ fun DashboardScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(enabled = wavePayEnabled) {
|
.clickable(enabled = wavePayEnabled) {
|
||||||
scope.launch { drawerState.close() }
|
scope.launch { drawerState.close() }
|
||||||
onWavePayClick()
|
|
||||||
}
|
}
|
||||||
.padding(horizontal = 24.dp, vertical = 14.dp),
|
.padding(horizontal = 24.dp, vertical = 14.dp),
|
||||||
color = if (wavePayEnabled) Black else White,
|
color = if (wavePayEnabled) Black else White,
|
||||||
@ -113,28 +108,14 @@ fun DashboardScreen(
|
|||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
CenterAlignedTopAppBar(
|
AppBar(
|
||||||
title = {
|
title = "Dashboard",
|
||||||
Text(text = "Dashboard", color = White, fontWeight = FontWeight.SemiBold)
|
icon = Icons.Default.Menu,
|
||||||
},
|
onIconClick = {
|
||||||
navigationIcon = {
|
scope.launch {
|
||||||
IconButton(
|
drawerState.open()
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
drawerState.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_menu),
|
|
||||||
contentDescription = "Menu Icon",
|
|
||||||
tint = White
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = Primary
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@ -175,6 +156,16 @@ fun DashboardScreen(
|
|||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = { try {
|
||||||
|
NewPrintReceipt.getInstance().testPrint()
|
||||||
|
println("printing...")
|
||||||
|
}catch (e: Exception){
|
||||||
|
println("printing error $e")
|
||||||
|
} }
|
||||||
|
) {
|
||||||
|
Text(text = "test")
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
@ -187,7 +178,7 @@ fun DashboardScreen(
|
|||||||
contentColor = Primary,
|
contentColor = Primary,
|
||||||
iconTint = Primary,
|
iconTint = Primary,
|
||||||
border = null,
|
border = null,
|
||||||
onClick = { onAmountClick("Sale") },
|
onClick = { onNavigateAmount("Sale") },
|
||||||
)
|
)
|
||||||
SquareButton(
|
SquareButton(
|
||||||
title = "Sign On",
|
title = "Sign On",
|
||||||
@ -197,7 +188,7 @@ fun DashboardScreen(
|
|||||||
contentColor = Primary,
|
contentColor = Primary,
|
||||||
iconTint = Primary,
|
iconTint = Primary,
|
||||||
border = null,
|
border = null,
|
||||||
onClick = { onAmountClick("Sign On") }
|
onClick = { onNavigateAmount("Sign On") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +205,7 @@ fun DashboardScreen(
|
|||||||
contentColor = Primary,
|
contentColor = Primary,
|
||||||
iconTint = Primary,
|
iconTint = Primary,
|
||||||
border = null,
|
border = null,
|
||||||
onClick = onSettlementClick
|
onClick = {}
|
||||||
)
|
)
|
||||||
SquareButton(
|
SquareButton(
|
||||||
title = "Others",
|
title = "Others",
|
||||||
@ -224,7 +215,7 @@ fun DashboardScreen(
|
|||||||
contentColor = Primary,
|
contentColor = Primary,
|
||||||
iconTint = Primary,
|
iconTint = Primary,
|
||||||
border = null,
|
border = null,
|
||||||
onClick = onCardClick
|
onClick = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,12 +232,7 @@ private fun DashboardScreenPreview() {
|
|||||||
DashboardScreen(
|
DashboardScreen(
|
||||||
settlementEnabled = true,
|
settlementEnabled = true,
|
||||||
wavePayEnabled = true,
|
wavePayEnabled = true,
|
||||||
onAmountClick = {},
|
onNavigateAmount = {}
|
||||||
onTransactionClick = {},
|
|
||||||
onSettlementClick = {},
|
|
||||||
onHistoryClick = {},
|
|
||||||
onCardClick = {},
|
|
||||||
onWavePayClick = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,632 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.dashboard
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.BarChart
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
|
import androidx.compose.material.icons.filled.Dashboard
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material.icons.filled.Sync
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.DrawerValue
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
|
import androidx.compose.material3.NavigationDrawerItem
|
||||||
|
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
|
import androidx.compose.material3.rememberDrawerState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
import com.mob.utsmyanmar.R
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.preview.P2Preview
|
||||||
|
import com.mob.utsmyanmar.ui.preview.P3Preview
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.utsmyanmar.paylibs.sign_on.EchoTestProcess
|
||||||
|
import com.utsmyanmar.paylibs.sign_on.SignOnListener
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DashboardScreen2(
|
||||||
|
onNavigateAmount: (String) -> Unit = {},
|
||||||
|
onNavigateSignOn: () -> Unit = {},
|
||||||
|
onNavigateSeeMore: () -> Unit = {},
|
||||||
|
onNavigateSettlement: () -> Unit = {},
|
||||||
|
onNavigateVersion: () -> Unit = {},
|
||||||
|
deviceInfoViewModel: DeviceInfoViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val deviceInfo by deviceInfoViewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
deviceInfoViewModel.loadDeviceInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val mainHandler = remember { Handler(Looper.getMainLooper()) }
|
||||||
|
var showHostActionDialog by remember { mutableStateOf(false) }
|
||||||
|
var activeHostAction by remember { mutableStateOf("Log-On") }
|
||||||
|
var isHostActionRunning by remember { mutableStateOf(false) }
|
||||||
|
var dialogMessage by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val isOnline = true
|
||||||
|
|
||||||
|
fun confirmationMessage(action: String) = "Do you want to start ${action.lowercase()}?"
|
||||||
|
fun processingMessage(action: String) = "Sending ${action.lowercase()} request to host..."
|
||||||
|
fun successMessage(action: String) = "$action success."
|
||||||
|
fun failureMessage(action: String, resultCode: Int?) =
|
||||||
|
"$action failed. Response code: ${resultCode ?: -1}"
|
||||||
|
|
||||||
|
fun networkFailureMessage(action: String) = "Network error during $action."
|
||||||
|
fun openHostActionDialog(action: String) {
|
||||||
|
activeHostAction = action
|
||||||
|
isHostActionRunning = false
|
||||||
|
dialogMessage = confirmationMessage(action)
|
||||||
|
showHostActionDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showHostActionDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
if (!isHostActionRunning) {
|
||||||
|
showHostActionDialog = false
|
||||||
|
}
|
||||||
|
}, title = {
|
||||||
|
Text(
|
||||||
|
text = activeHostAction, color = Color.LegacyRed, fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}, text = {
|
||||||
|
Text(text = dialogMessage)
|
||||||
|
}, confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
if (isHostActionRunning) return@TextButton
|
||||||
|
|
||||||
|
if (dialogMessage != confirmationMessage(activeHostAction)) {
|
||||||
|
showHostActionDialog = false
|
||||||
|
dialogMessage = confirmationMessage(activeHostAction)
|
||||||
|
return@TextButton
|
||||||
|
}
|
||||||
|
|
||||||
|
isHostActionRunning = true
|
||||||
|
dialogMessage = processingMessage(activeHostAction)
|
||||||
|
|
||||||
|
val signOnProcess = EchoTestProcess.getInstance()
|
||||||
|
val request = when (activeHostAction) {
|
||||||
|
"Echo Test" -> signOnProcess.enqueue(false)
|
||||||
|
"Log-Off" -> signOnProcess.enqueueLogOff()
|
||||||
|
else -> signOnProcess.enqueueLogOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
request.startSignOn(object : SignOnListener {
|
||||||
|
override fun onSuccessSignOn() {
|
||||||
|
mainHandler.post {
|
||||||
|
isHostActionRunning = false
|
||||||
|
dialogMessage = successMessage(activeHostAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailureSignOn(resultCode: Int?) {
|
||||||
|
mainHandler.post {
|
||||||
|
isHostActionRunning = false
|
||||||
|
mainHandler.post {
|
||||||
|
isHostActionRunning = false
|
||||||
|
dialogMessage = failureMessage(activeHostAction, resultCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onNetworkFailSignOn(message: String?) {
|
||||||
|
mainHandler.post {
|
||||||
|
isHostActionRunning = false
|
||||||
|
dialogMessage = message?.takeIf { it.isNotBlank() }
|
||||||
|
?: networkFailureMessage(activeHostAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
Text(
|
||||||
|
text = when {
|
||||||
|
isHostActionRunning -> "Processing"
|
||||||
|
dialogMessage == confirmationMessage(activeHostAction) -> "Start"
|
||||||
|
else -> "Close"
|
||||||
|
}, color = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, dismissButton = {
|
||||||
|
if (!isHostActionRunning) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showHostActionDialog = false
|
||||||
|
dialogMessage = confirmationMessage(activeHostAction)
|
||||||
|
}) {
|
||||||
|
Text(text = "Cancel", color = Color.Gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, containerColor = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalNavigationDrawer(
|
||||||
|
drawerState = drawerState, drawerContent = {
|
||||||
|
ModalDrawerSheet(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.78f),
|
||||||
|
drawerContainerColor = Color.White,
|
||||||
|
drawerShape = RoundedCornerShape(topEnd = 24.dp, bottomEnd = 24.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color.IvoryBeige)
|
||||||
|
.padding(horizontal = 20.dp, vertical = 28.dp)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(80.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.White), contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.logo_mob),
|
||||||
|
contentDescription = "mob logo",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(100.dp)
|
||||||
|
.padding(16.dp),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = "MOB Merchant",
|
||||||
|
color = Color.CrimsonRed,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "S/N:${deviceInfo.serialNumber}",
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (isOnline) "Online" else "Offline",
|
||||||
|
color = if (isOnline) Color.Success else Color.Error,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
DrawerItem("Log-On", Icons.Default.Dashboard) {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
openHostActionDialog("Log-On")
|
||||||
|
}
|
||||||
|
DrawerItem("Echo Test", Icons.Default.Sync) {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
openHostActionDialog("Echo Test")
|
||||||
|
}
|
||||||
|
DrawerItem("Log-Off", Icons.Default.Dashboard) {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
openHostActionDialog("Log-Off")
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "System Management",
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
)
|
||||||
|
DrawerItem("Function", Icons.Default.Dashboard) {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
}
|
||||||
|
DrawerItem("Version", Icons.Default.Dashboard) {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
onNavigateVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.IvoryBeige, topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Dashboard",
|
||||||
|
icon = Icons.Default.Menu,
|
||||||
|
onIconClick = { scope.launch { drawerState.open() } })
|
||||||
|
}) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
//top section
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
AdvertisingArea()
|
||||||
|
}
|
||||||
|
//center section
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
SummaryCard()
|
||||||
|
}
|
||||||
|
//bottom section
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1.5f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
MenuGrid(
|
||||||
|
onNavigateAmount = onNavigateAmount,
|
||||||
|
onNavigateSignOn = onNavigateSignOn,
|
||||||
|
onNavigateSeeMore = onNavigateSeeMore,
|
||||||
|
onNavigateSettlement = onNavigateSettlement
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DrawerItem(
|
||||||
|
title: String, icon: ImageVector, onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
NavigationDrawerItem(
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = title, fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = false,
|
||||||
|
onClick = onClick,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon, contentDescription = title
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
|
||||||
|
colors = NavigationDrawerItemDefaults.colors(
|
||||||
|
unselectedContainerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||||
|
unselectedIconColor = Color.LegacyRed,
|
||||||
|
unselectedTextColor = Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AdvertisingArea() {
|
||||||
|
|
||||||
|
val imageArray = listOf(
|
||||||
|
"https://i.ytimg.com/vi/eRUVxGRp1Ms/maxresdefault.jpg",
|
||||||
|
"https://i.ytimg.com/vi/AwvmgTPd7qw/maxresdefault.jpg",
|
||||||
|
"https://mma.prnewswire.com/media/2080956/SUNMI_3rd_generation_products_T3_PRO_series_V3_MIX.jpg?p=facebook"
|
||||||
|
)
|
||||||
|
val pageState = rememberPagerState(pageCount = { imageArray.size })
|
||||||
|
|
||||||
|
LaunchedEffect(pageState) {
|
||||||
|
while (true) {
|
||||||
|
delay(10000)
|
||||||
|
val nextPage = (pageState.currentPage + 1) % imageArray.size
|
||||||
|
|
||||||
|
pageState.animateScrollToPage(
|
||||||
|
page = nextPage, animationSpec = tween(durationMillis = 700)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
shape = RoundedCornerShape(0.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
) {
|
||||||
|
HorizontalPager(
|
||||||
|
state = pageState, modifier = Modifier.fillMaxSize()
|
||||||
|
) { page ->
|
||||||
|
AsyncImage(
|
||||||
|
model = imageArray[page],
|
||||||
|
contentDescription = "ads images",
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SummaryCard() {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
elevation = CardDefaults.cardElevation(4.dp)
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
SummaryItem(
|
||||||
|
title = "Sales Today",
|
||||||
|
value = "MMK 2,000,000",
|
||||||
|
subtitle = "47 Transactions",
|
||||||
|
icon = Icons.Default.BarChart,
|
||||||
|
iconBg = Color.CrimsonRed
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
color = Color.Gray,
|
||||||
|
modifier = Modifier.padding(vertical = 6.dp, horizontal = 6.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
SummaryItem(
|
||||||
|
title = "Settlement",
|
||||||
|
value = "Completed",
|
||||||
|
subtitle = "Today 11:00 PM",
|
||||||
|
icon = Icons.Default.Check,
|
||||||
|
iconBg = Color.CrimsonRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalDivider(color = Color.Gray, modifier = Modifier.height(160.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.75f)
|
||||||
|
.padding(start = 18.dp),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
|
Text(
|
||||||
|
text = "Last Sync", fontSize = 12.sp
|
||||||
|
)
|
||||||
|
IconCircle(Icons.Default.Sync, Color.CrimsonRed)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "5 mins ago",
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SummaryItem(
|
||||||
|
title: String,
|
||||||
|
value: String,
|
||||||
|
subtitle: String,
|
||||||
|
icon: ImageVector,
|
||||||
|
iconBg: androidx.compose.ui.graphics.Color
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.Top, modifier = Modifier.padding(6.dp)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(title, fontSize = 12.sp)
|
||||||
|
Text(
|
||||||
|
value, fontSize = 14.sp, color = Color.LegacyRed, fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(subtitle, fontSize = 12.sp)
|
||||||
|
}
|
||||||
|
IconCircle(icon, iconBg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun IconCircle(
|
||||||
|
icon: ImageVector, color: androidx.compose.ui.graphics.Color
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(38.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color.copy(alpha = 0.1f)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = color,
|
||||||
|
modifier = Modifier.size(22.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MenuGrid(
|
||||||
|
onNavigateAmount: (String) -> Unit,
|
||||||
|
onNavigateSignOn: () -> Unit,
|
||||||
|
onNavigateSeeMore: () -> Unit,
|
||||||
|
onNavigateSettlement: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
MenuCard(title = "Sale", icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_terminal),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}, modifier = Modifier.weight(1f), onClick = { onNavigateAmount("Sale") })
|
||||||
|
MenuCard(title = "MMQR", icon = {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_mmqr_logo),
|
||||||
|
contentDescription = "mmqr image",
|
||||||
|
modifier = Modifier.height(48.dp)
|
||||||
|
)
|
||||||
|
}, modifier = Modifier.weight(1f))
|
||||||
|
MenuCard("History", icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_history),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
MenuCard(
|
||||||
|
title = "Sign On", icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_sign_on),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}, modifier = Modifier.weight(1f), onClick = onNavigateSignOn
|
||||||
|
)
|
||||||
|
MenuCard(
|
||||||
|
title = "Settlement", icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_settlement),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}, modifier = Modifier.weight(1f), onClick = onNavigateSettlement
|
||||||
|
)
|
||||||
|
MenuCard(
|
||||||
|
title = "See More", icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_see_more),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}, modifier = Modifier.weight(1f), onClick = onNavigateSeeMore
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MenuCard(
|
||||||
|
title: String,
|
||||||
|
icon: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.height(100.dp)
|
||||||
|
.then(
|
||||||
|
if (onClick != null) {
|
||||||
|
Modifier.clickable(onClick = onClick)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
elevation = CardDefaults.cardElevation(5.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(6.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
icon()
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color.Black,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ChevronRight,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.LegacyRed,
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@P2Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewDashboardScreen2() {
|
||||||
|
DashboardScreen2()
|
||||||
|
}
|
||||||
|
|
||||||
|
@P3Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewDashboardScreen3() {
|
||||||
|
DashboardScreen2()
|
||||||
|
}
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.dashboard
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
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.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.AccountBalanceWallet
|
||||||
|
import androidx.compose.material.icons.filled.CreditCard
|
||||||
|
import androidx.compose.material.icons.filled.Lock
|
||||||
|
import androidx.compose.material.icons.filled.LockOpen
|
||||||
|
import androidx.compose.material.icons.filled.Replay
|
||||||
|
import androidx.compose.material.icons.filled.SwapHoriz
|
||||||
|
import androidx.compose.material.icons.filled.Undo
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.graphics.vector.ImageVector
|
||||||
|
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.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.preview.P2Preview
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SeeMoreScreen(
|
||||||
|
onBack: () -> Unit = {},
|
||||||
|
onNavigateAmount: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val items = listOf(
|
||||||
|
SeeMoreItem("Void", Icons.Default.Undo),
|
||||||
|
SeeMoreItem("Refund", Icons.Default.Replay),
|
||||||
|
SeeMoreItem("Pre-Auth", Icons.Default.Lock),
|
||||||
|
SeeMoreItem("Pre-Auth Void", Icons.Default.LockOpen),
|
||||||
|
SeeMoreItem("Pre-Auth Complete", Icons.Default.CreditCard),
|
||||||
|
SeeMoreItem("Pre-Auth Complete Void", Icons.Default.SwapHoriz),
|
||||||
|
SeeMoreItem("Cash Out", Icons.Default.AccountBalanceWallet)
|
||||||
|
)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.IvoryBeige,
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "See More",
|
||||||
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
onIconClick = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(2),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
items(items) { item ->
|
||||||
|
MoreMenuCard(
|
||||||
|
title = item.title,
|
||||||
|
icon = item.icon,
|
||||||
|
onClick = { onNavigateAmount(item.title) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MoreMenuCard(
|
||||||
|
title: String,
|
||||||
|
icon: ImageVector,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(132.dp)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
elevation = CardDefaults.cardElevation(4.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(14.dp),
|
||||||
|
verticalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(Color.CrimsonRed, RoundedCornerShape(14.dp))
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title,
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class SeeMoreItem(
|
||||||
|
val title: String,
|
||||||
|
val icon: ImageVector
|
||||||
|
)
|
||||||
|
|
||||||
|
@P2Preview
|
||||||
|
@Composable
|
||||||
|
private fun PreviewSeeMoreScreen() {
|
||||||
|
SeeMoreScreen()
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.device_info
|
||||||
|
|
||||||
|
data class DeviceInfoUiState(
|
||||||
|
val hardwareVersion: String = "",
|
||||||
|
val firmwareVersion: String = "",
|
||||||
|
val serialNumber: String = "",
|
||||||
|
val deviceModel: String = "",
|
||||||
|
val finalVersion: String = "",
|
||||||
|
val payHardwareVersion: String = "",
|
||||||
|
val romVersion: String = "",
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val errorMessage: String? = null
|
||||||
|
)
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.device_info
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.sunmi.pay.hardware.aidl.AidlConstants
|
||||||
|
import com.utsmyanmar.baselib.BaseApplication
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
|
||||||
|
class DeviceInfoViewModel : ViewModel() {
|
||||||
|
var TAG = DeviceInfoViewModel::class.simpleName;
|
||||||
|
private val _uiState = MutableStateFlow(DeviceInfoUiState())
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
fun loadDeviceInfo() {
|
||||||
|
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val hardwareVersion = getParams(AidlConstants.SysParam.HARDWARE_VERSION)
|
||||||
|
val firmwareVersion = getParams(AidlConstants.SysParam.FIRMWARE_VERSION)
|
||||||
|
val serialNo = getParams(AidlConstants.SysParam.SN)
|
||||||
|
val deviceModel = getParams(AidlConstants.SysParam.DEVICE_MODEL)
|
||||||
|
val finalVersion = SystemParamsOperation.getInstance().finalVersion ?: ""
|
||||||
|
val payHardwareVersion = getPayHardwareVersion()
|
||||||
|
val romVersion = getRomVersion()
|
||||||
|
|
||||||
|
|
||||||
|
_uiState.value = DeviceInfoUiState(
|
||||||
|
hardwareVersion = hardwareVersion,
|
||||||
|
firmwareVersion = firmwareVersion,
|
||||||
|
serialNumber = serialNo,
|
||||||
|
deviceModel = deviceModel,
|
||||||
|
finalVersion = finalVersion,
|
||||||
|
payHardwareVersion = payHardwareVersion,
|
||||||
|
romVersion = romVersion,
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
isLoading = false,
|
||||||
|
errorMessage = e.message ?: "Failed to load device info"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getParams(name: String): String {
|
||||||
|
return try {
|
||||||
|
BaseApplication.getInstance()
|
||||||
|
.basicOptBinder
|
||||||
|
?.getSysParam(name)
|
||||||
|
?: ""
|
||||||
|
} catch (e: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPayHardwareVersion():String {
|
||||||
|
return try {
|
||||||
|
BaseApplication.getInstance().applicationContext.packageManager.getPackageInfo(
|
||||||
|
"com.sunmi.pay.hardware_v3",
|
||||||
|
0
|
||||||
|
).versionName ?: "PHV?"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
"PHV?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRomVersion():String {
|
||||||
|
return try {
|
||||||
|
android.os.Build.VERSION.RELEASE ?: "UNKNOWN"
|
||||||
|
}catch (e: Exception){
|
||||||
|
LogUtil.d(TAG, "get rom version error " + e)
|
||||||
|
"UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.input_amount
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.mob.utsmyanmar.model.ProcessCode
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AmountRoute(
|
||||||
|
action: String,
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onNavigateCardWaiting: () -> Unit
|
||||||
|
) {
|
||||||
|
InputAmount(
|
||||||
|
title = action.ifBlank { "Amount" },
|
||||||
|
onBackClick = onBack,
|
||||||
|
onChargeClick = { amount ->
|
||||||
|
sharedViewModel.amount.value = amount
|
||||||
|
sharedViewModel.setAmountExist(true)
|
||||||
|
sharedViewModel.setCardDataExist(false)
|
||||||
|
sharedViewModel.setTransMenu(null)
|
||||||
|
val config = action.toTransactionConfig()
|
||||||
|
sharedViewModel.transactionName.value = config.transactionName
|
||||||
|
sharedViewModel.transactionsType.value = config.transactionType
|
||||||
|
sharedViewModel.processCode.value = config.processCode
|
||||||
|
|
||||||
|
onNavigateCardWaiting()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TransactionConfig(
|
||||||
|
val transactionName: String,
|
||||||
|
val transactionType: TransactionsType,
|
||||||
|
val processCode: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun String.toTransactionConfig(): TransactionConfig {
|
||||||
|
return when (trim().lowercase()) {
|
||||||
|
"void" -> TransactionConfig(
|
||||||
|
transactionName = "SALE",
|
||||||
|
transactionType = TransactionsType.VOID,
|
||||||
|
processCode = ProcessCode.SALE_VOID + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
"refund" -> TransactionConfig(
|
||||||
|
transactionName = "REFUND",
|
||||||
|
transactionType = TransactionsType.REFUND,
|
||||||
|
processCode = ProcessCode.REFUND + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
"pre-auth" -> TransactionConfig(
|
||||||
|
transactionName = "PRE_AUTH",
|
||||||
|
transactionType = TransactionsType.PRE_AUTH_SALE,
|
||||||
|
processCode = ProcessCode.PRE_AUTH_SALE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
"pre-auth void" -> TransactionConfig(
|
||||||
|
transactionName = "PRE_AUTH",
|
||||||
|
transactionType = TransactionsType.PRE_AUTH_VOID,
|
||||||
|
processCode = ProcessCode.PRE_AUTH_VOID + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
"pre-auth complete" -> TransactionConfig(
|
||||||
|
transactionName = "PRE_AUTH_COMPLETE",
|
||||||
|
transactionType = TransactionsType.PRE_AUTH_COMPLETE,
|
||||||
|
processCode = ProcessCode.PRE_AUTH_COMPLETE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
"pre-auth complete void" -> TransactionConfig(
|
||||||
|
transactionName = "PRE_AUTH_COMPLETE",
|
||||||
|
transactionType = TransactionsType.PRE_AUTH_COMPLETE_VOID,
|
||||||
|
processCode = ProcessCode.PRE_AUTH_COMPLETE_VOID + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
"cash out" -> TransactionConfig(
|
||||||
|
transactionName = "CASH_OUT",
|
||||||
|
transactionType = TransactionsType.CASH_OUT,
|
||||||
|
processCode = ProcessCode.CASH_ADVANCE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> TransactionConfig(
|
||||||
|
transactionName = "SALE",
|
||||||
|
transactionType = TransactionsType.SALE,
|
||||||
|
processCode = ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,315 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.input_amount
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.rounded.Backspace
|
||||||
|
import androidx.compose.material.icons.rounded.Backspace
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
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.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 kotlin.collections.List
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InputAmount(
|
||||||
|
title: String = "Amount",
|
||||||
|
onBackClick: () -> Unit = {},
|
||||||
|
onChargeClick: (String) -> Unit = {}
|
||||||
|
){
|
||||||
|
|
||||||
|
var amount by remember { mutableStateOf("") }
|
||||||
|
val prefixLabel = "MMK"
|
||||||
|
val supportingText = "Enter the amount to continue"
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = { AppBar(title = "Amount") },
|
||||||
|
containerColor = Color.IvoryBeige
|
||||||
|
) {paddingValues ->
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
//first container
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.weight(1f),
|
||||||
|
){
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd)
|
||||||
|
.clickable(enabled = true) {
|
||||||
|
amount = amount.dropLast(1)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Backspace,
|
||||||
|
contentDescription = "Delete",
|
||||||
|
tint = Color.LegacyRed,
|
||||||
|
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Enter Amount",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
|
if (prefixLabel.isNotBlank()) {
|
||||||
|
Text(
|
||||||
|
text = prefixLabel,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
modifier = Modifier.padding(end = 10.dp, bottom = 6.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = formatAmount(amount.ifEmpty { "0" }),
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 32.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = supportingText,
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//second container
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(3f),
|
||||||
|
verticalArrangement = Arrangement.Bottom
|
||||||
|
){
|
||||||
|
NumericKeypad(
|
||||||
|
onKeyClick = { value ->
|
||||||
|
amount = appendAmountValue(amount, value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
//third container
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(0.5f)
|
||||||
|
){
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.White,
|
||||||
|
contentColor = Color.LegacyRed
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (amount.isNotEmpty()) {
|
||||||
|
onChargeClick(amount)
|
||||||
|
} },
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
enabled = amount.isNotEmpty(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.LegacyRed,
|
||||||
|
contentColor = Color.White,
|
||||||
|
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
|
||||||
|
disabledContentColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Next",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NumericKeypad(
|
||||||
|
onKeyClick: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val keys : List<List<String>> = listOf(
|
||||||
|
listOf("1", "2", "3"),
|
||||||
|
listOf("4", "5", "6"),
|
||||||
|
listOf("7", "8", "9"),
|
||||||
|
listOf(".", "0", "00")
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
keys.forEach { row ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
row.forEach { key ->
|
||||||
|
KeypadButton(
|
||||||
|
text = key,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = { onKeyClick(key) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun KeypadButton(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val enabled = text.isNotBlank()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.height(66.dp)
|
||||||
|
.shadow(
|
||||||
|
elevation = 2.dp,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
clip = false
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.clickable(enabled = enabled) { onClick() },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = if (enabled) Color.LegacyRed else Color.White,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendAmountValue(current: String, value: String): String {
|
||||||
|
if (value == ".") {
|
||||||
|
if (current.contains(".")) return current
|
||||||
|
return if (current.isEmpty()) "0." else "$current."
|
||||||
|
}
|
||||||
|
|
||||||
|
val decimalIndex = current.indexOf('.')
|
||||||
|
return if (decimalIndex >= 0) {
|
||||||
|
val decimalPart = current.substring(decimalIndex + 1)
|
||||||
|
val remainingDecimalDigits = 2 - decimalPart.length
|
||||||
|
if (remainingDecimalDigits <= 0) {
|
||||||
|
current
|
||||||
|
} else {
|
||||||
|
current + value.take(remainingDecimalDigits)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val wholeDigitsCount = current.filter(Char::isDigit).length
|
||||||
|
val remainingWholeDigits = 9 - wholeDigitsCount
|
||||||
|
if (remainingWholeDigits <= 0) {
|
||||||
|
current
|
||||||
|
} else {
|
||||||
|
current + value.take(remainingWholeDigits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatAmount(value: String): String {
|
||||||
|
val normalized = value.ifEmpty { "0" }
|
||||||
|
val wholePart = normalized.substringBefore(".").ifEmpty { "0" }
|
||||||
|
val groupedWholePart = "%,d".format(wholePart.toLongOrNull() ?: 0L)
|
||||||
|
|
||||||
|
if (!normalized.contains(".")) {
|
||||||
|
return groupedWholePart
|
||||||
|
}
|
||||||
|
|
||||||
|
val decimalPart = normalized.substringAfter(".", "")
|
||||||
|
return if (normalized.endsWith(".")) {
|
||||||
|
"$groupedWholePart."
|
||||||
|
} else {
|
||||||
|
"$groupedWholePart.$decimalPart"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@P2Preview
|
||||||
|
@P3Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewInputAmount(){
|
||||||
|
InputAmount();
|
||||||
|
}
|
||||||
@ -1,34 +1,223 @@
|
|||||||
package com.mob.utsmyanmar.ui.navigation
|
package com.mob.utsmyanmar.ui.navigation
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.mob.utsmyanmar.ui.amount.AmountScreen
|
import com.mob.utsmyanmar.model.ProcessCode
|
||||||
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen
|
import com.mob.utsmyanmar.ui.input_amount.AmountRoute
|
||||||
|
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingScreen
|
||||||
|
import com.mob.utsmyanmar.ui.cardwaiting.CardWaitingViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.dashboard.DashboardScreen2
|
||||||
|
import com.mob.utsmyanmar.ui.dashboard.SeeMoreScreen
|
||||||
|
import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.pinpad.PinPadRoute
|
||||||
|
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardRoute
|
||||||
|
import com.mob.utsmyanmar.ui.processing_card.ProcessingCardViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.print_receipt.PrintReceiptScreen
|
||||||
|
import com.mob.utsmyanmar.ui.refund_rrn.InputRrnRoute
|
||||||
|
import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen
|
||||||
|
import com.mob.utsmyanmar.ui.sign_on.SignOnRoute
|
||||||
|
import com.mob.utsmyanmar.ui.sending_to_host.ProcessingRoute
|
||||||
|
import com.mob.utsmyanmar.ui.settlement.SettlementScreen
|
||||||
|
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute
|
||||||
|
import com.mob.utsmyanmar.ui.sale_void.TranDetailPage
|
||||||
|
import com.mob.utsmyanmar.ui.sale_void.VoidViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen
|
||||||
|
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.version.VersionScreen
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
|
||||||
|
@SuppressLint("ContextCastToActivity")
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavGraph(
|
fun AppNavGraph(
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
) {
|
) {
|
||||||
|
val activity = LocalContext.current as ComponentActivity
|
||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = Routes.Dashboard.route
|
startDestination = Routes.Dashboard.route
|
||||||
) {
|
) {
|
||||||
composable(Routes.Dashboard.route) {
|
composable(Routes.Dashboard.route) {
|
||||||
DashboardScreen(
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity);
|
||||||
settlementEnabled = true,
|
DashboardScreen2(
|
||||||
wavePayEnabled = true,
|
onNavigateAmount = { action ->
|
||||||
onAmountClick = { action ->
|
if(action == "Sale"){
|
||||||
navController.navigate(Routes.Amount.createRoute(action))
|
sharedViewModel.transactionsType.value = TransactionsType.SALE;
|
||||||
|
sharedViewModel.processCode.value = ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
navController.navigate(Routes.Amount.createRoute(action)) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onTransactionClick = {},
|
onNavigateSignOn = {
|
||||||
onSettlementClick = {},
|
navController.navigate(Routes.SignOn.route) {
|
||||||
onHistoryClick = {},
|
launchSingleTop = true
|
||||||
onCardClick = {},
|
}
|
||||||
onWavePayClick = {}
|
},
|
||||||
|
onNavigateSeeMore = {
|
||||||
|
navController.navigate(Routes.SeeMore.route) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNavigateSettlement = {
|
||||||
|
navController.navigate(Routes.Settlement.route) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNavigateVersion = {
|
||||||
|
navController.navigate(Routes.Version.route);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.SeeMore.route) {
|
||||||
|
SeeMoreScreen(
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onNavigateAmount = { action ->
|
||||||
|
if (action == "Void") {
|
||||||
|
navController.navigate(Routes.VoidTrace.route) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navController.navigate(Routes.Amount.createRoute(action)) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.Version.route){
|
||||||
|
val deviceInfoViewModel: DeviceInfoViewModel = hiltViewModel();
|
||||||
|
VersionScreen(
|
||||||
|
onBack = {navController.popBackStack()},
|
||||||
|
deviceInfoViewModel = deviceInfoViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.VoidTrace.route) {
|
||||||
|
val voidViewModel: VoidViewModel = hiltViewModel()
|
||||||
|
|
||||||
|
VoidTraceScreen(
|
||||||
|
voidViewModel = voidViewModel,
|
||||||
|
onNavigateTranDetail = { trace ->
|
||||||
|
navController.navigate(Routes.VoidTranDetail.createRoute(trace)) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.Settlement.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
|
||||||
|
SettlementScreen(
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
onBack = { navController.popBackStack(Routes.Dashboard.route,false) },
|
||||||
|
onStartSettlement = {
|
||||||
|
sharedViewModel.transactionsType.value = com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT
|
||||||
|
navController.navigate(Routes.Processing.route) {
|
||||||
|
popUpTo(Routes.Settlement.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(
|
||||||
|
route = Routes.VoidTranDetail.route,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument("trace") {
|
||||||
|
type = NavType.StringType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) { backStackEntry ->
|
||||||
|
val voidViewModel: VoidViewModel = hiltViewModel()
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
val trace = backStackEntry.arguments?.getString("trace").orEmpty()
|
||||||
|
|
||||||
|
TranDetailPage(
|
||||||
|
voidViewModel = voidViewModel,
|
||||||
|
trace = trace,
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onProceedVoid = { payDetail ->
|
||||||
|
sharedViewModel.transactionsType.value = com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.VOID
|
||||||
|
sharedViewModel.payDetail.value = payDetail
|
||||||
|
navController.navigate(Routes.Processing.route) {
|
||||||
|
popUpTo(Routes.VoidTranDetail.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.SignOn.route) {
|
||||||
|
SignOnRoute(
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onNavigateResult = { isSuccess, message ->
|
||||||
|
navController.navigate(Routes.SignOnResult.createRoute(isSuccess, message)) {
|
||||||
|
popUpTo(Routes.SignOn.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(
|
||||||
|
route = Routes.SignOnResult.route,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument("isSuccess") {
|
||||||
|
type = NavType.BoolType
|
||||||
|
},
|
||||||
|
navArgument("message") {
|
||||||
|
type = NavType.StringType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) { backStackEntry ->
|
||||||
|
val isSuccess = backStackEntry.arguments?.getBoolean("isSuccess") ?: false
|
||||||
|
val message = backStackEntry.arguments?.getString("message").orEmpty()
|
||||||
|
|
||||||
|
SignOnResultScreen(
|
||||||
|
isSuccess = isSuccess,
|
||||||
|
message = message,
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onDone = {
|
||||||
|
navController.navigate(Routes.Dashboard.route) {
|
||||||
|
popUpTo(Routes.Dashboard.route) {
|
||||||
|
inclusive = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRetry = {
|
||||||
|
navController.navigate(Routes.SignOn.route) {
|
||||||
|
popUpTo(Routes.SignOnResult.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +229,202 @@ fun AppNavGraph(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
) { backStackEntry ->
|
) { backStackEntry ->
|
||||||
AmountScreen(
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
|
||||||
|
AmountRoute(
|
||||||
action = backStackEntry.arguments?.getString("action").orEmpty(),
|
action = backStackEntry.arguments?.getString("action").orEmpty(),
|
||||||
onBackClick = { navController.popBackStack() }
|
sharedViewModel = sharedViewModel,
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onNavigateCardWaiting = {
|
||||||
|
navController.navigate(Routes.CardWaiting.route) {
|
||||||
|
popUpTo(Routes.Amount.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.CardWaiting.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
val cardReaderViewModel: CardReaderViewModel = hiltViewModel(activity)
|
||||||
|
val cardWaitingViewModel: CardWaitingViewModel = viewModel(
|
||||||
|
factory = CardWaitingViewModel.provideFactory(
|
||||||
|
cardReadViewModel = cardReaderViewModel,
|
||||||
|
sharedViewModel = sharedViewModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
CardWaitingScreen(
|
||||||
|
viewModel = cardWaitingViewModel,
|
||||||
|
amount = formatAmountForDisplay(sharedViewModel.amount.value),
|
||||||
|
onManualEntry = {},
|
||||||
|
onProcessingCard = {
|
||||||
|
navController.navigate(Routes.ProcessingCard.route) {
|
||||||
|
popUpTo(Routes.CardWaiting.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onMain = {
|
||||||
|
navController.navigate(Routes.Dashboard.route) {
|
||||||
|
popUpTo(Routes.Dashboard.route) {
|
||||||
|
inclusive = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.ProcessingCard.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
val cardReaderViewModel: CardReaderViewModel = hiltViewModel(activity)
|
||||||
|
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
|
||||||
|
val pinPadViewModel: PinPadViewModel = hiltViewModel(activity)
|
||||||
|
val emvTransactionViewModel: EmvTransactionProcessViewModel = hiltViewModel(activity)
|
||||||
|
val processingCardViewModel: ProcessingCardViewModel = viewModel(
|
||||||
|
factory = ProcessingCardViewModel.provideFactory(
|
||||||
|
cardReadViewModel = cardReaderViewModel,
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
transProcessViewModel = transProcessViewModel,
|
||||||
|
pinPadViewModel = pinPadViewModel,
|
||||||
|
emvTransactionViewModel = emvTransactionViewModel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ProcessingCardRoute(
|
||||||
|
viewModel = processingCardViewModel,
|
||||||
|
onNavigatePinPad = {
|
||||||
|
pinPadViewModel.resetSessionState()
|
||||||
|
navController.navigate(Routes.PinPad.route) {
|
||||||
|
popUpTo(Routes.ProcessingCard.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNavigateInputAmount = { navController.popBackStack(Routes.Amount.route, false) },
|
||||||
|
onNavigateProcessing = {},
|
||||||
|
onNavigateEmvTransaction = {},
|
||||||
|
onNavigateError = {},
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onShowDecline = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.PinPad.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
val pinPadViewModel: PinPadViewModel = hiltViewModel(activity)
|
||||||
|
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
|
||||||
|
|
||||||
|
PinPadRoute(
|
||||||
|
pinPadViewModel = pinPadViewModel,
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
transProcessViewModel = transProcessViewModel,
|
||||||
|
onNavigateInputRrn = {
|
||||||
|
navController.navigate(Routes.InputRrn.route) {
|
||||||
|
popUpTo(Routes.PinPad.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNavigateProcessing = {
|
||||||
|
navController.navigate(Routes.Processing.route) {
|
||||||
|
popUpTo(Routes.PinPad.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.InputRrn.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
|
||||||
|
InputRrnRoute(
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onNavigateProcessing = {
|
||||||
|
navController.navigate(Routes.Processing.route) {
|
||||||
|
popUpTo(Routes.InputRrn.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.Processing.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
val transProcessViewModel: TransProcessViewModel = hiltViewModel(activity)
|
||||||
|
|
||||||
|
ProcessingRoute(
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
transProcessViewModel = transProcessViewModel,
|
||||||
|
onNavigateTransactionResult = {
|
||||||
|
navController.navigate(Routes.TransactionResult.route) {
|
||||||
|
popUpTo(Routes.Processing.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.TransactionResult.route) {
|
||||||
|
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
|
||||||
|
|
||||||
|
TransactionResultRoute(
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
onNavigateMain = {
|
||||||
|
navController.navigate(Routes.Dashboard.route) {
|
||||||
|
popUpTo(Routes.Dashboard.route) {
|
||||||
|
inclusive = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNavigatePrintReceipt = {
|
||||||
|
navController.navigate(Routes.PrintReceipt.route) {
|
||||||
|
popUpTo(Routes.TransactionResult.route) {
|
||||||
|
inclusive = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShowError = {},
|
||||||
|
onShowSuccess = {},
|
||||||
|
onShowPrinterDialog = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(Routes.PrintReceipt.route) {
|
||||||
|
PrintReceiptScreen(
|
||||||
|
onPrint = {},
|
||||||
|
onDone = {
|
||||||
|
navController.navigate(Routes.Dashboard.route) {
|
||||||
|
popUpTo(Routes.Dashboard.route) {
|
||||||
|
inclusive = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatAmountForDisplay(amount: String?): String {
|
||||||
|
val normalizedAmount = amount.orEmpty()
|
||||||
|
val value = normalizedAmount.toLongOrNull() ?: return normalizedAmount.ifBlank { "0" }
|
||||||
|
return "%,d".format(value)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,30 @@
|
|||||||
package com.mob.utsmyanmar.ui.navigation
|
package com.mob.utsmyanmar.ui.navigation
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
sealed class Routes(val route: String) {
|
sealed class Routes(val route: String) {
|
||||||
data object Dashboard : Routes("dashboard")
|
data object Dashboard : Routes("dashboard")
|
||||||
data object Amount : Routes("amount/{action}") {
|
data object Amount : Routes("amount/{action}") {
|
||||||
fun createRoute(action: String): String = "amount/$action"
|
fun createRoute(action: String): String = "amount/${Uri.encode(action)}"
|
||||||
}
|
}
|
||||||
|
data object SeeMore : Routes("see_more")
|
||||||
|
data object Settlement : Routes("settlement")
|
||||||
|
data object VoidTrace : Routes("void_trace")
|
||||||
|
data object VoidTranDetail : Routes("void_tran_detail/{trace}") {
|
||||||
|
fun createRoute(trace: String): String = "void_tran_detail/${Uri.encode(trace)}"
|
||||||
|
}
|
||||||
|
data object SignOn : Routes("sign_on")
|
||||||
|
data object SignOnResult : Routes("sign_on_result/{isSuccess}/{message}") {
|
||||||
|
fun createRoute(isSuccess: Boolean, message: String): String {
|
||||||
|
return "sign_on_result/$isSuccess/${Uri.encode(message)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data object CardWaiting : Routes("card_waiting")
|
||||||
|
data object ProcessingCard : Routes("processing_card")
|
||||||
|
data object PinPad : Routes("pin_pad")
|
||||||
|
data object InputRrn : Routes("input_rrn")
|
||||||
|
data object Processing : Routes("processing")
|
||||||
|
data object TransactionResult : Routes("transaction_result")
|
||||||
|
data object PrintReceipt : Routes("print_receipt")
|
||||||
|
data object Version : Routes("version")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.pinpad
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.mob.utsmyanmar.model.PinPadStatus
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
|
||||||
|
import com.utsmyanmar.paylibs.Constant
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PinPadRoute(
|
||||||
|
pinPadViewModel: PinPadViewModel,
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
transProcessViewModel: TransProcessViewModel,
|
||||||
|
onNavigateInputRrn: () -> Unit,
|
||||||
|
onNavigateProcessing: () -> Unit,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
val pinText by pinPadViewModel.pinText.collectAsStateWithLifecycle()
|
||||||
|
val alertMsg by pinPadViewModel.alertMsg.collectAsStateWithLifecycle()
|
||||||
|
val pinStatus by pinPadViewModel.pinStatus.collectAsStateWithLifecycle()
|
||||||
|
val canGoBack = sharedViewModel.transactionsType.value != TransactionsType.SALE
|
||||||
|
|
||||||
|
BackHandler(enabled = canGoBack) {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(pinStatus) {
|
||||||
|
when (pinStatus) {
|
||||||
|
PinPadStatus.ON_CONFIRM,
|
||||||
|
PinPadStatus.ON_NEXT_SCREEN -> {
|
||||||
|
val payDetail = pinPadViewModel.getPayDetail()
|
||||||
|
payDetail?.tradeAnswerCode = Constant.ANSWER_CODE_APPROVED
|
||||||
|
sharedViewModel.payDetail.value = payDetail
|
||||||
|
transProcessViewModel.resetTransactionStatus()
|
||||||
|
if (sharedViewModel.transactionsType.value == TransactionsType.REFUND) {
|
||||||
|
onNavigateInputRrn()
|
||||||
|
} else {
|
||||||
|
onNavigateProcessing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PinPadStatus.ON_CANCEL,
|
||||||
|
PinPadStatus.ON_TIMEOUT,
|
||||||
|
PinPadStatus.ON_CARD_REMOVED -> {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
pinPadViewModel.cancelPinPad()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PinPadScreen(
|
||||||
|
pinText = pinText,
|
||||||
|
alertMessage = alertMsg,
|
||||||
|
canGoBack = canGoBack,
|
||||||
|
onBack = onBack,
|
||||||
|
onKeyboardReady = { keyboard ->
|
||||||
|
pinPadViewModel.startPinPadProcess(keyboard)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
181
app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadScreen.kt
Normal file
181
app/src/main/java/com/mob/utsmyanmar/ui/pinpad/PinPadScreen.kt
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.pinpad
|
||||||
|
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
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 androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import com.mob.utsmyanmar.R
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Black
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Primary
|
||||||
|
import com.mob.utsmyanmar.ui.theme.White
|
||||||
|
import com.utsmyanmar.baselib.ui.CustomPinPadKeyboard
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun PinPadScreen(
|
||||||
|
pinText: String,
|
||||||
|
alertMessage: String?,
|
||||||
|
canGoBack: Boolean,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onKeyboardReady: (CustomPinPadKeyboard) -> Unit
|
||||||
|
) {
|
||||||
|
var keyboardView by remember { mutableStateOf<CustomPinPadKeyboard?>(null) }
|
||||||
|
var isStarted by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(keyboardView) {
|
||||||
|
val keyboard = keyboardView ?: return@LaunchedEffect
|
||||||
|
if (!isStarted) {
|
||||||
|
isStarted = true
|
||||||
|
onKeyboardReady(keyboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "PIN ENTRY",
|
||||||
|
color = White,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
if (canGoBack) {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_left_arrow),
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Primary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = White
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.background(White)
|
||||||
|
.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = Primary,
|
||||||
|
shape = RoundedCornerShape(24.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 28.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = "Please Input PIN",
|
||||||
|
color = White,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(14.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = if (pinText.isBlank()) "------" else pinText,
|
||||||
|
color = White,
|
||||||
|
fontSize = 30.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
letterSpacing = 4.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!alertMessage.isNullOrBlank()) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
text = alertMessage,
|
||||||
|
color = White,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Enter your PIN on the secured keypad below.",
|
||||||
|
color = Black,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
AndroidView(
|
||||||
|
factory = { context ->
|
||||||
|
CustomPinPadKeyboard(context).also {
|
||||||
|
keyboardView = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (canGoBack) {
|
||||||
|
"Cancel on device or use back to exit."
|
||||||
|
} else {
|
||||||
|
"Cancel on device to exit."
|
||||||
|
},
|
||||||
|
color = Black,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,561 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.pinpad
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.RemoteException
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.mob.utsmyanmar.config.Constants
|
||||||
|
import com.mob.utsmyanmar.config.SunmiPayManager
|
||||||
|
import com.mob.utsmyanmar.model.PinPadStatus
|
||||||
|
import com.sunmi.pay.hardware.aidl.AidlConstants
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.AidlErrorCodeV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.bean.PinPadDataV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2
|
||||||
|
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2
|
||||||
|
import com.utsmyanmar.baselib.ui.CustomPinPadKeyboard
|
||||||
|
import com.utsmyanmar.checkxread.sdk.SunmiSDK
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.model.TradeData
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class PinPadViewModel @Inject constructor(
|
||||||
|
private val sunmiPayManager: SunmiPayManager
|
||||||
|
) : ViewModel() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PinPadViewModel"
|
||||||
|
private const val PIN_CONFIRM_SUCCESS_STATUS = 0
|
||||||
|
private const val ON_CONFIRM_CLICK = 2
|
||||||
|
private const val ON_CANCEL_CLICK = 3
|
||||||
|
private const val ON_ERROR_PIN_PAD = 4
|
||||||
|
private const val ON_NUMBER_CLICK = 5
|
||||||
|
private const val ON_EMPTY_PIN_BLOCK = 6
|
||||||
|
private const val ON_TIMEOUT_PIN_PAD = 7
|
||||||
|
private const val ON_ERROR_DUKPT = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mPinPadOptV2: PinPadOptV2? = null
|
||||||
|
private var mPinType = 0
|
||||||
|
private var mWidth = 239
|
||||||
|
private var mHeight = 130
|
||||||
|
private var mInterval = 1
|
||||||
|
private var mKeyboardCoordinate = intArrayOf(0, 661)
|
||||||
|
private var mCancelWidth = 112
|
||||||
|
private var mCancelHeight = 112
|
||||||
|
private var mCancelCoordinate = intArrayOf(0, 48)
|
||||||
|
private var dukptIndex = 0
|
||||||
|
private var tmkIndex = 9
|
||||||
|
private val PIK_INDEX = 11
|
||||||
|
private var tradeData: TradeData? = null
|
||||||
|
private var payDetail: PayDetail? = null
|
||||||
|
private var pan: String = ""
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UI States
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val _pinText = MutableStateFlow("")
|
||||||
|
val pinText = _pinText.asStateFlow()
|
||||||
|
|
||||||
|
private val _alertMsg = MutableStateFlow<String?>(null)
|
||||||
|
val alertMsg = _alertMsg.asStateFlow()
|
||||||
|
|
||||||
|
private val _errorCode = MutableStateFlow<Int?>(null)
|
||||||
|
val errorCode = _errorCode.asStateFlow()
|
||||||
|
|
||||||
|
private val _transType = MutableStateFlow<TransactionsType?>(null)
|
||||||
|
val transType = _transType.asStateFlow()
|
||||||
|
|
||||||
|
private val _pinStatus =
|
||||||
|
MutableStateFlow<PinPadStatus?>(null)
|
||||||
|
|
||||||
|
val pinStatus = _pinStatus.asStateFlow()
|
||||||
|
private var isTerminalStateReached = false
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trade Data
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun setTradeData(tradeData: TradeData) {
|
||||||
|
this.tradeData = tradeData
|
||||||
|
|
||||||
|
payDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
pan = payDetail?.cardNo ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTradeData(): TradeData? {
|
||||||
|
return tradeData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPayDetail(payDetail: PayDetail) {
|
||||||
|
this.payDetail = payDetail
|
||||||
|
|
||||||
|
tradeData = TradeData().apply {
|
||||||
|
this.payDetail = payDetail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPayDetail(): PayDetail? {
|
||||||
|
return payDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTransType(type: TransactionsType?) {
|
||||||
|
_transType.value = type
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetSessionState() {
|
||||||
|
_pinText.value = ""
|
||||||
|
_alertMsg.value = null
|
||||||
|
_errorCode.value = null
|
||||||
|
_pinStatus.value = null
|
||||||
|
isTerminalStateReached = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pin Pad
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startPinPadProcess(
|
||||||
|
customPinPadKeyboard: CustomPinPadKeyboard
|
||||||
|
) {
|
||||||
|
resetSessionState()
|
||||||
|
|
||||||
|
// dukptIndex = SystemParamsOperation.getInstance().tmkIndex.toInt()
|
||||||
|
if(!sunmiPayManager.isReady()){
|
||||||
|
_alertMsg.value =
|
||||||
|
"Sunmi Pay SDK is not ready"
|
||||||
|
|
||||||
|
_pinStatus.value =
|
||||||
|
PinPadStatus.ON_ERROR
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
initData()
|
||||||
|
// testInjectPIK()
|
||||||
|
initPinPad(customPinPadKeyboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initData() {
|
||||||
|
|
||||||
|
mPinPadOptV2 = sunmiPayManager.pinPadOptV2
|
||||||
|
|
||||||
|
if (mPinPadOptV2 == null) {
|
||||||
|
Log.d(TAG, "PinPad service are not ready!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
val result = mPinPadOptV2?.setAntiExhaustiveProtectionMode(3)
|
||||||
|
|
||||||
|
if ((result ?: -1) >= 0) {
|
||||||
|
LogUtil.d(
|
||||||
|
TAG,
|
||||||
|
"Pin anti exhaustive result:$result"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LogUtil.d(
|
||||||
|
TAG, "Pin Anti Exhaustive failed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: RemoteException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelPinPad() {
|
||||||
|
try {
|
||||||
|
if (!isTerminalStateReached) {
|
||||||
|
mPinPadOptV2?.cancelInputPin()
|
||||||
|
Log.d(TAG, "PinPad Canceled")
|
||||||
|
}
|
||||||
|
} catch (e: RemoteException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Init Pin Pad
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun initPinPad(
|
||||||
|
customPinPadKeyboard: CustomPinPadKeyboard
|
||||||
|
) {
|
||||||
|
LogUtil.e(TAG, "Init Pin Pad PAN:$pan")
|
||||||
|
|
||||||
|
var timeout = Constants.PIN_PAD_TIMEOUT
|
||||||
|
val pinPadOrder = !SystemParamsOperation.getInstance().isRandomPinPad
|
||||||
|
|
||||||
|
if (SunmiSDK.getInstance().checkCardExist() != 2) {
|
||||||
|
timeout = Constants.TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val config = PinPadConfigV2().apply {
|
||||||
|
maxInput = 6
|
||||||
|
minInput = 0
|
||||||
|
pinPadType = 1 // custom keyboard
|
||||||
|
algorithmType = 0
|
||||||
|
pinType = mPinType
|
||||||
|
this.timeout = timeout * 1000
|
||||||
|
isOrderNumKey = pinPadOrder
|
||||||
|
keySystem = AidlConstants.Security.SEC_MKSK
|
||||||
|
pinKeyIndex = PIK_INDEX
|
||||||
|
pinblockFormat = AidlConstants.PinBlockFormat.SEC_PIN_BLK_ISO_FMT0
|
||||||
|
this.pan = getPanBytes(this@PinPadViewModel.pan)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mPinPadOptV2?.initPinPad(config, mPinPadListener)
|
||||||
|
|
||||||
|
LogUtil.e(TAG, "pinpad result:$result")
|
||||||
|
|
||||||
|
if (result.isNullOrEmpty()) {
|
||||||
|
_alertMsg.value = "PinPad init failed"
|
||||||
|
_pinStatus.value = PinPadStatus.ON_ERROR
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyboardCoordinate(result, customPinPadKeyboard)
|
||||||
|
|
||||||
|
_pinText.value = ""
|
||||||
|
customPinPadKeyboard.keepScreenOn = true
|
||||||
|
customPinPadKeyboard.setKeyBoard(result)
|
||||||
|
customPinPadKeyboard.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
_alertMsg.value = e.message
|
||||||
|
_pinStatus.value = PinPadStatus.ON_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testInjectPIK() {
|
||||||
|
try {
|
||||||
|
val securityOptV2 = sunmiPayManager.securityOptV2 ?: return
|
||||||
|
|
||||||
|
val pik = ByteUtil.hexStr2Bytes(
|
||||||
|
"33DD20C9A0B5B861F2914D44BC2AF055"
|
||||||
|
)
|
||||||
|
|
||||||
|
val kcv = ByteUtil.hexStr2Bytes(
|
||||||
|
"28DBDB489D28BC92"
|
||||||
|
)
|
||||||
|
|
||||||
|
val code = securityOptV2.savePlaintextKey(
|
||||||
|
AidlConstants.Security.KEY_TYPE_PIK,
|
||||||
|
pik,
|
||||||
|
kcv,
|
||||||
|
AidlConstants.Security.KEY_ALG_TYPE_3DES,
|
||||||
|
PIK_INDEX
|
||||||
|
)
|
||||||
|
|
||||||
|
LogUtil.e(TAG, "saveTestPIK result:$code")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keyboard
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun getKeyboardCoordinate(
|
||||||
|
keyBoardText: String,
|
||||||
|
customPinPadKeyboard: CustomPinPadKeyboard
|
||||||
|
) {
|
||||||
|
|
||||||
|
customPinPadKeyboard
|
||||||
|
.viewTreeObserver
|
||||||
|
.addOnGlobalLayoutListener(
|
||||||
|
object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
|
||||||
|
customPinPadKeyboard
|
||||||
|
.viewTreeObserver
|
||||||
|
.removeOnGlobalLayoutListener(this)
|
||||||
|
|
||||||
|
val textView: TextView =
|
||||||
|
customPinPadKeyboard.key_0
|
||||||
|
|
||||||
|
textView.getLocationOnScreen(
|
||||||
|
mKeyboardCoordinate
|
||||||
|
)
|
||||||
|
|
||||||
|
mWidth = textView.width
|
||||||
|
mHeight = textView.height
|
||||||
|
|
||||||
|
mInterval = 1
|
||||||
|
|
||||||
|
importPinPadData(keyBoardText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importPinPadData(text: String) {
|
||||||
|
|
||||||
|
val pinPadData = PinPadDataV2()
|
||||||
|
|
||||||
|
pinPadData.numX = mKeyboardCoordinate[0]
|
||||||
|
pinPadData.numY = mKeyboardCoordinate[1]
|
||||||
|
|
||||||
|
pinPadData.numW = mWidth
|
||||||
|
pinPadData.numH = mHeight
|
||||||
|
|
||||||
|
pinPadData.lineW = mInterval
|
||||||
|
|
||||||
|
pinPadData.cancelX = mCancelCoordinate[0]
|
||||||
|
pinPadData.cancelY = mCancelCoordinate[1]
|
||||||
|
|
||||||
|
pinPadData.cancelW = mCancelWidth
|
||||||
|
pinPadData.cancelH = mCancelHeight
|
||||||
|
|
||||||
|
pinPadData.lineW = 0
|
||||||
|
|
||||||
|
pinPadData.rows = 5
|
||||||
|
pinPadData.clos = 3
|
||||||
|
|
||||||
|
keyMap(text, pinPadData)
|
||||||
|
|
||||||
|
try {
|
||||||
|
mPinPadOptV2?.importPinPadData(pinPadData)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun keyMap(
|
||||||
|
str: String,
|
||||||
|
data: PinPadDataV2
|
||||||
|
) {
|
||||||
|
|
||||||
|
data.keyMap = ByteArray(64)
|
||||||
|
|
||||||
|
var j = 0
|
||||||
|
|
||||||
|
for (i in 0 until 15) {
|
||||||
|
|
||||||
|
when (i) {
|
||||||
|
|
||||||
|
9, 12 -> {
|
||||||
|
data.keyMap[i] = 0x1B
|
||||||
|
}
|
||||||
|
|
||||||
|
13 -> {
|
||||||
|
data.keyMap[i] = 0x0C
|
||||||
|
}
|
||||||
|
|
||||||
|
11, 14 -> {
|
||||||
|
data.keyMap[i] = 0x0D
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
data.keyMap[i] =
|
||||||
|
str[j].code.toByte()
|
||||||
|
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Password View
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun showPasswordView(len: Int) {
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
|
||||||
|
repeat(len) {
|
||||||
|
sb.append("*")
|
||||||
|
}
|
||||||
|
|
||||||
|
_pinText.value = sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val handler =
|
||||||
|
Handler(Looper.getMainLooper()) { msg ->
|
||||||
|
when (msg.what) {
|
||||||
|
ON_NUMBER_CLICK -> {
|
||||||
|
showPasswordView(msg.arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_CONFIRM_CLICK -> {
|
||||||
|
LogUtil.d(TAG, "ON CLICK CONFIRM")
|
||||||
|
isTerminalStateReached = true
|
||||||
|
_pinStatus.value = PinPadStatus.ON_CONFIRM
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_CANCEL_CLICK -> {
|
||||||
|
LogUtil.d(TAG, "ON CLICK CANCEL")
|
||||||
|
if (!isTerminalStateReached) {
|
||||||
|
_pinStatus.value = PinPadStatus.ON_CANCEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_ERROR_PIN_PAD -> {
|
||||||
|
LogUtil.d(TAG, "ON ERROR CODE: ${msg.arg1}")
|
||||||
|
_errorCode.value = msg.arg1
|
||||||
|
isTerminalStateReached = true
|
||||||
|
_pinStatus.value = PinPadStatus.ON_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_EMPTY_PIN_BLOCK -> {
|
||||||
|
_pinStatus.value = PinPadStatus.ON_EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_TIMEOUT_PIN_PAD -> {
|
||||||
|
isTerminalStateReached = true
|
||||||
|
_pinStatus.value = PinPadStatus.ON_TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_ERROR_DUKPT -> {
|
||||||
|
_alertMsg.value = "Try Again!"
|
||||||
|
isTerminalStateReached = true
|
||||||
|
_pinStatus.value = PinPadStatus.ON_ERROR_DUKPT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Listener
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val mPinPadListener =
|
||||||
|
object : PinPadListenerV2.Stub() {
|
||||||
|
|
||||||
|
override fun onPinLength(len: Int) {
|
||||||
|
handler.obtainMessage(
|
||||||
|
ON_NUMBER_CLICK,
|
||||||
|
len,
|
||||||
|
0
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfirm(
|
||||||
|
status: Int,
|
||||||
|
pinBlock: ByteArray?
|
||||||
|
) {
|
||||||
|
|
||||||
|
LogUtil.e(
|
||||||
|
TAG,
|
||||||
|
"onConfirm status:$status and pinblock :${ByteUtil.bytes2HexStr(pinBlock)}"
|
||||||
|
)
|
||||||
|
if (status != PIN_CONFIRM_SUCCESS_STATUS) {
|
||||||
|
_alertMsg.value = "PinPad confirm failed: $status"
|
||||||
|
_pinStatus.value = PinPadStatus.ON_ERROR
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCardValid = SunmiSDK.getInstance().checkCardExist() == 2 ||
|
||||||
|
payDetail?.cardType == AidlConstants.CardType.MAGNETIC.getValue() ||
|
||||||
|
payDetail?.cardType == -9
|
||||||
|
|
||||||
|
if (!isCardValid) {
|
||||||
|
isTerminalStateReached = true
|
||||||
|
_pinStatus.value = PinPadStatus.ON_CARD_REMOVED
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pinBlock != null) {
|
||||||
|
payDetail?.pinCipher = ByteUtil.bytes2HexStr(pinBlock)
|
||||||
|
} else {
|
||||||
|
payDetail?.pinCipher = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_COMPLETE ||
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_VOID ||
|
||||||
|
transType.value == TransactionsType.REFUND
|
||||||
|
) {
|
||||||
|
isTerminalStateReached = true
|
||||||
|
_pinStatus.value = PinPadStatus.ON_NEXT_SCREEN
|
||||||
|
} else {
|
||||||
|
handler.obtainMessage(
|
||||||
|
ON_CONFIRM_CLICK
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
if (!isTerminalStateReached) {
|
||||||
|
handler.obtainMessage(
|
||||||
|
ON_CANCEL_CLICK
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(code: Int) {
|
||||||
|
|
||||||
|
val msg =
|
||||||
|
AidlErrorCodeV2
|
||||||
|
.valueOf(code)
|
||||||
|
.msg
|
||||||
|
|
||||||
|
LogUtil.d(
|
||||||
|
TAG,
|
||||||
|
"error code:$code - message :$msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
when (code) {
|
||||||
|
|
||||||
|
-60001 -> {
|
||||||
|
handler.obtainMessage(
|
||||||
|
ON_TIMEOUT_PIN_PAD
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
-3025 -> {
|
||||||
|
handler.obtainMessage(
|
||||||
|
ON_ERROR_DUKPT
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
handler.obtainMessage(
|
||||||
|
ON_ERROR_PIN_PAD,
|
||||||
|
code,
|
||||||
|
code,
|
||||||
|
code
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPanBytes(pan: String): ByteArray {
|
||||||
|
|
||||||
|
if (pan.length < 13) {
|
||||||
|
return ByteArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pan
|
||||||
|
.substring(pan.length - 13, pan.length - 1)
|
||||||
|
.toByteArray(StandardCharsets.US_ASCII)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt
Normal file
20
app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.preview
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
@Preview(
|
||||||
|
name = "P2",
|
||||||
|
device = "spec:width=720px,height=1440px,dpi=350",
|
||||||
|
showBackground = true,
|
||||||
|
showSystemUi = true
|
||||||
|
)
|
||||||
|
annotation class P2Preview
|
||||||
|
|
||||||
|
@Preview(
|
||||||
|
name = "P3",
|
||||||
|
device = "spec:width=720px,height=1600px,dpi=270",
|
||||||
|
showBackground = true,
|
||||||
|
showSystemUi = true
|
||||||
|
)
|
||||||
|
annotation class P3Preview
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.print_receipt
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.utsmyanmar.paylibs.print.PrintReceipt
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PrintReceiptScreen(
|
||||||
|
onPrint: () -> Unit,
|
||||||
|
onDone: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope();
|
||||||
|
val receiptOffsetY = remember {
|
||||||
|
Animatable(0f);
|
||||||
|
}
|
||||||
|
val animationDuration = 3000;
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AppBar(title = "Receipt")
|
||||||
|
},
|
||||||
|
containerColor = Color.IvoryBeige
|
||||||
|
) { paddingValues ->
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
|
||||||
|
ReceiptPreview(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.graphicsLayer{
|
||||||
|
translationY = receiptOffsetY.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onDone,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Cancel",
|
||||||
|
style = MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(50.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.LegacyRed,
|
||||||
|
contentColor = Color.White,
|
||||||
|
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
|
||||||
|
disabledContentColor = Color.White.copy(alpha = 0.5f),
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
onClick = {
|
||||||
|
// onPrint()
|
||||||
|
try {
|
||||||
|
PrintReceipt.getInstance().printNow()
|
||||||
|
scope.launch {
|
||||||
|
launch {
|
||||||
|
receiptOffsetY.animateTo(
|
||||||
|
targetValue = -1500f,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = animationDuration,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
delay(5000)
|
||||||
|
launch {
|
||||||
|
receiptOffsetY.animateTo(
|
||||||
|
targetValue = 0f,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 0,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
Log.d("PrintReceipt", "error with $e")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Print",
|
||||||
|
style = MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReceiptPreview(
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(Color.White)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = Color.Gray,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
.padding(20.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "MOB POS",
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Transaction Receipt",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ReceiptDivider()
|
||||||
|
ReceiptRow("Merchant", "MOB UTS Myanmar")
|
||||||
|
ReceiptRow("Terminal ID", "POS001")
|
||||||
|
ReceiptRow("Transaction", "SALE")
|
||||||
|
ReceiptRow("Amount", "500.00 MMK")
|
||||||
|
ReceiptRow("Card Type", "MPU")
|
||||||
|
ReceiptRow("Card No", "1234 **** **** 5678")
|
||||||
|
ReceiptRow("Status", "SUCCESS")
|
||||||
|
ReceiptRow("Date", "13/05/2026")
|
||||||
|
ReceiptRow("Time", "14:30:22")
|
||||||
|
ReceiptRow("Ref No", "REF123456789")
|
||||||
|
ReceiptDivider()
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Thank You",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Please keep this receipt",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReceiptRow(
|
||||||
|
label: String,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 6.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReceiptDivider() {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 10.dp),
|
||||||
|
text = "--------------------------------",
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, showSystemUi = true)
|
||||||
|
@Composable
|
||||||
|
fun PreviewPrintReceiptScreen() {
|
||||||
|
MaterialTheme {
|
||||||
|
PrintReceiptScreen(
|
||||||
|
onPrint = {},
|
||||||
|
onDone = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.processing_card
|
||||||
|
|
||||||
|
sealed interface ProcessingCardEvent {
|
||||||
|
data object StartProcess : ProcessingCardEvent
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.processing_card
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProcessingCardRoute(
|
||||||
|
viewModel: ProcessingCardViewModel,
|
||||||
|
onNavigatePinPad: () -> Unit,
|
||||||
|
onNavigateInputAmount: () -> Unit,
|
||||||
|
onNavigateProcessing: () -> Unit,
|
||||||
|
onNavigateEmvTransaction: () -> Unit,
|
||||||
|
onNavigateError: (String) -> Unit,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onShowDecline: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.onEvent(ProcessingCardEvent.StartProcess)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.uiEvent.collect { event ->
|
||||||
|
when (event) {
|
||||||
|
ProcessingCardUiEvent.NavigateToPinPad -> onNavigatePinPad()
|
||||||
|
ProcessingCardUiEvent.NavigateToInputAmount -> onNavigateInputAmount()
|
||||||
|
ProcessingCardUiEvent.NavigateToProcessing -> onNavigateProcessing()
|
||||||
|
ProcessingCardUiEvent.NavigateToEmvTransaction -> onNavigateEmvTransaction()
|
||||||
|
is ProcessingCardUiEvent.NavigateToError -> onNavigateError(event.message)
|
||||||
|
is ProcessingCardUiEvent.ShowDeclineAndBack -> {
|
||||||
|
onShowDecline(event.message)
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
ProcessingCardUiEvent.Back -> onBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessingCardScreen(state = state)
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.processing_card
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProcessingCardScreen(
|
||||||
|
state: ProcessingCardState
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
if (state.isLoading) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = state.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.processing_card
|
||||||
|
|
||||||
|
data class ProcessingCardState(
|
||||||
|
val title: String = "Processing Card",
|
||||||
|
val message: String = "Please wait...",
|
||||||
|
val isLoading: Boolean = true
|
||||||
|
)
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.processing_card
|
||||||
|
|
||||||
|
sealed interface ProcessingCardUiEvent {
|
||||||
|
data object NavigateToPinPad : ProcessingCardUiEvent
|
||||||
|
data object NavigateToInputAmount : ProcessingCardUiEvent
|
||||||
|
data object NavigateToProcessing : ProcessingCardUiEvent
|
||||||
|
data object NavigateToEmvTransaction : ProcessingCardUiEvent
|
||||||
|
data class NavigateToError(val message: String) : ProcessingCardUiEvent
|
||||||
|
data class ShowDeclineAndBack(val message: String) : ProcessingCardUiEvent
|
||||||
|
data object Back : ProcessingCardUiEvent
|
||||||
|
}
|
||||||
@ -0,0 +1,267 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.processing_card
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.mob.utsmyanmar.model.CardTransactionType
|
||||||
|
import com.mob.utsmyanmar.utils.TransactionUtil
|
||||||
|
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.EmvTransactionProcessViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
|
||||||
|
import com.utsmyanmar.checkxread.model.CardDataX
|
||||||
|
import com.utsmyanmar.checkxread.readcard.MAGXReadCard
|
||||||
|
import com.utsmyanmar.checkxread.readcard.MPUXReadCard
|
||||||
|
import com.utsmyanmar.checkxread.readcard.ReadCardResultX
|
||||||
|
import com.utsmyanmar.checkxread.util.CardTypeX
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.model.TradeData
|
||||||
|
import com.utsmyanmar.paylibs.model.enums.TransCVM
|
||||||
|
import com.utsmyanmar.paylibs.utils.POSUtil
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.enums.TransMenu
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import com.utsmyanmar.paylibs.utils.params.Params
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
|
||||||
|
class ProcessingCardViewModel(
|
||||||
|
private val cardReadViewModel: CardReaderViewModel,
|
||||||
|
private val sharedViewModel: SharedViewModel,
|
||||||
|
private val transProcessViewModel: TransProcessViewModel,
|
||||||
|
private val pinPadViewModel: PinPadViewModel,
|
||||||
|
private val emvTransactionViewModel: EmvTransactionProcessViewModel
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = ProcessingCardViewModel::class.java.simpleName
|
||||||
|
|
||||||
|
fun provideFactory(
|
||||||
|
cardReadViewModel: CardReaderViewModel,
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
transProcessViewModel: TransProcessViewModel,
|
||||||
|
pinPadViewModel: PinPadViewModel,
|
||||||
|
emvTransactionViewModel: EmvTransactionProcessViewModel
|
||||||
|
): ViewModelProvider.Factory {
|
||||||
|
return object : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return ProcessingCardViewModel(
|
||||||
|
cardReadViewModel = cardReadViewModel,
|
||||||
|
sharedViewModel = sharedViewModel,
|
||||||
|
transProcessViewModel = transProcessViewModel,
|
||||||
|
pinPadViewModel = pinPadViewModel,
|
||||||
|
emvTransactionViewModel = emvTransactionViewModel
|
||||||
|
) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(ProcessingCardState())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private val _uiEvent = Channel<ProcessingCardUiEvent>()
|
||||||
|
val uiEvent = _uiEvent.receiveAsFlow()
|
||||||
|
|
||||||
|
fun onEvent(event: ProcessingCardEvent) {
|
||||||
|
when (event) {
|
||||||
|
ProcessingCardEvent.StartProcess -> {
|
||||||
|
resetEmvTransResult()
|
||||||
|
checkCardTransactionType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetEmvTransResult() {
|
||||||
|
emvTransactionViewModel.resetTransactionStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCardTransactionType() {
|
||||||
|
when (cardReadViewModel.getCardTransactionType()) {
|
||||||
|
CardTransactionType.MPU -> readMPUCard()
|
||||||
|
CardTransactionType.EMV -> handlePreEmvProcess()
|
||||||
|
CardTransactionType.FALLBACK -> readMAGStripe(
|
||||||
|
isFallback = true,
|
||||||
|
isNeedPin = true
|
||||||
|
)
|
||||||
|
|
||||||
|
CardTransactionType.MAG -> readMAGStripe(
|
||||||
|
isFallback = false,
|
||||||
|
isNeedPin = true
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Log.d(
|
||||||
|
"ProcessingCardViewModel",
|
||||||
|
"error on CardTransactionType : ${cardReadViewModel.getCardTransactionType()}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readMPUCard() {
|
||||||
|
cardReadViewModel.startReadXProcess(
|
||||||
|
MPUXReadCard.getInstance(),
|
||||||
|
object : ReadCardResultX {
|
||||||
|
override fun onSuccess(cardDataX: CardDataX) {
|
||||||
|
transProcessViewModel.setTransType(sharedViewModel.transactionsType.value)
|
||||||
|
pinPadViewModel.setTransType(sharedViewModel.transactionsType.value)
|
||||||
|
val tradeData = TransactionUtil.initMPUTransaction(cardDataX, CardTypeX.IC)
|
||||||
|
transProcessViewModel.setTradeData(tradeData)
|
||||||
|
pinPadViewModel.setTradeData(tradeData)
|
||||||
|
|
||||||
|
when {
|
||||||
|
sharedViewModel._transMenu.value == TransMenu.PRE_AUTH_PARTIAL_VOID -> {
|
||||||
|
sendUiEvent(
|
||||||
|
ProcessingCardUiEvent.NavigateToError(
|
||||||
|
"Function not supported"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedViewModel.setCardDataExist(true)
|
||||||
|
|
||||||
|
if (sharedViewModel.getAmountExist().value == false) {
|
||||||
|
sendUiEvent(ProcessingCardUiEvent.NavigateToInputAmount)
|
||||||
|
} else {
|
||||||
|
sendUiEvent(ProcessingCardUiEvent.NavigateToPinPad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(code: Int, message: String) {
|
||||||
|
LogUtil.d(TAG, "Failure at $code message: $message")
|
||||||
|
sharedViewModel.setCardDataExist(false)
|
||||||
|
|
||||||
|
sendUiEvent(
|
||||||
|
ProcessingCardUiEvent.ShowDeclineAndBack(
|
||||||
|
"FAILURE :$message"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readMAGStripe(
|
||||||
|
isFallback: Boolean,
|
||||||
|
isNeedPin: Boolean
|
||||||
|
) {
|
||||||
|
cardReadViewModel.startReadXProcess(
|
||||||
|
MAGXReadCard.getInstance(),
|
||||||
|
object : ReadCardResultX {
|
||||||
|
override fun onSuccess(cardDataX: CardDataX) {
|
||||||
|
sharedViewModel.setEmvTrans(false)
|
||||||
|
|
||||||
|
if (isNeedPin) {
|
||||||
|
transProcessViewModel.setTransType(sharedViewModel.transactionsType.value)
|
||||||
|
pinPadViewModel.setTransType(sharedViewModel.transactionsType.value)
|
||||||
|
|
||||||
|
val tradeData = TransactionUtil.initMagStripeTransaction(cardDataX, isFallback)
|
||||||
|
|
||||||
|
transProcessViewModel.setTradeData(tradeData)
|
||||||
|
pinPadViewModel.setTradeData(tradeData)
|
||||||
|
} else {
|
||||||
|
transProcessViewModel.setTransType(sharedViewModel.transactionsType.value)
|
||||||
|
|
||||||
|
val tradeData = TransactionUtil.initMagStripeTransaction(cardDataX, isFallback)
|
||||||
|
val processCode = sharedViewModel.processCode.value
|
||||||
|
val amount = sharedViewModel.amount.value
|
||||||
|
val payDetail: PayDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetail.amount = POSUtil.getInstance().convertAmount(amount)
|
||||||
|
|
||||||
|
if (!TextUtils.equals(processCode, "")) {
|
||||||
|
payDetail.processCode = processCode
|
||||||
|
}
|
||||||
|
|
||||||
|
payDetail.transCVM = TransCVM.SIGNATURE
|
||||||
|
transProcessViewModel.setTradeData(tradeData)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
sharedViewModel.getTransMenu().value == TransMenu.PRE_AUTH_PARTIAL_VOID -> {
|
||||||
|
sharedViewModel.setTransMenu(null)
|
||||||
|
sendUiEvent(
|
||||||
|
ProcessingCardUiEvent.NavigateToError(
|
||||||
|
"Function not supported"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedViewModel.setCardDataExist(true)
|
||||||
|
|
||||||
|
if (
|
||||||
|
sharedViewModel.getTransMenu().value != TransMenu.PRE_AUTH_FULL_VOID &&
|
||||||
|
sharedViewModel.getAmountExist().value == false
|
||||||
|
) {
|
||||||
|
sendUiEvent(ProcessingCardUiEvent.NavigateToInputAmount)
|
||||||
|
} else {
|
||||||
|
if (isNeedPin) {
|
||||||
|
sendUiEvent(ProcessingCardUiEvent.NavigateToPinPad)
|
||||||
|
} else {
|
||||||
|
sendUiEvent(ProcessingCardUiEvent.NavigateToProcessing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(code: Int, message: String) {
|
||||||
|
LogUtil.d(TAG, "Failure at $code message: $message")
|
||||||
|
|
||||||
|
sendUiEvent(
|
||||||
|
ProcessingCardUiEvent.ShowDeclineAndBack(
|
||||||
|
"FAILURE :$message"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePreEmvProcess() {
|
||||||
|
emvTransactionViewModel.transType.value =
|
||||||
|
sharedViewModel.transactionsType.value
|
||||||
|
|
||||||
|
if (SystemParamsOperation.getInstance().isEmvEnabled) {
|
||||||
|
prepareEmvTransaction()
|
||||||
|
sendUiEvent(ProcessingCardUiEvent.NavigateToEmvTransaction)
|
||||||
|
} else {
|
||||||
|
sendUiEvent(
|
||||||
|
ProcessingCardUiEvent.NavigateToError(
|
||||||
|
"Please enable EMV"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareEmvTransaction() {
|
||||||
|
sharedViewModel.setEmvTrans(true)
|
||||||
|
|
||||||
|
val cardType = cardReadViewModel.cardTypeData.value ?: return
|
||||||
|
|
||||||
|
val tradeData: TradeData = Params.newTrade(false)
|
||||||
|
val payDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetail.cardType = cardType
|
||||||
|
|
||||||
|
emvTransactionViewModel.setTradeData(tradeData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendUiEvent(event: ProcessingCardUiEvent) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiEvent.send(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.refund_rrn
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import com.mob.utsmyanmar.ui.components.NumericEntryScreen
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.utsmyanmar.paylibs.utils.POSUtil
|
||||||
|
|
||||||
|
private const val RRN_MAX_LENGTH = 12
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InputRrnRoute(
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onNavigateProcessing: () -> Unit
|
||||||
|
) {
|
||||||
|
var rrn by rememberSaveable {
|
||||||
|
mutableStateOf(sharedViewModel.rrNNo.value.orEmpty())
|
||||||
|
}
|
||||||
|
var errorMessage by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
NumericEntryScreen(
|
||||||
|
title = "Input RRN",
|
||||||
|
prompt = "Enter RRN",
|
||||||
|
displayValue = rrn.ifEmpty { "0" },
|
||||||
|
supportingText = errorMessage ?: "Enter the retrieval reference number to continue",
|
||||||
|
confirmText = "Next",
|
||||||
|
onBackClick = onBack,
|
||||||
|
onCancelClick = onBack,
|
||||||
|
onConfirmClick = {
|
||||||
|
val trimmedRrn = rrn.trim()
|
||||||
|
if (POSUtil.getInstance().checkNumberField(trimmedRrn)) {
|
||||||
|
errorMessage = "Invalid RRN"
|
||||||
|
return@NumericEntryScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedViewModel.rrNNo.value = trimmedRrn
|
||||||
|
sharedViewModel.payDetail.value?.let { payDetail ->
|
||||||
|
payDetail.referNo = trimmedRrn
|
||||||
|
sharedViewModel.payDetail.value = payDetail
|
||||||
|
}
|
||||||
|
onNavigateProcessing()
|
||||||
|
},
|
||||||
|
onKeyClick = { value ->
|
||||||
|
rrn = appendRrnValue(rrn, value)
|
||||||
|
errorMessage = null
|
||||||
|
},
|
||||||
|
onDeleteClick = {
|
||||||
|
rrn = rrn.dropLast(1)
|
||||||
|
errorMessage = null
|
||||||
|
},
|
||||||
|
confirmEnabled = rrn.isNotEmpty(),
|
||||||
|
canDelete = rrn.isNotEmpty(),
|
||||||
|
keys = listOf(
|
||||||
|
listOf("1", "2", "3"),
|
||||||
|
listOf("4", "5", "6"),
|
||||||
|
listOf("7", "8", "9"),
|
||||||
|
listOf("", "0", "00")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendRrnValue(current: String, value: String): String {
|
||||||
|
if (value.isBlank()) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
val remainingDigits = RRN_MAX_LENGTH - current.length
|
||||||
|
if (remainingDigits <= 0) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
return current + value.take(remainingDigits)
|
||||||
|
}
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sale_void
|
||||||
|
|
||||||
|
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.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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.utils.POSUtil
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TranDetailPage(
|
||||||
|
voidViewModel: VoidViewModel,
|
||||||
|
trace: String,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onProceedVoid: (PayDetail) -> Unit
|
||||||
|
) {
|
||||||
|
val transaction by voidViewModel.searchTransaction(trace).observeAsState()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.IvoryBeige,
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Transaction Detail",
|
||||||
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
onIconClick = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
transaction?.let { payDetail ->
|
||||||
|
TransactionDetailsCard(transaction = payDetail)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Review the transaction before proceeding to void.",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onBack,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.White,
|
||||||
|
contentColor = Color.LegacyRed
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Back")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { onProceedVoid(payDetail) },
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.LegacyRed,
|
||||||
|
contentColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Void")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Transaction not found.",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TransactionDetailsCard(transaction: PayDetail) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Previous Sale",
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
DetailRow("Trace", transaction.voucherNo)
|
||||||
|
DetailRow("Amount", POSUtil.getInstance().formatAmount(transaction.amount))
|
||||||
|
DetailRow("Card No", transaction.cardNo)
|
||||||
|
DetailRow("Reference", transaction.referNo)
|
||||||
|
DetailRow("Approval", transaction.approvalCode.orEmpty())
|
||||||
|
DetailRow("Date", transaction.tradeDate)
|
||||||
|
DetailRow("Time", transaction.tradeTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DetailRow(
|
||||||
|
label: String,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = value.ifBlank { "-" },
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,313 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sale_void
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.rounded.Backspace
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
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.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VoidTraceScreen(
|
||||||
|
voidViewModel: VoidViewModel,
|
||||||
|
onNavigateTranDetail: (String) -> Unit,
|
||||||
|
onBack: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val tag = "VoidTraceScreen"
|
||||||
|
var traceNumber by remember { mutableStateOf("") }
|
||||||
|
var searchedTrace by remember { mutableStateOf("") }
|
||||||
|
var lastNavigatedTrace by remember { mutableStateOf<String?>(null) }
|
||||||
|
val displayTraceNumber = traceNumber.padStart(6, '0').ifEmpty { "000000" }
|
||||||
|
val recentTransactions by voidViewModel.getLastThreeTransactions().observeAsState(emptyList())
|
||||||
|
|
||||||
|
val transactionSource: LiveData<PayDetail?> = remember(searchedTrace) {
|
||||||
|
if (searchedTrace.isBlank()) {
|
||||||
|
MutableLiveData<PayDetail?>(null)
|
||||||
|
} else {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
voidViewModel.getVoidTrans(
|
||||||
|
TransactionsType.SALE.value,
|
||||||
|
searchedTrace
|
||||||
|
) as LiveData<PayDetail?>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val transaction by transactionSource.observeAsState()
|
||||||
|
val hasSearched = searchedTrace.isNotBlank()
|
||||||
|
val headerMessage = if (hasSearched && transaction == null) {
|
||||||
|
"Warning: no previous sale transaction found for trace $searchedTrace."
|
||||||
|
} else {
|
||||||
|
"Input trace number to find previous sale transaction."
|
||||||
|
}
|
||||||
|
val headerColor = if (hasSearched && transaction == null) {
|
||||||
|
Color.LegacyRed
|
||||||
|
} else {
|
||||||
|
Color.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(searchedTrace) {
|
||||||
|
if (searchedTrace != lastNavigatedTrace) {
|
||||||
|
lastNavigatedTrace = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(transaction?.voucherNo) {
|
||||||
|
val matchedTrace = transaction?.voucherNo ?: return@LaunchedEffect
|
||||||
|
if (lastNavigatedTrace == matchedTrace) return@LaunchedEffect
|
||||||
|
lastNavigatedTrace = matchedTrace
|
||||||
|
onNavigateTranDetail(matchedTrace)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LaunchedEffect(searchedTrace, transaction) {
|
||||||
|
Log.d(
|
||||||
|
tag,
|
||||||
|
"Recent DB traces: ${
|
||||||
|
recentTransactions.joinToString { detail ->
|
||||||
|
"${detail.voucherNo}:${detail.transType}:${detail.tradeAnswerCode}"
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
if (searchedTrace.isNotBlank()) {
|
||||||
|
Log.d(
|
||||||
|
tag,
|
||||||
|
"Search trace=$searchedTrace result=${transaction?.voucherNo ?: "NOT_FOUND"}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.IvoryBeige,
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Void",
|
||||||
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
onIconClick = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = headerMessage,
|
||||||
|
color = headerColor,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Trace Number",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = displayTraceNumber,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 32.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Enter 6-digit trace number",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
TraceNumberKeypad(
|
||||||
|
onNumberClick = { value ->
|
||||||
|
if (traceNumber.length < 6) {
|
||||||
|
traceNumber = (traceNumber + value).take(6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 18.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onBack,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.White,
|
||||||
|
contentColor = Color.LegacyRed
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
searchedTrace = traceNumber.padStart(6, '0')
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(56.dp),
|
||||||
|
enabled = traceNumber.isNotBlank(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.LegacyRed,
|
||||||
|
contentColor = Color.White,
|
||||||
|
disabledContainerColor = Color.LegacyRed.copy(alpha = 0.5f),
|
||||||
|
disabledContentColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Enter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(top = 110.dp, end = 16.dp)
|
||||||
|
.clickable(enabled = traceNumber.isNotEmpty()) {
|
||||||
|
traceNumber = traceNumber.dropLast(1)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Icon(
|
||||||
|
imageVector = Icons.Rounded.Backspace,
|
||||||
|
contentDescription = "Delete",
|
||||||
|
tint = if (traceNumber.isNotEmpty()) Color.LegacyRed else Color.Gray,
|
||||||
|
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TraceNumberKeypad(
|
||||||
|
onNumberClick: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val keys = listOf(
|
||||||
|
listOf("1", "2", "3"),
|
||||||
|
listOf("4", "5", "6"),
|
||||||
|
listOf("7", "8", "9"),
|
||||||
|
listOf("", "0", "00")
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
keys.forEach { row ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
row.forEach { key ->
|
||||||
|
if (key.isBlank()) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(66.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TraceKeypadButton(
|
||||||
|
text = key,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = { onNumberClick(key) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TraceKeypadButton(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.height(66.dp)
|
||||||
|
.shadow(
|
||||||
|
elevation = 2.dp,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
clip = false
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.clickable { onClick() },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sale_void
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.utsmyanmar.baselib.repo.Repository
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.system.SingleLiveEvent
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class VoidViewModel @Inject constructor(
|
||||||
|
private val repository: Repository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val inputTrace = SingleLiveEvent<String>()
|
||||||
|
val lists = SingleLiveEvent<List<PayDetail>>()
|
||||||
|
|
||||||
|
fun getVoidTransactions(transType: Int): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getVoidableTransactions(transType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGenericVoidTransactions(
|
||||||
|
transType: Int,
|
||||||
|
voucherNo: String,
|
||||||
|
isEmv: Boolean
|
||||||
|
): LiveData<PayDetail> {
|
||||||
|
return repository.getGenericVoidTransaction(transType, voucherNo, isEmv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVoidTrans(transType: Int, voucherNo: String): LiveData<PayDetail> {
|
||||||
|
return repository.getVoidTransaction(transType, voucherNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun observeVoidTrans(transType: Int) {
|
||||||
|
getVoidTransactions(transType).observeForever(object : Observer<List<PayDetail>> {
|
||||||
|
override fun onChanged(payDetails: List<PayDetail>) {
|
||||||
|
if (lists.value == null) {
|
||||||
|
lists.postValue(payDetails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchTransaction(voucherNo: String): LiveData<PayDetail> {
|
||||||
|
return repository.searchTransaction(voucherNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastThreeTransactions(): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getLastThreeTransactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sending_to_host
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.mob.utsmyanmar.model.TransResultStatus
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import com.mob.utsmyanmar.viewmodel.ProcessingTransaction
|
||||||
|
|
||||||
|
private const val MOCK_HOST_DELAY_MS = 2000L
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProcessingRoute(
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
transProcessViewModel: TransProcessViewModel,
|
||||||
|
onNavigateTransactionResult: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
val transStatus by transProcessViewModel.transResultStatus.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
transProcessViewModel.startOnlineProcess()
|
||||||
|
}
|
||||||
|
LaunchedEffect(transStatus) {
|
||||||
|
// sharedViewModel.saveMockHostResultForTesting()
|
||||||
|
// delay(MOCK_HOST_DELAY_MS)
|
||||||
|
|
||||||
|
|
||||||
|
when(transStatus) {
|
||||||
|
|
||||||
|
|
||||||
|
TransResultStatus.SUCCESS -> onNavigateTransactionResult()
|
||||||
|
TransResultStatus.FAIL -> onNavigateTransactionResult()
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessingScreen()
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sending_to_host
|
||||||
|
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
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.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.Black
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Primary
|
||||||
|
import com.mob.utsmyanmar.ui.theme.White
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProcessingScreen() {
|
||||||
|
Scaffold(
|
||||||
|
containerColor = White
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.background(White)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 32.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = Primary,
|
||||||
|
shape = RoundedCornerShape(24.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 32.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(color = White)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Processing",
|
||||||
|
color = White,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Please wait while your transaction is being processed.",
|
||||||
|
color = White,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Transaction processing in progress.",
|
||||||
|
color = Black,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.settlement
|
||||||
|
|
||||||
|
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.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.utils.POSUtil
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettlementScreen(
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onStartSettlement: () -> Unit
|
||||||
|
) {
|
||||||
|
val records by sharedViewModel.getSettlementRecords().observeAsState(emptyList())
|
||||||
|
val totalAmount = records.sumOf { it.amount }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.IvoryBeige,
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Settlement",
|
||||||
|
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
onIconClick = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(18.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "Settlement Summary",
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
SummaryRow("Record Count", records.size.toString())
|
||||||
|
SummaryRow("Total Amount", POSUtil.getInstance().formatAmount(totalAmount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Unsettled Records",
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White)
|
||||||
|
) {
|
||||||
|
if (records.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = "No records available for settlement.",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
modifier = Modifier.padding(18.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||||
|
) {
|
||||||
|
items(records) { record ->
|
||||||
|
SettlementRecordRow(record = record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onStartSettlement,
|
||||||
|
enabled = records.isNotEmpty(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.LegacyRed,
|
||||||
|
contentColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Start Settlement")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SummaryRow(
|
||||||
|
label: String,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SettlementRecordRow(record: PayDetail) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = record.transType.ifBlank { "UNKNOWN" },
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = POSUtil.getInstance().formatAmount(record.amount),
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Trace ${record.voucherNo.ifBlank { "-" }}",
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Card ${record.cardNo.ifBlank { "-" }}",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
HorizontalDivider(color = Color.Gray.copy(alpha = 0.25f))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,440 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.settlement
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.mob.utsmyanmar.config.Constants
|
||||||
|
import com.mob.utsmyanmar.model.SettlementType
|
||||||
|
import com.mob.utsmyanmar.model.TransactionStatus
|
||||||
|
import com.utsmyanmar.baselib.repo.Repository
|
||||||
|
import com.utsmyanmar.paylibs.Constant
|
||||||
|
import com.utsmyanmar.paylibs.batch_upload.BatchListener
|
||||||
|
import com.utsmyanmar.paylibs.batch_upload.BatchUploadProcess
|
||||||
|
import com.utsmyanmar.paylibs.isobuilder.ISOMode
|
||||||
|
import com.utsmyanmar.paylibs.isobuilder.builderx.ISOMsgX
|
||||||
|
import com.utsmyanmar.paylibs.isobuilder.builderx.ISOVersion
|
||||||
|
import com.utsmyanmar.paylibs.model.MsgField
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.model.SettleData
|
||||||
|
import com.utsmyanmar.paylibs.model.TradeData
|
||||||
|
import com.utsmyanmar.paylibs.network.ISOCallback
|
||||||
|
import com.utsmyanmar.paylibs.network.ISOSocket
|
||||||
|
import com.utsmyanmar.paylibs.utils.MessageType
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.enums.HostName
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.BitmapConfig
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionType
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import com.utsmyanmar.paylibs.utils.params.Params
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class SettlementUiState(
|
||||||
|
val saleCount: Int = 0,
|
||||||
|
val saleAmount: Long = 0L,
|
||||||
|
|
||||||
|
val preCount: Int = 0,
|
||||||
|
val preAmount: Long = 0L,
|
||||||
|
|
||||||
|
val refundCount: Int = 0,
|
||||||
|
val refundAmount: Long = 0L,
|
||||||
|
|
||||||
|
val caCount: Int = 0,
|
||||||
|
val caAmount: Long = 0L,
|
||||||
|
|
||||||
|
val settlementType: SettlementType? = null,
|
||||||
|
val isNoData: Boolean = false,
|
||||||
|
val isSentData: Boolean = false,
|
||||||
|
val bottomLayout: Int = 0,
|
||||||
|
|
||||||
|
val status: TransactionStatus? = null,
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface SettlementEvent {
|
||||||
|
data class ShowStatus(val status: TransactionStatus) : SettlementEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SettlementViewModel @Inject constructor(
|
||||||
|
private val repository: Repository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = SettlementViewModel::class.java.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
private var payDetail: PayDetail? = null
|
||||||
|
private var payDetails: List<PayDetail>? = null
|
||||||
|
|
||||||
|
private val deleteTrans = arrayListOf<PayDetail>()
|
||||||
|
|
||||||
|
private var flag = false
|
||||||
|
private var errorFlag = false
|
||||||
|
private var isSecondCall = false
|
||||||
|
private var batchIndex = 0
|
||||||
|
|
||||||
|
private var bitmap = ""
|
||||||
|
|
||||||
|
private val isoMsgX: ISOMsgX =
|
||||||
|
ISOMsgX.ISOMsgXBuilder(
|
||||||
|
ISOVersion.VERSION_1993,
|
||||||
|
ISOMode.BOTH_HEADER_TPDU,
|
||||||
|
HostName.BPC
|
||||||
|
).build()
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(SettlementUiState())
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _events = Channel<SettlementEvent>(Channel.BUFFERED)
|
||||||
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
|
fun getLastSettlement(voucherNo: String): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getLastSettlement(voucherNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSettlement(): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getSettlement()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSettlementPOS(): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getSettlementPOS()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeleteTrans(batchNo: String): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getDeleteTrans(batchNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAdditionalSettlementPOS(): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getAdditionalSettlementPOS(
|
||||||
|
SystemParamsOperation.getInstance().getCurrentBatchNum()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPayDetails(list: List<PayDetail>) {
|
||||||
|
payDetails = list
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSettlementSummary(
|
||||||
|
saleCount: Int,
|
||||||
|
saleAmount: Long,
|
||||||
|
preCount: Int,
|
||||||
|
preAmount: Long,
|
||||||
|
refundCount: Int,
|
||||||
|
refundAmount: Long,
|
||||||
|
caCount: Int,
|
||||||
|
caAmount: Long
|
||||||
|
) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
saleCount = saleCount,
|
||||||
|
saleAmount = saleAmount,
|
||||||
|
preCount = preCount,
|
||||||
|
preAmount = preAmount,
|
||||||
|
refundCount = refundCount,
|
||||||
|
refundAmount = refundAmount,
|
||||||
|
caCount = caCount,
|
||||||
|
caAmount = caAmount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSettlementType(type: SettlementType) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(settlementType = type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePayDetail(payDetail: PayDetail) {
|
||||||
|
repository.updatePayDetail(payDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertPayDetail(payDetail: PayDetail) {
|
||||||
|
repository.insertPayDetail(payDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDB() {
|
||||||
|
payDetails?.forEach { pay ->
|
||||||
|
repository.deletePayDetail(pay)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTrans.forEach { pay ->
|
||||||
|
repository.deletePayDetail(pay)
|
||||||
|
}
|
||||||
|
|
||||||
|
payDetails = emptyList()
|
||||||
|
deleteTrans.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startSettlementProcess() {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(isLoading = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemParamsOperation.getInstance().getIncrementBatchNo()
|
||||||
|
|
||||||
|
requestOnlineProcessSettlement()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startPrintSettlementProcess() {
|
||||||
|
// PrintReceipt.getInstance().printSettlementReceiptPOS(...)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testServiceClass() {
|
||||||
|
LogUtil.d(TAG, "SettlementViewModel works!")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestOnlineProcessSettlement() {
|
||||||
|
val state = _uiState.value
|
||||||
|
|
||||||
|
val hostName = HostName.BPC
|
||||||
|
val field60 = SystemParamsOperation.getInstance().getCurrentBatchNum()
|
||||||
|
|
||||||
|
val sale2Count = state.saleCount + state.preCount
|
||||||
|
val sale2Amount = state.saleAmount + state.preAmount
|
||||||
|
|
||||||
|
val totalSaleCount = String.format(Locale.getDefault(), "%03d", sale2Count)
|
||||||
|
val totalSaleAmount = String.format(Locale.getDefault(), "%010d00", sale2Amount)
|
||||||
|
|
||||||
|
val totalRefundCount = String.format(Locale.getDefault(), "%03d", state.refundCount)
|
||||||
|
val totalRefundAmount = String.format(
|
||||||
|
Locale.getDefault(),
|
||||||
|
"%010d00",
|
||||||
|
if (state.refundAmount == 0L) 0 else state.refundAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
val totalDebitSaleCount = String.format(Locale.getDefault(), "%03d", state.caCount)
|
||||||
|
val totalDebitSaleAmount = String.format(
|
||||||
|
Locale.getDefault(),
|
||||||
|
"%010d00",
|
||||||
|
if (state.caAmount == 0L) 0 else state.caAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
val totalERefundCount = String.format(Locale.getDefault(), "%03d", 0)
|
||||||
|
val totalERefundAmount = String.format(Locale.getDefault(), "%010d00", 0)
|
||||||
|
|
||||||
|
val tradeData = Params.newTrade(true)
|
||||||
|
val currentPayDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetail = currentPayDetail
|
||||||
|
|
||||||
|
bitmap = BitmapConfig.BPC_SETTLEMENT
|
||||||
|
|
||||||
|
currentPayDetail.transType = TransactionsType.SETTLEMENT.name
|
||||||
|
currentPayDetail.transactionType = TransactionType.SETTLEMENT
|
||||||
|
|
||||||
|
if (!flag) {
|
||||||
|
currentPayDetail.processCode = TransactionsType.SETTLEMENT.processCode
|
||||||
|
} else {
|
||||||
|
bitmap = BitmapConfig.BPC_SETTLEMENT_TRAILER
|
||||||
|
currentPayDetail.processCode = "910000"
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPayDetail.batchNo = SystemParamsOperation.getInstance().getCurrentBatchNum()
|
||||||
|
|
||||||
|
currentPayDetail.settleList =
|
||||||
|
"${state.saleCount}:${state.saleAmount}-" +
|
||||||
|
"${state.caCount}:${state.caAmount}-" +
|
||||||
|
"${state.refundCount}:${state.refundAmount}-" +
|
||||||
|
"${state.preCount}:${state.preAmount}"
|
||||||
|
|
||||||
|
val settleData = SettleData(
|
||||||
|
state.saleCount,
|
||||||
|
state.saleAmount,
|
||||||
|
state.preCount,
|
||||||
|
state.preAmount,
|
||||||
|
state.refundCount,
|
||||||
|
state.refundAmount,
|
||||||
|
state.caCount,
|
||||||
|
state.caAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
currentPayDetail.settleDataObj = settleData
|
||||||
|
|
||||||
|
if (hostName == HostName.BPC) {
|
||||||
|
val totalAmount =
|
||||||
|
state.saleAmount + state.preAmount + state.refundAmount + state.caAmount
|
||||||
|
|
||||||
|
val settlementData = if (state.refundAmount != 0L) {
|
||||||
|
val creditTotal = state.saleAmount + state.preAmount + state.caAmount
|
||||||
|
val subTotal = creditTotal - state.refundAmount
|
||||||
|
|
||||||
|
if (subTotal < 0L) {
|
||||||
|
"D" + String.format(Locale.getDefault(), "%012d", kotlin.math.abs(subTotal))
|
||||||
|
} else {
|
||||||
|
"C" + String.format(Locale.getDefault(), "%012d", subTotal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"C" + String.format(Locale.getDefault(), "%012d", totalAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPayDetail.settleData = settlementData
|
||||||
|
currentPayDetail.amount = totalAmount
|
||||||
|
} else {
|
||||||
|
currentPayDetail.settleData =
|
||||||
|
totalSaleCount +
|
||||||
|
totalSaleAmount +
|
||||||
|
totalRefundCount +
|
||||||
|
totalRefundAmount +
|
||||||
|
totalDebitSaleCount +
|
||||||
|
totalDebitSaleAmount +
|
||||||
|
totalERefundCount +
|
||||||
|
totalERefundAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
tradeData.payDetail = currentPayDetail
|
||||||
|
tradeData.field60 = field60
|
||||||
|
|
||||||
|
val sendBytes = isoMsgX.buildISOPackets(
|
||||||
|
tradeData,
|
||||||
|
bitmap,
|
||||||
|
MessageType.SETTLEMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "Starting SETTLEMENT process...")
|
||||||
|
|
||||||
|
ISOSocket.getInstance().enqueue(
|
||||||
|
sendBytes,
|
||||||
|
sendBytes.size,
|
||||||
|
false,
|
||||||
|
object : ISOCallback {
|
||||||
|
|
||||||
|
override fun onReceive(bytes: ByteArray, length: Int) {
|
||||||
|
val responseMap: Map<String, MsgField>? =
|
||||||
|
isoMsgX.parseISOPackets(bytes, length)
|
||||||
|
|
||||||
|
if (responseMap != null) {
|
||||||
|
val resultStr = try {
|
||||||
|
responseMap["F039"]?.dataStr.orEmpty()
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
currentPayDetail.isNeedReversal = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPayDetail.tradeAnswerCode = resultStr
|
||||||
|
|
||||||
|
when {
|
||||||
|
resultStr == Constant.ANSWER_CODE_ACCEPT ||
|
||||||
|
resultStr == Constant.ANSWER_CODE_APPROVED -> {
|
||||||
|
currentPayDetail.isNeedReversal = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resultStr == "95" || resultStr == "095" -> {
|
||||||
|
currentPayDetail.isNeedReversal = !flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorFlag = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(msg: String) {
|
||||||
|
if (msg != Constants.REVERSAL) {
|
||||||
|
if (!isSecondCall) {
|
||||||
|
ISOSocket.getInstance().switchIp()
|
||||||
|
|
||||||
|
postStatus(TransactionStatus.ON_SECONDARY)
|
||||||
|
|
||||||
|
isSecondCall = true
|
||||||
|
requestOnlineProcessSettlement()
|
||||||
|
} else {
|
||||||
|
postStatus(TransactionStatus.ON_ERROR)
|
||||||
|
|
||||||
|
currentPayDetail.isNeedReversal = true
|
||||||
|
isSecondCall = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
postStatus(TransactionStatus.ON_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
if (currentPayDetail.isNeedReversal) {
|
||||||
|
flag = true
|
||||||
|
batchUploadProcess()
|
||||||
|
} else {
|
||||||
|
flag = false
|
||||||
|
batchIndex = 0
|
||||||
|
|
||||||
|
updateDB()
|
||||||
|
insertPayDetail(currentPayDetail)
|
||||||
|
|
||||||
|
if (errorFlag) {
|
||||||
|
postStatus(TransactionStatus.ON_ERROR)
|
||||||
|
} else {
|
||||||
|
postStatus(TransactionStatus.ON_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(isLoading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun batchUploadProcess() {
|
||||||
|
postStatus(TransactionStatus.ON_BATCH_UPLOAD)
|
||||||
|
|
||||||
|
val currentPayDetails = payDetails
|
||||||
|
|
||||||
|
if (currentPayDetails.isNullOrEmpty()) {
|
||||||
|
requestOnlineProcessSettlement()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val uploadPayDetail = currentPayDetails[batchIndex]
|
||||||
|
|
||||||
|
val tradeData = TradeData().apply {
|
||||||
|
payDetail = uploadPayDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
BatchUploadProcess.getInstance()
|
||||||
|
.enqueue(tradeData)
|
||||||
|
.startBatchUpload(object : BatchListener {
|
||||||
|
|
||||||
|
override fun onSuccessBatch() {
|
||||||
|
if (batchIndex < currentPayDetails.size - 1) {
|
||||||
|
LogUtil.d(TAG, "Pay detail size: ${currentPayDetails.size}")
|
||||||
|
LogUtil.d(TAG, "Count value: $batchIndex")
|
||||||
|
|
||||||
|
batchIndex++
|
||||||
|
batchUploadProcess()
|
||||||
|
} else {
|
||||||
|
requestOnlineProcessSettlement()
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtil.e(TAG, "Batch Upload Success")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailBatch() {
|
||||||
|
LogUtil.e(TAG, "Batch Upload Fail")
|
||||||
|
postStatus(TransactionStatus.ON_ERROR)
|
||||||
|
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(isLoading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postStatus(status: TransactionStatus) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(status = status)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
_events.send(SettlementEvent.ShowStatus(status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
262
app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnScreen.kt
Normal file
262
app/src/main/java/com/mob/utsmyanmar/ui/sign_on/SignOnScreen.kt
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sign_on
|
||||||
|
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material.icons.filled.ErrorOutline
|
||||||
|
import androidx.compose.material.icons.filled.Sync
|
||||||
|
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.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
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.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color as AppColor
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignOnRoute(
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onNavigateResult: (Boolean, String) -> Unit,
|
||||||
|
signOnViewModel: SignOnViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val state by signOnViewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(signOnViewModel) {
|
||||||
|
signOnViewModel.resultEvents.collect { result ->
|
||||||
|
onNavigateResult(result.isSuccess, result.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SignOnScreen(
|
||||||
|
state = state,
|
||||||
|
onBack = onBack,
|
||||||
|
onStartSignOn = signOnViewModel::startSignOn
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignOnScreen(
|
||||||
|
state: SignOnUiState,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onStartSignOn: () -> Unit
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
containerColor = AppColor.IvoryBeige,
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Sign On",
|
||||||
|
icon = Icons.Default.ArrowBack,
|
||||||
|
onIconClick = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = AppColor.White),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(20.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Sync,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = AppColor.LegacyRed,
|
||||||
|
modifier = Modifier.height(42.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Host Sign On",
|
||||||
|
fontSize = 22.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = AppColor.LegacyRed
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Run sign on and verify immediately whether the host accepted or rejected the terminal.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = AppColor.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = AppColor.White)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(20.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Current Status",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = AppColor.Black
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = if (state.isLoading) AppColor.GoldenGlow.copy(alpha = 0.18f) else AppColor.SkylineBlue.copy(alpha = 0.12f),
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
if (state.isLoading) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
CircularProgressIndicator(color = AppColor.LegacyRed)
|
||||||
|
Text(text = state.statusText, color = AppColor.Black)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(text = state.statusText, color = AppColor.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onStartSignOn,
|
||||||
|
enabled = !state.isLoading,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = AppColor.CrimsonRed,
|
||||||
|
disabledContainerColor = AppColor.Gray
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = if (state.isLoading) "Processing..." else "Start Sign On")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.canRetry) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onStartSignOn,
|
||||||
|
enabled = !state.isLoading,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Retry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignOnResultScreen(
|
||||||
|
isSuccess: Boolean,
|
||||||
|
message: String,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
onRetry: () -> Unit
|
||||||
|
) {
|
||||||
|
val accent = if (isSuccess) AppColor.Success else AppColor.LegacyRed
|
||||||
|
val background = if (isSuccess) AppColor.Success.copy(alpha = 0.12f) else AppColor.CrimsonRed.copy(alpha = 0.12f)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
containerColor = AppColor.IvoryBeige,
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Sign On Result",
|
||||||
|
icon = Icons.Default.ArrowBack,
|
||||||
|
onIconClick = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(24.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = AppColor.White)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(background, RoundedCornerShape(50))
|
||||||
|
.padding(18.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isSuccess) Icons.Default.CheckCircle else Icons.Default.ErrorOutline,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = accent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = if (isSuccess) "Sign On Success" else "Sign On Failed",
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = accent
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = message,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
if (!isSuccess) {
|
||||||
|
Button(
|
||||||
|
onClick = onRetry,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = AppColor.CrimsonRed)
|
||||||
|
) {
|
||||||
|
Text(text = "Try Again")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onDone,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Back To Dashboard")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.sign_on
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.utsmyanmar.paylibs.sign_on.SignOnListener
|
||||||
|
import com.utsmyanmar.paylibs.sign_on.SignOnProcess
|
||||||
|
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
|
||||||
|
|
||||||
|
data class SignOnUiState(
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val statusText: String = "Ready to start sign on.",
|
||||||
|
val canRetry: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SignOnResult(
|
||||||
|
val isSuccess: Boolean,
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class SignOnViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(SignOnUiState())
|
||||||
|
val uiState: StateFlow<SignOnUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _resultEvents = MutableSharedFlow<SignOnResult>()
|
||||||
|
val resultEvents: SharedFlow<SignOnResult> = _resultEvents.asSharedFlow()
|
||||||
|
|
||||||
|
fun startSignOn() {
|
||||||
|
if (_uiState.value.isLoading) return
|
||||||
|
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = true,
|
||||||
|
statusText = "Signing on to host...",
|
||||||
|
canRetry = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SignOnProcess.getInstance()
|
||||||
|
.enqueue()
|
||||||
|
.startSignOn(object : SignOnListener {
|
||||||
|
override fun onSuccessSignOn() {
|
||||||
|
dispatchResult(
|
||||||
|
SignOnResult(
|
||||||
|
isSuccess = true,
|
||||||
|
message = "Sign on completed successfully."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailureSignOn(resultCode: Int?) {
|
||||||
|
dispatchResult(
|
||||||
|
SignOnResult(
|
||||||
|
isSuccess = false,
|
||||||
|
message = "Sign on failed. Response code: ${resultCode ?: -1}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onNetworkFailSignOn(message: String?) {
|
||||||
|
dispatchResult(
|
||||||
|
SignOnResult(
|
||||||
|
isSuccess = false,
|
||||||
|
message = message?.takeIf { it.isNotBlank() } ?: "Network error during sign on."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchResult(result: SignOnResult) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
statusText = result.message,
|
||||||
|
canRetry = !result.isSuccess
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_resultEvents.emit(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,13 +5,22 @@ import androidx.compose.ui.graphics.Color
|
|||||||
val Purple80 = Color(0xFFD0BCFF)
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
val Purple40 = Color(0xFF6650a4)
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
val Pink40 = Color(0xFF7D5260)
|
val Pink40 = Color(0xFF7D5260)
|
||||||
|
|
||||||
val Primary = Color(0xFFCA2027)
|
val Primary = Color(0xFFCA2027)
|
||||||
|
|
||||||
val White = Color(0xFFFFFFFF)
|
val White = Color(0xFFFFFFFF)
|
||||||
|
val Black = Color(0xFF000000)
|
||||||
|
|
||||||
val Black = Color(0xFF000000)
|
object Color {
|
||||||
|
val IvoryBeige = Color(0xFFFFF5E6)
|
||||||
|
val LegacyRed = Color(0xFF6F0D1E)
|
||||||
|
val CrimsonRed = Color(0xFFCD2029)
|
||||||
|
val GoldenGlow = Color(0xFFFFCF66)
|
||||||
|
val SkylineBlue = Color(0xFF47A2DA)
|
||||||
|
val White = Color(0xFFFFFFFF)
|
||||||
|
val Black = Color(0xFF000000)
|
||||||
|
val Gray = Color(0xFF898989)
|
||||||
|
val Success = Color(0xFF007E33)
|
||||||
|
val Error = Color(0xFFCD2029)
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.transaction_result
|
||||||
|
|
||||||
|
sealed interface TransactionResultEvent {
|
||||||
|
data object Start: TransactionResultEvent
|
||||||
|
data object BackClick: TransactionResultEvent
|
||||||
|
data object RetryPrint: TransactionResultEvent
|
||||||
|
data object PrintLater: TransactionResultEvent
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.transaction_result
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransactionResultRoute(
|
||||||
|
viewModel: TransactionResultViewModel = hiltViewModel(),
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
onNavigateMain: () -> Unit,
|
||||||
|
onNavigatePrintReceipt: () -> Unit,
|
||||||
|
onShowError: (String) -> Unit,
|
||||||
|
onShowSuccess: (String) -> Unit,
|
||||||
|
onShowPrinterDialog: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
val canGoBack = sharedViewModel.transactionsType.value != TransactionsType.SALE
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.onEvent(
|
||||||
|
TransactionResultEvent.Start,
|
||||||
|
sharedViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.uiEvent.collect { event ->
|
||||||
|
when (event) {
|
||||||
|
TransactionResultUiEvent.NavigateMain -> onNavigateMain()
|
||||||
|
TransactionResultUiEvent.NavigatePrintReceipt -> onNavigatePrintReceipt()
|
||||||
|
is TransactionResultUiEvent.ShowError -> onShowError(event.message)
|
||||||
|
is TransactionResultUiEvent.ShowSuccess -> onShowSuccess(event.message)
|
||||||
|
is TransactionResultUiEvent.ShowPrinterDialog -> onShowPrinterDialog(event.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionResultScreen(
|
||||||
|
state = state,
|
||||||
|
canGoBack = canGoBack,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.onEvent(it, sharedViewModel)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,289 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.transaction_result
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CalendarMonth
|
||||||
|
import androidx.compose.material.icons.filled.Numbers
|
||||||
|
import androidx.compose.material.icons.filled.Print
|
||||||
|
import androidx.compose.material.icons.rounded.Check
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
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.preview.P2Preview
|
||||||
|
import com.mob.utsmyanmar.ui.theme.Color
|
||||||
|
import com.utsmyanmar.paylibs.print.PrintReceipt
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransactionResultScreen(
|
||||||
|
state: TransactionResultState,
|
||||||
|
canGoBack: Boolean,
|
||||||
|
onEvent: (TransactionResultEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
BackHandler(enabled = canGoBack) {
|
||||||
|
onEvent(TransactionResultEvent.BackClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.IvoryBeige
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.IvoryBeige)
|
||||||
|
.padding(paddingValues)
|
||||||
|
.statusBarsPadding()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(horizontal = 20.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
SuccessIcon()
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = state.title,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = state.message.ifBlank { "Your payment has been processed successfully." },
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
|
||||||
|
AmountCard(amount = "50,000")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
InfoCard(
|
||||||
|
date = "26 May 2026",
|
||||||
|
time = "12:06 PM",
|
||||||
|
transactionId = "1234568333"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SuccessIcon() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(132.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(132.dp)
|
||||||
|
.background(
|
||||||
|
color = Color.Success.copy(alpha = 0.12f),
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.background(
|
||||||
|
color = Color.Success,
|
||||||
|
shape = CircleShape
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Check,
|
||||||
|
contentDescription = "Success",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(64.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AmountCard(
|
||||||
|
amount: String
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(130.dp)
|
||||||
|
.shadow(
|
||||||
|
elevation = 6.dp,
|
||||||
|
shape = RoundedCornerShape(22.dp),
|
||||||
|
clip = false
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(22.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(vertical = 18.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "PAID AMOUNT",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = amount,
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 30.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "MMK",
|
||||||
|
color = Color.LegacyRed,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoCard(
|
||||||
|
date: String,
|
||||||
|
time: String,
|
||||||
|
transactionId: String
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(130.dp)
|
||||||
|
.shadow(
|
||||||
|
elevation = 4.dp,
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
clip = false
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 18.dp),
|
||||||
|
verticalArrangement = Arrangement.SpaceAround
|
||||||
|
) {
|
||||||
|
InfoItem(
|
||||||
|
icon = Icons.Default.CalendarMonth,
|
||||||
|
title = "Date & Time",
|
||||||
|
value = date,
|
||||||
|
subValue = time,
|
||||||
|
// modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
InfoItem(
|
||||||
|
icon = Icons.Default.Numbers,
|
||||||
|
title = "Transaction ID",
|
||||||
|
value = transactionId,
|
||||||
|
subValue = "",
|
||||||
|
// modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
title: String,
|
||||||
|
value: String,
|
||||||
|
subValue: String,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(46.dp)
|
||||||
|
.background(
|
||||||
|
color = Color.LegacyRed.copy(alpha = 0.1f),
|
||||||
|
shape = CircleShape
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.LegacyRed,
|
||||||
|
modifier = Modifier.size(25.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
if (subValue.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = subValue,
|
||||||
|
color = Color.Black,
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@P2Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewTransactionResultScreen() {
|
||||||
|
TransactionResultScreen(
|
||||||
|
state = TransactionResultState(
|
||||||
|
"Success"
|
||||||
|
),
|
||||||
|
canGoBack = true,
|
||||||
|
onEvent = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.transaction_result
|
||||||
|
|
||||||
|
data class TransactionResultState(
|
||||||
|
val title: String = "Transaction Result",
|
||||||
|
val message: String = "",
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
)
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.transaction_result
|
||||||
|
|
||||||
|
sealed interface TransactionResultUiEvent {
|
||||||
|
data object NavigateMain : TransactionResultUiEvent
|
||||||
|
data object NavigatePrintReceipt : TransactionResultUiEvent
|
||||||
|
data class ShowError(val message: String) : TransactionResultUiEvent
|
||||||
|
data class ShowSuccess(val message: String) : TransactionResultUiEvent
|
||||||
|
data class ShowPrinterDialog(val message: String) : TransactionResultUiEvent
|
||||||
|
}
|
||||||
@ -0,0 +1,281 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.transaction_result
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.mob.utsmyanmar.model.ecr.ECRResultStatus
|
||||||
|
import com.mob.utsmyanmar.utils.CoreUtils
|
||||||
|
import com.mob.utsmyanmar.viewmodel.SharedViewModel
|
||||||
|
import com.utsmyanmar.baselib.emv.EmvParamOperation
|
||||||
|
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
|
||||||
|
import com.utsmyanmar.ecr.ECRHelper
|
||||||
|
import com.utsmyanmar.ecr.ECRProcess
|
||||||
|
import com.utsmyanmar.ecr.data.model.TransactionsResp
|
||||||
|
import com.utsmyanmar.paylibs.Constant
|
||||||
|
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.core_utils.ByteUtil
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class TransactionResultViewModel @Inject constructor(
|
||||||
|
private val emvParamOperation: EmvParamOperation
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "TransactionResultVM"
|
||||||
|
private const val RESULT_TIMEOUT = 3000L
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(TransactionResultState())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private val _uiEvent = Channel<TransactionResultUiEvent>()
|
||||||
|
val uiEvent = _uiEvent.receiveAsFlow()
|
||||||
|
|
||||||
|
private var started = false
|
||||||
|
|
||||||
|
fun onEvent(
|
||||||
|
event: TransactionResultEvent,
|
||||||
|
sharedViewModel: SharedViewModel
|
||||||
|
) {
|
||||||
|
when (event) {
|
||||||
|
TransactionResultEvent.Start -> start(sharedViewModel)
|
||||||
|
TransactionResultEvent.BackClick -> isCardInside()
|
||||||
|
TransactionResultEvent.RetryPrint -> startPrintProcess(sharedViewModel, false)
|
||||||
|
TransactionResultEvent.PrintLater -> isCardInside()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun start(sharedViewModel: SharedViewModel) {
|
||||||
|
if (started) return
|
||||||
|
started = true
|
||||||
|
|
||||||
|
sharedViewModel.setPrintStatus(PrintStatus.FIRST_PRINT)
|
||||||
|
sharedViewModel.printXStatus.value = null
|
||||||
|
|
||||||
|
updateTitle(sharedViewModel)
|
||||||
|
observePrintStatus(sharedViewModel)
|
||||||
|
|
||||||
|
if (sharedViewModel.isEcr.value == true) {
|
||||||
|
ecrAction(sharedViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
startResultTimeout(sharedViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTitle(sharedViewModel: SharedViewModel) {
|
||||||
|
val payDetail = sharedViewModel.payDetail.value
|
||||||
|
val resultCode = payDetail?.tradeAnswerCode.orEmpty()
|
||||||
|
val qrStatus = payDetail?.qrTransStatus ?: -100
|
||||||
|
|
||||||
|
val isDemoMode = SystemParamsOperation.getInstance().demoStatus
|
||||||
|
|
||||||
|
val isSuccess = isDemoMode ||
|
||||||
|
resultCode == Constant.ANSWER_CODE_ACCEPT ||
|
||||||
|
resultCode == Constant.ANSWER_CODE_APPROVED
|
||||||
|
|
||||||
|
val title = when {
|
||||||
|
isSuccess -> "Transaction Success"
|
||||||
|
qrStatus == 1 || qrStatus == -1 || qrStatus == 2 || qrStatus == 3 -> "QR Pay"
|
||||||
|
else -> "Error"
|
||||||
|
}
|
||||||
|
|
||||||
|
val message = when {
|
||||||
|
isSuccess -> "Transaction approved"
|
||||||
|
payDetail?.tradeAnswerCode?.isNotBlank() == true -> payDetail.tradeAnswerCode
|
||||||
|
else -> "Transaction failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.value = _state.value.copy(
|
||||||
|
title = title,
|
||||||
|
message = message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startResultTimeout(sharedViewModel: SharedViewModel) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
delay(RESULT_TIMEOUT)
|
||||||
|
|
||||||
|
val payDetail = sharedViewModel.payDetail.value
|
||||||
|
|
||||||
|
if (payDetail == null) {
|
||||||
|
navigateMain()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val transactionType = sharedViewModel.transactionsType.value
|
||||||
|
|
||||||
|
when {
|
||||||
|
isNonApprovedTrade(payDetail) &&
|
||||||
|
isNonWavepayTransaction(transactionType) -> {
|
||||||
|
startPrintProcess(sharedViewModel, false)
|
||||||
|
isCardInside()
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionType == TransactionsType.SETTLEMENT -> {
|
||||||
|
startPrintProcess(sharedViewModel, true)
|
||||||
|
sendUiEvent(
|
||||||
|
TransactionResultUiEvent.ShowSuccess(
|
||||||
|
"Configs are updated"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
navigateMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
isWavePayNonSuccessTransaction(transactionType, payDetail) -> {
|
||||||
|
startPrintProcess(sharedViewModel, false)
|
||||||
|
navigateMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
SystemParamsOperation.getInstance()
|
||||||
|
.setLastSuccessTrnx(payDetail.voucherNo)
|
||||||
|
|
||||||
|
emvParamOperation.loadEmvTerminalParam()
|
||||||
|
navigatePrint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNonApprovedTrade(payDetail: PayDetail): Boolean {
|
||||||
|
return payDetail.tradeAnswerCode != Constant.ANSWER_CODE_APPROVED &&
|
||||||
|
payDetail.tradeAnswerCode != Constant.ANSWER_CODE_ACCEPT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNonWavepayTransaction(type: TransactionsType?): Boolean {
|
||||||
|
return type != TransactionsType.WAVEPAY &&
|
||||||
|
type != TransactionsType.WAVE_INQUIRY_STATUS &&
|
||||||
|
type != TransactionsType.WAVEPAY_REFUND
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isWavePayNonSuccessTransaction(
|
||||||
|
type: TransactionsType?,
|
||||||
|
payDetail: PayDetail
|
||||||
|
): Boolean {
|
||||||
|
return (type == TransactionsType.WAVEPAY && payDetail.qrTransStatus != 1) ||
|
||||||
|
(type == TransactionsType.WAVE_INQUIRY_STATUS && payDetail.qrTransStatus != 1) ||
|
||||||
|
(type == TransactionsType.WAVEPAY_REFUND && payDetail.qrTransStatus != 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observePrintStatus(sharedViewModel: SharedViewModel) {
|
||||||
|
sharedViewModel.printXStatus.observeForever { status ->
|
||||||
|
when (status) {
|
||||||
|
PrintStatus.EMPTY_PAPER_ROLL -> {
|
||||||
|
startPrintProcess(sharedViewModel, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintStatus.DONE_PRINT -> {
|
||||||
|
isCardInside()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPrintProcess(
|
||||||
|
sharedViewModel: SharedViewModel,
|
||||||
|
isSettlement: Boolean
|
||||||
|
) {
|
||||||
|
PrintHelper.getInstance().checkPaperRollStatus(
|
||||||
|
object : PaperRollStatusCallback {
|
||||||
|
|
||||||
|
override fun paperRollIsReady() {
|
||||||
|
if (isSettlement) {
|
||||||
|
sharedViewModel.startPrintProcessSettlement()
|
||||||
|
} else {
|
||||||
|
sharedViewModel.startPrintProcess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paperRollIsEmpty() {
|
||||||
|
sendUiEvent(
|
||||||
|
TransactionResultUiEvent.ShowPrinterDialog(
|
||||||
|
"Paper roll not ready"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paperRollLipIsOpened() {
|
||||||
|
sendUiEvent(
|
||||||
|
TransactionResultUiEvent.ShowPrinterDialog(
|
||||||
|
"Printer lip is opened. Please close lip."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unknownStatusOccur(code: Int) {
|
||||||
|
sendUiEvent(
|
||||||
|
TransactionResultUiEvent.ShowError(
|
||||||
|
"Check printer status: $code"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getECRResponseMessage(
|
||||||
|
sharedViewModel: SharedViewModel
|
||||||
|
): String {
|
||||||
|
val resp: TransactionsResp =
|
||||||
|
CoreUtils.getInstance(sharedViewModel).generateResponseMsg()
|
||||||
|
|
||||||
|
return ECRProcess.generateECRResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getECRResponseCMHL(
|
||||||
|
sharedViewModel: SharedViewModel
|
||||||
|
): ByteArray {
|
||||||
|
return CoreUtils.getInstance(sharedViewModel)
|
||||||
|
.generateCMHLResponse(ECRResultStatus.RESPONSE_RECEIVED)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ecrAction(sharedViewModel: SharedViewModel) {
|
||||||
|
if (SystemParamsOperation.getInstance().isCMHLEnabled) {
|
||||||
|
val response = getECRResponseCMHL(sharedViewModel)
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "ECR Response: ${ByteUtil.bytes2HexStr(response)}")
|
||||||
|
|
||||||
|
ECRHelper.send(response)
|
||||||
|
CoreUtils.getInstance(sharedViewModel).responseACKCMHL()
|
||||||
|
} else {
|
||||||
|
val response = getECRResponseMessage(sharedViewModel)
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "ECR Response: $response")
|
||||||
|
|
||||||
|
ECRHelper.send(response.toByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCardInside() {
|
||||||
|
navigateMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateMain() {
|
||||||
|
sendUiEvent(TransactionResultUiEvent.NavigateMain)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigatePrint() {
|
||||||
|
sendUiEvent(TransactionResultUiEvent.NavigatePrintReceipt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendUiEvent(event: TransactionResultUiEvent) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiEvent.send(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
app/src/main/java/com/mob/utsmyanmar/ui/version/Version.kt
Normal file
207
app/src/main/java/com/mob/utsmyanmar/ui/version/Version.kt
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package com.mob.utsmyanmar.ui.version
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.*
|
||||||
|
import androidx.compose.material.icons.filled.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.*
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.mob.utsmyanmar.ui.components.appbar.AppBar
|
||||||
|
import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel
|
||||||
|
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 VersionScreen(
|
||||||
|
onBack: () -> Unit,
|
||||||
|
deviceInfoViewModel: DeviceInfoViewModel
|
||||||
|
) {
|
||||||
|
|
||||||
|
val deviceInfo by deviceInfoViewModel.uiState.collectAsState();
|
||||||
|
val itemSpace = 20.dp;
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
deviceInfoViewModel.loadDeviceInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = "Version",
|
||||||
|
onIconClick = onBack,
|
||||||
|
icon = Icons.Default.ChevronLeft
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = Color.IvoryBeige
|
||||||
|
)
|
||||||
|
{ paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text("Device Information")
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.White
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
) {
|
||||||
|
Item(
|
||||||
|
title = "PROD NAME",
|
||||||
|
value = deviceInfo.deviceModel,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_function),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(itemSpace))
|
||||||
|
Item(
|
||||||
|
title = "TERMINAL SERIAL",
|
||||||
|
value = deviceInfo.serialNumber,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_four_vertical_bars),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(itemSpace))
|
||||||
|
Item(
|
||||||
|
title = "HARDWARE VER",
|
||||||
|
value = deviceInfo.hardwareVersion,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_gear),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(itemSpace))
|
||||||
|
Item(
|
||||||
|
title = "FIRMWARE VER",
|
||||||
|
value = deviceInfo.firmwareVersion,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_code),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(itemSpace))
|
||||||
|
Item(
|
||||||
|
title = "S/W VER",
|
||||||
|
value = deviceInfo.finalVersion,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_settings),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(itemSpace))
|
||||||
|
Item(
|
||||||
|
title = "PAYHARDWARE SERVICE VER",
|
||||||
|
value = deviceInfo.payHardwareVersion,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_protection_shield),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(itemSpace))
|
||||||
|
Item(
|
||||||
|
title = "ROM VER",
|
||||||
|
value = deviceInfo.romVersion,
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_function),
|
||||||
|
contentDescription = "icon",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color.LegacyRed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Item(
|
||||||
|
title: String,
|
||||||
|
value: String,
|
||||||
|
icon: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(50.dp)
|
||||||
|
.background(
|
||||||
|
Color.CrimsonRed.copy(alpha = 0.1f),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = title, fontSize = 14.sp)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(text = ":")
|
||||||
|
Text(text = value, fontSize = 14.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@P2Preview
|
||||||
|
@P3Preview
|
||||||
|
@Composable
|
||||||
|
fun P3PreviewVersionScreen() {
|
||||||
|
VersionScreen(
|
||||||
|
onBack = {},
|
||||||
|
deviceInfoViewModel = hiltViewModel()
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.mob.utsmyanmar.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
object AppContextHolder {
|
||||||
|
private lateinit var appContext: Context
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
appContext = context.applicationContext
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(): Context {
|
||||||
|
return appContext
|
||||||
|
}
|
||||||
|
}
|
||||||
1037
app/src/main/java/com/mob/utsmyanmar/utils/CoreUtils.kt
Normal file
1037
app/src/main/java/com/mob/utsmyanmar/utils/CoreUtils.kt
Normal file
File diff suppressed because it is too large
Load Diff
17
app/src/main/java/com/mob/utsmyanmar/utils/ECRSetups.kt
Normal file
17
app/src/main/java/com/mob/utsmyanmar/utils/ECRSetups.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.mob.utsmyanmar.utils
|
||||||
|
|
||||||
|
import com.utsmyanmar.ecr.data.model.Transactions
|
||||||
|
|
||||||
|
interface ECRSetups {
|
||||||
|
fun setUpECREchoTest()
|
||||||
|
fun setUpECRSale(trans: Transactions): Boolean
|
||||||
|
fun setUpECRQR(trans: Transactions): Boolean
|
||||||
|
fun setUpECRVoid(trans: Transactions): Boolean
|
||||||
|
fun setUpECRCashAdvance(trans: Transactions): Boolean
|
||||||
|
fun setUpECRPreAuth(trans: Transactions): Boolean
|
||||||
|
fun setUpECRPreAuthVoid(trans: Transactions): Boolean
|
||||||
|
fun setUpECRPreAuthComplete(trans: Transactions): Boolean
|
||||||
|
fun setUpECRPreAuthCompleteVoid(trans: Transactions): Boolean
|
||||||
|
fun setUpECRSettlement()
|
||||||
|
fun setUpECRRefund(trans: Transactions): Boolean
|
||||||
|
}
|
||||||
24
app/src/main/java/com/mob/utsmyanmar/utils/ECRSetupsCMHL.kt
Normal file
24
app/src/main/java/com/mob/utsmyanmar/utils/ECRSetupsCMHL.kt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package com.mob.utsmyanmar.utils
|
||||||
|
|
||||||
|
import com.kizzy.cmhl.models.PingRequest
|
||||||
|
import com.kizzy.cmhl.models.PreAuthCancellationRequest
|
||||||
|
import com.kizzy.cmhl.models.PreAuthCompletionRequest
|
||||||
|
import com.kizzy.cmhl.models.PreAuthRequest
|
||||||
|
import com.kizzy.cmhl.models.QrPaymentRequest
|
||||||
|
import com.kizzy.cmhl.models.SaleRequest
|
||||||
|
import com.kizzy.cmhl.models.SettlementRequest
|
||||||
|
import com.kizzy.cmhl.models.VoidQrPaymentRequest
|
||||||
|
import com.kizzy.cmhl.models.VoidRequest
|
||||||
|
|
||||||
|
interface ECRSetupsCMHL {
|
||||||
|
fun setUpECREchoTest()
|
||||||
|
fun setupPingRequest(trans: PingRequest)
|
||||||
|
fun setUpECRSale(trans: SaleRequest): Boolean
|
||||||
|
fun setUpECRQR(trans: QrPaymentRequest): Boolean
|
||||||
|
fun setUpECRQRVoid(trans: VoidQrPaymentRequest): Boolean
|
||||||
|
fun setUpECRVoid(trans: VoidRequest): Boolean
|
||||||
|
fun setUpECRPreAuth(trans: PreAuthRequest): Boolean
|
||||||
|
fun setUpECRPreAuthVoid(trans: PreAuthCancellationRequest): Boolean
|
||||||
|
fun setUpECRPreAuthComplete(trans: PreAuthCompletionRequest): Boolean
|
||||||
|
fun setUpECRSettlement(trans: SettlementRequest)
|
||||||
|
}
|
||||||
14
app/src/main/java/com/mob/utsmyanmar/utils/OldECRSetups.kt
Normal file
14
app/src/main/java/com/mob/utsmyanmar/utils/OldECRSetups.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.mob.utsmyanmar.utils
|
||||||
|
|
||||||
|
interface OldECRSetups {
|
||||||
|
fun setUpECRTest()
|
||||||
|
fun setUpECRSale(msg: String): Boolean
|
||||||
|
fun setUpECRVoid(msg: String): Boolean
|
||||||
|
fun setUpECRCashAdvance(msg: String): Boolean
|
||||||
|
fun setUpECRPreAuth(msg: String): Boolean
|
||||||
|
fun setUpECRPreAuthVoid(msg: String): Boolean
|
||||||
|
fun setUpECRPreAuthComplete(msg: String): Boolean
|
||||||
|
fun setUpECRPreAuthCompleteVoid(msg: String): Boolean
|
||||||
|
fun setUpECRSettlement()
|
||||||
|
fun setUpECRRefund(msg: String): Boolean
|
||||||
|
}
|
||||||
120
app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt
Normal file
120
app/src/main/java/com/mob/utsmyanmar/utils/TransactionUtil.kt
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package com.mob.utsmyanmar.utils
|
||||||
|
|
||||||
|
import com.mob.utsmyanmar.config.Constants
|
||||||
|
import com.utsmyanmar.checkxread.model.CardDataX
|
||||||
|
import com.utsmyanmar.checkxread.util.CardTypeX
|
||||||
|
import com.utsmyanmar.paylibs.model.CardInfo
|
||||||
|
import com.utsmyanmar.paylibs.model.MAGCardInfo
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.model.TradeData
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import com.utsmyanmar.paylibs.utils.params.Params
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
|
||||||
|
object TransactionUtil {
|
||||||
|
private const val TAG = "TransactionUtil"
|
||||||
|
private const val MPU_CARD_SCHEME = "MPU"
|
||||||
|
private const val VISA_CARD_SCHEME = "VISA"
|
||||||
|
private const val MASTER_CARD_SCHEME = "MASTER"
|
||||||
|
private const val UPI_CARD_SCHEME = "UPI"
|
||||||
|
private val qrTerminalId: String
|
||||||
|
get() = SystemParamsOperation.getInstance().secHostTerminalId
|
||||||
|
|
||||||
|
private val qrMerchantId: String
|
||||||
|
get() = SystemParamsOperation.getInstance().secHostMerchantId
|
||||||
|
|
||||||
|
fun getQRMerchantId(): String {
|
||||||
|
return qrMerchantId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getQRTerminalId(): String {
|
||||||
|
return qrTerminalId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initMPUTransaction(
|
||||||
|
cardDataX: CardDataX,
|
||||||
|
cardTypeX: CardTypeX
|
||||||
|
): TradeData {
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "CardDataX : $cardDataX")
|
||||||
|
|
||||||
|
val tradeData = Params.newTrade(false)
|
||||||
|
val payDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetail.cardNo = cardDataX.pan
|
||||||
|
payDetail.expDate = cardDataX.exp
|
||||||
|
payDetail.cardType = cardTypeX.value
|
||||||
|
payDetail.accountType = MPU_CARD_SCHEME
|
||||||
|
payDetail.cardHolderName = cardDataX.cardHolderName
|
||||||
|
|
||||||
|
val cardInfo = CardInfo()
|
||||||
|
|
||||||
|
val magCardInfo = MAGCardInfo()
|
||||||
|
magCardInfo.track2Cipher = cardDataX.track2
|
||||||
|
|
||||||
|
cardInfo.magCardInfo = magCardInfo
|
||||||
|
|
||||||
|
payDetail.cardInfo = cardInfo
|
||||||
|
|
||||||
|
return tradeData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initMagStripeTransaction(
|
||||||
|
cardDataX: CardDataX,
|
||||||
|
isFallback: Boolean
|
||||||
|
): TradeData {
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "CardDataX : $cardDataX")
|
||||||
|
|
||||||
|
val tradeData = Params.newTrade(false)
|
||||||
|
val payDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetail.cardNo = cardDataX.pan
|
||||||
|
payDetail.expDate = cardDataX.exp
|
||||||
|
|
||||||
|
payDetail.accountType = when {
|
||||||
|
cardDataX.pan.startsWith("4") -> VISA_CARD_SCHEME
|
||||||
|
cardDataX.pan.startsWith("5") -> MASTER_CARD_SCHEME
|
||||||
|
cardDataX.pan.startsWith("6") -> UPI_CARD_SCHEME
|
||||||
|
else -> MPU_CARD_SCHEME
|
||||||
|
}
|
||||||
|
|
||||||
|
payDetail.cardType = if (isFallback) {
|
||||||
|
-9
|
||||||
|
} else {
|
||||||
|
CardTypeX.MAG.value
|
||||||
|
}
|
||||||
|
|
||||||
|
payDetail.cardHolderName = cardDataX.cardHolderName
|
||||||
|
|
||||||
|
val cardInfo = CardInfo()
|
||||||
|
|
||||||
|
val magCardInfo = MAGCardInfo()
|
||||||
|
magCardInfo.track2Cipher = cardDataX.track2
|
||||||
|
|
||||||
|
cardInfo.magCardInfo = magCardInfo
|
||||||
|
|
||||||
|
payDetail.cardInfo = cardInfo
|
||||||
|
|
||||||
|
return tradeData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initWalletTransaction(
|
||||||
|
transactionType: TransactionsType
|
||||||
|
): PayDetail {
|
||||||
|
|
||||||
|
val tradeData = Params.newTrade(true)
|
||||||
|
val payDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetail.accountType = Constants.WALLET
|
||||||
|
payDetail.transType = transactionType.name
|
||||||
|
payDetail.transactionType = transactionType.value
|
||||||
|
payDetail.terminalNo = qrTerminalId
|
||||||
|
payDetail.merchantNo = qrMerchantId
|
||||||
|
payDetail.currencyCode = "104"
|
||||||
|
payDetail.isCanceled = false
|
||||||
|
|
||||||
|
return payDetail
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
package com.mob.utsmyanmar.viewmodel
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.mob.utsmyanmar.model.CardTransactionType
|
||||||
|
import com.utsmyanmar.checkxread.CheckXRead
|
||||||
|
import com.utsmyanmar.checkxread.checkcard.CheckCardResultX
|
||||||
|
import com.utsmyanmar.checkxread.model.CardDataX
|
||||||
|
import com.utsmyanmar.checkxread.readcard.ReadCardResultX
|
||||||
|
import com.utsmyanmar.checkxread.readcard.ReadCardX
|
||||||
|
import com.utsmyanmar.checkxread.sdk.SunmiSDK
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class CardReaderViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = CardReaderViewModel::class.java.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mainThreadHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
private var oneTimeFlag = false
|
||||||
|
|
||||||
|
private var cardTransactionType: CardTransactionType? = null
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UI States
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val _errorCode = MutableStateFlow("")
|
||||||
|
val errorCode = _errorCode.asStateFlow()
|
||||||
|
|
||||||
|
private val _cardData = MutableStateFlow<CardDataX?>(null)
|
||||||
|
val cardData = _cardData.asStateFlow()
|
||||||
|
|
||||||
|
private var _cardTypeData = MutableStateFlow<Int?>(null)
|
||||||
|
val cardTypeData = _cardTypeData.asStateFlow()
|
||||||
|
|
||||||
|
private val _payDetail = MutableStateFlow<PayDetail?>(null)
|
||||||
|
val payDetail = _payDetail.asStateFlow()
|
||||||
|
|
||||||
|
private val _checkCardAlertMsg =
|
||||||
|
MutableStateFlow<String?>(null)
|
||||||
|
val checkCardAlertMsg =
|
||||||
|
_checkCardAlertMsg.asStateFlow()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transaction Type
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun setCardTransactionType(
|
||||||
|
cardTransactionType: CardTransactionType
|
||||||
|
) {
|
||||||
|
this.cardTransactionType = cardTransactionType
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCardTransactionType(): CardTransactionType? {
|
||||||
|
return cardTransactionType
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check Card Process
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startCheckXProcess(
|
||||||
|
allType: Int,
|
||||||
|
timeOut: Int,
|
||||||
|
cardResultX: CheckCardResultX
|
||||||
|
) {
|
||||||
|
CheckXRead.getInstance()
|
||||||
|
.startCheckXProcess(
|
||||||
|
allType,
|
||||||
|
timeOut,
|
||||||
|
cardResultX
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isReaderReady(): Boolean {
|
||||||
|
return SunmiSDK.getInstance().readCardOptV2 != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelCheckXProcess() {
|
||||||
|
CheckXRead.getInstance()
|
||||||
|
.cancelCheckXProcess()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read Card Process
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startReadXProcess(
|
||||||
|
readCardX: ReadCardX,
|
||||||
|
readCardResultX: ReadCardResultX
|
||||||
|
) {
|
||||||
|
CheckXRead.getInstance()
|
||||||
|
.startReadXProcess(
|
||||||
|
readCardX,
|
||||||
|
readCardResultX
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alert Message
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun setCheckCardAlertMsg(
|
||||||
|
msg: String,
|
||||||
|
isAutoHide: Boolean
|
||||||
|
) {
|
||||||
|
_checkCardAlertMsg.value = msg
|
||||||
|
|
||||||
|
if (isAutoHide) {
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
_checkCardAlertMsg.value = null
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetUI() {
|
||||||
|
_checkCardAlertMsg.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One Time Flag
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun resetOneTimeFlag() {
|
||||||
|
oneTimeFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancel Card Checking
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun cancelCheckCard() {
|
||||||
|
if (isReaderReady()) {
|
||||||
|
SunmiSDK.getInstance()
|
||||||
|
.cancelCheckCard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCardData(value: Int){
|
||||||
|
_cardTypeData.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,429 @@
|
|||||||
|
package com.mob.utsmyanmar.viewmodel
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.Message
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.utsmyanmar.baselib.emv.EmvParamOperation
|
||||||
|
import com.utsmyanmar.baselib.repo.Repository
|
||||||
|
import com.utsmyanmar.baselib.util.enums.EmvResultStatus
|
||||||
|
import com.utsmyanmar.baselib.viewModel.EmvBaseViewModel
|
||||||
|
import com.utsmyanmar.checkxread.util.CardTypeX
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.model.TradeData
|
||||||
|
import com.utsmyanmar.paylibs.model.enums.TransCVM
|
||||||
|
import com.utsmyanmar.paylibs.network.ISOSocket
|
||||||
|
import com.utsmyanmar.paylibs.reversal.ReversalAction
|
||||||
|
import com.utsmyanmar.paylibs.reversal.ReversalListener
|
||||||
|
import com.utsmyanmar.paylibs.system.SingleLiveEvent
|
||||||
|
import com.utsmyanmar.paylibs.transactions.TransactionsOperation
|
||||||
|
import com.utsmyanmar.paylibs.transactions.TransactionsOperationListener
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionType
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
import com.mob.utsmyanmar.model.TransResultStatus
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class EmvTransactionProcessViewModel @Inject constructor(
|
||||||
|
private val repository: Repository,
|
||||||
|
emvParamOperation: EmvParamOperation
|
||||||
|
) : EmvBaseViewModel(repository, emvParamOperation), ProcessingTransaction {
|
||||||
|
|
||||||
|
private val transResult = SingleLiveEvent<TransResultStatus>()
|
||||||
|
|
||||||
|
override fun handleMsg(msg: Message) {
|
||||||
|
when (msg.what) {
|
||||||
|
|
||||||
|
PIN_CLICK_NUMBER -> {
|
||||||
|
showPasswordView(msg.arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
PIN_CLICK_CONFIRM -> {
|
||||||
|
if (!mPayDetail.PINCipher.isNullOrEmpty()) {
|
||||||
|
importPinInputStatus(0)
|
||||||
|
} else if (isOfflinePinEntered) {
|
||||||
|
importPinInputStatus(0)
|
||||||
|
} else {
|
||||||
|
importPinInputStatus(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_COMPLETE ||
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_VOID ||
|
||||||
|
transType.value == TransactionsType.REFUND
|
||||||
|
) {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.ON_NEXT_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PIN_CLICK_CANCEL -> {
|
||||||
|
importPinInputStatus(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
PIN_ERROR -> {
|
||||||
|
increasedKSN()
|
||||||
|
importPinInputStatus(3)
|
||||||
|
handleTransactionFail(msg.arg1, PIN_PAD_FAILED)
|
||||||
|
}
|
||||||
|
|
||||||
|
PIN_CLICK_EMPTY -> {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.PIN_EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_APP_SELECT -> {
|
||||||
|
val candiNames = msg.obj as Array<String>
|
||||||
|
LogUtil.d(TAG, "CandiNames size:${candiNames.size}")
|
||||||
|
candiList.value = candiNames
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.SELECT_APP)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_FINAL_APP_SELECT -> {
|
||||||
|
importFinalAppSelectStatus(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_CONFIRM_CARD_NO -> {
|
||||||
|
importCardNoStatus(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_CERT_VERIFY -> {
|
||||||
|
importCertStatus(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_SIGNATURE -> {
|
||||||
|
mPayDetail.setIsFreeSign(false)
|
||||||
|
mPayDetail.transCVM = TransCVM.SIGNATURE
|
||||||
|
importSignatureStatus(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_SHOW_PIN_PAD -> {
|
||||||
|
startPinProcess()
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_ONLINE_PROCESS -> {
|
||||||
|
handleTransactionProcess()
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_ERROR -> {
|
||||||
|
handleTransactionFail(msg.arg1, msg.obj.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_TRY_AGAIN -> {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.TRY_AGAIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_CONFIRM_CODE_VERIFY -> {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.CONFIRM_CODE_VERIFY)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_SUCCESS_ONLINE -> {
|
||||||
|
transResult.postValue(TransResultStatus.SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_FAILURE_ONLINE -> {
|
||||||
|
transResult.postValue(TransResultStatus.FAIL)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_SUCCESS_OFFLINE -> {
|
||||||
|
transResult.postValue(TransResultStatus.OFFLINE_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
EMV_FAILURE_OFFLINE -> {
|
||||||
|
transResult.postValue(TransResultStatus.OFFLINE_FAILURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCancelEmvTransaction() {
|
||||||
|
LogUtil.e(TAG, "Terminate Transaction : $mProcessStep")
|
||||||
|
|
||||||
|
when (mProcessStep) {
|
||||||
|
EMV_APP_SELECT -> importAppSelect(-1)
|
||||||
|
EMV_FINAL_APP_SELECT -> importFinalAppSelectStatus(-1)
|
||||||
|
EMV_CONFIRM_CARD_NO -> importCardNoStatus(1)
|
||||||
|
EMV_CERT_VERIFY -> importCertStatus(1)
|
||||||
|
PIN_ERROR -> importPinInputStatus(3)
|
||||||
|
EMV_ONLINE_PROCESS -> importOnlineProcessStatus(1)
|
||||||
|
EMV_SIGNATURE -> importSignatureStatus(1)
|
||||||
|
EMV_SHOW_PIN_PAD -> importPinInputStatus(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPayDetail(payDetail: PayDetail) {
|
||||||
|
mPayDetail = payDetail
|
||||||
|
mTradeData = TradeData().apply {
|
||||||
|
setPayDetail(payDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPayDetail(): PayDetail {
|
||||||
|
return mPayDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetProcessStepCount() {
|
||||||
|
mProcessStep = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetTransactionStatus() {
|
||||||
|
emvResultStatus.clear()
|
||||||
|
transResult.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTransStatus(): SingleLiveEvent<TransResultStatus> {
|
||||||
|
return transResult
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCardLessTrans(): Boolean {
|
||||||
|
return transType.value == TransactionsType.VOID ||
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_COMPLETE_VOID ||
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_VOID ||
|
||||||
|
transType.value == TransactionsType.REFUND
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPinPadCancel() {
|
||||||
|
importPinInputStatus(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startOnlineProcess() {
|
||||||
|
isReversal = false
|
||||||
|
|
||||||
|
TransactionsOperation.getInstance()
|
||||||
|
.getStartOperation(mTradeData, transType.value)
|
||||||
|
.checkOperation(object : TransactionsOperationListener {
|
||||||
|
|
||||||
|
override fun onSuccess(tradeData: TradeData) {
|
||||||
|
val payDetailRes = tradeData.payDetail
|
||||||
|
|
||||||
|
payDetailRes.invoiceNo =
|
||||||
|
SystemParamsOperation.getInstance().incrementInvoiceNum
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "Transaction Operation Success: ${payDetailRes.tradeAnswerCode}")
|
||||||
|
LogUtil.d(TAG, "Transaction Type: ${payDetailRes.transactionType}")
|
||||||
|
|
||||||
|
if (
|
||||||
|
TextUtils.equals(payDetailRes.tradeAnswerCode, RC_APPROVED_V1) ||
|
||||||
|
TextUtils.equals(payDetailRes.tradeAnswerCode, RC_APPROVED_V2)
|
||||||
|
) {
|
||||||
|
when (transType.value) {
|
||||||
|
TransactionsType.VOID -> processVoidDB(payDetailRes)
|
||||||
|
TransactionsType.REFUND -> processRefundDB(payDetailRes)
|
||||||
|
TransactionsType.PRE_AUTH_VOID -> processPreVoidDb(payDetailRes)
|
||||||
|
TransactionsType.PRE_AUTH_COMPLETE -> processPreCompDb(payDetailRes)
|
||||||
|
TransactionsType.PRE_AUTH_COMPLETE_VOID -> processPreCompVoidDb(payDetailRes)
|
||||||
|
else -> insertDB(payDetailRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
payDetailResult.value = payDetailRes
|
||||||
|
|
||||||
|
if (isCardLessTrans()) {
|
||||||
|
transResult.value = TransResultStatus.SUCCESS
|
||||||
|
} else if (payDetailRes.cardType == CardTypeX.MANUAL.value) {
|
||||||
|
transResult.value = TransResultStatus.SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
payDetailResult.value = payDetailRes
|
||||||
|
|
||||||
|
if (isCardLessTrans()) {
|
||||||
|
transResult.value = TransResultStatus.FAIL
|
||||||
|
} else if (payDetailRes.cardType == CardTypeX.MANUAL.value) {
|
||||||
|
transResult.value = TransResultStatus.FAIL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReversal(tradeData: TradeData) {
|
||||||
|
LogUtil.d(TAG, "<<<< On Reversal >>>>")
|
||||||
|
|
||||||
|
isReversal = true
|
||||||
|
transResult.postValue(TransResultStatus.REVERSAL_PREPARE)
|
||||||
|
|
||||||
|
var reversalDelay = 15000
|
||||||
|
|
||||||
|
val delayValue = SystemParamsOperation.getInstance().reversalDelay
|
||||||
|
if (!delayValue.isNullOrEmpty()) {
|
||||||
|
reversalDelay = "${delayValue}000".toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
val payDetailReversal = tradeData.payDetail
|
||||||
|
|
||||||
|
if (
|
||||||
|
payDetailReversal.transactionType == TransactionType.SALE ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.VOID ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.REFUND ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.PRE_SALE ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.CASH_ADVANCE ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.PRE_SALE_CANCEL ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.PRE_SALE_COMPLETE ||
|
||||||
|
payDetailReversal.transactionType == TransactionType.PRE_SALE_COMPLETE_VOID
|
||||||
|
) {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.REVERSAL_PROCESS)
|
||||||
|
transResult.postValue(TransResultStatus.REVERSAL_PROCESS)
|
||||||
|
callReversal(tradeData)
|
||||||
|
} else {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.REVERSAL_FAIL)
|
||||||
|
transResult.postValue(TransResultStatus.REVERSAL_FAIL)
|
||||||
|
}
|
||||||
|
}, reversalDelay.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(message: String) {
|
||||||
|
LogUtil.e(TAG, "<<<< On Error >>>>")
|
||||||
|
LogUtil.e(TAG, "error message:$message")
|
||||||
|
|
||||||
|
if (TextUtils.equals(message, TRY_SECONDARY)) {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.SECONDARY)
|
||||||
|
transResult.postValue(TransResultStatus.SECONDARY)
|
||||||
|
startOnlineProcess()
|
||||||
|
} else {
|
||||||
|
importOnlineProcessStatus(1)
|
||||||
|
setNetWorkErrorDesc(message)
|
||||||
|
errorCodeMsg.value = message
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.NETWORK_ERROR)
|
||||||
|
transResult.postValue(TransResultStatus.NETWORK_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun insertDB(payResult: PayDetail) {
|
||||||
|
payResult.PINCipher = ""
|
||||||
|
repository.insertPayDetail(payResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processVoidDB(payResult: PayDetail) {
|
||||||
|
mPayDetail.setIsCanceled(true)
|
||||||
|
updatePayDetail(mPayDetail)
|
||||||
|
repository.insertPayDetail(updateCurrentDateAndTime(payResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processPreVoidDb(payResult: PayDetail) {
|
||||||
|
oldTransPayDetail.setIsCanceled(true)
|
||||||
|
updatePayDetail(oldTransPayDetail)
|
||||||
|
repository.insertPayDetail(updateCurrentDateAndTime(payResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processPreCompDb(payResult: PayDetail) {
|
||||||
|
if (oldTransPayDetail.amount == payResult.amount) {
|
||||||
|
oldTransPayDetail.setIsCanceled(true)
|
||||||
|
updatePayDetail(oldTransPayDetail)
|
||||||
|
} else {
|
||||||
|
oldTransPayDetail.setIsCanceled(false)
|
||||||
|
updatePayDetail(oldTransPayDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.insertPayDetail(updateCurrentDateAndTime(payResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processPreCompVoidDb(payResult: PayDetail) {
|
||||||
|
mPayDetail.setIsCanceled(true)
|
||||||
|
updatePayDetail(mPayDetail)
|
||||||
|
repository.insertPayDetail(updateCurrentDateAndTime(payResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processRefundDB(payResult: PayDetail) {
|
||||||
|
mPayDetail.setReturnGood(true)
|
||||||
|
updatePayDetail(mPayDetail)
|
||||||
|
repository.insertPayDetail(updateCurrentDateAndTime(payResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTransactionFail(code: Int, description: String) {
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
LogUtil.d(TAG, "On handleTransactionFail ::")
|
||||||
|
|
||||||
|
if (isReversal) {
|
||||||
|
transResult.value = TransResultStatus.REVERSAL_PREPARE
|
||||||
|
} else {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.ERROR)
|
||||||
|
transResult.value = TransResultStatus.FAIL
|
||||||
|
errorCodeMsg.postValue("$code:$description")
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTransactionProcess() {
|
||||||
|
val cardNo = mPayDetail.cardNo
|
||||||
|
|
||||||
|
if (cardNo.isNullOrEmpty()) {
|
||||||
|
getCardInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_COMPLETE ||
|
||||||
|
transType.value == TransactionsType.PRE_AUTH_VOID ||
|
||||||
|
transType.value == TransactionsType.REFUND
|
||||||
|
) {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.ON_NEXT_SCREEN)
|
||||||
|
} else {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.SUCCESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun callReversal(tradeData: TradeData) {
|
||||||
|
if (transType.value == TransactionsType.VOID) {
|
||||||
|
tradeData.payDetail.icC55 = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
ReversalAction.getInstance()
|
||||||
|
.setData(tradeData)
|
||||||
|
.enqueue()
|
||||||
|
.startReversal(object : ReversalListener {
|
||||||
|
|
||||||
|
override fun onSuccessReversal() {
|
||||||
|
importOnlineProcessStatus(0)
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.REVERSAL_SUCCESS)
|
||||||
|
transResult.postValue(TransResultStatus.REVERSAL_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNetworkFail(msg: String) {
|
||||||
|
importOnlineProcessStatus(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailReversal(msg: String) {
|
||||||
|
importOnlineProcessStatus(0)
|
||||||
|
|
||||||
|
if (!isSecondCall) {
|
||||||
|
if (!TextUtils.equals(msg, REVERSAL)) {
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.REVERSAL_SECONDARY)
|
||||||
|
transResult.postValue(TransResultStatus.REVERSAL_SECONDARY)
|
||||||
|
|
||||||
|
ISOSocket.getInstance().switchIp()
|
||||||
|
isSecondCall = true
|
||||||
|
|
||||||
|
callReversal(tradeData)
|
||||||
|
} else {
|
||||||
|
saveNeedReversal()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveNeedReversal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveNeedReversal() {
|
||||||
|
mPayDetail.setNeedReversal(true)
|
||||||
|
mPayDetail.transactionType = TransactionType.REVERSAL
|
||||||
|
mPayDetail.transType = REVERSAL
|
||||||
|
mPayDetail.pid = null
|
||||||
|
|
||||||
|
repository.insertPayDetail(mPayDetail)
|
||||||
|
|
||||||
|
emvResultStatus.postValue(EmvResultStatus.REVERSAL_FAIL)
|
||||||
|
transResult.postValue(TransResultStatus.REVERSAL_FAIL)
|
||||||
|
|
||||||
|
isSecondCall = false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = EmvTransactionProcessViewModel::class.java.simpleName
|
||||||
|
private const val TRY_SECONDARY = "TRY_SECONDARY"
|
||||||
|
private const val PIN_PAD_FAILED = "Pin pad failed"
|
||||||
|
private const val RC_APPROVED_V1 = "00"
|
||||||
|
private const val RC_APPROVED_V2 = "000"
|
||||||
|
private const val REVERSAL = "REVERSAL"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.mob.utsmyanmar.viewmodel
|
||||||
|
|
||||||
|
import com.mob.utsmyanmar.model.TransResultStatus
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.system.SingleLiveEvent
|
||||||
|
|
||||||
|
interface ProcessingTransaction {
|
||||||
|
fun resetTransactionStatus()
|
||||||
|
|
||||||
|
fun getTransStatus(): SingleLiveEvent<TransResultStatus>
|
||||||
|
|
||||||
|
fun startOnlineProcess()
|
||||||
|
|
||||||
|
fun insertDB(payResult: PayDetail)
|
||||||
|
|
||||||
|
fun processVoidDB(payResult: PayDetail)
|
||||||
|
|
||||||
|
fun processPreVoidDb(payResult: PayDetail)
|
||||||
|
|
||||||
|
fun processPreCompDb(payResult: PayDetail)
|
||||||
|
|
||||||
|
fun processPreCompVoidDb(payResult: PayDetail)
|
||||||
|
|
||||||
|
fun processRefundDB(payResult: PayDetail)
|
||||||
|
}
|
||||||
@ -0,0 +1,663 @@
|
|||||||
|
package com.mob.utsmyanmar.viewmodel
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.mob.utsmyanmar.model.SettlementType
|
||||||
|
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
|
||||||
|
import com.utsmyanmar.baselib.network.model.sirius.SiriusResponse
|
||||||
|
import com.utsmyanmar.baselib.repo.Repository
|
||||||
|
import com.utsmyanmar.ecr.data.TransType
|
||||||
|
import com.utsmyanmar.ecr.data.model.Transactions
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.print.printx.PrintXReceipt
|
||||||
|
import com.utsmyanmar.paylibs.print.printx.PrintXStatus
|
||||||
|
import com.utsmyanmar.paylibs.system.SingleLiveEvent
|
||||||
|
import com.utsmyanmar.paylibs.system.SystemDateTime
|
||||||
|
import com.utsmyanmar.paylibs.utils.AccountType
|
||||||
|
import com.utsmyanmar.paylibs.utils.PrintStatus
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.enums.HostType
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionType
|
||||||
|
import com.utsmyanmar.paylibs.utils.enums.TransMenu
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import sunmi.sunmiui.utils.LogUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SharedViewModel @Inject constructor(
|
||||||
|
private val repository: Repository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SharedViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
val transactionsType = SingleLiveEvent<TransactionsType>()
|
||||||
|
|
||||||
|
val settlementType = SingleLiveEvent<SettlementType>()
|
||||||
|
|
||||||
|
val accountType = SingleLiveEvent<AccountType>()
|
||||||
|
|
||||||
|
val amount = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val totalAmount = SingleLiveEvent<Long>()
|
||||||
|
|
||||||
|
val cardNo = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val processCode = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val payDetail = SingleLiveEvent<PayDetail>()
|
||||||
|
|
||||||
|
val printStatus = SingleLiveEvent<PrintStatus>()
|
||||||
|
|
||||||
|
val merchantName = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val transactionName = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val payDetailList = SingleLiveEvent<List<PayDetail>>()
|
||||||
|
|
||||||
|
val isEcr = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
val traceNo = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val rrNNo = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val approvalCode = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val isEcrFinished = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
val isEmv = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
private val settlementStatus = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
private val wavePayStatus = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val sendMsg = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val qrData = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val qrRefNum = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val ecrCMD = SingleLiveEvent<TransType>()
|
||||||
|
|
||||||
|
val _transMenu = SingleLiveEvent<TransMenu?>()
|
||||||
|
|
||||||
|
val twoBtnLayout = MutableLiveData(0)
|
||||||
|
|
||||||
|
val oneBtnLayout = MutableLiveData(8)
|
||||||
|
|
||||||
|
private val reprintBtnLayout = MutableLiveData(8)
|
||||||
|
|
||||||
|
val isReprint = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
var signBitmap: Bitmap? = null
|
||||||
|
|
||||||
|
val hostType = SingleLiveEvent<HostType>()
|
||||||
|
|
||||||
|
private val printReceiptButtons = MutableLiveData(0)
|
||||||
|
|
||||||
|
val printReceiptMsg = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val reprintTransTypeMsg = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val ecrTrans = SingleLiveEvent<Transactions>()
|
||||||
|
|
||||||
|
val _manualEntryStatus = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val fullVoidPreauthStatus = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val partialVoidPreauthStatus = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val printXStatus = SingleLiveEvent<PrintStatus>()
|
||||||
|
|
||||||
|
private val _errorFragmentMsg = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
private val _successFragmentMsg = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
private val _currencyText = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val tapCardStatus = MutableLiveData(1)
|
||||||
|
|
||||||
|
val tapDeviceStatus = MutableLiveData(1)
|
||||||
|
|
||||||
|
val insertCardStatus = MutableLiveData(1)
|
||||||
|
|
||||||
|
val swipeCardStatus = MutableLiveData(0)
|
||||||
|
|
||||||
|
val countDownTxt = MutableLiveData<String>()
|
||||||
|
|
||||||
|
val mmqrLoading = MutableLiveData<Int>()
|
||||||
|
|
||||||
|
val isMMPay = MutableLiveData<Int>()
|
||||||
|
|
||||||
|
val isWavePay = MutableLiveData<Int>()
|
||||||
|
|
||||||
|
val mockData = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
val qrPayVisibility = MutableLiveData<Int>()
|
||||||
|
|
||||||
|
val loadingView = MutableLiveData(8)
|
||||||
|
|
||||||
|
val loadingMsg = SingleLiveEvent<String>()
|
||||||
|
|
||||||
|
private val isFallback = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
private val _isCardDataExist = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
private val _isAmountExist = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
private var mPayDetail = PayDetail()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setPrintStatus(PrintStatus.FIRST_PRINT)
|
||||||
|
isReprint.value = false
|
||||||
|
cardNo.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSettlementStatus(status: Boolean) {
|
||||||
|
settlementStatus.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSettlementStatus(): MutableLiveData<Boolean> {
|
||||||
|
return settlementStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setWavePayStatus(status: Boolean) {
|
||||||
|
wavePayStatus.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWavePayStatus(): MutableLiveData<Boolean> {
|
||||||
|
return wavePayStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEmvTrans(status: Boolean) {
|
||||||
|
isEmv.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEmvTrans(): SingleLiveEvent<Boolean> {
|
||||||
|
return isEmv
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPayDetail(): PayDetail {
|
||||||
|
return mPayDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cachePayDetail(payDetail: PayDetail) {
|
||||||
|
mPayDetail = payDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrintReceiptButtons(visible: Boolean) {
|
||||||
|
printReceiptButtons.value = if (visible) 0 else 8
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postPrintReceiptButtons(visible: Boolean) {
|
||||||
|
printReceiptButtons.postValue(if (visible) 0 else 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPrintReceiptButtons(): MutableLiveData<Int> {
|
||||||
|
return printReceiptButtons
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReprintBtnLayout(): MutableLiveData<Int> {
|
||||||
|
return reprintBtnLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setReprintBtnLayout(visible: Boolean) {
|
||||||
|
reprintBtnLayout.value = if (visible) 0 else 8
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrintReceiptMsg(msg: String) {
|
||||||
|
printReceiptMsg.value = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postPrintReceiptMsg(msg: String) {
|
||||||
|
printReceiptMsg.postValue(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrintStatus(printStatus: PrintStatus) {
|
||||||
|
this.printStatus.value = printStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postPrintStatus(printStatus: PrintStatus) {
|
||||||
|
this.printStatus.postValue(printStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPrintStatusEvent(): SingleLiveEvent<PrintStatus> {
|
||||||
|
return printStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsFallback(status: Boolean) {
|
||||||
|
isFallback.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIsFallback(): SingleLiveEvent<Boolean> {
|
||||||
|
return isFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCardDataExist(exist: Boolean) {
|
||||||
|
_isCardDataExist.value = exist
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCardDataExist(): SingleLiveEvent<Boolean> {
|
||||||
|
return _isCardDataExist
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAmountExist(exist: Boolean) {
|
||||||
|
_isAmountExist.value = exist
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAmountExist(): SingleLiveEvent<Boolean> {
|
||||||
|
return _isAmountExist
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getManualEntryStatus(): MutableLiveData<Boolean> {
|
||||||
|
return _manualEntryStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setManualEntryStatus(status: Boolean) {
|
||||||
|
_manualEntryStatus.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getErrorFragmentMsg(): SingleLiveEvent<String> {
|
||||||
|
return _errorFragmentMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set_errorFragmentMsg(msg: String) {
|
||||||
|
_errorFragmentMsg.value = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSuccessFragmentMsg(): SingleLiveEvent<String> {
|
||||||
|
return _successFragmentMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set_currencyText(msg: String) {
|
||||||
|
_currencyText.value = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get_currencyText(): SingleLiveEvent<String> {
|
||||||
|
return _currencyText
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set_successFragmentMsg(msg: String) {
|
||||||
|
_successFragmentMsg.value = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTransMenu(): SingleLiveEvent<TransMenu?> {
|
||||||
|
return _transMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTransMenu(transMenu: TransMenu?) {
|
||||||
|
_transMenu.value = transMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadingMsg(msg: String) {
|
||||||
|
loadingView.value = 0
|
||||||
|
loadingMsg.value = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissLoadingMsg() {
|
||||||
|
loadingView.value = 8
|
||||||
|
loadingMsg.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getParams(siriusRequest: SiriusRequest): Observable<SiriusResponse> {
|
||||||
|
return repository.getParams(siriusRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printReceipt(isMerchantCopy: Boolean) {
|
||||||
|
PrintXReceipt.getInstance()
|
||||||
|
.printSmileReceipt(payDetail.value, isMerchantCopy, object : PrintXStatus {
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
|
||||||
|
if (isMerchantCopy) {
|
||||||
|
|
||||||
|
if (!SystemParamsOperation.getInstance().demoStatus) {
|
||||||
|
if (SystemParamsOperation.getInstance().printISOStatus) {
|
||||||
|
PrintXReceipt.getInstance()
|
||||||
|
.printSmileISoReceipt(payDetail.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postPrintStatus(PrintStatus.FIRST_PRINT_DONE)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setPrintStatus(PrintStatus.SECOND_PRINT_DONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure() {
|
||||||
|
LogUtil.d(TAG, "Print Status Result Failure!")
|
||||||
|
printXStatus.postValue(PrintStatus.EMPTY_PAPER_ROLL)
|
||||||
|
postPrintReceiptButtons(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startPrintReceipt(isFirstPrint: Boolean) {
|
||||||
|
|
||||||
|
payDetail.value?.let {
|
||||||
|
cachePayDetail(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintXReceipt.getInstance()
|
||||||
|
.printSmileReceipt(getPayDetail(), isFirstPrint, object : PrintXStatus {
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
|
||||||
|
if (isFirstPrint) {
|
||||||
|
|
||||||
|
if (!SystemParamsOperation.getInstance().demoStatus) {
|
||||||
|
if (SystemParamsOperation.getInstance().printISOStatus) {
|
||||||
|
PrintXReceipt.getInstance()
|
||||||
|
.printSmileISoReceipt(getPayDetail())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postPrintStatus(PrintStatus.FIRST_PRINT_DONE)
|
||||||
|
|
||||||
|
if (isEcr.value == true &&
|
||||||
|
SystemParamsOperation.getInstance()
|
||||||
|
.isAutoPrintCustomerCopy
|
||||||
|
) {
|
||||||
|
printReceiptButtons.postValue(8)
|
||||||
|
} else {
|
||||||
|
printReceiptButtons.postValue(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
postPrintStatus(PrintStatus.SECOND_PRINT_DONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure() {
|
||||||
|
LogUtil.d(TAG, "Print Status Result Failure!")
|
||||||
|
|
||||||
|
if (isFirstPrint) {
|
||||||
|
setPrintStatus(PrintStatus.EMPTY_PAPER_ROLL_FIRST)
|
||||||
|
} else {
|
||||||
|
setPrintStatus(PrintStatus.EMPTY_PAPER_ROLL_SECOND)
|
||||||
|
}
|
||||||
|
|
||||||
|
postPrintReceiptButtons(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startPrintProcess() {
|
||||||
|
|
||||||
|
LogUtil.d(TAG, "Print status : ${getPrintStatusEvent().value}")
|
||||||
|
|
||||||
|
when (getPrintStatusEvent().value) {
|
||||||
|
|
||||||
|
PrintStatus.FIRST_PRINT -> {
|
||||||
|
printReceipt(true)
|
||||||
|
postPrintReceiptMsg("Printing Receipt for Merchant")
|
||||||
|
postPrintReceiptButtons(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintStatus.SECOND_PRINT -> {
|
||||||
|
printReceipt(false)
|
||||||
|
postPrintReceiptMsg("Printing Receipt for Customer")
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintStatus.NOT_PRINT -> {
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startPrintProcessSettlement() {
|
||||||
|
|
||||||
|
val detail = payDetail.value ?: return
|
||||||
|
|
||||||
|
PrintXReceipt.getInstance()
|
||||||
|
.printSmileSettlementReport(detail, object : PrintXStatus {
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure() {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastThreeTransactions(): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getLastThreeTransactions()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSettlementRecords(): LiveData<List<PayDetail>> {
|
||||||
|
return repository.getSettlementPOS()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReversalTransaction(voucherNo: String): LiveData<PayDetail> {
|
||||||
|
return repository.getReversalTransaction(voucherNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePayDetail(payDetail: PayDetail) {
|
||||||
|
repository.updatePayDetail(payDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertPayDetail(payDetail: PayDetail) {
|
||||||
|
repository.insertPayDetail(payDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveMockHostResultForTesting() {
|
||||||
|
when (transactionsType.value) {
|
||||||
|
TransactionsType.SETTLEMENT -> saveMockSettlementForTesting()
|
||||||
|
TransactionsType.VOID -> saveMockApprovedVoidForTesting()
|
||||||
|
TransactionsType.REFUND -> saveMockApprovedRefundForTesting()
|
||||||
|
else -> saveMockApprovedSaleForVoidTesting()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveMockApprovedSaleForVoidTesting() {
|
||||||
|
val detail = payDetail.value ?: return
|
||||||
|
|
||||||
|
val systemParams = SystemParamsOperation.getInstance()
|
||||||
|
val mockTraceNo = systemParams.incrementSerialNum
|
||||||
|
val mockInvoiceNo = systemParams.incrementInvoiceNum
|
||||||
|
val mockApprovalCode = mockTraceNo.takeLast(6).padStart(6, '0')
|
||||||
|
val mockReferenceNo = buildString {
|
||||||
|
append(SystemDateTime.getYYMMDD())
|
||||||
|
append(SystemDateTime.getHHmmss())
|
||||||
|
}.takeLast(12)
|
||||||
|
|
||||||
|
detail.voucherNo = mockTraceNo
|
||||||
|
detail.invoiceNo = mockInvoiceNo
|
||||||
|
detail.referNo = mockReferenceNo
|
||||||
|
detail.approvalCode = mockApprovalCode
|
||||||
|
detail.authNo = mockApprovalCode
|
||||||
|
detail.tradeAnswerCode = "00"
|
||||||
|
detail.tradeResultDes = "MOCK APPROVED"
|
||||||
|
detail.transactionType = TransactionType.SALE
|
||||||
|
detail.transType = TransactionsType.SALE.name
|
||||||
|
detail.isCanceled = false
|
||||||
|
detail.isSettle = false
|
||||||
|
detail.isNeedReversal = false
|
||||||
|
detail.isReturnGood = false
|
||||||
|
detail.TradeDate = SystemDateTime.getMMDD()
|
||||||
|
detail.TradeTime = SystemDateTime.getHHmmss()
|
||||||
|
detail.tradeDateAndTime = SystemDateTime.getMMDDhhmmss()
|
||||||
|
detail.tradeDateTime = SystemDateTime.getYYMMDDhhmmss()
|
||||||
|
detail.transDate = SystemDateTime.getTodayDateFormat()
|
||||||
|
detail.transTime = SystemDateTime.getTodayTimeFormat()
|
||||||
|
|
||||||
|
repository.insertPayDetail(detail)
|
||||||
|
payDetail.value = detail
|
||||||
|
traceNo.value = mockTraceNo
|
||||||
|
rrNNo.value = mockReferenceNo
|
||||||
|
approvalCode.value = mockApprovalCode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveMockApprovedVoidForTesting() {
|
||||||
|
val originalSale = payDetail.value ?: return
|
||||||
|
|
||||||
|
originalSale.isCanceled = true
|
||||||
|
repository.updatePayDetail(originalSale)
|
||||||
|
|
||||||
|
val systemParams = SystemParamsOperation.getInstance()
|
||||||
|
val mockTraceNo = systemParams.incrementSerialNum
|
||||||
|
val mockInvoiceNo = systemParams.incrementInvoiceNum
|
||||||
|
val mockApprovalCode = mockTraceNo.takeLast(6).padStart(6, '0')
|
||||||
|
val mockReferenceNo = buildString {
|
||||||
|
append(SystemDateTime.getYYMMDD())
|
||||||
|
append(SystemDateTime.getHHmmss())
|
||||||
|
}.takeLast(12)
|
||||||
|
|
||||||
|
val voidDetail = PayDetail().apply {
|
||||||
|
merchantName = originalSale.merchantName
|
||||||
|
merchantNo = originalSale.merchantNo
|
||||||
|
terminalNo = originalSale.terminalNo
|
||||||
|
CardNo = originalSale.CardNo
|
||||||
|
cardType = originalSale.cardType
|
||||||
|
EXPDate = originalSale.EXPDate
|
||||||
|
cardHolderName = originalSale.cardHolderName
|
||||||
|
processCode = TransactionsType.VOID.processCode
|
||||||
|
amount = originalSale.amount
|
||||||
|
transPlatform = originalSale.transPlatform
|
||||||
|
transactionType = TransactionType.VOID
|
||||||
|
transType = TransactionsType.VOID.name
|
||||||
|
currencyCode = originalSale.currencyCode
|
||||||
|
currency = originalSale.currency
|
||||||
|
batchNo = originalSale.batchNo
|
||||||
|
voucherNo = mockTraceNo
|
||||||
|
invoiceNo = mockInvoiceNo
|
||||||
|
referNo = mockReferenceNo
|
||||||
|
approvalCode = mockApprovalCode
|
||||||
|
authNo = mockApprovalCode
|
||||||
|
accountType = originalSale.accountType
|
||||||
|
tradeAnswerCode = "00"
|
||||||
|
tradeResultDes = "MOCK VOID APPROVED"
|
||||||
|
TradeDate = SystemDateTime.getMMDD()
|
||||||
|
TradeTime = SystemDateTime.getHHmmss()
|
||||||
|
tradeDateAndTime = SystemDateTime.getMMDDhhmmss()
|
||||||
|
tradeDateTime = SystemDateTime.getYYMMDDhhmmss()
|
||||||
|
transDate = SystemDateTime.getTodayDateFormat()
|
||||||
|
transTime = SystemDateTime.getTodayTimeFormat()
|
||||||
|
originalPOSNum = originalSale.voucherNo
|
||||||
|
originalReferNo = originalSale.referNo
|
||||||
|
originalAuthNo = originalSale.approvalCode
|
||||||
|
originalBathNo = originalSale.batchNo
|
||||||
|
originalTransDate = originalSale.TradeDate
|
||||||
|
originalAmount = originalSale.amount
|
||||||
|
isCanceled = false
|
||||||
|
isSettle = false
|
||||||
|
isNeedReversal = false
|
||||||
|
isReturnGood = false
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.insertPayDetail(voidDetail)
|
||||||
|
payDetail.value = voidDetail
|
||||||
|
traceNo.value = mockTraceNo
|
||||||
|
rrNNo.value = mockReferenceNo
|
||||||
|
approvalCode.value = mockApprovalCode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveMockApprovedRefundForTesting() {
|
||||||
|
val refundDetail = payDetail.value ?: return
|
||||||
|
|
||||||
|
val systemParams = SystemParamsOperation.getInstance()
|
||||||
|
val mockTraceNo = systemParams.incrementSerialNum
|
||||||
|
val mockInvoiceNo = systemParams.incrementInvoiceNum
|
||||||
|
val mockApprovalCode = mockTraceNo.takeLast(6).padStart(6, '0')
|
||||||
|
val refundRrn = rrNNo.value?.trim().orEmpty()
|
||||||
|
|
||||||
|
refundDetail.voucherNo = mockTraceNo
|
||||||
|
refundDetail.invoiceNo = mockInvoiceNo
|
||||||
|
refundDetail.referNo = refundRrn
|
||||||
|
refundDetail.approvalCode = mockApprovalCode
|
||||||
|
refundDetail.authNo = mockApprovalCode
|
||||||
|
refundDetail.tradeAnswerCode = "00"
|
||||||
|
refundDetail.tradeResultDes = "MOCK REFUND APPROVED"
|
||||||
|
refundDetail.transactionType = TransactionType.REFUND
|
||||||
|
refundDetail.transType = TransactionsType.REFUND.name
|
||||||
|
refundDetail.isCanceled = false
|
||||||
|
refundDetail.isSettle = false
|
||||||
|
refundDetail.isNeedReversal = false
|
||||||
|
refundDetail.isReturnGood = false
|
||||||
|
refundDetail.TradeDate = SystemDateTime.getMMDD()
|
||||||
|
refundDetail.TradeTime = SystemDateTime.getHHmmss()
|
||||||
|
refundDetail.tradeDateAndTime = SystemDateTime.getMMDDhhmmss()
|
||||||
|
refundDetail.tradeDateTime = SystemDateTime.getYYMMDDhhmmss()
|
||||||
|
refundDetail.transDate = SystemDateTime.getTodayDateFormat()
|
||||||
|
refundDetail.transTime = SystemDateTime.getTodayTimeFormat()
|
||||||
|
|
||||||
|
repository.insertPayDetail(refundDetail)
|
||||||
|
payDetail.value = refundDetail
|
||||||
|
traceNo.value = mockTraceNo
|
||||||
|
rrNNo.value = refundRrn
|
||||||
|
approvalCode.value = mockApprovalCode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveMockSettlementForTesting() {
|
||||||
|
val systemParams = SystemParamsOperation.getInstance()
|
||||||
|
val mockTraceNo = systemParams.incrementSerialNum
|
||||||
|
val mockInvoiceNo = systemParams.incrementInvoiceNum
|
||||||
|
|
||||||
|
val settlementDetail = PayDetail().apply {
|
||||||
|
merchantNo = systemParams.merchantId
|
||||||
|
merchantName = systemParams.merchantName
|
||||||
|
terminalNo = systemParams.terminalId
|
||||||
|
voucherNo = mockTraceNo
|
||||||
|
invoiceNo = mockInvoiceNo
|
||||||
|
batchNo = systemParams.systemParamsSettings.batchNumStart
|
||||||
|
processCode = TransactionsType.SETTLEMENT.processCode
|
||||||
|
transactionType = TransactionType.SETTLEMENT
|
||||||
|
transType = TransactionsType.SETTLEMENT.name
|
||||||
|
currencyCode = systemParams.currencyType.removed0CurrencyCode
|
||||||
|
tradeAnswerCode = "00"
|
||||||
|
tradeResultDes = "MOCK SETTLEMENT APPROVED"
|
||||||
|
TradeDate = SystemDateTime.getMMDD()
|
||||||
|
TradeTime = SystemDateTime.getHHmmss()
|
||||||
|
tradeDateAndTime = SystemDateTime.getMMDDhhmmss()
|
||||||
|
tradeDateTime = SystemDateTime.getYYMMDDhhmmss()
|
||||||
|
transDate = SystemDateTime.getTodayDateFormat()
|
||||||
|
transTime = SystemDateTime.getTodayTimeFormat()
|
||||||
|
isSettle = false
|
||||||
|
isNeedReversal = false
|
||||||
|
isCanceled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
payDetail.value = settlementDetail
|
||||||
|
traceNo.value = mockTraceNo
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enableCardStatusIcon(
|
||||||
|
tapCard: Boolean,
|
||||||
|
tapDevice: Boolean,
|
||||||
|
insertCard: Boolean,
|
||||||
|
swipeCard: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
tapCardStatus.value = if (tapCard) 1 else 0
|
||||||
|
tapDeviceStatus.value = if (tapDevice) 1 else 0
|
||||||
|
insertCardStatus.value = if (insertCard) 1 else 0
|
||||||
|
swipeCardStatus.value = if (swipeCard) 1 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetParamsMain() {
|
||||||
|
|
||||||
|
isEcrFinished.postValue(true)
|
||||||
|
isEcr.postValue(false)
|
||||||
|
|
||||||
|
setAmountExist(false)
|
||||||
|
setCardDataExist(false)
|
||||||
|
setTransMenu(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateButtonStatus() {
|
||||||
|
setSettlementStatus(
|
||||||
|
SystemParamsOperation.getInstance().settlementStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
setWavePayStatus(
|
||||||
|
SystemParamsOperation.getInstance().wavePayStatus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,348 @@
|
|||||||
|
package com.mob.utsmyanmar.viewmodel
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.mob.utsmyanmar.model.TransResultStatus
|
||||||
|
import com.utsmyanmar.baselib.repo.Repository
|
||||||
|
import com.utsmyanmar.paylibs.model.PayDetail
|
||||||
|
import com.utsmyanmar.paylibs.model.TradeData
|
||||||
|
import com.utsmyanmar.paylibs.network.ISOSocket
|
||||||
|
import com.utsmyanmar.paylibs.reversal.ReversalAction
|
||||||
|
import com.utsmyanmar.paylibs.reversal.ReversalListener
|
||||||
|
import com.utsmyanmar.paylibs.system.SystemDateTime
|
||||||
|
import com.utsmyanmar.paylibs.transactions.TransactionsOperation
|
||||||
|
import com.utsmyanmar.paylibs.transactions.TransactionsOperationListener
|
||||||
|
import com.utsmyanmar.paylibs.utils.PrintStatus
|
||||||
|
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
|
||||||
|
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class TransProcessViewModel @Inject constructor(
|
||||||
|
private val repository: Repository
|
||||||
|
) : ViewModel(), ProcessingTransaction {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val RC_APPROVED_V1 = "00"
|
||||||
|
private const val RC_APPROVED_V2 = "000"
|
||||||
|
private const val TRY_SECONDARY = "TRY_SECONDARY"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var pan: String = ""
|
||||||
|
|
||||||
|
private lateinit var tradeData: TradeData
|
||||||
|
|
||||||
|
var payDetail: PayDetail? = null
|
||||||
|
|
||||||
|
private var oldTransPayDetail: PayDetail? = null
|
||||||
|
|
||||||
|
private var isSecondCall = false
|
||||||
|
private var isThirdCall = false
|
||||||
|
|
||||||
|
/*
|
||||||
|
* States
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val _transResultStatus =
|
||||||
|
MutableStateFlow<TransResultStatus?>(null)
|
||||||
|
|
||||||
|
val transResultStatus =
|
||||||
|
_transResultStatus.asStateFlow()
|
||||||
|
|
||||||
|
private val _transType = MutableStateFlow<TransactionsType?>(null)
|
||||||
|
|
||||||
|
val transType =_transType.asStateFlow()
|
||||||
|
|
||||||
|
fun setTransType(type: TransactionsType?) {
|
||||||
|
_transType.value = type
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _printStatus =
|
||||||
|
MutableStateFlow(PrintStatus.FIRST_PRINT)
|
||||||
|
|
||||||
|
val printStatus =
|
||||||
|
_printStatus.asStateFlow()
|
||||||
|
|
||||||
|
private val _payDetailResult =
|
||||||
|
MutableStateFlow<PayDetail?>(null)
|
||||||
|
|
||||||
|
val payDetailResult =
|
||||||
|
_payDetailResult.asStateFlow()
|
||||||
|
|
||||||
|
private val _errorMessage =
|
||||||
|
MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
|
val errorMessage =
|
||||||
|
_errorMessage.asStateFlow()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun setTradeData(tradeData: TradeData) {
|
||||||
|
this.tradeData = tradeData
|
||||||
|
payDetail = tradeData.payDetail
|
||||||
|
pan = payDetail?.cardNo ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTradeData(): TradeData {
|
||||||
|
return tradeData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePayDetail(payDetail: PayDetail) {
|
||||||
|
this.payDetail = payDetail
|
||||||
|
|
||||||
|
tradeData = TradeData().apply {
|
||||||
|
this.payDetail = payDetail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOldTransPayDetail(payDetail: PayDetail) {
|
||||||
|
oldTransPayDetail = payDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transaction
|
||||||
|
*/
|
||||||
|
|
||||||
|
override fun startOnlineProcess() {
|
||||||
|
|
||||||
|
TransactionsOperation.getInstance()
|
||||||
|
.getStartOperation(
|
||||||
|
tradeData,
|
||||||
|
transType.value
|
||||||
|
)
|
||||||
|
.checkOperation(object : TransactionsOperationListener {
|
||||||
|
|
||||||
|
override fun onSuccess(tradeData: TradeData) {
|
||||||
|
|
||||||
|
val payDetailRes =
|
||||||
|
tradeData.payDetail
|
||||||
|
|
||||||
|
if (
|
||||||
|
TextUtils.equals(
|
||||||
|
payDetailRes.tradeAnswerCode,
|
||||||
|
RC_APPROVED_V1
|
||||||
|
) ||
|
||||||
|
TextUtils.equals(
|
||||||
|
payDetailRes.tradeAnswerCode,
|
||||||
|
RC_APPROVED_V2
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
payDetailRes.invoiceNo =
|
||||||
|
SystemParamsOperation
|
||||||
|
.getInstance()
|
||||||
|
.incrementInvoiceNum
|
||||||
|
|
||||||
|
when (transType.value) {
|
||||||
|
|
||||||
|
TransactionsType.VOID -> {
|
||||||
|
processVoidDB(payDetailRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionsType.REFUND -> {
|
||||||
|
processRefundDB(payDetailRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionsType.PRE_AUTH_VOID -> {
|
||||||
|
processPreVoidDb(payDetailRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionsType.PRE_AUTH_COMPLETE -> {
|
||||||
|
processPreCompDb(payDetailRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionsType.PRE_AUTH_COMPLETE_VOID -> {
|
||||||
|
processPreCompVoidDb(payDetailRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
insertDB(payDetailRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_payDetailResult.value =
|
||||||
|
payDetailRes
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.SUCCESS
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_payDetailResult.value =
|
||||||
|
payDetailRes
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.FAIL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReversal(
|
||||||
|
tradeData: TradeData
|
||||||
|
) {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.REVERSAL_PREPARE
|
||||||
|
|
||||||
|
callReversal(tradeData)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(message: String) {
|
||||||
|
|
||||||
|
if (message == TRY_SECONDARY) {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.SECONDARY
|
||||||
|
|
||||||
|
startOnlineProcess()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_errorMessage.value = message
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Database
|
||||||
|
*/
|
||||||
|
|
||||||
|
override fun insertDB(payResult: PayDetail) {
|
||||||
|
|
||||||
|
payDetail?.pinCipher = ""
|
||||||
|
|
||||||
|
repository.insertPayDetail(
|
||||||
|
payDetail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processVoidDB(payResult: PayDetail) {
|
||||||
|
|
||||||
|
payDetail?.isCanceled = true
|
||||||
|
|
||||||
|
payDetail?.let {
|
||||||
|
repository.updatePayDetail(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.insertPayDetail(
|
||||||
|
updateCurrentDateAndTime(payResult)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processRefundDB(payResult: PayDetail) {
|
||||||
|
|
||||||
|
oldTransPayDetail?.apply {
|
||||||
|
isReturnGood = true
|
||||||
|
isCanceled = true
|
||||||
|
|
||||||
|
repository.updatePayDetail(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.insertPayDetail(
|
||||||
|
updateCurrentDateAndTime(payResult)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processPreVoidDb(payResult: PayDetail) {}
|
||||||
|
override fun processPreCompDb(payResult: PayDetail) {}
|
||||||
|
override fun processPreCompVoidDb(payResult: PayDetail) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reversal
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun callReversal(tradeData: TradeData) {
|
||||||
|
|
||||||
|
payDetail = tradeData.payDetail
|
||||||
|
|
||||||
|
ReversalAction.getInstance()
|
||||||
|
.setData(tradeData)
|
||||||
|
.enqueue()
|
||||||
|
.startReversal(object : ReversalListener {
|
||||||
|
|
||||||
|
override fun onSuccessReversal() {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.REVERSAL_SUCCESS
|
||||||
|
|
||||||
|
payDetail?.let {
|
||||||
|
repository.insertPayDetail(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNetworkFail(msg: String) {
|
||||||
|
|
||||||
|
SystemParamsOperation
|
||||||
|
.getInstance()
|
||||||
|
.setSecondHostEnable(true)
|
||||||
|
|
||||||
|
if (
|
||||||
|
SystemParamsOperation
|
||||||
|
.getInstance()
|
||||||
|
.isSecondHostEnabled
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (!isSecondCall) {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.REVERSAL_SECONDARY
|
||||||
|
|
||||||
|
ISOSocket.getInstance()
|
||||||
|
.switchIp()
|
||||||
|
|
||||||
|
isSecondCall = true
|
||||||
|
|
||||||
|
callReversal(tradeData)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.REVERSAL_FAIL
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.REVERSAL_FAIL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailReversal(msg: String) {
|
||||||
|
|
||||||
|
_transResultStatus.value =
|
||||||
|
TransResultStatus.REVERSAL_FAIL
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utils
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun updateCurrentDateAndTime(
|
||||||
|
payDetail: PayDetail
|
||||||
|
): PayDetail {
|
||||||
|
|
||||||
|
payDetail.tradeDate =
|
||||||
|
SystemDateTime.getMMDD()
|
||||||
|
|
||||||
|
payDetail.tradeTime =
|
||||||
|
SystemDateTime.getHHmmss()
|
||||||
|
|
||||||
|
return payDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetTransactionStatus() {
|
||||||
|
_transResultStatus.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTransStatus() = TODO()
|
||||||
|
}
|
||||||
37
app/src/main/res/drawable/ic_circle_wrong.xml
Normal file
37
app/src/main/res/drawable/ic_circle_wrong.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="14dp"
|
||||||
|
android:height="14dp"
|
||||||
|
android:viewportWidth="14"
|
||||||
|
android:viewportHeight="14">
|
||||||
|
<path
|
||||||
|
android:pathData="M9.078,4.669L4.669,9.078M4.669,4.669L9.078,9.078"
|
||||||
|
android:strokeAlpha="0.8"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6.873,12.997C10.255,12.997 12.997,10.255 12.997,6.873C12.997,3.491 10.255,0.75 6.873,0.75C3.491,0.75 0.75,3.491 0.75,6.873C0.75,10.255 3.491,12.997 6.873,12.997Z"
|
||||||
|
android:strokeAlpha="0.8"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
20
app/src/main/res/drawable/ic_code.xml
Normal file
20
app/src/main/res/drawable/ic_code.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="16" android:viewportWidth="16" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M4.573,5.227L1.96,7.84L4.573,10.453M11.107,5.227L13.72,7.84L11.107,10.453M9.147,2.613L6.533,13.067" android:strokeColor="#6F0D1E" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
20
app/src/main/res/drawable/ic_four_vertical_bars.xml
Normal file
20
app/src/main/res/drawable/ic_four_vertical_bars.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="12" android:viewportWidth="12" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#6F0D1E" android:fillType="evenOdd" android:pathData="M11.025,-0C11.22,-0 11.407,0.077 11.545,0.215C11.683,0.353 11.76,0.54 11.76,0.735L11.76,11.025C11.76,11.22 11.683,11.407 11.545,11.545C11.407,11.683 11.22,11.76 11.025,11.76C10.83,11.76 10.643,11.683 10.505,11.545C10.367,11.407 10.29,11.22 10.29,11.025L10.29,0.735C10.29,0.54 10.367,0.353 10.505,0.215C10.643,0.077 10.83,-0 11.025,-0ZM0.735,-0C0.93,-0 1.117,0.077 1.255,0.215C1.393,0.353 1.47,0.54 1.47,0.735L1.47,11.025C1.47,11.22 1.393,11.407 1.255,11.545C1.117,11.683 0.93,11.76 0.735,11.76C0.54,11.76 0.353,11.683 0.215,11.545C0.077,11.407 -0,11.22 -0,11.025L-0,0.735C-0,0.54 0.077,0.353 0.215,0.215C0.353,0.077 0.54,-0 0.735,-0ZM7.595,-0C7.79,-0 7.977,0.077 8.115,0.215C8.253,0.353 8.33,0.54 8.33,0.735L8.33,11.025C8.33,11.22 8.253,11.407 8.115,11.545C7.977,11.683 7.79,11.76 7.595,11.76C7.4,11.76 7.213,11.683 7.075,11.545C6.937,11.407 6.86,11.22 6.86,11.025L6.86,0.735C6.86,0.54 6.937,0.353 7.075,0.215C7.213,0.077 7.4,-0 7.595,-0ZM4.165,-0C4.36,-0 4.547,0.077 4.685,0.215C4.823,0.353 4.9,0.54 4.9,0.735L4.9,11.025C4.9,11.22 4.823,11.407 4.685,11.545C4.547,11.683 4.36,11.76 4.165,11.76C3.97,11.76 3.783,11.683 3.645,11.545C3.507,11.407 3.43,11.22 3.43,11.025L3.43,0.735C3.43,0.54 3.507,0.353 3.645,0.215C3.783,0.077 3.97,-0 4.165,-0Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
25
app/src/main/res/drawable/ic_function.xml
Normal file
25
app/src/main/res/drawable/ic_function.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="12dp"
|
||||||
|
android:height="12dp"
|
||||||
|
android:viewportWidth="12"
|
||||||
|
android:viewportHeight="12">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0.653C0,0.48 0.069,0.314 0.191,0.191C0.314,0.069 0.48,0 0.653,0H4.573C4.747,0 4.913,0.069 5.035,0.191C5.158,0.314 5.227,0.48 5.227,0.653V4.573C5.227,4.747 5.158,4.913 5.035,5.035C4.913,5.158 4.747,5.227 4.573,5.227H0.653C0.48,5.227 0.314,5.158 0.191,5.035C0.069,4.913 0,4.747 0,4.573V0.653ZM0,7.187C0,7.013 0.069,6.847 0.191,6.725C0.314,6.602 0.48,6.533 0.653,6.533H4.573C4.747,6.533 4.913,6.602 5.035,6.725C5.158,6.847 5.227,7.013 5.227,7.187V11.107C5.227,11.28 5.158,11.446 5.035,11.569C4.913,11.691 4.747,11.76 4.573,11.76H0.653C0.48,11.76 0.314,11.691 0.191,11.569C0.069,11.446 0,11.28 0,11.107V7.187ZM6.533,0.653C6.533,0.48 6.602,0.314 6.725,0.191C6.847,0.069 7.013,0 7.187,0H11.107C11.28,0 11.446,0.069 11.569,0.191C11.691,0.314 11.76,0.48 11.76,0.653V4.573C11.76,4.747 11.691,4.913 11.569,5.035C11.446,5.158 11.28,5.227 11.107,5.227H7.187C7.013,5.227 6.847,5.158 6.725,5.035C6.602,4.913 6.533,4.747 6.533,4.573V0.653ZM6.533,7.187C6.533,7.013 6.602,6.847 6.725,6.725C6.847,6.602 7.013,6.533 7.187,6.533H11.107C11.28,6.533 11.446,6.602 11.569,6.725C11.691,6.847 11.76,7.013 11.76,7.187V11.107C11.76,11.28 11.691,11.446 11.569,11.569C11.446,11.691 11.28,11.76 11.107,11.76H7.187C7.013,11.76 6.847,11.691 6.725,11.569C6.602,11.446 6.533,11.28 6.533,11.107V7.187Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"/>
|
||||||
|
</vector>
|
||||||
20
app/src/main/res/drawable/ic_gear.xml
Normal file
20
app/src/main/res/drawable/ic_gear.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="16" android:viewportWidth="16" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#6F0D1E" android:pathData="M15.595,7.53C15.642,7.538 15.68,7.585 15.68,7.632V9.614C15.679,9.638 15.67,9.661 15.655,9.68C15.64,9.698 15.619,9.711 15.595,9.716L13.717,10.066C13.691,10.072 13.668,10.084 13.649,10.101C13.629,10.118 13.615,10.14 13.606,10.165L13.019,11.629C13.009,11.653 13.004,11.679 13.006,11.705C13.008,11.731 13.016,11.757 13.03,11.779L14.097,13.333C14.11,13.354 14.115,13.378 14.113,13.402C14.11,13.426 14.1,13.449 14.084,13.467L12.683,14.867C12.665,14.883 12.643,14.893 12.619,14.896C12.595,14.898 12.571,14.892 12.55,14.88L11.023,13.832C11.001,13.818 10.976,13.811 10.95,13.81C10.925,13.809 10.899,13.815 10.877,13.826L10.204,14.186C10.194,14.191 10.183,14.194 10.172,14.195C10.161,14.196 10.149,14.195 10.139,14.191C10.128,14.187 10.119,14.181 10.111,14.173C10.103,14.165 10.097,14.156 10.093,14.145L8.704,10.79C8.696,10.768 8.695,10.743 8.702,10.72C8.71,10.698 8.725,10.678 8.745,10.665L8.913,10.562C8.945,10.542 8.987,10.509 9.02,10.48C9.419,10.225 9.725,9.847 9.892,9.403C10.058,8.96 10.076,8.474 9.943,8.019C9.81,7.564 9.534,7.165 9.154,6.88C8.775,6.596 8.314,6.442 7.84,6.442C7.366,6.442 6.905,6.596 6.526,6.88C6.146,7.165 5.87,7.564 5.737,8.019C5.604,8.474 5.622,8.96 5.788,9.403C5.955,9.847 6.261,10.225 6.66,10.48C6.693,10.51 6.735,10.543 6.767,10.562L6.935,10.665C6.955,10.678 6.97,10.698 6.978,10.72C6.985,10.743 6.984,10.768 6.976,10.79L5.587,14.145C5.583,14.155 5.577,14.165 5.569,14.173C5.561,14.181 5.552,14.187 5.541,14.191C5.531,14.194 5.519,14.196 5.508,14.195C5.497,14.194 5.486,14.191 5.476,14.186L4.803,13.826C4.781,13.815 4.755,13.809 4.73,13.81C4.704,13.811 4.679,13.818 4.657,13.831L3.129,14.88C3.109,14.892 3.085,14.898 3.061,14.896C3.037,14.894 3.014,14.884 2.996,14.867L1.595,13.467C1.579,13.449 1.568,13.426 1.566,13.402C1.564,13.378 1.57,13.354 1.582,13.333L2.649,11.778C2.663,11.756 2.671,11.731 2.673,11.705C2.675,11.679 2.671,11.653 2.66,11.629L2.074,10.165C2.065,10.14 2.05,10.119 2.031,10.102C2.012,10.085 1.988,10.073 1.963,10.066L0.085,9.716C0.061,9.71 0.04,9.698 0.025,9.679C0.009,9.66 0.001,9.637 0,9.613L0,7.632C0,7.585 0.039,7.538 0.085,7.53L2.01,7.172C2.036,7.166 2.06,7.154 2.079,7.137C2.099,7.12 2.115,7.098 2.125,7.074L2.715,5.696C2.725,5.673 2.729,5.647 2.728,5.621C2.726,5.595 2.719,5.57 2.705,5.548L1.584,3.913C1.571,3.892 1.566,3.868 1.568,3.844C1.57,3.82 1.58,3.798 1.596,3.78L2.997,2.378C3.014,2.361 3.037,2.351 3.061,2.349C3.086,2.347 3.11,2.353 3.13,2.366L4.794,3.508C4.833,3.535 4.9,3.54 4.943,3.519L6.278,2.973C6.324,2.958 6.367,2.907 6.376,2.86L6.746,0.869C6.752,0.845 6.765,0.824 6.783,0.809C6.802,0.794 6.825,0.785 6.849,0.783H8.831C8.879,0.783 8.925,0.822 8.934,0.868L9.304,2.86C9.31,2.885 9.322,2.908 9.339,2.928C9.356,2.948 9.378,2.963 9.402,2.973L10.737,3.519C10.761,3.529 10.787,3.534 10.813,3.532C10.839,3.53 10.865,3.522 10.887,3.508L12.55,2.366C12.571,2.353 12.595,2.348 12.619,2.35C12.643,2.352 12.666,2.363 12.683,2.379L14.084,3.78C14.118,3.813 14.123,3.874 14.097,3.913L12.974,5.548C12.961,5.57 12.954,5.595 12.952,5.621C12.95,5.647 12.955,5.673 12.965,5.696L13.555,7.074C13.566,7.098 13.581,7.119 13.601,7.136C13.621,7.153 13.644,7.165 13.67,7.172L15.595,7.53Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
24
app/src/main/res/drawable/ic_history.xml
Normal file
24
app/src/main/res/drawable/ic_history.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="30dp"
|
||||||
|
android:height="30dp"
|
||||||
|
android:viewportWidth="30"
|
||||||
|
android:viewportHeight="30">
|
||||||
|
<path
|
||||||
|
android:pathData="M14.625,29.25C10.887,29.25 7.631,28.011 4.856,25.534C2.08,23.056 0.489,19.962 0.081,16.25H3.412C3.792,19.067 5.045,21.396 7.171,23.237C9.298,25.079 11.782,26 14.625,26C17.794,26 20.482,24.897 22.69,22.69C24.898,20.483 26.001,17.795 26,14.625C25.999,11.455 24.896,8.767 22.69,6.562C20.484,4.356 17.796,3.252 14.625,3.25C12.756,3.25 11.009,3.683 9.384,4.55C7.759,5.417 6.392,6.608 5.281,8.125H9.75V11.375H0V1.625H3.25V5.444C4.631,3.71 6.317,2.37 8.309,1.422C10.3,0.474 12.405,0 14.625,0C16.656,0 18.559,0.386 20.334,1.159C22.108,1.931 23.652,2.973 24.965,4.285C26.278,5.597 27.321,7.141 28.093,8.916C28.865,10.692 29.251,12.595 29.25,14.625C29.249,16.655 28.863,18.558 28.093,20.334C27.323,22.109 26.28,23.653 24.965,24.965C23.65,26.277 22.106,27.319 20.334,28.093C18.561,28.867 16.658,29.252 14.625,29.25ZM19.175,21.45L13,15.275V6.5H16.25V13.975L21.45,19.175L19.175,21.45Z"
|
||||||
|
android:fillColor="#6F0D1E"/>
|
||||||
|
</vector>
|
||||||
24
app/src/main/res/drawable/ic_insert_card.xml
Normal file
24
app/src/main/res/drawable/ic_insert_card.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="800dp"
|
||||||
|
android:height="800dp"
|
||||||
|
android:viewportWidth="15.133"
|
||||||
|
android:viewportHeight="15.133">
|
||||||
|
<path
|
||||||
|
android:pathData="M14.487,0H0.647C0.306,0 0.028,0.277 0.028,0.619v2.165c0,0.342 0.277,0.619 0.619,0.619h0.966h0.074h0.902l-0.726,10.54c0,0.654 0.532,1.186 1.186,1.188l9.033,0.002c0.653,0 1.188,-0.53 1.188,-1.187L12.552,3.402h0.891c0.005,0 0.008,-0.002 0.013,-0.002v0.002h1.031c0.341,0 0.618,-0.277 0.618,-0.619V0.619C15.105,0.277 14.828,0 14.487,0zM7.883,14.339L3.05,14.337c-0.218,0 -0.395,-0.178 -0.395,-0.396L3.441,2.518c0,-0.218 0.178,-0.395 0.396,-0.395l4.051,0.001L7.883,14.339zM12.084,14.339h-1.062L10.246,2.125h1.061c0.22,0 0.396,0.177 0.396,0.395l0.776,11.424C12.478,14.164 12.301,14.341 12.084,14.339zM12.524,2.963L12.524,2.963l-0.03,-0.442c0,-0.655 -0.531,-1.187 -1.188,-1.187L3.837,1.331c-0.654,0 -1.187,0.532 -1.187,1.186L2.62,2.965H2.619H1.717l0.006,-0.084c0.02,-1.148 0.96,-2.077 2.113,-2.077l7.47,0.003c0.564,0 1.096,0.22 1.496,0.62c0.391,0.392 0.609,0.908 0.617,1.459l0.006,0.077C13.425,2.963 12.524,2.963 12.524,2.963zM3.387,10.312l1.075,-0.009l0.025,3.125l-1.074,0.009L3.387,10.312z"
|
||||||
|
android:fillColor="#030104"/>
|
||||||
|
</vector>
|
||||||
25
app/src/main/res/drawable/ic_lock.xml
Normal file
25
app/src/main/res/drawable/ic_lock.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="11dp"
|
||||||
|
android:height="14dp"
|
||||||
|
android:viewportWidth="11"
|
||||||
|
android:viewportHeight="14">
|
||||||
|
<path
|
||||||
|
android:pathData="M1.306,13.716C0.947,13.716 0.64,13.588 0.384,13.333C0.128,13.077 0,12.77 0,12.41V5.878C0,5.519 0.128,5.212 0.384,4.956C0.64,4.701 0.948,4.573 1.306,4.572H1.959V3.266C1.959,2.362 2.278,1.592 2.915,0.956C3.552,0.319 4.322,0 5.225,0C6.128,-0 6.899,0.318 7.536,0.956C8.174,1.593 8.492,2.363 8.491,3.266V4.572H9.144C9.503,4.572 9.811,4.7 10.067,4.956C10.323,5.212 10.451,5.52 10.45,5.878V12.41C10.45,12.769 10.323,13.077 10.067,13.333C9.811,13.589 9.504,13.717 9.144,13.716H1.306ZM1.306,12.41H9.144V5.878H1.306V12.41ZM6.148,10.066C6.404,9.811 6.532,9.504 6.532,9.144C6.532,8.784 6.404,8.477 6.148,8.222C5.893,7.967 5.585,7.839 5.225,7.838C4.866,7.837 4.558,7.965 4.303,8.222C4.048,8.479 3.92,8.786 3.919,9.144C3.918,9.502 4.046,9.81 4.303,10.067C4.56,10.324 4.867,10.452 5.225,10.45C5.583,10.449 5.891,10.32 6.148,10.066ZM3.266,4.572H7.185V3.266C7.185,2.721 6.994,2.259 6.613,1.878C6.232,1.497 5.77,1.306 5.225,1.306C4.681,1.306 4.218,1.497 3.837,1.878C3.456,2.259 3.266,2.721 3.266,3.266V4.572Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"/>
|
||||||
|
</vector>
|
||||||
66
app/src/main/res/drawable/ic_mmqr_logo.xml
Normal file
66
app/src/main/res/drawable/ic_mmqr_logo.xml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="2399.6936dp" android:viewportHeight="2400" android:viewportWidth="1566.2" android:width="1566dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m1445.6,1955c-3.5,-6.8 -1.5,-19.9 3.4,-21.7 6.7,-2.6 31.1,11.3 37.6,-10.3 1.5,-5 2.1,-18.4 -7.1,-37.7 -14.8,-31.1 -34.7,-81.6 -38.2,-88.6C1438.1,1790.7 1427.3,1794.3 1427.3,1787.6c0,-2.9 8.4,-2.4 33.4,-2.4 24.9,0 31.4,-0.7 31.4,2.4 0,6.5 -19.6,1.2 -13.6,17.1 20.2,53.4 26.6,72.2 27,72.2 0.4,0 14.3,-37.7 25.2,-67.5 6,-16.3 -18.6,-16.6 -19.8,-21.8 -0.7,-3 1.7,-2.7 28.3,-2.4 22.5,0.3 25.9,0 25.9,2.4 0,5.9 -10,-0.1 -20.5,18.1 -5.1,8.9 -27.5,63.8 -44.9,109.1 -14.7,38.2 -22.2,47.2 -39.5,47.1 -3.6,-0 -11.5,-0.8 -14.6,-6.9z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m246.8,1955c-3.5,-6.8 -1.5,-19.9 3.4,-21.7 6.7,-2.6 31.1,11.3 37.6,-10.3 1.5,-5 2.1,-18.4 -7.1,-37.7 -14.8,-31.1 -34.7,-81.6 -38.2,-88.6C239.3,1790.7 228.5,1794.3 228.5,1787.6c0,-2.9 8.4,-2.4 33.4,-2.4 24.9,0 31.4,-0.7 31.4,2.4 0,6.5 -19.6,1.2 -13.6,17.1 20.2,53.4 26.6,72.2 27,72.2 0.4,0 14.3,-37.7 25.2,-67.5 6,-16.3 -18.6,-16.6 -19.8,-21.8 -0.7,-3 1.7,-2.7 28.3,-2.4 22.5,0.3 25.9,0 25.9,2.4 0,5.9 -10,-0.1 -20.5,18.1 -5.1,8.9 -27.5,63.8 -44.9,109.1 -14.7,38.2 -22.2,47.2 -39.5,47.1 -3.6,-0 -11.5,-0.8 -14.6,-6.9z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m1330.7,1913.6c-11,0 -33,-11.4 -33,-35.3 0,-24 13.7,-33.3 35.2,-38.6 17.8,-4.4 41.4,-5 41.4,-5 0,0 0.6,-31.9 -1.4,-36.6 -3,-6.9 -7.1,-10.5 -16.2,-10.5 -9.1,0 -17,6.6 -18.2,18.8 -0.4,17.9 -13.2,22.8 -28.3,14.9 -6.5,-4.3 -4.6,-16.8 3.9,-25.1 11.1,-10.8 25.3,-14.5 48.5,-14.5 16.4,0 30.8,1.4 39.5,20.1 4.6,9.9 1.3,73.1 2.8,92.3 0.5,5.9 3.9,7.7 9.7,7.7 3.4,0 5.7,-0.4 5.7,4.7 0,1.8 -31.7,5.5 -35.3,5.6 -8.1,0.1 -8.7,-6.8 -10.7,-16.2 0,0 -10.6,17.7 -43.6,17.7zM1348.4,1900.7c11.5,0 19.5,-8.7 22.3,-14.1 5.5,-10.7 3.6,-44.7 3.6,-44.7 -34.5,1.3 -44.7,12.8 -44.7,30.6 0,17.8 7.4,28.3 18.8,28.3z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m931.5,1913.6c-11,0 -33,-11.4 -33,-35.3 0,-24 13.7,-33.3 35.2,-38.6 17.8,-4.4 41.4,-5 41.4,-5 0,0 0.6,-31.9 -1.4,-36.6 -3,-6.9 -7.1,-10.5 -16.2,-10.5 -9.1,0 -17,6.6 -18.2,18.8 -0.4,17.9 -13.2,22.8 -28.3,14.9 -6.5,-4.3 -4.6,-16.8 3.9,-25.1 11.1,-10.8 25.3,-14.5 48.5,-14.5 16.4,0 30.8,1.4 39.5,20.1 4.6,9.9 1.3,73.1 2.8,92.3 0.5,5.9 3.9,7.7 9.7,7.7 3.4,0 5.7,-0.4 5.7,4.7 0,1.8 -31.7,5.5 -35.3,5.6 -8.1,0.1 -8.7,-6.8 -10.7,-16.2 0,0 -10.6,17.7 -43.6,17.7zM949.2,1900.7c11.5,0 19.5,-8.7 22.3,-14.1 5.5,-10.7 3.6,-44.7 3.6,-44.7 -34.5,1.3 -44.7,12.8 -44.7,30.6 0,17.8 7.4,28.3 18.8,28.3z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m407.5,1913.6c-11,0 -33,-11.4 -33,-35.3 0,-24 13.7,-33.3 35.2,-38.6 17.8,-4.4 41.4,-5 41.4,-5 0,0 0.6,-31.9 -1.4,-36.6 -3,-6.9 -7.1,-10.5 -16.2,-10.5 -9.1,0 -17,6.6 -18.2,18.8 -0.4,17.9 -13.2,22.8 -28.3,14.9 -6.5,-4.3 -4.6,-16.8 3.9,-25.1C401.8,1785.4 416,1781.7 439.3,1781.7c16.4,0 30.8,1.4 39.5,20.1 4.6,9.9 1.3,73.1 2.8,92.3 0.5,5.9 3.9,7.7 9.7,7.7 3.4,0 5.7,-0.4 5.7,4.7 0,1.8 -31.7,5.5 -35.3,5.6 -8.1,0.1 -8.7,-6.8 -10.7,-16.2 0,0 -10.6,17.7 -43.6,17.7zM425.1,1900.7c11.5,0 19.5,-8.7 22.3,-14.1 5.5,-10.7 3.6,-44.7 3.6,-44.7 -34.5,1.3 -44.7,12.8 -44.7,30.6 0,17.8 7.4,28.3 18.8,28.3z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m1141.1,1906.6c0,-2.9 0.2,-4.7 9.4,-4.7 9.9,0 11.8,-3.5 11.8,-17.7l0,-127.2c0,-14.1 -1.9,-17.7 -11.8,-17.7 -9.2,0 -9.4,-1.8 -9.4,-4.7 0,-3 1.2,-3.5 37.7,-3.5 69.5,0 106,1.4 106,52.4 0,51 -35.7,52.4 -89.5,52.4 -0,0 0,48.3 0,48.3 0,14.1 1.9,17.7 11.8,17.7 9.4,0 9.4,0.9 9.4,4.7 0,2 -1.2,3.5 -37.7,3.5 -36.5,0 -37.7,-0.6 -37.7,-3.5zM1250.6,1783.5c0,-51.7 -55.3,-44.2 -55.3,-44.2l0,88.3c0,0 55.3,7.5 55.3,-44.2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m1032.8,1906.6c0,-7.8 14.5,-0.4 17.7,-13 1.7,-6.9 1.7,-88.3 -0.1,-94.5 -1.4,-4.9 -3.9,-6.8 -9.2,-6.8 -5.7,0 -8.4,-2 -8.4,-4.7 0,-5.6 38.8,-5.9 43.6,-5.9 3.3,0 3.7,0.4 4.7,11.1 0.5,5.6 -0.3,10.4 0,10.8 0.9,0.9 8.7,-21.9 35.3,-21.9 11.7,0 19.6,6.9 19.6,20 0,6.3 -9.8,13 -17.2,13 -7.9,0 -10.7,-3 -14.3,-11.5 -2.9,-7.1 -4.5,-7.9 -9.2,-3.3 -7.8,7.8 -14.2,19.8 -14.2,23 0,10.8 -1.8,63.9 -0,70.7 3.4,12.5 17.7,5.2 17.7,13 0,3.3 -1.2,3.5 -33,3.5 -31.8,0 -33,-0.2 -33,-3.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="M700.1,1910.1C670.7,1910.1 666.5,1909.9 666.5,1906.6c0,-8.2 13.7,1.8 17.7,-13 2.4,-9.1 0,-68.3 0,-68.3 -4.5,-45.6 -16.5,-30.3 -16.5,-36.5 -0.1,-6.2 32.8,-6.2 39.7,-6.4 6.9,-0.3 6.8,10 6.8,10L714.2,1804.1c0,0 14.7,-23.6 41.8,-23.6 27.1,0 33.6,23.6 33.6,23.6 0,0 15.9,-23.6 43,-23.6 36.6,0 35.3,44.7 35.3,44.7 0,0 -1.3,59.2 1.2,68.3 4,14.8 17.7,4.7 17.7,13 0,3.2 -4.1,3.5 -33.6,3.5 -29.4,0 -33.6,-0.2 -33.6,-3.5 0,-8.2 13.7,1.8 17.7,-13 2.4,-9.1 0,-68.3 0,-68.3 0,0 1.2,-31.8 -20,-31.8 -21.2,0 -24.7,31.8 -24.7,31.8 0,0 -2.4,59.2 0,68.3 4,14.8 17.7,4.7 17.7,13 0,3.3 -4.1,3.5 -33.6,3.5 -29.4,0 -33.6,-0.2 -33.6,-3.5 0,-8.2 13.7,1.8 17.7,-13 2.4,-9.1 0,-68.3 0,-68.3 0,0 1.2,-31.8 -20,-31.8 -21.2,0 -24.7,31.8 -24.7,31.8 0,0 -2.4,59.2 0,68.3 4,14.8 17.7,4.7 17.7,13 0,3.3 -4.1,3.5 -33.6,3.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="M543.5,1910.1C514,1910.1 509.9,1909.9 509.9,1906.6c0,-8.2 13.7,1.8 17.7,-13 2.4,-9.1 0,-68.3 0,-68.3 -4.5,-45.6 -16.5,-30.3 -16.5,-36.5 -0.1,-6.2 32.8,-6.2 39.7,-6.4 6.9,-0.3 6.8,10 6.8,10L557.6,1804.1c0,0 11.9,-23.6 41.8,-23.6 42.4,0 37.7,44.7 37.7,44.7 0,0 -1.3,59.2 1.2,68.3 4,14.8 17.7,4.7 17.7,13 0,3.3 -4.1,3.5 -33.6,3.5 -29.4,0 -33.6,-0.2 -33.6,-3.5 0,-8.2 13.7,1.8 17.7,-13 2.4,-9.1 0,-68.3 0,-68.3 0,0 0,-31.8 -21.2,-31.8 -21.2,0 -25.9,31.8 -25.9,31.8 0,0 -2.4,59.2 0,68.3 4,14.8 17.7,4.7 17.7,13 0,3.3 -4.1,3.5 -33.6,3.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m147.2,1906.6c0,-7.1 15.3,-4.2 17.7,-8.2 5.9,-10.2 2.7,-143.7 2.7,-143.7L108.3,1904.2L95,1904.2L35.3,1754.7c0,0 -1.6,140.7 9.4,143.7 11.1,3 17.7,1.2 17.7,8.2 0,3.3 -4.1,3.5 -31.2,3.5 -27.1,0 -31.2,-0.2 -31.2,-3.5 0,-7.1 6.6,-5.3 17.7,-8.2 11,-3 7.1,-143.7 7.1,-143.7 0,0 0.3,-13 -11.9,-15.4 -7.6,-1.5 -10.6,-3.7 -8.2,-6.1 18.7,-2.6 54.7,-2.9 57.2,-1.4C65.8,1734 113.1,1861.8 113.1,1861.8c0,0 47.8,-123.1 52.8,-128.7 3.5,-3.9 54.4,-3.4 54.4,-0.1 0,7.1 -20,3.1 -20,12.8 0,89.8 -3.5,142.3 2.4,152.5 2.4,4.1 17.7,1.2 17.7,8.2 0,3.3 -7,3.5 -36.5,3.5 -29.5,0 -36.5,-0.2 -36.5,-3.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m1148.2,2400 l-24.7,-30.6c0,0 -25.9,9.4 -62.4,9.4 -107.2,0 -176.6,-74.2 -176.6,-186.1 0,-111.9 68.3,-186.1 176.6,-186.1 108.3,0 176.6,74.2 176.6,186.1 0,100.1 -55.3,141.3 -55.3,141.3l55.3,65.9zM1065.8,2292.8 L1024.5,2238.7l78.9,0l17.7,22.4c0,0 14.7,-23.6 14.7,-68.3 0,-89.5 -48.9,-100.1 -74.8,-100.1 -25.9,0 -74.8,10.6 -74.8,100.1 0,89.5 47.7,100.1 79.5,100.1zM321.5,2372.9l0,-213.2l-84.8,213.2l-56.5,0l-84.8,-213.2l0,213.2l-95.4,0l0,-361.5l122.5,0l86,206.1 86,-206.1l122.5,0l0,361.5zM764.3,2372.9l0,-213.2l-84.8,213.2l-56.5,0l-84.8,-213.2l0,213.2l-95.4,0l0,-361.5l122.5,0l86,206.1 86,-206.1l122.5,0l0,361.5L764.3,2372.9ZM1262.4,2374.1 L1257.1,2011.4l154.3,0c52.1,0 80.7,16.5 80.7,16.5 17,4.6 61.8,36.6 61.8,106 0,69.3 -44.8,100.2 -61.8,104.8l74.2,135.4l-108.3,0l-62.4,-118.9l-34.2,0l0,118.9zM1410.8,2177.4c8.4,0 40.2,-6.9 39.5,-43.6 -0.7,-36.7 -31.1,-43.6 -39.5,-43.6l-49.5,0l0,87.1z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#fdb913" android:pathData="M781.9,941m-781.4,0a781.4,781.4 0,1 1,1562.7 0a781.4,781.4 0,1 1,-1562.7 0" android:strokeWidth="1.7"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="M599.4,1585.1 L608.8,1547.4l346.2,0l9.4,37.7 -365.1,-0zM620.6,1499.1l322.7,0l9.4,37.7L611.2,1536.8ZM623,1488.5 L632.4,1449.7l299.1,0l9.4,38.9zM634.7,1439.1 L644.2,1402.6l275.6,0l9.4,36.5zM646.5,1392 L655.9,1353.1l252,0l9.4,38.9z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m982.1,1585.1c0,0 11.8,-22.9 14.1,-30.6 2.3,-7.7 5.2,-25.5 4.7,-44.7 -0.5,-19.3 -7.7,-43.6 -13,-67.1 -5.2,-23.5 4.7,-41.2 4.7,-41.2l44.7,0c0,0 -5.5,22.9 -3.5,41.2 1.9,18.3 11.6,56 13,67.1 1.3,11.1 3,25.9 -3.5,44.7 -6.5,18.8 -11.8,30.6 -11.8,30.6zM1037.5,1585.1c0,0 4.6,-11.6 11.8,-30.6 7.2,-19 3.9,-35.5 3.5,-44.7 -0.4,-9.2 -10.1,-47.7 -13,-67.1 -2.8,-19.5 3.5,-41.2 3.5,-41.2l15.3,-0c0,0 -5.3,16.2 -5.3,27.1 0,10.8 1.8,14.1 1.8,14.1l326.2,0l-11.6,13.5 -12.2,13.5L1062.2,1469.7c0,0 2.7,6.5 7.1,40 4.4,33.6 -1.2,44.7 -1.2,44.7l197.9,0l-20.1,15.3 -21.6,15.3zM978,1576.8c0,0 -23,-103 -24.1,-107.2 -3.9,-14.4 -3.4,-20.8 -2.3,-27.1 5.5,-29.8 18.7,-41.2 18.7,-41.2l16.5,0c0,0 -11,11.3 -4.7,41.2 6.3,30 12.9,55.5 13,67.1 0,11.7 -0.2,28.8 -4.7,44.7 -4.5,15.9 -12.4,22.4 -12.4,22.4zM931.5,1380.2l-7.1,-27.1l521.8,0l-8.6,13.5 -9,13.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m581.7,1585.1c0,0 -11.8,-22.9 -14.1,-30.6 -2.3,-7.7 -5.2,-25.5 -4.7,-44.7 0.5,-19.3 7.7,-43.6 13,-67.1 5.2,-23.5 -4.7,-41.2 -4.7,-41.2l-44.7,0c0,0 5.5,22.9 3.5,41.2 -1.9,18.3 -11.6,56 -13,67.1 -1.3,11.1 -3,25.9 3.5,44.7 6.5,18.8 11.8,30.6 11.8,30.6zM526.4,1585.1c0,0 -4.6,-11.6 -11.8,-30.6 -7.2,-19 -3.9,-35.5 -3.5,-44.7 0.4,-9.2 10.1,-47.7 13,-67.1 2.8,-19.5 -3.5,-41.2 -3.5,-41.2L505.2,1401.4c0,0 5.3,16.2 5.3,27.1 0,10.8 -1.8,14.1 -1.8,14.1L182.5,1442.6l11.6,13.5 12.2,13.5L501.7,1469.7c0,0 -2.7,6.5 -7.1,40 -4.4,33.6 1.2,44.7 1.2,44.7L297.9,1554.5l20.1,15.3 21.6,15.3zM585.9,1576.8c0,0 23,-103 24.1,-107.2 3.9,-14.4 3.4,-20.8 2.3,-27.1C606.7,1412.8 593.5,1401.4 593.5,1401.4l-16.5,0c0,0 11,11.3 4.7,41.2 -6.3,30 -12.9,55.5 -13,67.1 -0,11.7 0.2,28.8 4.7,44.7 4.5,15.9 12.4,22.4 12.4,22.4zM632.4,1380.2l7.1,-27.1L117.6,1353.1l8.6,13.5 9,13.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="M902.1,1344.8L902.1,1108.1l18.8,8.2l0,228.5zM1089.3,1344.8l0,-150.7l18.8,8.2l0,142.5zM926.8,1280.1L926.8,1119.9l156.6,70.7l0,89.5c0,0 -8.6,-0.7 -14.1,-21.2 -5.5,-20.5 -14,-52.9 -64.2,-53 -50.1,-0 -58.7,32.5 -64.2,53 -5.5,20.5 -14.1,21.2 -14.1,21.2zM902.1,1101.1l0,-27.1l294.4,127.2 69.5,-24.7l0,-18.8l11.8,21.2 -43.6,36.9 -22.4,26.7zM988,1104.6 L1134.1,1129.3 1107,1155.3zM1119.9,1155.3 L1197.6,1077.5l0,77.7zM912.7,1071.6l68.3,0l0,30.6zM902.1,1064.6l0,-61.2l64.8,61.2zM973.9,1064.6 L797.3,893.8 792.5,856.1 963.3,1017.5 1031.6,1002.2l0,-31.8l11.8,31.8 -40,45.9 -3.5,16.5zM1008,1064.6 L1011.6,1048.1l98.9,0l0,16.5zM1017.5,1041 L1044.6,1011.6 1110.5,966.8l0,74.2l-93,-0zM965.7,1008l-27.1,-24.7 76.5,15.3zM892.6,939.7l43.6,0l0,40z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m670.1,1131.7l0,-25.9l111.9,70.7 111.9,-70.7l0,25.9l-111.9,70.7zM781.9,1169.4 L681.8,1105.8l200.2,0zM670.1,1098.7l0,-96.6l223.7,0l0,96.6zM693.6,996.3 L781.9,972.7 870.3,996.3zM771.3,931.3L771.3,904.4l10.6,-94.2 10.6,94.2l0,26L781.9,940.9m0,9.4 l15,-16.3L887.9,995.1 781.9,966.9 676,995.1 767,935.3M685.4,981l80.1,-76.5l0,21.2zM798.4,925.6l0,-21.2l80.1,76.5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="M661.8,1344.8L661.8,1108.1l-18.8,8.2l0,228.5zM474.6,1344.8l0,-150.7l-18.8,8.2l0,142.5zM637.1,1280.1L637.1,1119.9l-156.6,70.7l0,89.5c0,0 8.6,-0.7 14.1,-21.2 5.5,-20.5 14,-52.9 64.2,-53 50.1,-0 58.7,32.5 64.2,53 5.5,20.5 14.1,21.2 14.1,21.2zM661.8,1101.1l0,-27.1l-294.4,127.2 -69.5,-24.7l0,-18.8l-11.8,21.2 43.6,36.9 22.4,26.7zM575.9,1104.6 L429.8,1129.3 456.9,1155.3zM444,1155.3 L366.2,1077.5l0,77.7zM651.2,1071.6l-68.3,0l0,30.6zM661.8,1064.6l0,-61.2l-64.8,61.2zM590,1064.6 L766.6,893.8 771.3,856.1 600.6,1017.5 532.3,1002.2l0,-31.8l-11.8,31.8 40,45.9 3.5,16.5zM555.8,1064.6 L552.3,1048.1l-98.9,0l0,16.5zM546.4,1041 L519.3,1011.6 453.4,966.8l0,74.2l93,-0zM598.2,1008l27.1,-24.7 -76.5,15.3zM671.2,939.7l-43.6,0l0,40z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m573.5,915l126,0l-21.2,18.8l-104.8,0zM864.4,915l126,0l0,18.8l-104.8,0zM573.5,910.3 L573.5,857.3 650,910.3L573.5,910.3ZM664.2,910.3 L632.4,887.9 666.5,867.9l81.3,0l-44.7,42.4zM860.8,910.3 L816.1,867.9l81.3,0l34.2,20 -31.8,22.4zM913.8,910.3 L990.4,857.3l0,53zM674.8,863.2l0,-37.7l97.7,0l-2.4,20 -18.8,17.7zM812.6,863.2 L793.7,845.5 791.4,825.5l97.7,0l0,37.7zM630,820.8l0,-20l146,0l-2.4,20zM787.8,800.8 L933.9,800.8L933.9,820.8L790.2,820.8ZM630,796.1L630,743.1l65.9,53 -65.9,-0zM786.7,796.1 L781.9,765.5 777.2,796.1l-37.7,0l-15.3,-18.8 10.6,-11.8 2.4,7.1l16.5,0l28.3,-33 28.3,33l16.5,0l3.5,-7.1 9.4,11.8 -15.3,18.8zM833.8,796.1 L849.1,777.2 830.2,756l0,-8.2l24.7,0l34.2,23.6 -30.6,24.7zM867.9,796.1 L933.9,743.1l0,53zM705.4,796.1 L674.8,771.3 708.9,747.8l25.9,0l0,8.2l-20,21.2 15.3,18.8zM741.9,765.5l0,-17.7l24.7,0l-15.3,17.7zM812.6,765.5 L797.3,747.8l25.9,0l0,17.7zM793.7,743.1 L781.9,730.1 770.2,743.1l-61.2,0l0,-27.1l146,0l0,27.1z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m677.1,692.4l209.6,0l0,18.8L677.1,711.3ZM677.1,685.4l0,-33l41.9,33zM727.8,685.4 L700.7,663 727.8,644.2l8.2,0l-5.9,18.8 15.3,22.4zM751.3,685.4L736,663l4.7,-15.3 3.5,10.6l14.1,0l23.6,-22.4 23.6,22.4l14.1,0l3.5,-10.6 4.7,15.3 -15.3,22.4 -61.2,-0zM818.4,685.4 L833.8,663 827.9,644.2l8.2,0l27.1,18.8 -27.1,22.4 -17.7,0zM845.5,685.4 L886.8,652.4l0,33zM747.8,652.4l-2.4,-8.2l17.7,0l-8.2,8.2zM809,652.4 L800.8,644.2l17.7,0l-2.4,8.2zM730.1,612.4l103.6,0l0,24.7l-40,0l-11.8,-9.4 -11.8,9.4l-40,0z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m706.6,592.7l150.7,0L857.3,607.1L706.6,607.1ZM706.6,587.3l0,-25.1l30.2,25.1zM743,587.3 L723.5,570.3 743,555.9l5.9,0l-4.2,14.4 11,17zM759.9,587.3 L748.9,570.3 752.3,558.6 754.8,566.7l10.2,0l16.9,-17 16.9,17l10.2,0l2.5,-8.1 3.4,11.7 -11,17 -44,-0zM808.2,587.3 L819.2,570.3 815,555.9l5.9,0l19.5,14.4 -19.5,17 -12.7,0zM827.7,587.3 L857.3,562.2l0,25.1zM757.4,562.2 L755.7,555.9l12.7,0l-5.9,6.3zM801.4,562.2 L795.5,555.9l12.7,0l-1.7,6.3zM744.7,531.7l74.5,0l0,18.8L790.4,550.5L781.9,543.4 773.5,550.5l-28.8,0z" android:strokeWidth="0.9"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m723.1,513.8l117.8,0L840.8,525.2L723.1,525.2ZM723.1,509.5l0,-20l23.6,20zM751.5,509.5 L736.3,495.9 751.5,484.5l4.6,0l-3.3,11.4 8.6,13.6zM764.7,509.5 L756.1,495.9 758.8,486.6 760.8,493l7.9,0L781.9,479.5l13.2,13.6l7.9,0l2,-6.4 2.6,9.3 -8.6,13.6 -34.4,-0zM802.5,509.5 L811.1,495.9 807.7,484.5l4.6,0l15.2,11.4 -15.2,13.6zM817.7,509.5 L840.8,489.5l0,20zM762.8,489.5 L761.4,484.5l9.9,0l-4.6,5zM797.2,489.5 L792.5,484.5l9.9,0l-1.3,5zM752.8,465.2l58.2,0l0,15l-22.5,0L781.9,474.5 775.3,480.2l-22.5,0z" android:strokeWidth="0.7"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m736,449.9l91.9,0l0,8.2l-91.9,0zM736,443.6l0,-18.4l18.4,18.4zM758.2,443.6 L746.3,431.1 758.2,420.5l3.6,0l-2.6,10.5 6.7,12.5zM768.5,443.6 L761.8,431.1 763.9,422.5 765.4,428.4l6.2,0l10.3,-12.5 10.3,12.5l6.2,0l1.5,-5.9 2.1,8.6 -6.7,12.5 -26.8,-0zM797.9,443.6 L804.6,431.1 802.1,420.5l3.6,0l11.9,10.5 -11.9,12.5 -7.7,0zM809.8,443.6L827.9,425.2l0,18.4zM767,425.2 L765.9,420.5l7.7,0l-3.6,4.6zM793.8,425.2 L790.2,420.5l7.7,0l-1,4.6zM758.4,383.9l4.7,0l18.8,9.4 18.8,-9.4l4.7,0l0,30.6l-10.6,0l-13,-11.8 -13,11.8l-10.6,0z" android:strokeWidth="0.6"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#17479e" android:pathData="m781.9,387.4 l-18.8,-9.4 -14.1,4.7 10.6,-22.4l44.7,0l10.6,22.4 -14.1,-4.7zM752.5,354.5l0,-9.4l58.9,0L811.4,354.5ZM752.5,339.2l0,-11.8l58.9,0l0,11.8zM761.9,322.7 L756,315.6 764.3,300.3l35.3,0l8.2,15.3 -5.9,7.1 -40,0zM765.5,280.3l33,0l0,14.1l-33,0zM759.6,274.4 L754.9,261.4l54.2,0l-4.7,13zM754.9,255.5 L763.1,243.8l37.7,0l8.2,11.8zM781.9,236.7c-6.9,0 -9.4,-1.5 -10.6,-3.5 -1.2,-2 -1.5,-5.9 0,-35.3 0.6,-11.8 10.6,-197.8 10.6,-197.8 0,0 10,186.1 10.6,197.8 1.6,29.4 1.2,33.2 0,35.3 -1.2,2.1 -3.7,3.5 -10.6,3.5z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
35
app/src/main/res/drawable/ic_protection_shield.xml
Normal file
35
app/src/main/res/drawable/ic_protection_shield.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="17dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="17">
|
||||||
|
<path
|
||||||
|
android:pathData="M7.922,9.629C8.565,9.629 9.182,9.373 9.637,8.918M7.922,9.629C7.278,9.629 6.661,9.373 6.206,8.918M7.922,9.629V11.449M9.637,8.918C10.092,8.463 10.348,7.846 10.348,7.202M9.637,8.918L10.924,10.205M10.348,7.202C10.348,6.559 10.092,5.942 9.637,5.486M10.348,7.202H12.168M9.637,5.486C9.182,5.031 8.565,4.776 7.922,4.776M9.637,5.486L10.924,4.2M7.922,4.776C7.278,4.776 6.661,5.031 6.206,5.486M7.922,4.776V2.956M6.206,5.486C5.751,5.942 5.495,6.559 5.495,7.202M6.206,5.486L4.919,4.2M5.495,7.202C5.495,7.846 5.751,8.463 6.206,8.918M5.495,7.202H3.675M6.206,8.918L4.919,10.205M7.517,2.956H8.326M10.638,3.913L11.21,4.486M12.168,6.798V7.607M11.21,9.919L10.638,10.491M8.326,11.449H7.517M5.205,10.491L4.633,9.919M3.675,7.607V6.798M4.633,4.486L5.205,3.913"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M1.47,7.798V2.824C1.469,2.636 1.522,2.453 1.623,2.295C1.723,2.137 1.867,2.012 2.037,1.934C3.867,1.135 5.844,0.732 7.84,0.751C9.836,0.732 11.814,1.135 13.643,1.934C13.813,2.012 13.957,2.137 14.057,2.295C14.158,2.453 14.211,2.636 14.21,2.824V7.798C14.21,10.21 13.144,13.551 8.544,15.32C8.091,15.494 7.589,15.494 7.136,15.32C2.534,13.547 1.47,10.231 1.47,7.798Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
64
app/src/main/res/drawable/ic_see_more.xml
Normal file
64
app/src/main/res/drawable/ic_see_more.xml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="800dp"
|
||||||
|
android:height="800dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h48v48h-48z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillAlpha="0.01"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,4L18,4A2,2 0,0 1,20 6L20,18A2,2 0,0 1,18 20L6,20A2,2 0,0 1,4 18L4,6A2,2 0,0 1,6 4z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:strokeColor="#6F0D1E"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,28L18,28A2,2 0,0 1,20 30L20,42A2,2 0,0 1,18 44L6,44A2,2 0,0 1,4 42L4,30A2,2 0,0 1,6 28z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:strokeColor="#6F0D1E"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M30,4L42,4A2,2 0,0 1,44 6L44,18A2,2 0,0 1,42 20L30,20A2,2 0,0 1,28 18L28,6A2,2 0,0 1,30 4z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:strokeColor="#6F0D1E"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M28,28H44"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M36,36H44"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M28,44H44"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#6F0D1E"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
20
app/src/main/res/drawable/ic_settings.xml
Normal file
20
app/src/main/res/drawable/ic_settings.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="16" android:viewportWidth="16" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#6F0D1E" android:pathData="M6.043,14.373L5.782,12.283C5.64,12.228 5.507,12.163 5.382,12.087C5.257,12.01 5.135,11.929 5.014,11.842L3.071,12.658L1.274,9.555L2.956,8.281C2.945,8.205 2.94,8.131 2.94,8.061V7.62C2.94,7.549 2.945,7.475 2.956,7.399L1.274,6.125L3.071,3.022L5.014,3.838C5.134,3.751 5.259,3.67 5.39,3.593C5.521,3.517 5.651,3.452 5.782,3.397L6.043,1.307H9.637L9.898,3.397C10.04,3.452 10.173,3.517 10.299,3.593C10.424,3.67 10.546,3.751 10.666,3.838L12.609,3.022L14.406,6.125L12.724,7.399C12.735,7.475 12.74,7.549 12.74,7.62V8.06C12.74,8.131 12.729,8.205 12.707,8.281L14.39,9.555L12.593,12.658L10.666,11.842C10.546,11.929 10.421,12.01 10.29,12.087C10.159,12.163 10.029,12.228 9.898,12.283L9.637,14.373H6.043ZM7.873,10.127C8.504,10.127 9.043,9.903 9.49,9.457C9.936,9.011 10.159,8.472 10.159,7.84C10.159,7.208 9.936,6.669 9.49,6.223C9.043,5.777 8.504,5.553 7.873,5.553C7.23,5.553 6.688,5.777 6.247,6.223C5.806,6.669 5.586,7.208 5.586,7.84C5.586,8.472 5.807,9.011 6.248,9.457C6.689,9.903 7.23,10.127 7.873,10.127Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
24
app/src/main/res/drawable/ic_settlement.xml
Normal file
24
app/src/main/res/drawable/ic_settlement.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="34dp"
|
||||||
|
android:height="34dp"
|
||||||
|
android:viewportWidth="34"
|
||||||
|
android:viewportHeight="34">
|
||||||
|
<path
|
||||||
|
android:pathData="M10.094,1.125C10.592,1.125 11.018,1.175 11.372,1.274C11.726,1.374 12.047,1.507 12.335,1.673C12.623,1.839 12.877,2.01 13.099,2.188C13.32,2.365 13.541,2.536 13.763,2.702C13.984,2.868 14.227,2.995 14.493,3.084C14.759,3.173 15.063,3.228 15.406,3.25H31.875C32.174,3.25 32.451,3.305 32.705,3.416C32.96,3.527 33.181,3.676 33.369,3.864C33.557,4.052 33.712,4.279 33.834,4.545C33.956,4.811 34.011,5.087 34,5.375V11.75H31.875V5.375H15.406C15.063,5.375 14.764,5.425 14.51,5.524C14.255,5.624 14.012,5.757 13.779,5.923C13.547,6.089 13.32,6.26 13.099,6.438C12.877,6.615 12.628,6.786 12.352,6.952C12.075,7.118 11.754,7.245 11.389,7.334C11.023,7.423 10.592,7.478 10.094,7.5H2.125V26.625H4.25V28.75H0V3.25C0,2.951 0.055,2.674 0.166,2.42C0.277,2.165 0.426,1.944 0.614,1.756C0.802,1.568 1.029,1.413 1.295,1.291C1.561,1.169 1.837,1.114 2.125,1.125H10.094ZM10.094,5.375C10.359,5.375 10.592,5.353 10.791,5.309C10.99,5.264 11.173,5.192 11.339,5.093C11.505,4.993 11.665,4.883 11.82,4.761C11.975,4.639 12.152,4.49 12.352,4.313C12.163,4.146 11.992,4.003 11.837,3.881C11.682,3.759 11.516,3.648 11.339,3.549C11.162,3.449 10.979,3.377 10.791,3.333C10.603,3.289 10.37,3.261 10.094,3.25H2.125V5.375H10.094ZM34,13.875V33H6.375V13.875H34ZM31.875,16H8.5V30.875H31.875V16ZM29.75,20.25H23.375V18.125H29.75V20.25ZM27.625,24.5H23.375V22.375H27.625V24.5ZM27.625,28.75H23.375V26.625H27.625V28.75ZM21.25,28.75H10.625V18.125H21.25V28.75ZM19.125,20.25H12.75V26.625H19.125V20.25Z"
|
||||||
|
android:fillColor="#6F0D1E"/>
|
||||||
|
</vector>
|
||||||
29
app/src/main/res/drawable/ic_sign_on.xml
Normal file
29
app/src/main/res/drawable/ic_sign_on.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="35dp"
|
||||||
|
android:height="36dp"
|
||||||
|
android:viewportWidth="35"
|
||||||
|
android:viewportHeight="36">
|
||||||
|
<path
|
||||||
|
android:pathData="M18.074,20.77C17.568,20.441 17.131,20.015 16.789,19.517C16.447,19.019 16.206,18.458 16.081,17.867C15.956,17.276 15.948,16.667 16.058,16.073C16.169,15.479 16.395,14.912 16.724,14.406L21.74,6.694C22.064,6.176 22.488,5.728 22.987,5.375C23.486,5.023 24.051,4.773 24.647,4.641C25.243,4.509 25.86,4.497 26.462,4.606C27.063,4.715 27.636,4.943 28.148,5.276C28.66,5.609 29.101,6.041 29.445,6.546C29.788,7.051 30.028,7.62 30.149,8.218C30.27,8.817 30.271,9.434 30.152,10.033C30.032,10.632 29.794,11.202 29.452,11.708L24.438,19.422C23.773,20.445 22.729,21.161 21.535,21.414C20.342,21.667 19.097,21.435 18.074,20.77ZM12.458,21.234C11.686,19.783 11.317,18.151 11.389,16.509C11.462,14.867 11.973,13.274 12.869,11.896L17.883,4.184C19.214,2.139 21.302,0.706 23.69,0.2C24.872,-0.051 26.091,-0.066 27.279,0.155C28.467,0.376 29.6,0.829 30.613,1.488C31.626,2.146 32.499,2.998 33.183,3.995C33.866,4.991 34.347,6.112 34.597,7.294C34.848,8.476 34.863,9.696 34.642,10.884C34.421,12.071 33.968,13.204 33.31,14.217L28.295,21.929C27.423,23.272 26.215,24.363 24.792,25.096C23.368,25.829 21.778,26.177 20.178,26.107C18.579,26.037 17.025,25.551 15.671,24.696C14.317,23.841 13.209,22.648 12.458,21.234Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6.694,29.83C6.187,29.5 5.751,29.074 5.409,28.576C5.067,28.079 4.826,27.518 4.701,26.927C4.575,26.336 4.568,25.726 4.678,25.132C4.788,24.538 5.015,23.972 5.344,23.465L10.36,15.753C10.684,15.236 11.108,14.787 11.607,14.435C12.106,14.082 12.67,13.833 13.267,13.701C13.863,13.569 14.48,13.557 15.081,13.666C15.682,13.775 16.256,14.003 16.768,14.336C17.28,14.669 17.721,15.101 18.064,15.606C18.408,16.111 18.647,16.679 18.769,17.278C18.89,17.877 18.891,18.494 18.771,19.093C18.652,19.692 18.414,20.261 18.072,20.767L13.058,28.482C12.393,29.504 11.349,30.221 10.155,30.474C8.962,30.726 7.717,30.495 6.694,29.83ZM1.077,30.294C0.305,28.843 -0.063,27.211 0.009,25.569C0.081,23.927 0.592,22.334 1.489,20.956L6.503,13.247C7.162,12.234 8.013,11.36 9.009,10.677C10.005,9.993 11.126,9.512 12.308,9.261C13.49,9.011 14.71,8.996 15.898,9.216C17.086,9.437 18.218,9.89 19.231,10.549C20.244,11.207 21.117,12.059 21.801,13.055C22.485,14.051 22.966,15.172 23.216,16.354C23.467,17.536 23.482,18.756 23.261,19.943C23.04,21.131 22.588,22.264 21.929,23.277L16.915,30.989C16.043,32.331 14.835,33.423 13.411,34.156C11.988,34.889 10.397,35.237 8.798,35.167C7.198,35.097 5.644,34.61 4.29,33.756C2.936,32.901 1.829,31.708 1.077,30.294Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
24
app/src/main/res/drawable/ic_success.xml
Normal file
24
app/src/main/res/drawable/ic_success.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="800dp"
|
||||||
|
android:height="800dp"
|
||||||
|
android:viewportWidth="52"
|
||||||
|
android:viewportHeight="52">
|
||||||
|
<path
|
||||||
|
android:pathData="M26,2C12.7,2 2,12.7 2,26s10.7,24 24,24s24,-10.7 24,-24S39.3,2 26,2zM39.4,20L24.1,35.5c-0.6,0.6 -1.6,0.6 -2.2,0L13.5,27c-0.6,-0.6 -0.6,-1.6 0,-2.2l2.2,-2.2c0.6,-0.6 1.6,-0.6 2.2,0l4.4,4.5c0.4,0.4 1.1,0.4 1.5,0L35,15.5c0.6,-0.6 1.6,-0.6 2.2,0l2.2,2.2C40.1,18.3 40.1,19.3 39.4,20z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
24
app/src/main/res/drawable/ic_terminal.xml
Normal file
24
app/src/main/res/drawable/ic_terminal.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="27dp"
|
||||||
|
android:height="46dp"
|
||||||
|
android:viewportWidth="27"
|
||||||
|
android:viewportHeight="46">
|
||||||
|
<path
|
||||||
|
android:pathData="M2.777,46C2.041,46 1.334,45.709 0.813,45.192C0.293,44.674 0,43.972 0,43.24L0,10.841C0,9.317 1.244,8.081 2.777,8.081H6.067C6.173,8.081 6.27,8.104 6.359,8.15L6.355,8.148V0.322C6.355,0.144 6.499,0 6.679,0H18.289C18.468,0 18.613,0.144 18.613,0.322V8.102C18.663,8.088 18.715,8.082 18.767,8.083H21.689C23.222,8.083 24.466,9.319 24.466,10.843V18.569C24.552,18.525 24.648,18.503 24.746,18.505H26.205C26.564,18.505 26.855,18.795 26.855,19.151V35.964C26.855,36.136 26.787,36.3 26.665,36.421C26.543,36.542 26.378,36.61 26.205,36.61H24.746C24.647,36.613 24.549,36.59 24.462,36.545L24.466,36.547V43.24C24.466,43.972 24.173,44.674 23.653,45.192C23.132,45.709 22.425,46 21.689,46H2.777ZM16.902,39.807V41.981C16.903,42.067 16.937,42.149 16.998,42.209C17.059,42.27 17.142,42.304 17.228,42.305H19.801C19.887,42.304 19.969,42.27 20.031,42.209C20.092,42.149 20.126,42.067 20.127,41.981V39.807C20.126,39.722 20.091,39.64 20.03,39.58C19.969,39.52 19.887,39.486 19.801,39.485H17.224C17.138,39.486 17.056,39.52 16.995,39.58C16.934,39.64 16.899,39.722 16.898,39.807H16.902ZM10.815,39.807V41.981C10.816,42.067 10.85,42.149 10.911,42.209C10.972,42.27 11.055,42.304 11.141,42.305H13.712C13.798,42.304 13.881,42.27 13.942,42.209C14.003,42.149 14.038,42.067 14.038,41.981V39.807C14.037,39.722 14.002,39.64 13.941,39.58C13.88,39.52 13.798,39.486 13.712,39.485H11.139C11.054,39.486 10.971,39.52 10.91,39.58C10.849,39.64 10.816,39.722 10.815,39.807ZM4.729,39.807V41.983C4.729,42.161 4.873,42.305 5.053,42.305H7.626C7.712,42.304 7.794,42.27 7.855,42.209C7.916,42.149 7.951,42.067 7.951,41.981V39.807C7.95,39.722 7.916,39.64 7.855,39.58C7.794,39.52 7.712,39.486 7.626,39.485H5.053C4.967,39.486 4.885,39.521 4.825,39.581C4.764,39.641 4.73,39.722 4.729,39.807ZM16.9,34.88V37.053C16.901,37.139 16.935,37.221 16.996,37.282C17.057,37.342 17.14,37.376 17.226,37.377H19.799C19.885,37.376 19.968,37.342 20.029,37.282C20.09,37.221 20.124,37.139 20.125,37.053V34.878C20.124,34.792 20.089,34.711 20.029,34.651C19.968,34.591 19.886,34.557 19.801,34.556H17.222C17.136,34.557 17.055,34.591 16.994,34.651C16.934,34.711 16.899,34.792 16.898,34.878L16.9,34.88ZM10.814,34.88V37.053C10.814,37.139 10.849,37.221 10.91,37.282C10.97,37.342 11.053,37.376 11.139,37.377H13.712C13.798,37.376 13.881,37.342 13.942,37.282C14.003,37.221 14.038,37.139 14.038,37.053V34.878C14.037,34.792 14.003,34.711 13.942,34.651C13.882,34.591 13.8,34.557 13.714,34.556H11.137C11.052,34.557 10.97,34.591 10.91,34.651C10.849,34.711 10.814,34.794 10.814,34.88ZM4.727,34.88V37.055C4.727,37.233 4.872,37.377 5.051,37.377H7.624C7.71,37.376 7.793,37.342 7.854,37.282C7.915,37.221 7.949,37.139 7.95,37.053V34.878C7.949,34.792 7.914,34.711 7.854,34.651C7.793,34.591 7.711,34.557 7.626,34.556H5.051C4.965,34.557 4.883,34.591 4.823,34.651C4.762,34.711 4.728,34.794 4.727,34.88ZM16.898,29.95V32.125C16.898,32.304 17.043,32.447 17.222,32.447H19.799C19.884,32.446 19.966,32.412 20.027,32.352C20.087,32.292 20.122,32.21 20.123,32.125V29.95C20.122,29.864 20.087,29.783 20.026,29.723C19.965,29.662 19.883,29.628 19.797,29.628H17.224C17.138,29.628 17.056,29.662 16.995,29.723C16.934,29.783 16.899,29.864 16.898,29.95ZM10.812,29.95V32.125C10.812,32.304 10.956,32.447 11.136,32.447H13.712C13.798,32.446 13.88,32.412 13.94,32.352C14.001,32.292 14.035,32.21 14.036,32.125V29.95C14.035,29.864 14,29.783 13.939,29.723C13.878,29.662 13.796,29.628 13.71,29.628H11.137C11.052,29.628 10.969,29.662 10.908,29.723C10.847,29.783 10.813,29.864 10.812,29.95ZM4.725,29.95V32.125C4.725,32.304 4.87,32.447 5.049,32.447H7.626C7.711,32.446 7.793,32.412 7.854,32.352C7.914,32.292 7.949,32.21 7.95,32.125V29.95C7.949,29.864 7.914,29.783 7.853,29.723C7.792,29.662 7.71,29.628 7.624,29.628H5.051C4.965,29.629 4.883,29.663 4.823,29.723C4.762,29.783 4.726,29.865 4.725,29.95ZM4.32,17.083V26.498C4.32,26.676 4.465,26.82 4.644,26.82H19.614C19.699,26.819 19.781,26.785 19.842,26.725C19.902,26.664 19.937,26.583 19.938,26.498V17.083C19.937,16.997 19.903,16.915 19.841,16.855C19.781,16.794 19.698,16.76 19.612,16.759H4.646C4.56,16.76 4.477,16.794 4.416,16.855C4.355,16.915 4.321,16.997 4.32,17.083ZM3.348,11.943V14.224C3.348,14.758 3.784,15.191 4.322,15.191H20.52C20.778,15.191 21.026,15.09 21.209,14.908C21.391,14.726 21.494,14.48 21.494,14.224V11.943C21.494,11.686 21.391,11.44 21.209,11.258C21.026,11.077 20.778,10.975 20.52,10.975H18.611V12.911H19.552V13.252H5.296V12.911H5.915C6.071,12.911 6.226,12.873 6.364,12.801L6.359,12.803V11.082C6.221,11.011 6.068,10.974 5.913,10.975H4.318C4.06,10.975 3.813,11.078 3.631,11.259C3.448,11.441 3.348,11.686 3.348,11.943Z"
|
||||||
|
android:fillColor="#6F0D1E"/>
|
||||||
|
</vector>
|
||||||
24
app/src/main/res/drawable/ic_version.xml
Normal file
24
app/src/main/res/drawable/ic_version.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="12dp"
|
||||||
|
android:height="14dp"
|
||||||
|
android:viewportWidth="12"
|
||||||
|
android:viewportHeight="14">
|
||||||
|
<path
|
||||||
|
android:pathData="M11.212,7.908C11.822,8.309 11.794,9.239 11.127,9.591L6.436,12.071C6.247,12.17 6.038,12.223 5.825,12.223C5.612,12.223 5.402,12.17 5.214,12.071L0.522,9.591C-0.144,9.238 -0.173,8.309 0.438,7.908L0.479,7.934L5.214,10.438C5.402,10.537 5.612,10.589 5.825,10.589C6.038,10.589 6.247,10.537 6.436,10.438L11.127,7.958C11.156,7.942 11.184,7.926 11.212,7.908ZM11.212,5.295C11.346,5.384 11.457,5.505 11.534,5.648C11.61,5.79 11.65,5.95 11.65,6.112C11.65,6.273 11.61,6.433 11.534,6.575C11.457,6.718 11.346,6.839 11.212,6.929L11.127,6.978L6.436,9.458C6.265,9.548 6.077,9.599 5.884,9.608C5.691,9.617 5.498,9.582 5.32,9.508L5.215,9.458L0.522,6.978C-0.144,6.625 -0.173,5.696 0.438,5.295L0.479,5.321L5.214,7.824C5.385,7.915 5.573,7.966 5.766,7.975C5.959,7.983 6.152,7.949 6.33,7.875L6.436,7.824L11.127,5.344C11.156,5.329 11.184,5.312 11.212,5.295ZM6.436,0.152L11.127,2.632C11.824,2.999 11.824,3.997 11.127,4.364L6.436,6.845C6.247,6.945 6.038,6.997 5.825,6.997C5.612,6.997 5.402,6.945 5.214,6.845L0.522,4.364C-0.174,3.996 -0.174,2.999 0.522,2.632L5.214,0.152C5.402,0.052 5.612,0 5.825,0C6.038,0 6.247,0.052 6.436,0.152Z"
|
||||||
|
android:fillColor="#6F0D1E"/>
|
||||||
|
</vector>
|
||||||
49
app/src/main/res/drawable/ic_wifi.xml
Normal file
49
app/src/main/res/drawable/ic_wifi.xml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="800dp"
|
||||||
|
android:height="800dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M4.91,11.84C9.21,8.52 14.8,8.52 19.1,11.84"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#292D32"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M2,8.36C8.06,3.68 15.94,3.68 22,8.36"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#292D32"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6.79,15.49C9.94,13.05 14.05,13.05 17.2,15.49"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#292D32"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M9.4,19.15C10.98,17.93 13.03,17.93 14.61,19.15"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#292D32"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
BIN
app/src/main/res/drawable/img.png
Normal file
BIN
app/src/main/res/drawable/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
46
app/src/main/res/drawable/upside_down_arrows.xml
Normal file
46
app/src/main/res/drawable/upside_down_arrows.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M8.067,6.041C7.921,5.894 7.838,5.694 7.838,5.486C7.838,5.279 7.921,5.079 8.067,4.932L10.419,2.581C10.491,2.506 10.578,2.446 10.673,2.405C10.769,2.364 10.872,2.343 10.976,2.342C11.08,2.341 11.183,2.361 11.279,2.4C11.376,2.44 11.463,2.498 11.537,2.571C11.61,2.645 11.669,2.732 11.708,2.829C11.747,2.925 11.767,3.028 11.766,3.132C11.766,3.236 11.744,3.339 11.703,3.435C11.662,3.53 11.602,3.617 11.527,3.689L9.176,6.041C9.029,6.188 8.829,6.27 8.622,6.27C8.414,6.27 8.214,6.188 8.067,6.041Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M13.878,6.041C13.731,6.188 13.532,6.27 13.324,6.27C13.116,6.27 12.917,6.188 12.77,6.041L10.419,3.689C10.276,3.541 10.197,3.343 10.199,3.138C10.201,2.932 10.283,2.736 10.428,2.591C10.574,2.445 10.77,2.363 10.976,2.361C11.181,2.359 11.379,2.438 11.527,2.581L13.878,4.932C14.025,5.079 14.108,5.279 14.108,5.487C14.108,5.694 14.025,5.894 13.878,6.041Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.973,3.919C11.181,3.919 11.38,4.002 11.527,4.149C11.674,4.295 11.757,4.495 11.757,4.703V10.973C11.757,11.181 11.674,11.38 11.527,11.527C11.38,11.674 11.181,11.757 10.973,11.757C10.765,11.757 10.566,11.674 10.419,11.527C10.272,11.38 10.189,11.181 10.189,10.973V4.703C10.189,4.495 10.272,4.295 10.419,4.149C10.566,4.002 10.765,3.919 10.973,3.919ZM7.608,9.635C7.755,9.782 7.838,9.981 7.838,10.189C7.838,10.397 7.755,10.596 7.608,10.743L5.257,13.095C5.109,13.238 4.911,13.316 4.706,13.315C4.5,13.313 4.303,13.231 4.158,13.085C4.013,12.94 3.93,12.743 3.929,12.538C3.927,12.332 4.006,12.134 4.149,11.986L6.5,9.635C6.647,9.488 6.846,9.406 7.054,9.406C7.262,9.406 7.461,9.488 7.608,9.635Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M1.797,9.635C1.944,9.488 2.144,9.406 2.351,9.406C2.559,9.406 2.759,9.488 2.905,9.635L5.257,11.986C5.332,12.059 5.391,12.145 5.432,12.241C5.474,12.336 5.495,12.439 5.496,12.543C5.497,12.647 5.477,12.751 5.438,12.847C5.398,12.943 5.34,13.031 5.267,13.104C5.193,13.178 5.105,13.236 5.009,13.276C4.913,13.315 4.81,13.335 4.706,13.334C4.601,13.333 4.499,13.311 4.403,13.27C4.307,13.229 4.221,13.17 4.149,13.095L1.797,10.743C1.65,10.596 1.568,10.397 1.568,10.189C1.568,9.981 1.65,9.782 1.797,9.635Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4.703,11.757C4.495,11.757 4.295,11.674 4.149,11.527C4.002,11.38 3.919,11.181 3.919,10.973V4.703C3.919,4.495 4.002,4.295 4.149,4.149C4.295,4.002 4.495,3.919 4.703,3.919C4.911,3.919 5.11,4.002 5.257,4.149C5.404,4.295 5.487,4.495 5.487,4.703V10.973C5.487,11.181 5.404,11.38 5.257,11.527C5.11,11.674 4.911,11.757 4.703,11.757Z"
|
||||||
|
android:fillColor="#6F0D1E"
|
||||||
|
android:fillAlpha="0.8"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
BIN
app/src/main/res/ic_launcher-web.png
Normal file
BIN
app/src/main/res/ic_launcher-web.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@ -1,6 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@ -1,6 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user