diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..812ad7c --- /dev/null +++ b/CLAUDE.md @@ -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//` 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` diff --git a/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt b/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt index d02cb50..bba17b2 100644 --- a/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt +++ b/app/src/main/java/com/mob/utsmyanmar/MainActivity.kt @@ -1,6 +1,7 @@ package com.mob.utsmyanmar import android.os.Bundle +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent @@ -14,9 +15,10 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { - installSplashScreen() +// installSplashScreen() super.onCreate(savedInstanceState) // enableEdgeToEdge() + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) setContent { MOBPOSTheme { val navController = rememberNavController() diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt index 5a98547..ce7ee3f 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen2.kt @@ -365,7 +365,7 @@ private fun AdvertisingArea() { val imageArray = listOf( "https://i.ytimg.com/vi/eRUVxGRp1Ms/maxresdefault.jpg", - "https://sunmi.ua/wp-content/themes/yootheme/cache/04/p6-3-en-04a68bb5.jpeg", + "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 }) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt b/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt index 3cf2398..a450eaa 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/preview/Preview.kt @@ -13,7 +13,7 @@ annotation class P2Preview @Preview( name = "P3", - device = "spec:width=720px,height=1600px,dpi=300", + device = "spec:width=720px,height=1600px,dpi=270", showBackground = true, showSystemUi = true ) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/version/Version.kt b/app/src/main/java/com/mob/utsmyanmar/ui/version/Version.kt index 42205e8..db88836 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/version/Version.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/version/Version.kt @@ -1,42 +1,23 @@ package com.mob.utsmyanmar.ui.version 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.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ChevronLeft -import androidx.compose.material.icons.filled.Square -import androidx.compose.material3.Card -import androidx.compose.material3.CardColors -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.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.vector.ImageVector -import androidx.compose.ui.text.style.TextAlign +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 androidx.lifecycle.viewmodel.compose.viewModel 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( @@ -81,43 +62,92 @@ fun VersionScreen( Item( title = "PROD NAME", value = deviceInfo.deviceModel, - icon = Icons.Default.Square + 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 = Icons.Default.Square + 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 = Icons.Default.Square + 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 = Icons.Default.Square + 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 = Icons.Default.Square + 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 = Icons.Default.Square + 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 = Icons.Default.Square + icon = { + Icon( + painter = painterResource(R.drawable.ic_function), + contentDescription = "icon", + modifier = Modifier.size(24.dp), + tint = Color.LegacyRed + ) + } ) } } @@ -129,7 +159,7 @@ fun VersionScreen( fun Item( title: String, value: String, - icon: ImageVector + icon: @Composable () -> Unit, ) { Row( modifier = Modifier.fillMaxWidth(), @@ -137,24 +167,23 @@ fun Item( verticalAlignment = Alignment.CenterVertically, ) { Row( - modifier = Modifier.weight(1.4f), + modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { Box( - modifier = Modifier.size(40.dp).background( - color = Color.CrimsonRed.copy(alpha = 0.2f), - shape = CircleShape - ), + modifier = Modifier + .size(50.dp) + .background( + Color.CrimsonRed.copy(alpha = 0.1f), + shape = RoundedCornerShape(12.dp) + ), contentAlignment = Alignment.Center - ){ - Icon( - imageVector = icon, - contentDescription = "icon" - ) + ) { + icon() } - - Text(text = title) + + Text(text = title, fontSize = 14.sp) } Row( modifier = Modifier.weight(1f), @@ -162,11 +191,12 @@ fun Item( horizontalArrangement = Arrangement.SpaceBetween ) { Text(text = ":") - Text(text = value) + Text(text = value, fontSize = 14.sp) } } } +@P2Preview @P3Preview @Composable fun P3PreviewVersionScreen() { diff --git a/app/src/main/res/drawable/ic_circle_wrong.xml b/app/src/main/res/drawable/ic_circle_wrong.xml new file mode 100644 index 0000000..b64570f --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_wrong.xml @@ -0,0 +1,37 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_code.xml b/app/src/main/res/drawable/ic_code.xml new file mode 100644 index 0000000..00c19bd --- /dev/null +++ b/app/src/main/res/drawable/ic_code.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_four_vertical_bars.xml b/app/src/main/res/drawable/ic_four_vertical_bars.xml new file mode 100644 index 0000000..6fd2efd --- /dev/null +++ b/app/src/main/res/drawable/ic_four_vertical_bars.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_function.xml b/app/src/main/res/drawable/ic_function.xml new file mode 100644 index 0000000..40fa841 --- /dev/null +++ b/app/src/main/res/drawable/ic_function.xml @@ -0,0 +1,25 @@ + + + + diff --git a/app/src/main/res/drawable/ic_gear.xml b/app/src/main/res/drawable/ic_gear.xml new file mode 100644 index 0000000..e40e809 --- /dev/null +++ b/app/src/main/res/drawable/ic_gear.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..bb9afa0 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,25 @@ + + + + diff --git a/app/src/main/res/drawable/ic_protection_shield.xml b/app/src/main/res/drawable/ic_protection_shield.xml new file mode 100644 index 0000000..ba90001 --- /dev/null +++ b/app/src/main/res/drawable/ic_protection_shield.xml @@ -0,0 +1,35 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..b1da8a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_version.xml b/app/src/main/res/drawable/ic_version.xml new file mode 100644 index 0000000..77b3fea --- /dev/null +++ b/app/src/main/res/drawable/ic_version.xml @@ -0,0 +1,24 @@ + + + + diff --git a/app/src/main/res/drawable/upside_down_arrows.xml b/app/src/main/res/drawable/upside_down_arrows.xml new file mode 100644 index 0000000..e954415 --- /dev/null +++ b/app/src/main/res/drawable/upside_down_arrows.xml @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/baselib/build.gradle b/baselib/build.gradle index 0c37240..3c9059a 100644 --- a/baselib/build.gradle +++ b/baselib/build.gradle @@ -58,7 +58,7 @@ android { dependencies { def lottieVersion = "3.5.0" - def roomVersion = "2.4.3" + def roomVersion = "2.8.4" implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')