taler-android

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

commit 864549b79b76aede5e82b6b2e1b3f7b6a4f1345f
parent 1f9f8ec3957a3a214593df7922a0da40657dbe2e
Author: Iván Ávalos <avalos@disroot.org>
Date:   Thu, 24 Oct 2024 22:08:05 +0200

[pos] Render product images

Diffstat:
Mmerchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt | 13+++++++++++++
Mmerchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt | 16++++++++++++++++
Mmerchant-terminal/src/main/res/layout/list_item_order.xml | 16+++++++++++++++-
Mmerchant-terminal/src/main/res/layout/list_item_product.xml | 15++++++++++++++-
Mmerchant-terminal/src/main/res/values/strings.xml | 2++
Mtaler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt | 15+++++++++++++++
Mwallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt | 2--
Mwallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt | 23++++++++++-------------
8 files changed, 85 insertions(+), 17 deletions(-)

diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderAdapter.kt @@ -19,7 +19,10 @@ package net.taler.merchantpos.order import android.view.LayoutInflater import android.view.MotionEvent import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup +import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.selection.ItemDetailsLookup import androidx.recyclerview.selection.ItemKeyProvider @@ -79,12 +82,22 @@ internal class OrderAdapter : Adapter<OrderViewHolder>() { private val quantity: TextView = v.findViewById(R.id.quantity) private val name: TextView = v.findViewById(R.id.name) private val price: TextView = v.findViewById(R.id.price) + private val image: ImageView = v.findViewById(R.id.image) fun bind(product: ConfigProduct, selected: Boolean) { v.isActivated = selected quantity.text = product.quantity.toString() name.text = product.localizedDescription price.text = product.totalPrice.amountStr + + // base64 encoded image + val bitmap = product.imageBitmap + if (bitmap == null) { + image.visibility = GONE + } else { + image.visibility = VISIBLE + image.setImageBitmap(bitmap) + } } } diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt @@ -16,11 +16,16 @@ package net.taler.merchantpos.order +import android.graphics.BitmapFactory.decodeByteArray import android.os.Bundle +import android.util.Base64 import android.view.LayoutInflater import android.view.View +import android.view.View.GONE import android.view.View.INVISIBLE +import android.view.View.VISIBLE import android.view.ViewGroup +import android.widget.ImageView import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -103,10 +108,21 @@ private class ProductAdapter( inner class ProductViewHolder(private val v: View) : ViewHolder(v) { private val name: TextView = v.findViewById(R.id.name) private val price: TextView = v.findViewById(R.id.price) + private val image: ImageView = v.findViewById(R.id.image) fun bind(product: ConfigProduct) { name.text = product.localizedDescription price.text = product.price.amountStr + + // base64 encoded image + val bitmap = product.imageBitmap + if (bitmap == null) { + image.visibility = GONE + } else { + image.visibility = VISIBLE + image.setImageBitmap(bitmap) + } + v.setOnClickListener { listener.onProductSelected(product) } } } diff --git a/merchant-terminal/src/main/res/layout/list_item_order.xml b/merchant-terminal/src/main/res/layout/list_item_order.xml @@ -35,6 +35,20 @@ app:layout_constraintVertical_bias="0.0" tools:text="31" /> + <ImageView + android:id="@+id/image" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginHorizontal="10dp" + app:layout_constraintTop_toTopOf="@id/name" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/quantity" + android:visibility="gone" + android:contentDescription="@string/product_image" + app:layout_constraintVertical_bias="0.0" + tools:visibility="visible" + tools:src="@drawable/ic_launcher_background"/> + <TextView android:id="@+id/name" android:layout_width="0dp" @@ -43,7 +57,7 @@ android:layout_marginEnd="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/price" - app:layout_constraintStart_toEndOf="@+id/quantity" + app:layout_constraintStart_toEndOf="@+id/image" app:layout_constraintTop_toTopOf="parent" tools:text="An order product item that in some cases could have a very long name" /> diff --git a/merchant-terminal/src/main/res/layout/list_item_product.xml b/merchant-terminal/src/main/res/layout/list_item_product.xml @@ -29,6 +29,19 @@ android:layout_height="wrap_content" android:padding="8dp"> + <ImageView + android:id="@+id/image" + android:layout_width="64dp" + android:layout_height="64dp" + android:padding="10dp" + android:contentDescription="@string/product_image" + android:visibility="gone" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:visibility="visible" + tools:src="@drawable/ic_launcher_background"/> + <TextView android:id="@+id/name" android:layout_width="0dp" @@ -37,7 +50,7 @@ android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toBottomOf="@id/image" tools:text="Steak and two Eggs" /> <TextView diff --git a/merchant-terminal/src/main/res/values/strings.xml b/merchant-terminal/src/main/res/values/strings.xml @@ -8,6 +8,8 @@ <string name="menu_settings">Settings</string> <string name="menu_reload">Reload</string> + <string name="product_image">Product image</string> + <string name="order_label_title">Order #%s</string> <!-- The placeholder is the total order amount with currency --> <string name="order_total">Total: %s</string> diff --git a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt @@ -16,11 +16,16 @@ package net.taler.common +import android.graphics.Bitmap +import android.graphics.BitmapFactory.decodeByteArray import android.os.Build +import android.util.Base64 import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.taler.common.TalerUtils.getLocalizedString +val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$") + @Serializable data class ContractTerms( val summary: String, @@ -52,6 +57,16 @@ abstract class Product { } else { description } + + val imageBitmap: Bitmap? + get() = image?.let { + REGEX_PRODUCT_IMAGE.matchEntire(it)?.let { match -> + match.groups[2]?.value?.let { group -> + val decodedString = Base64.decode(group, Base64.DEFAULT) + decodeByteArray(decodedString, 0, decodedString.size) + } + } + } } @Serializable diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -37,8 +37,6 @@ import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse import org.json.JSONObject -val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$") - sealed class PayStatus { data object None : PayStatus() data object Loading : PayStatus() diff --git a/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt b/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt @@ -18,8 +18,6 @@ package net.taler.wallet.payment import android.content.Context import android.graphics.Bitmap -import android.graphics.BitmapFactory.decodeByteArray -import android.util.Base64 import android.view.LayoutInflater import android.view.View import android.view.View.GONE @@ -76,20 +74,19 @@ internal class ProductAdapter(private val listener: ProductImageClickListener) : fun bind(product: ContractProduct) { quantity.text = product.quantity.toString() - val productImage = product.image - if (productImage == null) { + + // base64 encoded image + val bitmap = product.imageBitmap + if (bitmap == null) { image.visibility = GONE - } else REGEX_PRODUCT_IMAGE.matchEntire(productImage)?.let { match -> - match.groups[2]?.value?.let { group -> - image.visibility = VISIBLE - val decodedString = Base64.decode(group, Base64.DEFAULT) - val bitmap = decodeByteArray(decodedString, 0, decodedString.size) - image.setImageBitmap(bitmap) - image.setOnClickListener { - listener.onImageClick(bitmap) - } + } else { + image.visibility = VISIBLE + image.setImageBitmap(bitmap) + image.setOnClickListener { + listener.onImageClick(bitmap) } } + name.text = product.description if (product.totalPrice != null) {