summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/net/taler/wallet/payment/ContractTerms.kt6
-rw-r--r--app/src/main/java/net/taler/wallet/payment/PaymentManager.kt21
-rw-r--r--app/src/main/java/net/taler/wallet/payment/ProductAdapter.kt16
-rw-r--r--app/src/main/res/layout/list_item_product.xml31
4 files changed, 64 insertions, 10 deletions
diff --git a/app/src/main/java/net/taler/wallet/payment/ContractTerms.kt b/app/src/main/java/net/taler/wallet/payment/ContractTerms.kt
index a9f75ed..da91dea 100644
--- a/app/src/main/java/net/taler/wallet/payment/ContractTerms.kt
+++ b/app/src/main/java/net/taler/wallet/payment/ContractTerms.kt
@@ -29,20 +29,22 @@ data class ContractTerms(
)
interface Product {
- val id: String
+ val id: String?
val description: String
val price: Amount
val location: String?
+ val image: String?
}
@JsonIgnoreProperties("totalPrice")
data class ContractProduct(
@JsonProperty("product_id")
- override val id: String,
+ override val id: String?,
override val description: String,
override val price: Amount,
@JsonProperty("delivery_location")
override val location: String?,
+ override val image: String?,
val quantity: Int
) : Product {
diff --git a/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index a00a686..ee0edaf 100644
--- a/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -26,6 +26,9 @@ import net.taler.wallet.Amount
import net.taler.wallet.TAG
import net.taler.wallet.backend.WalletBackendApi
import org.json.JSONObject
+import java.net.MalformedURLException
+
+val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
class PaymentManager(
private val walletBackendApi: WalletBackendApi,
@@ -61,7 +64,12 @@ class PaymentManager(
}
else -> {
val status = result.getString("status")
- mPayStatus.postValue(getPayStatusUpdate(status, result))
+ try {
+ mPayStatus.postValue(getPayStatusUpdate(status, result))
+ } catch (e: Exception) {
+ Log.e(TAG, "Error getting PayStatusUpdate", e)
+ mPayStatus.postValue(PayStatus.Error(e.message ?: "unknown error"))
+ }
}
}
}
@@ -80,7 +88,16 @@ class PaymentManager(
}
private fun getContractTerms(json: JSONObject): ContractTerms {
- return mapper.readValue(json.getString("contractTermsRaw"))
+ val terms: ContractTerms = mapper.readValue(json.getString("contractTermsRaw"))
+ // validate product images
+ terms.products.forEach { product ->
+ product.image?.let { image ->
+ if (REGEX_PRODUCT_IMAGE.matchEntire(image) == null) {
+ throw MalformedURLException("Invalid image data URL for ${product.description}")
+ }
+ }
+ }
+ return terms
}
@UiThread
diff --git a/app/src/main/java/net/taler/wallet/payment/ProductAdapter.kt b/app/src/main/java/net/taler/wallet/payment/ProductAdapter.kt
index 3a0dca6..8519dcb 100644
--- a/app/src/main/java/net/taler/wallet/payment/ProductAdapter.kt
+++ b/app/src/main/java/net/taler/wallet/payment/ProductAdapter.kt
@@ -16,9 +16,14 @@
package net.taler.wallet.payment
+import android.graphics.BitmapFactory.decodeByteArray
+import android.util.Base64
import android.view.LayoutInflater
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.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
@@ -50,11 +55,22 @@ internal class ProductAdapter : RecyclerView.Adapter<ProductViewHolder>() {
internal inner class ProductViewHolder(v: View) : ViewHolder(v) {
private val quantity: TextView = v.findViewById(R.id.quantity)
+ private val image: ImageView = v.findViewById(R.id.image)
private val name: TextView = v.findViewById(R.id.name)
private val price: TextView = v.findViewById(R.id.price)
fun bind(product: ContractProduct) {
quantity.text = product.quantity.toString()
+ if (product.image == null) {
+ image.visibility = GONE
+ } else {
+ image.visibility = VISIBLE
+ // product.image was validated before, so non-null below
+ val match = REGEX_PRODUCT_IMAGE.matchEntire(product.image)!!
+ val decodedString = Base64.decode(match.groups[2]!!.value, Base64.DEFAULT)
+ val bitmap = decodeByteArray(decodedString, 0, decodedString.size)
+ image.setImageBitmap(bitmap)
+ }
name.text = product.description
price.text = product.totalPrice.toString()
}
diff --git a/app/src/main/res/layout/list_item_product.xml b/app/src/main/res/layout/list_item_product.xml
index 606022e..fe6ba23 100644
--- a/app/src/main/res/layout/list_item_product.xml
+++ b/app/src/main/res/layout/list_item_product.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of GNU Taler
~ (C) 2020 Taler Systems S.A.
~
@@ -20,9 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="8dp"
- android:paddingTop="8dp"
- android:paddingEnd="8dp">
+ android:padding="8dp">
<TextView
android:id="@+id/quantity"
@@ -30,27 +27,49 @@
android:layout_height="wrap_content"
android:gravity="end"
android:minWidth="24dp"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.0"
tools:text="31" />
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginStart="8dp"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintDimensionRatio="H,4:3"
+ app:layout_constraintEnd_toStartOf="@+id/name"
+ app:layout_constraintStart_toEndOf="@+id/quantity"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_max="64dp"
+ tools:ignore="ContentDescription"
+ tools:srcCompat="@tools:sample/avatars"
+ tools:visibility="visible" />
+
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
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"
+ app:layout_constraintVertical_bias="0.0"
tools:text="A product item that in some cases could have a very long name" />
<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.0"
tools:text="23.42" />
</androidx.constraintlayout.widget.ConstraintLayout>