taler-android

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

commit 7e5ed5c3027431ca83307b978b97ddef842d4a82
parent 4b245e77729800056add4b573bb24d48855d480c
Author: Bohdan Potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Sat, 17 Jan 2026 10:04:45 +0100

[merchant-terminal] Adding new order creation method with only amount

Diffstat:
Mmerchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt | 13+++++++++++--
Amerchant-terminal/src/main/java/net/taler/merchantpos/amount/AmountEntryFragment.kt | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmerchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt | 4++--
Mmerchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt | 4++--
Mmerchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt | 11+++++++++--
Amerchant-terminal/src/main/res/color/amount_entry_key_background.xml | 9+++++++++
Amerchant-terminal/src/main/res/color/amount_entry_key_text.xml | 9+++++++++
Amerchant-terminal/src/main/res/drawable/ic_backspace.xml | 9+++++++++
Mmerchant-terminal/src/main/res/layout/app_bar_main.xml | 9++++-----
Amerchant-terminal/src/main/res/layout/fragment_amount_entry.xml | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmerchant-terminal/src/main/res/layout/fragment_payment_success.xml | 9+++++++--
Mmerchant-terminal/src/main/res/menu/activity_main_drawer.xml | 4++++
Mmerchant-terminal/src/main/res/navigation/nav_graph.xml | 15+++++++++++++++
Mmerchant-terminal/src/main/res/values-night/colors.xml | 48+++++++++++++++++++++++++++++++++++++++++++++++-
Amerchant-terminal/src/main/res/values-night/styles.xml | 19+++++++++++++++++++
Dmerchant-terminal/src/main/res/values-v35/styles.xml | 25-------------------------
Mmerchant-terminal/src/main/res/values/dimens.xml | 7+++++--
Mmerchant-terminal/src/main/res/values/strings.xml | 12+++++++++++-
Mmerchant-terminal/src/main/res/values/styles.xml | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
19 files changed, 716 insertions(+), 52 deletions(-)

diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt @@ -84,7 +84,15 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener { ui.navView.setNavigationItemSelectedListener(this) setSupportActionBar(ui.main.toolbar) - val appBarConfiguration = AppBarConfiguration(nav.graph, ui.drawerLayout) + val appBarConfiguration = AppBarConfiguration( + setOf( + R.id.nav_order, + R.id.nav_amountEntry, + R.id.nav_history, + R.id.nav_settings, + ), + ui.drawerLayout, + ) ui.main.toolbar.setupWithNavController(nav, appBarConfiguration) handleSetupIntent(intent) @@ -119,6 +127,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener { override fun onNavigationItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.nav_order -> nav.navigate(R.id.action_global_order) + R.id.nav_amountEntry -> nav.navigate(R.id.action_global_amountEntry) R.id.nav_history -> nav.navigate(R.id.action_global_merchantHistory) R.id.nav_settings-> nav.navigate(R.id.action_global_merchantSettings) } @@ -144,7 +153,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener { flags = FLAG_ACTIVITY_NEW_TASK } startActivity(intent) - } else if (currentDestination == R.id.nav_order) { + } else if (currentDestination == R.id.nav_order || currentDestination == R.id.nav_amountEntry) { if (reallyExit) super.onBackPressed() else { // this closes the app and causes orders to be lost, so let's confirm first diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/amount/AmountEntryFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/amount/AmountEntryFragment.kt @@ -0,0 +1,178 @@ +/* + * This file is part of GNU Taler + * (C) 2026 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.merchantpos.amount + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import net.taler.common.Amount +import net.taler.common.navigate +import net.taler.merchantpos.MainViewModel +import net.taler.merchantpos.R +import net.taler.merchantpos.amount.AmountEntryFragmentDirections.Companion.actionAmountEntryToProcessPayment +import net.taler.merchantpos.amount.AmountEntryFragmentDirections.Companion.actionGlobalConfigFetcher +import net.taler.merchantpos.amount.AmountEntryFragmentDirections.Companion.actionGlobalMerchantSettings +import net.taler.merchantpos.config.ConfigProduct +import net.taler.merchantpos.databinding.FragmentAmountEntryBinding +import net.taler.merchantpos.order.Order + +private const val QUICK_AMOUNT_ORDER_ID = -1 +private const val QUICK_AMOUNT_PRODUCT_ID = "quick_amount" + +class AmountEntryFragment : Fragment() { + + private val viewModel: MainViewModel by activityViewModels() + private val paymentManager by lazy { viewModel.paymentManager } + + private lateinit var ui: FragmentAmountEntryBinding + + private var selectedCurrency: String? = null + private var amount: Amount? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + ui = FragmentAmountEntryBinding.inflate(inflater, container, false) + return ui.root + } + + override fun onStart() { + super.onStart() + if (!viewModel.configManager.config.isValid()) { + navigate(actionGlobalMerchantSettings()) + } else if (viewModel.configManager.currency == null) { + navigate(actionGlobalConfigFetcher()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val configuredCurrency = viewModel.configManager.currency + if (configuredCurrency != null) { + bindCurrency(listOf(configuredCurrency)) + setCurrency(configuredCurrency) + } + + ui.key0.setOnClickListener { onDigitPressed('0') } + ui.key1.setOnClickListener { onDigitPressed('1') } + ui.key2.setOnClickListener { onDigitPressed('2') } + ui.key3.setOnClickListener { onDigitPressed('3') } + ui.key4.setOnClickListener { onDigitPressed('4') } + ui.key5.setOnClickListener { onDigitPressed('5') } + ui.key6.setOnClickListener { onDigitPressed('6') } + ui.key7.setOnClickListener { onDigitPressed('7') } + ui.key8.setOnClickListener { onDigitPressed('8') } + ui.key9.setOnClickListener { onDigitPressed('9') } + ui.keyClear.setOnClickListener { clearAmount() } + ui.keyBackspace.setOnClickListener { onBackspacePressed() } + + ui.chargeButton.setOnClickListener { onChargePressed() } + + render() + } + + private fun bindCurrency(currencies: List<String>) { + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, currencies) + ui.currencyView.setAdapter(adapter) + ui.currencyView.setOnItemClickListener { _, _, position, _ -> + val newCurrency = adapter.getItem(position) ?: return@setOnItemClickListener + setCurrency(newCurrency) + } + } + + private fun setCurrency(currency: String) { + selectedCurrency = currency + val currentAmount = amount + amount = when { + currentAmount == null -> Amount.zero(currency) + currentAmount.currency == currency -> currentAmount + else -> currentAmount.withCurrency(currency) + } + ui.currencyView.setText(currency, false) + render() + } + + private fun onDigitPressed(digit: Char) { + val currentAmount = amount ?: return + amount = currentAmount.addInputDigit(digit) ?: currentAmount + render() + } + + private fun onBackspacePressed() { + val currentAmount = amount ?: return + amount = currentAmount.removeInputDigit() ?: currentAmount + render() + } + + private fun clearAmount() { + val currency = selectedCurrency ?: return + amount = Amount.zero(currency) + render() + } + + private fun onChargePressed() { + val configuredCurrency = viewModel.configManager.currency ?: run { + navigate(actionGlobalConfigFetcher()) + return + } + val enteredCurrency = selectedCurrency ?: configuredCurrency + val enteredAmount = amount ?: Amount.zero(enteredCurrency) + + if (enteredAmount.isZero()) { + Toast.makeText(requireContext(), R.string.amount_entry_error_zero, Toast.LENGTH_LONG) + .show() + return + } + if (enteredCurrency != configuredCurrency) { + Toast.makeText(requireContext(), R.string.amount_entry_error_wrong_currency, Toast.LENGTH_LONG) + .show() + return + } + + val order = Order( + id = QUICK_AMOUNT_ORDER_ID, + currency = configuredCurrency, + availableCategories = emptyMap(), + ) + val product = ConfigProduct( + description = getString(R.string.amount_entry_product_description), + productId = QUICK_AMOUNT_PRODUCT_ID, + price = enteredAmount, + categories = listOf(Int.MIN_VALUE), + ) + order + product + + // Backend doesn't require products; omit them for this "quick amount" flow. + paymentManager.createPayment(order, includeProducts = false) + navigate(actionAmountEntryToProcessPayment()) + } + + private fun render() { + val currentAmount = amount + ui.amountView.text = currentAmount?.toString(showSymbol = false) ?: "0.00" + val enabled = currentAmount != null && !currentAmount.isZero() + ui.chargeButton.isEnabled = enabled + } +} diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt @@ -106,13 +106,13 @@ data class Order(val id: Int, val currency: String, val availableCategories: Map }.toMap() } - fun toContractTerms(): net.taler.common.Order { + fun toContractTerms(includeProducts: Boolean = true): net.taler.common.Order { val deadline = Timestamp.fromMillis(now() + HOURS.toMillis(1)) return net.taler.common.Order( summary = summary, summaryI18n = summaryI18n, amount = total, - products = products.map { it.toContractProduct() }, + products = if (includeProducts) products.map { it.toContractProduct() } else null, refundDeadline = deadline, wireTransferDeadline = deadline, payDeadline = deadline, 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 @@ -68,11 +68,11 @@ class PaymentManager( } @UiThread - fun createPayment(order: Order) = scope.launch { + fun createPayment(order: Order, includeProducts: Boolean = true) = scope.launch { val merchantConfig = configManager.merchantConfig!! mPayment.value = Payment(order, order.summary, configManager.currency!!) val request = PostOrderRequest( - contractTerms = order.toContractTerms(), + contractTerms = order.toContractTerms(includeProducts = includeProducts), refundDelay = RelativeTime.fromMillis(HOURS.toMillis(1)) ) api.postOrder(merchantConfig, request).handle(::onNetworkError) { orderResponse -> 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 @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG import com.google.android.material.snackbar.Snackbar @@ -36,7 +37,6 @@ import net.taler.lib.android.TalerNfcService.Companion.hasNfc import net.taler.merchantpos.MainViewModel import net.taler.merchantpos.R import net.taler.merchantpos.databinding.FragmentProcessPaymentBinding -import net.taler.merchantpos.payment.ProcessPaymentFragmentDirections.Companion.actionProcessPaymentToPaymentSuccess class ProcessPaymentFragment : Fragment() { @@ -78,7 +78,14 @@ class ProcessPaymentFragment : Fragment() { } if (payment.paid) { model.orderManager.onOrderPaid(payment.order.id) - navigate(actionProcessPaymentToPaymentSuccess()) + val nav = findNavController() + val previousDestinationId = nav.previousBackStackEntry?.destination?.id + val options = previousDestinationId?.let { + NavOptions.Builder() + .setPopUpTo(it, false) + .build() + } + nav.navigate(R.id.paymentSuccess, null, options) return } if (payment.claimed) { diff --git a/merchant-terminal/src/main/res/color/amount_entry_key_background.xml b/merchant-terminal/src/main/res/color/amount_entry_key_background.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:state_enabled="false" + android:alpha="0.12" + android:color="?attr/colorOnSurface" /> + <item android:color="?attr/colorSecondaryContainer" /> +</selector> + diff --git a/merchant-terminal/src/main/res/color/amount_entry_key_text.xml b/merchant-terminal/src/main/res/color/amount_entry_key_text.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:state_enabled="false" + android:alpha="0.38" + android:color="?attr/colorOnSurface" /> + <item android:color="?attr/colorOnSecondaryContainer" /> +</selector> + diff --git a/merchant-terminal/src/main/res/drawable/ic_backspace.xml b/merchant-terminal/src/main/res/drawable/ic_backspace.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M 11.398438 16 L 14 13.398438 L 16.601562 16 L 18 14.601562 L 15.398438 12 L 18 9.398438 L 16.601562 8 L 14 10.601562 L 11.398438 8 L 10 9.398438 L 12.601562 12 L 10 14.601562 Z M 9 20 C 8.683594 20 8.382812 19.929688 8.101562 19.789062 C 7.816406 19.644531 7.582031 19.449219 7.398438 19.199219 L 2 12 L 7.398438 4.800781 C 7.582031 4.550781 7.816406 4.355469 8.101562 4.210938 C 8.382812 4.070312 8.683594 4 9 4 L 20 4 C 20.550781 4 21.019531 4.195312 21.414062 4.585938 C 21.804688 4.980469 22 5.449219 22 6 L 22 18 C 22 18.550781 21.804688 19.019531 21.414062 19.414062 C 21.019531 19.804688 20.550781 20 20 20 Z M 4.5 12 L 9 18 L 20 18 L 20 6 L 9 6 Z M 14.5 12 Z M 14.5 12 " /> +</vector> diff --git a/merchant-terminal/src/main/res/layout/app_bar_main.xml b/merchant-terminal/src/main/res/layout/app_bar_main.xml @@ -23,15 +23,14 @@ <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="@style/AppTheme.AppBarOverlay"> + android:layout_height="wrap_content"> - <androidx.appcompat.widget.Toolbar + <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" - app:popupTheme="@style/AppTheme.PopupOverlay" /> + android:background="?attr/colorSurface" + app:titleTextColor="?attr/colorOnSurface" /> </com.google.android.material.appbar.AppBarLayout> diff --git a/merchant-terminal/src/main/res/layout/fragment_amount_entry.xml b/merchant-terminal/src/main/res/layout/fragment_amount_entry.xml @@ -0,0 +1,290 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2026 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="16dp"> + + <TextView + android:id="@+id/amountView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="start" + android:gravity="end" + android:maxLines="1" + android:paddingHorizontal="12dp" + android:textAppearance="?attr/textAppearanceHeadlineLarge" + android:textSize="56sp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toStartOf="@+id/currencyLayout" + app:layout_constraintHorizontal_chainStyle="packed" + tools:text="12.34" /> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/currencyLayout" + style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:boxBackgroundMode="outline" + app:endIconMode="dropdown_menu" + app:layout_constraintTop_toTopOf="@+id/amountView" + app:layout_constraintBottom_toBottomOf="@+id/amountView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/amountView"> + + <com.google.android.material.textfield.MaterialAutoCompleteTextView + android:id="@+id/currencyView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="none" + android:paddingHorizontal="16dp" + tools:text="EUR" /> + </com.google.android.material.textfield.TextInputLayout> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guidelineNumpadStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.2" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guidelineNumpadEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.8" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guidelineCharge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.8" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/numpad" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="12dp" + app:layout_constraintBottom_toTopOf="@+id/guidelineCharge" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/guidelineNumpadEnd" + app:layout_constraintStart_toEndOf="@+id/guidelineNumpadStart" + app:layout_constraintTop_toBottomOf="@+id/amountView"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key1" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="1" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key2" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@+id/key4" + app:layout_constraintHorizontal_chainStyle="spread" + app:layout_constraintVertical_chainStyle="spread" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key2" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="2" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key3" + app:layout_constraintStart_toEndOf="@+id/key1" + app:layout_constraintTop_toTopOf="@+id/key1" + app:layout_constraintBottom_toBottomOf="@+id/key1" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key3" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="3" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/key2" + app:layout_constraintTop_toTopOf="@+id/key1" + app:layout_constraintBottom_toBottomOf="@+id/key1" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key4" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="4" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/key1" + app:layout_constraintBottom_toTopOf="@+id/key7" + app:layout_constraintHorizontal_chainStyle="spread" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key5" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="5" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key6" + app:layout_constraintStart_toEndOf="@+id/key4" + app:layout_constraintTop_toTopOf="@+id/key4" + app:layout_constraintBottom_toBottomOf="@+id/key4" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key6" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="6" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/key5" + app:layout_constraintTop_toTopOf="@+id/key4" + app:layout_constraintBottom_toBottomOf="@+id/key4" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key7" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="7" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key8" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/key4" + app:layout_constraintBottom_toTopOf="@+id/keyClear" + app:layout_constraintHorizontal_chainStyle="spread" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key8" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="8" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key9" + app:layout_constraintStart_toEndOf="@+id/key7" + app:layout_constraintTop_toTopOf="@+id/key7" + app:layout_constraintBottom_toBottomOf="@+id/key7" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key9" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="9" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/key8" + app:layout_constraintTop_toTopOf="@+id/key7" + app:layout_constraintBottom_toBottomOf="@+id/key7" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/keyClear" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:singleLine="true" + android:text="@string/amount_entry_clear" + android:textColor="@color/amount_entry_key_text" + android:textSize="22sp" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/key0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/key7" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_chainStyle="spread" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/key0" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:text="0" + android:textColor="@color/amount_entry_key_text" + android:textSize="@dimen/amount_entry_key_text_size" + app:backgroundTint="@color/amount_entry_key_background" + app:layout_constraintEnd_toStartOf="@+id/keyBackspace" + app:layout_constraintStart_toEndOf="@+id/keyClear" + app:layout_constraintTop_toTopOf="@+id/keyClear" + app:layout_constraintBottom_toBottomOf="@+id/keyClear" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/keyBackspace" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="4dp" + android:contentDescription="@string/amount_entry_backspace" + android:gravity="center" + android:padding="0dp" + android:singleLine="true" + android:text="" + app:backgroundTint="@color/amount_entry_key_background" + app:icon="@drawable/ic_backspace" + app:iconGravity="textStart" + app:iconPadding="0dp" + app:iconSize="34dp" + app:iconTint="@color/amount_entry_key_text" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/key0" + app:layout_constraintTop_toTopOf="@+id/keyClear" + app:layout_constraintBottom_toBottomOf="@+id/keyClear" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + <Button + android:id="@+id/chargeButton" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginBottom="8dp" + android:backgroundTint="@color/complete_button_bottom" + android:text="@string/amount_entry_create_order_charge" + android:textAllCaps="false" + android:textSize="20sp" + app:layout_constraintTop_toBottomOf="@+id/guidelineCharge" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/guidelineNumpadEnd" + app:layout_constraintStart_toEndOf="@+id/guidelineNumpadStart" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/merchant-terminal/src/main/res/layout/fragment_payment_success.xml b/merchant-terminal/src/main/res/layout/fragment_payment_success.xml @@ -33,6 +33,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="spread_inside" + app:layout_constraintVertical_weight="1.6" tools:ignore="ContentDescription" /> <TextView @@ -45,6 +46,7 @@ android:textColor="@color/green" app:autoSizeMaxTextSize="42sp" app:autoSizeTextType="uniform" + app:layout_constraintVertical_weight="1.0" app:layout_constraintBottom_toTopOf="@+id/paymentButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -55,21 +57,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_percent="0.25" /> + app:layout_constraintGuide_percent="0.2" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guidelineRight" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_percent="0.75" /> + app:layout_constraintGuide_percent="0.8" /> <Button android:id="@+id/paymentButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="16dp" + android:minHeight="64dp" + android:paddingVertical="14dp" android:text="@string/payment_back_button" + android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/guidelineRight" app:layout_constraintStart_toStartOf="@+id/guidelineLeft" /> diff --git a/merchant-terminal/src/main/res/menu/activity_main_drawer.xml b/merchant-terminal/src/main/res/menu/activity_main_drawer.xml @@ -24,6 +24,10 @@ android:icon="@drawable/ic_move_money_24dp" android:title="@string/menu_order" /> <item + android:id="@+id/nav_amountEntry" + android:icon="@drawable/ic_dialpad" + android:title="@string/menu_amount_entry" /> + <item android:id="@+id/nav_history" android:icon="@drawable/ic_history_black_24dp" android:title="@string/menu_history" /> diff --git a/merchant-terminal/src/main/res/navigation/nav_graph.xml b/merchant-terminal/src/main/res/navigation/nav_graph.xml @@ -42,6 +42,16 @@ </fragment> <fragment + android:id="@+id/nav_amountEntry" + android:name="net.taler.merchantpos.amount.AmountEntryFragment" + android:label="@string/amount_entry_label" + tools:layout="@layout/fragment_amount_entry"> + <action + android:id="@+id/action_amountEntry_to_processPayment" + app:destination="@+id/processPayment" /> + </fragment> + + <fragment android:id="@+id/processPayment" android:name="net.taler.merchantpos.payment.ProcessPaymentFragment" android:label="@string/payment_process_label" @@ -123,6 +133,11 @@ app:launchSingleTop="true" app:popUpTo="@+id/nav_graph" /> <action + android:id="@+id/action_global_amountEntry" + app:destination="@+id/nav_amountEntry" + app:launchSingleTop="true" + app:popUpTo="@+id/nav_graph" /> + <action android:id="@+id/action_global_merchantHistory" app:destination="@+id/nav_history" app:launchSingleTop="true" /> diff --git a/merchant-terminal/src/main/res/values-night/colors.xml b/merchant-terminal/src/main/res/values-night/colors.xml @@ -1,4 +1,50 @@ <?xml version="1.0" encoding="utf-8"?> <resources> -<color name="colorError">#CF6679</color> + <!-- Dark theme palette (aligned with wallet's Material 3 palette, mapped to POS color names) --> + + <!-- Core colors --> + <color name="colorPrimary">#B4C5FF</color> + <color name="colorSecondary">#A4C9FF</color> + <color name="colorTertiary">#8DD1E5</color> + <color name="colorError">#FFB4AA</color> + <color name="colorNeutral">#11131A</color> + <color name="colorNeutralVariant">#45474B</color> + + <!-- Extended colors --> + <color name="colorOnPrimary">#002A78</color> + <color name="colorPrimaryContainer">#0042B3</color> + <color name="colorOnPrimaryContainer">#E5EBFF</color> + <color name="colorInversePrimary">#2756C7</color> + + <color name="colorOnSecondary">#00315D</color> + <color name="colorSecondaryContainer">#72A3E5</color> + <color name="colorOnSecondaryContainer">#003869</color> + + <color name="colorOnTertiary">#003641</color> + <color name="colorTertiaryContainer">#166577</color> + <color name="colorOnTertiaryContainer">#9CE0F5</color> + + <color name="colorOnError">#690003</color> + <color name="colorErrorContainer">#B3261E</color> + <color name="colorOnErrorContainer">#FFCBC4</color> + + <color name="colorOutline">#8F9095</color> + <color name="colorOutlineVariant">#45474B</color> + + <color name="colorBackground">#11131A</color> + <color name="colorOnBackground">#E2E2EB</color> + + <color name="colorSurface">#11131A</color> + <color name="colorOnSurface">#E5E2E1</color> + <color name="colorOnSurfaceVariant">#C6C6CB</color> + <color name="colorSurfaceVariant">#45474B</color> + + <color name="colorInverseOnSurface">#313030</color> + <color name="colorInverseSurface">#E5E2E1</color> + + <color name="colorSurfaceTint">#B4C5FF</color> + + <color name="colorShadow">#000000</color> + <color name="colorScrim">#000000</color> </resources> + diff --git a/merchant-terminal/src/main/res/values-night/styles.xml b/merchant-terminal/src/main/res/values-night/styles.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:tools="http://schemas.android.com/tools"> + + <style name="AppTheme" parent="AppTheme.Dark"> + <item name="windowActionModeOverlay">true</item> + </style> + + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + + <item name="android:statusBarColor" tools:targetApi="m">?colorSurface</item> + <item name="android:windowLightStatusBar" tools:targetApi="m">false</item> + <item name="android:navigationBarColor" tools:targetApi="o_mr1">?colorSurface</item> + <item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item> + </style> + +</resources> + diff --git a/merchant-terminal/src/main/res/values-v35/styles.xml b/merchant-terminal/src/main/res/values-v35/styles.xml @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ This file is part of GNU Taler - ~ (C) 2025 Taler Systems S.A. - ~ - ~ GNU Taler is free software; you can redistribute it and/or modify it under the - ~ terms of the GNU General Public License as published by the Free Software - ~ Foundation; either version 3, or (at your option) any later version. - ~ - ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. - ~ - ~ You should have received a copy of the GNU General Public License along with - ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - --> - -<resources> - <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> - <item name="colorPrimary">@color/colorPrimary</item> - <item name="colorOnPrimary">@color/colorOnPrimary</item> - <item name="colorPrimaryDark">@color/colorInversePrimary</item> - <item name="colorAccent">@color/colorTertiary</item> - </style> -</resources> -\ No newline at end of file diff --git a/merchant-terminal/src/main/res/values/dimens.xml b/merchant-terminal/src/main/res/values/dimens.xml @@ -3,4 +3,8 @@ <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen> <dimen name="nav_header_height">176dp</dimen> -</resources> -\ No newline at end of file + + <!-- Amount entry / dialpad --> + <dimen name="amount_entry_key_min_height">88dp</dimen> + <dimen name="amount_entry_key_text_size">34sp</dimen> +</resources> diff --git a/merchant-terminal/src/main/res/values/strings.xml b/merchant-terminal/src/main/res/values/strings.xml @@ -3,7 +3,8 @@ <string name="app_name_short">Merchant Terminal</string> <string name="project_name">GNU Taler</string> - <string name="menu_order">Orders</string> + <string name="menu_order">Regular order</string> + <string name="menu_amount_entry">Order by amount</string> <string name="menu_history">History</string> <string name="menu_settings">Settings</string> <string name="menu_reload">Reload</string> @@ -24,6 +25,15 @@ <string name="order_custom_product_default">Tip</string> <string name="order_custom_add_button">Add</string> + <string name="amount_entry_label">Amount</string> + <string name="amount_entry_charge">Charge</string> + <string name="amount_entry_create_order_charge">Create order (charge amount)</string> + <string name="amount_entry_clear">Clear</string> + <string name="amount_entry_backspace">Backspace</string> + <string name="amount_entry_product_description">Regular order</string> + <string name="amount_entry_error_zero">Enter an amount</string> + <string name="amount_entry_error_wrong_currency">Unsupported currency</string> + <string name="config_label">Merchant settings</string> <string name="config_old_label">JSON file (old)</string> <string name="config_setup_password">Password</string> diff --git a/merchant-terminal/src/main/res/values/styles.xml b/merchant-terminal/src/main/res/values/styles.xml @@ -1,11 +1,92 @@ -<resources> - <!-- Base application theme. --> - <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> - <!-- Customize your theme here. --> +<resources xmlns:tools="http://schemas.android.com/tools"> + + <style name="AppTheme.Light" parent="Theme.Material3.Light"> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorOnPrimary">@color/colorOnPrimary</item> + <item name="colorPrimaryContainer">@color/colorPrimaryContainer</item> + <item name="colorOnPrimaryContainer">@color/colorOnPrimaryContainer</item> + <!-- Legacy / compatibility --> + <item name="colorPrimaryDark">@color/colorInversePrimary</item> + <item name="colorAccent">@color/colorTertiary</item> + + <item name="colorSecondary">@color/colorSecondary</item> + <item name="colorOnSecondary">@color/colorOnSecondary</item> + <item name="colorSecondaryContainer">@color/colorSecondaryContainer</item> + <item name="colorOnSecondaryContainer">@color/colorOnSecondaryContainer</item> + + <item name="colorTertiary">@color/colorTertiary</item> + <item name="colorOnTertiary">@color/colorOnTertiary</item> + <item name="colorTertiaryContainer">@color/colorTertiaryContainer</item> + <item name="colorOnTertiaryContainer">@color/colorOnTertiaryContainer</item> + + <item name="colorError">@color/colorError</item> + <item name="colorOnError">@color/colorOnError</item> + <item name="colorErrorContainer">@color/colorErrorContainer</item> + <item name="colorOnErrorContainer">@color/colorOnErrorContainer</item> + + <item name="android:colorBackground">@color/colorBackground</item> + <item name="android:windowBackground">@color/colorBackground</item> + <item name="colorOnBackground">@color/colorOnBackground</item> + + <item name="colorSurface">@color/colorSurface</item> + <item name="colorOnSurface">@color/colorOnSurface</item> + <item name="colorSurfaceVariant">@color/colorSurfaceVariant</item> + <item name="colorOnSurfaceVariant">@color/colorOnSurfaceVariant</item> + <item name="colorOutline">@color/colorOutline</item> + + <item name="colorOnSurfaceInverse">@color/colorInverseOnSurface</item> + <item name="colorSurfaceInverse">@color/colorInverseSurface</item> + <item name="colorPrimaryInverse">@color/colorInversePrimary</item> + </style> + + <style name="AppTheme.Dark" parent="Theme.Material3.Dark"> + <item name="windowActionModeOverlay">true</item> + <item name="android:statusBarColor" tools:targetApi="m">?colorSurface</item> + <item name="android:windowLightStatusBar" tools:targetApi="m">false</item> + <item name="android:navigationBarColor" tools:targetApi="o_mr1">?colorSurface</item> + <item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item> + + <!-- Use the same resource names; values-night/colors.xml overrides them. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorOnPrimary">@color/colorOnPrimary</item> + <item name="colorPrimaryContainer">@color/colorPrimaryContainer</item> + <item name="colorOnPrimaryContainer">@color/colorOnPrimaryContainer</item> + <!-- Legacy / compatibility --> <item name="colorPrimaryDark">@color/colorInversePrimary</item> <item name="colorAccent">@color/colorTertiary</item> + + <item name="colorSecondary">@color/colorSecondary</item> + <item name="colorOnSecondary">@color/colorOnSecondary</item> + <item name="colorSecondaryContainer">@color/colorSecondaryContainer</item> + <item name="colorOnSecondaryContainer">@color/colorOnSecondaryContainer</item> + + <item name="colorTertiary">@color/colorTertiary</item> + <item name="colorOnTertiary">@color/colorOnTertiary</item> + <item name="colorTertiaryContainer">@color/colorTertiaryContainer</item> + <item name="colorOnTertiaryContainer">@color/colorOnTertiaryContainer</item> + + <item name="colorError">@color/colorError</item> + <item name="colorOnError">@color/colorOnError</item> + <item name="colorErrorContainer">@color/colorErrorContainer</item> + <item name="colorOnErrorContainer">@color/colorOnErrorContainer</item> + + <item name="android:colorBackground">@color/colorBackground</item> + <item name="android:windowBackground">@color/colorBackground</item> + <item name="colorOnBackground">@color/colorOnBackground</item> + + <item name="colorSurface">@color/colorSurface</item> + <item name="colorOnSurface">@color/colorOnSurface</item> + <item name="colorSurfaceVariant">@color/colorSurfaceVariant</item> + <item name="colorOnSurfaceVariant">@color/colorOnSurfaceVariant</item> + <item name="colorOutline">@color/colorOutline</item> + + <item name="colorOnSurfaceInverse">@color/colorInverseOnSurface</item> + <item name="colorSurfaceInverse">@color/colorInverseSurface</item> + <item name="colorPrimaryInverse">@color/colorInversePrimary</item> + </style> + + <style name="AppTheme" parent="AppTheme.Light"> + <item name="windowActionModeOverlay">true</item> </style> <style name="AppTheme.NoActionBar"> @@ -14,8 +95,4 @@ <item name="android:statusBarColor">@color/colorPrimary</item> </style> - <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> - - <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> - </resources>