taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit a26f5a6c4b012f625005d9d729a56a7d1d88f984
parent 5bf1f8c6655e5a39d3202edd32365097e5d043cf
Author: Bohdan Potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Tue, 19 May 2026 14:36:52 +0200

[pos] bug fixes

Diffstat:
Amerchant-terminal/SCREENSHOTS.md | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmerchant-terminal/build.gradle | 4++--
Mmerchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt | 8++++++--
Mmerchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt | 32++++++++++++++++++++++++++++++++
Mmerchant-terminal/src/main/java/net/taler/merchantpos/config/GeneralSettingsFragment.kt | 2--
Mmerchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt | 10++++++++++
Mmerchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt | 7+++++++
Mmerchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt | 3++-
8 files changed, 133 insertions(+), 7 deletions(-)

diff --git a/merchant-terminal/SCREENSHOTS.md b/merchant-terminal/SCREENSHOTS.md @@ -0,0 +1,74 @@ +# Merchant Terminal screenshots + +This module now has a debug-only screenshot mode for deterministic captures of real app screens. + +## What it does + +- Launches the app directly into a named scenario using an intent extra. +- Seeds fixed merchant config, products, totals, and product thumbnails in-process. +- Captures the resulting screen with `adb exec-out screencap`. + +Bundled fixture product images are generated locally by `merchant-terminal/scripts/generate-screenshot-product-images.sh`, so the default workflow does not depend on third-party image licenses. + +## Supported scenarios + +- `amount-entry` +- `order` +- `payment` +- `payment-success` + +## Generate fixture product images + +```bash +merchant-terminal/scripts/generate-screenshot-product-images.sh +``` + +The images are written to `merchant-terminal/src/debug/assets/screenshot-products/`. + +## Capture screenshots + +Connect an emulator or device, then run: + +```bash +merchant-terminal/scripts/capture-screenshots.sh +``` + +Custom output directory: + +```bash +merchant-terminal/scripts/capture-screenshots.sh /tmp/merchant-terminal-shots +``` + +Specific scenarios only: + +```bash +merchant-terminal/scripts/capture-screenshots.sh /tmp/merchant-terminal-shots order payment +``` + +## Intent hook + +The debug hook is activated with: + +```text +--es taler_pos_screenshot_scenario <scenario> +``` + +Example: + +```bash +adb shell am start -W \ + -n net.taler.merchantpos/.MainActivity \ + --es taler_pos_screenshot_scenario order +``` + +## If you want real photos later + +For external product photos, use sources with clear reuse terms and record the exact asset URLs and license notes alongside the files you import. + +Reasonable options: + +- Pexels license: https://www.pexels.com/license/ +- Unsplash license: https://unsplash.com/license +- Wikimedia Commons reuse guide: https://commons.wikimedia.org/wiki/Commons:Simple_media_reuse_guide + +For maximum simplicity in a repo, prefer public-domain or CC0 assets, or keep using locally generated fixture images for screenshot automation. diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle @@ -13,8 +13,8 @@ android { applicationId "net.taler.merchantpos" minSdkVersion 23 targetSdkVersion 36 - versionCode 21 - versionName "1.5.0" + versionCode 22 + versionName "1.5.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "BACKEND_API_VERSION", "\"20:0:8\"") diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt @@ -87,6 +87,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import kotlinx.coroutines.launch import net.taler.lib.android.TalerNfcService +import net.taler.merchantpos.debug.ScreenshotController import net.taler.merchantpos.compose.PosTheme import net.taler.merchantpos.config.Config import net.taler.merchantpos.config.ConfigFetcherFragment @@ -118,6 +119,8 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val screenshotStartDestination = ScreenshotController.prepareScenario(intent, model) + TalerNfcService.startService(this) model.paymentManager.payment.observe(this) { payment -> @@ -137,7 +140,7 @@ class MainActivity : AppCompatActivity() { PosTheme { MerchantTerminalApp( viewModel = model, - startDestination = determineStartDestination(), + startDestination = determineStartDestination(screenshotStartDestination), onNavControllerReady = { navController = it }, onExitRequested = ::handleExitRequest, ) @@ -258,7 +261,8 @@ class MainActivity : AppCompatActivity() { } } - private fun determineStartDestination(): PosDestination { + private fun determineStartDestination(overrideDestination: PosDestination? = null): PosDestination { + overrideDestination?.let { return it } return when { !model.configManager.config.isValid() -> PosDestination.Config model.configManager.merchantConfig == null || model.configManager.currency == null -> diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -235,6 +236,37 @@ class ConfigManager( } } + internal fun debugApplyFixture( + posConfig: PosConfig, + merchantConfig: MerchantConfig, + currency: String, + currencySpec: CurrencySpecification? = null, + initialOrderScreen: InitialOrderScreen = InitialOrderScreen.Inventory, + ) { + config = Config.New( + merchantUrl = merchantConfig.baseUrl, + accessToken = "", + savePassword = false, + ) + this.merchantConfig = merchantConfig + this.currency = currency + this.currencySpec = currencySpec + this.initialOrderScreen = initialOrderScreen + + val snapshot = CachedRuntimeConfig( + posConfig = posConfig, + merchantConfig = merchantConfig, + currency = currency, + currencySpec = currencySpec, + ) + saveCachedRuntimeConfig(snapshot) + runBlocking { + configurationReceivers.forEach { receiver -> + receiver.onConfigurationReceived(posConfig, currency, currencySpec) + } + } + } + private fun migrateLegacyPrefsIfNeeded() { val legacyVersion = prefs.getInt(SETTINGS_CONFIG_VERSION, CONFIG_VERSION_NEW) if (legacyVersion == CONFIG_VERSION_OLD) { diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/GeneralSettingsFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/GeneralSettingsFragment.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api @@ -190,7 +189,6 @@ private fun GeneralSettingsScreen( LazyColumn( modifier = Modifier .fillMaxSize() - .statusBarsPadding() .padding(20.dp), verticalArrangement = Arrangement.spacedBy(14.dp), ) { diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt @@ -243,6 +243,16 @@ class OrderManager(private val context: Context) : ConfigurationReceiver { } @UiThread + internal fun debugSeedCurrentOrder(productIds: List<String>) { + val orderId = currentOrderId.value ?: return + val liveOrder = order(orderId) + productIds.forEach { productId -> + productsById[productId]?.let(liveOrder::addProduct) + } + updateVisibleProducts() + } + + @UiThread internal fun onOrderPaid(orderId: Int) { if (currentOrderId.value == orderId) { if (hasPreviousOrder(orderId)) previousOrder() diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt @@ -147,6 +147,13 @@ class PaymentManager( } } + @UiThread + internal fun debugSetPayment(payment: Payment) { + checkTimer.cancel() + checkJob = null + mPayment.value = payment + } + private fun checkPayment(orderId: String) = scope.launch { val merchantConfig = configManager.merchantConfig!! api.checkOrder(merchantConfig, orderId).handle({ error -> diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt @@ -258,9 +258,10 @@ private fun TabletProcessPaymentScreen( textAlign = TextAlign.Center, ) } + Spacer(modifier = Modifier.weight(1f)) OutlinedButton( onClick = onCancel, - modifier = Modifier.align(Alignment.Start), + modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.outlinedButtonColors( containerColor = MaterialTheme.colorScheme.error, contentColor = MaterialTheme.colorScheme.onError,