gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-android] 03/10: Deserialize and render more wallet history


From: gnunet
Subject: [taler-wallet-android] 03/10: Deserialize and render more wallet history events
Date: Thu, 30 Jan 2020 18:40:11 +0100

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository wallet-android.

commit 1ec44435ab0ab098a04b3f4ee9f2599d99535c41
Author: Torsten Grote <address@hidden>
AuthorDate: Mon Dec 30 12:18:38 2019 -0300

    Deserialize and render more wallet history events
---
 app/src/main/java/net/taler/wallet/Amount.kt       | 120 ++++++++++++++
 .../main/java/net/taler/wallet/WalletViewModel.kt  |  26 ---
 .../java/net/taler/wallet/history/HistoryEvent.kt  | 176 ++++++++++++++++++++-
 .../net/taler/wallet/history/ReserveTransaction.kt |  16 ++
 .../taler/wallet/history/WalletHistoryAdapter.kt   |  89 +++++++++--
 app/src/main/res/drawable/ic_account_balance.xml   |   9 ++
 app/src/main/res/drawable/ic_add_circle.xml        |   9 ++
 app/src/main/res/drawable/ic_cancel.xml            |   9 ++
 app/src/main/res/drawable/ic_cash_usd_outline.xml  |   9 ++
 ...tory_withdrawn.xml => history_payment_sent.xml} |  37 +++--
 app/src/main/res/layout/history_row.xml            |  49 +++++-
 app/src/main/res/layout/history_withdrawn.xml      |  40 +++--
 app/src/main/res/values/colors.xml                 |   3 +
 app/src/main/res/values/strings.xml                |   8 +
 .../net/taler/wallet/history/HistoryEventTest.kt   | 171 ++++++++++++++++++++
 .../taler/wallet/history/ReserveTransactionTest.kt |  16 ++
 16 files changed, 695 insertions(+), 92 deletions(-)

diff --git a/app/src/main/java/net/taler/wallet/Amount.kt 
b/app/src/main/java/net/taler/wallet/Amount.kt
new file mode 100644
index 0000000..656228f
--- /dev/null
+++ b/app/src/main/java/net/taler/wallet/Amount.kt
@@ -0,0 +1,120 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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/>
+ */
+
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.taler.wallet
+
+import org.json.JSONObject
+import kotlin.math.round
+
+private const val FRACTIONAL_BASE = 1e8
+
+data class Amount(val currency: String, val amount: String) {
+    fun isZero(): Boolean {
+        return amount.toDouble() == 0.0
+    }
+
+    companion object {
+        fun fromJson(jsonAmount: JSONObject): Amount {
+            val amountCurrency = jsonAmount.getString("currency")
+            val amountValue = jsonAmount.getString("value")
+            val amountFraction = jsonAmount.getString("fraction")
+            val amountIntValue = Integer.parseInt(amountValue)
+            val amountIntFraction = Integer.parseInt(amountFraction)
+            return Amount(
+                amountCurrency,
+                (amountIntValue + amountIntFraction / 
FRACTIONAL_BASE).toString()
+            )
+        }
+
+        fun fromString(strAmount: String): Amount {
+            val components = strAmount.split(":")
+            return Amount(components[0], components[1])
+        }
+    }
+}
+
+class ParsedAmount(
+    /**
+     * name of the currency using either a three-character ISO 4217 currency 
code,
+     * or a regional currency identifier starting with a "*" followed by at 
most 10 characters.
+     * ISO 4217 exponents in the name are not supported,
+     * although the "fraction" is corresponds to an ISO 4217 exponent of 6.
+     */
+    val currency: String,
+
+    /**
+     * unsigned 32 bit value in the currency,
+     * note that "1" here would correspond to 1 EUR or 1 USD, depending on 
currency, not 1 cent.
+     */
+    val value: UInt,
+
+    /**
+     * unsigned 32 bit fractional value to be added to value
+     * representing an additional currency fraction,
+     * in units of one millionth (1e-6) of the base currency value.
+     * For example, a fraction of 500,000 would correspond to 50 cents.
+     */
+    val fraction: Double
+) {
+    companion object {
+        fun parseAmount(str: String): ParsedAmount {
+            val split = str.split(":")
+            check(split.size == 2)
+            val currency = split[0]
+            val valueSplit = split[1].split(".")
+            val value = valueSplit[0].toUInt()
+            val fraction: Double = if (valueSplit.size > 1) {
+                round("0.${valueSplit[1]}".toDouble() * FRACTIONAL_BASE)
+            } else 0.0
+            return ParsedAmount(currency, value, fraction)
+        }
+    }
+
+    operator fun minus(other: ParsedAmount): ParsedAmount {
+        check(currency == other.currency) { "Can only subtract from same 
currency" }
+        var resultValue = value
+        var resultFraction = fraction
+        if (resultFraction < other.fraction) {
+            if (resultValue < 1u) {
+                return ParsedAmount(currency, 0u, 0.0)
+            }
+            resultValue--
+            resultFraction += FRACTIONAL_BASE
+        }
+        check(resultFraction >= other.fraction)
+        resultFraction -= other.fraction
+        if (resultValue < other.value) {
+            return ParsedAmount(currency, 0u, 0.0)
+        }
+        resultValue -= other.value
+        return ParsedAmount(currency, resultValue, resultFraction)
+    }
+
+    fun toJSONString(): String {
+        return "$currency:${getValueString()}"
+    }
+
+    override fun toString(): String {
+        return "${getValueString()} $currency"
+    }
+
+    private fun getValueString(): String {
+        return "$value${(fraction / FRACTIONAL_BASE).toString().substring(1)}"
+    }
+
+}
diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt 
b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
index 94d2d8a..f556ff3 100644
--- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt
+++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
@@ -30,32 +30,6 @@ import org.json.JSONObject
 const val TAG = "taler-wallet"
 
 
-data class Amount(val currency: String, val amount: String) {
-    fun isZero(): Boolean {
-        return amount.toDouble() == 0.0
-    }
-
-    companion object {
-        const val FRACTIONAL_BASE = 1e8
-        fun fromJson(jsonAmount: JSONObject): Amount {
-            val amountCurrency = jsonAmount.getString("currency")
-            val amountValue = jsonAmount.getString("value")
-            val amountFraction = jsonAmount.getString("fraction")
-            val amountIntValue = Integer.parseInt(amountValue)
-            val amountIntFraction = Integer.parseInt(amountFraction)
-            return Amount(
-                amountCurrency,
-                (amountIntValue + amountIntFraction / 
FRACTIONAL_BASE).toString()
-            )
-        }
-
-        fun fromString(strAmount: String): Amount {
-            val components = strAmount.split(":")
-            return Amount(components[0], components[1])
-        }
-    }
-}
-
 data class BalanceEntry(val available: Amount, val pendingIncoming: Amount)
 
 
diff --git a/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt 
b/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt
index 34f3164..31473f6 100644
--- a/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt
+++ b/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt
@@ -1,10 +1,30 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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.wallet.history
 
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
 import com.fasterxml.jackson.annotation.*
 import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
 import com.fasterxml.jackson.annotation.JsonSubTypes.Type
 import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
 import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
+import net.taler.wallet.R
 
 enum class ReserveType {
     /**
@@ -20,7 +40,22 @@ enum class ReserveType {
 }
 
 @JsonInclude(NON_EMPTY)
-class ReserveCreationDetail(val type: ReserveType)
+class ReserveCreationDetail(val type: ReserveType, val bankUrl: String?)
+
+enum class RefreshReason {
+    @JsonProperty("manual")
+    MANUAL,
+    @JsonProperty("pay")
+    PAY,
+    @JsonProperty("refund")
+    REFUND,
+    @JsonProperty("abort-pay")
+    ABORT_PAY,
+    @JsonProperty("recoup")
+    RECOUP,
+    @JsonProperty("backup-restored")
+    BACKUP_RESTORED
+}
 
 
 @JsonInclude(NON_EMPTY)
@@ -45,7 +80,7 @@ class ReserveShortInfo(
     val reserveCreationDetail: ReserveCreationDetail
 )
 
-class History: ArrayList<HistoryEvent>()
+class History : ArrayList<HistoryEvent>()
 
 @JsonTypeInfo(
     use = NAME,
@@ -56,7 +91,11 @@ class History: ArrayList<HistoryEvent>()
     Type(value = ExchangeAddedEvent::class, name = "exchange-added"),
     Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"),
     Type(value = ReserveBalanceUpdatedEvent::class, name = 
"reserve-balance-updated"),
-    Type(value = HistoryWithdrawnEvent::class, name = "withdrawn")
+    Type(value = HistoryWithdrawnEvent::class, name = "withdrawn"),
+    Type(value = HistoryOrderAcceptedEvent::class, name = "order-accepted"),
+    Type(value = HistoryOrderRefusedEvent::class, name = "order-refused"),
+    Type(value = HistoryPaymentSentEvent::class, name = "payment-sent"),
+    Type(value = HistoryRefreshedEvent::class, name = "refreshed")
 )
 @JsonIgnoreProperties(
     value = [
@@ -64,7 +103,13 @@ class History: ArrayList<HistoryEvent>()
     ]
 )
 abstract class HistoryEvent(
-    val timestamp: Timestamp
+    val timestamp: Timestamp,
+    @get:LayoutRes
+    open val layout: Int = R.layout.history_row,
+    @get:StringRes
+    open val title: Int = 0,
+    @get:DrawableRes
+    open val icon: Int = R.drawable.ic_account_balance
 )
 
 
@@ -73,13 +118,17 @@ class ExchangeAddedEvent(
     timestamp: Timestamp,
     val exchangeBaseUrl: String,
     val builtIn: Boolean
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_exchange_added
+}
 
 @JsonTypeName("exchange-updated")
 class ExchangeUpdatedEvent(
     timestamp: Timestamp,
     val exchangeBaseUrl: String
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_exchange_updated
+}
 
 
 @JsonTypeName("reserve-balance-updated")
@@ -99,7 +148,9 @@ class ReserveBalanceUpdatedEvent(
      * considering ongoing withdrawals from that reserve.
      */
     val amountExpected: String
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+    override val title = R.string.history_event_reserve_balance_updated
+}
 
 @JsonTypeName("withdrawn")
 class HistoryWithdrawnEvent(
@@ -123,8 +174,94 @@ class HistoryWithdrawnEvent(
      * Amount that actually was added to the wallet's balance.
      */
     val amountWithdrawnEffective: String
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+    override val layout = R.layout.history_withdrawn
+    override val title = R.string.history_event_withdrawn
+    override val icon = R.drawable.history_withdrawn
+}
 
+@JsonTypeName("order-accepted")
+class HistoryOrderAcceptedEvent(
+    timestamp: Timestamp,
+    /**
+     * Condensed info about the order.
+     */
+    val orderShortInfo: OrderShortInfo
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.ic_add_circle
+    override val title = R.string.history_event_order_accepted
+}
+
+@JsonTypeName("order-refused")
+class HistoryOrderRefusedEvent(
+    timestamp: Timestamp,
+    /**
+     * Condensed info about the order.
+     */
+    val orderShortInfo: OrderShortInfo
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.ic_cancel
+    override val title = R.string.history_event_order_refused
+}
+
+@JsonTypeName("payment-sent")
+class HistoryPaymentSentEvent(
+    timestamp: Timestamp,
+    /**
+     * Condensed info about the order that we already paid for.
+     */
+    val orderShortInfo: OrderShortInfo,
+    /**
+     * Set to true if the payment has been previously sent
+     * to the merchant successfully, possibly with a different session ID.
+     */
+    val replay: Boolean,
+    /**
+     * Number of coins that were involved in the payment.
+     */
+    val numCoins: Int,
+    /**
+     * Amount that was paid, including deposit and wire fees.
+     */
+    val amountPaidWithFees: String,
+    /**
+     * Session ID that the payment was (re-)submitted under.
+     */
+    val sessionId: String?
+) : HistoryEvent(timestamp) {
+    override val layout = R.layout.history_payment_sent
+    override val title = R.string.history_event_payment_sent
+    override val icon = R.drawable.ic_cash_usd_outline
+}
+
+@JsonTypeName("refreshed")
+class HistoryRefreshedEvent(
+    timestamp: Timestamp,
+    /**
+     * Amount that is now available again because it has
+     * been refreshed.
+     */
+    val amountRefreshedEffective: String,
+    /**
+     * Amount that we spent for refreshing.
+     */
+    val amountRefreshedRaw: String,
+    /**
+     * Why was the refreshing done?
+     */
+    val refreshReason: RefreshReason,
+    val numInputCoins: Int,
+    val numRefreshedInputCoins: Int,
+    val numOutputCoins: Int,
+    /**
+     * Identifier for a refresh group, contains one or
+     * more refresh session IDs.
+     */
+    val refreshGroupId: String
+) : HistoryEvent(timestamp) {
+    override val icon = R.drawable.ic_history_black_24dp
+    override val title = R.string.history_event_refreshed
+}
 
 @JsonTypeInfo(
     use = NAME,
@@ -145,3 +282,26 @@ class WithdrawalSourceTip(
 class WithdrawalSourceReserve(
     val reservePub: String
 ) : WithdrawalSource()
+
+data class OrderShortInfo(
+    /**
+     * Wallet-internal identifier of the proposal.
+     */
+    val proposalId: String,
+    /**
+     * Order ID, uniquely identifies the order within a merchant instance.
+     */
+    val orderId: String,
+    /**
+     * Base URL of the merchant.
+     */
+    val merchantBaseUrl: String,
+    /**
+     * Amount that must be paid for the contract.
+     */
+    val amount: String,
+    /**
+     * Summary of the proposal, given by the merchant.
+     */
+    val summary: String
+)
diff --git a/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt 
b/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
index 880639f..f4cfcb8 100644
--- a/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
+++ b/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
@@ -1,3 +1,19 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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.wallet.history
 
 import com.fasterxml.jackson.annotation.JsonProperty
diff --git a/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt 
b/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
index 37dc742..14fc1e9 100644
--- a/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
+++ b/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
@@ -1,13 +1,31 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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.wallet.history
 
 import android.text.format.DateUtils.*
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.ImageView
 import android.widget.TextView
 import androidx.annotation.CallSuper
 import androidx.recyclerview.widget.RecyclerView.Adapter
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.wallet.ParsedAmount.Companion.parseAmount
 import net.taler.wallet.R
 
 
@@ -18,16 +36,14 @@ internal class WalletHistoryAdapter(private var history: 
History = History()) :
         setHasStableIds(false)
     }
 
-    override fun getItemViewType(position: Int): Int = when 
(history[position]) {
-        is HistoryWithdrawnEvent -> R.layout.history_withdrawn
-        else -> R.layout.history_row
-    }
+    override fun getItemViewType(position: Int): Int = history[position].layout
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
HistoryEventViewHolder {
         val view = LayoutInflater.from(parent.context).inflate(viewType, 
parent, false)
         return when (viewType) {
-            R.layout.history_withdrawn -> HistoryWithdrawnEventViewHolder(view)
-            else -> HistoryEventViewHolder(view)
+            R.layout.history_withdrawn -> HistoryWithdrawnViewHolder(view)
+            R.layout.history_payment_sent -> HistoryPaymentSentViewHolder(view)
+            else -> GenericHistoryEventViewHolder(view)
         }
     }
 
@@ -45,14 +61,17 @@ internal class WalletHistoryAdapter(private var history: 
History = History()) :
 
 }
 
-internal open class HistoryEventViewHolder(protected val v: View) : 
ViewHolder(v) {
+internal abstract class HistoryEventViewHolder(protected val v: View) : 
ViewHolder(v) {
 
-    protected val title: TextView = v.findViewById(R.id.title)
+    private val icon: ImageView = v.findViewById(R.id.icon)
+    private val title: TextView = v.findViewById(R.id.title)
     private val time: TextView = v.findViewById(R.id.time)
 
     @CallSuper
     open fun bind(event: HistoryEvent) {
-        title.text = event::class.java.simpleName
+        icon.setImageResource(event.icon)
+        if (event.title == 0) title.text = event::class.java.simpleName
+        else title.setText(event.title)
         time.text = getRelativeTime(event.timestamp.ms)
     }
 
@@ -61,22 +80,60 @@ internal open class HistoryEventViewHolder(protected val v: 
View) : ViewHolder(v
         return getRelativeTimeSpanString(timestamp, now, MINUTE_IN_MILLIS, 
FORMAT_ABBREV_RELATIVE)
     }
 
-    protected fun getString(resId: Int) = v.context.getString(resId)
+}
+
+internal class GenericHistoryEventViewHolder(v: View) : 
HistoryEventViewHolder(v) {
+
+    private val info: TextView = v.findViewById(R.id.info)
+
+    override fun bind(event: HistoryEvent) {
+        super.bind(event)
+        info.text = when (event) {
+            is ExchangeAddedEvent -> event.exchangeBaseUrl
+            is ExchangeUpdatedEvent -> event.exchangeBaseUrl
+            is ReserveBalanceUpdatedEvent -> 
parseAmount(event.amountReserveBalance).toString()
+            is HistoryPaymentSentEvent -> event.orderShortInfo.summary
+            is HistoryOrderAcceptedEvent -> event.orderShortInfo.summary
+            is HistoryOrderRefusedEvent -> event.orderShortInfo.summary
+            is HistoryRefreshedEvent -> {
+                "${parseAmount(event.amountRefreshedRaw)} - 
${parseAmount(event.amountRefreshedEffective)}"
+            }
+            else -> ""
+        }
+    }
 
 }
 
-internal class HistoryWithdrawnEventViewHolder(v: View) : 
HistoryEventViewHolder(v) {
+internal class HistoryWithdrawnViewHolder(v: View) : HistoryEventViewHolder(v) 
{
 
-    private val amountWithdrawnRaw: TextView = 
v.findViewById(R.id.amountWithdrawnRaw)
-    private val amountWithdrawnEffective: TextView = 
v.findViewById(R.id.amountWithdrawnEffective)
+    private val exchangeUrl: TextView = v.findViewById(R.id.exchangeUrl)
+    private val amountWithdrawn: TextView = 
v.findViewById(R.id.amountWithdrawn)
+    private val fee: TextView = v.findViewById(R.id.fee)
 
     override fun bind(event: HistoryEvent) {
         super.bind(event)
         event as HistoryWithdrawnEvent
 
-        title.text = getString(R.string.history_event_withdrawn)
-        amountWithdrawnRaw.text = event.amountWithdrawnRaw
-        amountWithdrawnEffective.text = event.amountWithdrawnEffective
+        exchangeUrl.text = event.exchangeBaseUrl
+        val parsedEffective = parseAmount(event.amountWithdrawnEffective)
+        val parsedRaw = parseAmount(event.amountWithdrawnRaw)
+        amountWithdrawn.text = parsedRaw.toString()
+        fee.text = (parsedRaw - parsedEffective).toString()
+    }
+
+}
+
+internal class HistoryPaymentSentViewHolder(v: View) : 
HistoryEventViewHolder(v) {
+
+    private val summary: TextView = v.findViewById(R.id.summary)
+    private val amountPaidWithFees: TextView = 
v.findViewById(R.id.amountPaidWithFees)
+
+    override fun bind(event: HistoryEvent) {
+        super.bind(event)
+        event as HistoryPaymentSentEvent
+
+        summary.text = event.orderShortInfo.summary
+        amountPaidWithFees.text = 
parseAmount(event.amountPaidWithFees).toString()
     }
 
 }
diff --git a/app/src/main/res/drawable/ic_account_balance.xml 
b/app/src/main/res/drawable/ic_account_balance.xml
new file mode 100644
index 0000000..03224e2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_account_balance.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            
android:pathData="M4,10v7h3v-7L4,10zM10,10v7h3v-7h-3zM2,22h19v-3L2,19v3zM16,10v7h3v-7h-3zM11.5,1L2,6v2h19L21,6l-9.5,-5z"
 />
+</vector>
diff --git a/app/src/main/res/drawable/ic_add_circle.xml 
b/app/src/main/res/drawable/ic_add_circle.xml
new file mode 100644
index 0000000..01d5f90
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_circle.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF000000"
+            android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 
10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_cancel.xml 
b/app/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000..7d2b57e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 
10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 
8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_cash_usd_outline.xml 
b/app/src/main/res/drawable/ic_cash_usd_outline.xml
new file mode 100644
index 0000000..604b40e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cash_usd_outline.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="#000"
+            android:pathData="M20,18H4V6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 
0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4M11,17H13V16H14A1,1 0 0,0 
15,15V12A1,1 0 0,0 14,11H11V10H15V8H13V7H11V8H10A1,1 0 0,0 9,9V12A1,1 0 0,0 
10,13H13V14H9V16H11V17Z" />
+</vector>
diff --git a/app/src/main/res/layout/history_withdrawn.xml 
b/app/src/main/res/layout/history_payment_sent.xml
similarity index 72%
copy from app/src/main/res/layout/history_withdrawn.xml
copy to app/src/main/res/layout/history_payment_sent.xml
index 8290c37..0c39133 100644
--- a/app/src/main/res/layout/history_withdrawn.xml
+++ b/app/src/main/res/layout/history_payment_sent.xml
@@ -22,50 +22,49 @@
 
     <TextView
             android:id="@+id/title"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
-            android:text="@string/history_event_withdrawn"
+            android:layout_marginEnd="8dp"
+            android:text="@string/history_event_payment_sent"
             android:textColor="?android:textColorPrimary"
             android:textSize="20sp"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintEnd_toStartOf="@+id/amountPaidWithFees"
             app:layout_constraintStart_toEndOf="@+id/icon"
             app:layout_constraintTop_toTopOf="parent" />
 
     <TextView
-            android:id="@+id/amountWithdrawnRaw"
+            android:id="@+id/summary"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
-            android:layout_marginEnd="8dp"
-            
app:layout_constraintBottom_toBottomOf="@+id/amountWithdrawnEffective"
-            app:layout_constraintEnd_toStartOf="@+id/amountWithdrawnEffective"
-            app:layout_constraintHorizontal_bias="1.0"
+            android:layout_marginTop="8dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/amountPaidWithFees"
+            app:layout_constraintHorizontal_bias="0.0"
             app:layout_constraintStart_toEndOf="@+id/icon"
-            app:layout_constraintTop_toTopOf="@+id/amountWithdrawnEffective"
-            tools:text="TESTKUDOS:10" />
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="Lots of books" />
 
     <TextView
-            android:id="@+id/amountWithdrawnEffective"
+            android:id="@+id/amountPaidWithFees"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="8dp"
-            android:textSize="16sp"
-            android:textColor="?android:textColorPrimary"
+            android:textColor="@color/red"
+            app:layout_constraintBottom_toTopOf="@+id/time"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/title"
-            tools:text="TESTKUDOS:9.8" />
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="0.2 TESTKUDOS" />
 
     <TextView
             android:id="@+id/time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="8dp"
             android:textSize="14sp"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/amountWithdrawnEffective"
             tools:text="23 min ago" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/history_row.xml 
b/app/src/main/res/layout/history_row.xml
index 2ea6d9d..cab7f0f 100644
--- a/app/src/main/res/layout/history_row.xml
+++ b/app/src/main/res/layout/history_row.xml
@@ -1,24 +1,57 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
+<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="wrap_content"
-        android:orientation="vertical"
         android:layout_margin="15dp">
 
+    <ImageView
+            android:id="@+id/icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/ic_account_balance"
+            app:tint="?android:colorControlNormal"
+            tools:ignore="ContentDescription" />
+
     <TextView
             android:id="@+id/title"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:textSize="20sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="My History Event" />
+
+    <TextView
+            android:id="@+id/info"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:textSize="24sp"
-            tools:text="My History Event"/>
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/time"
+            app:layout_constraintStart_toEndOf="@+id/icon"
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            tools:text="TextView" />
 
     <TextView
             android:id="@+id/time"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
             android:gravity="end"
             android:textSize="14sp"
-            tools:text="3 days ago"/>
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            tools:text="3 days ago" />
 
-</LinearLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/history_withdrawn.xml 
b/app/src/main/res/layout/history_withdrawn.xml
index 8290c37..e02046b 100644
--- a/app/src/main/res/layout/history_withdrawn.xml
+++ b/app/src/main/res/layout/history_withdrawn.xml
@@ -28,34 +28,44 @@
             android:text="@string/history_event_withdrawn"
             android:textColor="?android:textColorPrimary"
             android:textSize="20sp"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.0"
             app:layout_constraintStart_toEndOf="@+id/icon"
             app:layout_constraintTop_toTopOf="parent" />
 
     <TextView
-            android:id="@+id/amountWithdrawnRaw"
+            android:id="@+id/exchangeUrl"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="8dp"
-            android:layout_marginEnd="8dp"
-            
app:layout_constraintBottom_toBottomOf="@+id/amountWithdrawnEffective"
-            app:layout_constraintEnd_toStartOf="@+id/amountWithdrawnEffective"
-            app:layout_constraintHorizontal_bias="1.0"
+            android:layout_marginTop="8dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/fee"
+            app:layout_constraintHorizontal_bias="0.0"
             app:layout_constraintStart_toEndOf="@+id/icon"
-            app:layout_constraintTop_toTopOf="@+id/amountWithdrawnEffective"
-            tools:text="TESTKUDOS:10" />
+            app:layout_constraintTop_toBottomOf="@+id/title"
+            app:layout_constraintVertical_bias="0.0"
+            tools:text="http://taler.exchange.url"; />
 
     <TextView
-            android:id="@+id/amountWithdrawnEffective"
+            android:id="@+id/fee"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="8dp"
+            android:textColor="@color/red"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/amountWithdrawn"
+            tools:text="0.2 TESTKUDOS" />
+
+    <TextView
+            android:id="@+id/amountWithdrawn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/green"
             android:textSize="16sp"
-            android:textColor="?android:textColorPrimary"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/title"
-            tools:text="TESTKUDOS:9.8" />
+            app:layout_constraintHorizontal_bias="1.0"
+            app:layout_constraintStart_toEndOf="@+id/title"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="10 TESTKUDOS" />
 
     <TextView
             android:id="@+id/time"
@@ -65,7 +75,7 @@
             android:textSize="14sp"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/amountWithdrawnEffective"
+            app:layout_constraintTop_toBottomOf="@+id/fee"
             tools:text="23 min ago" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/values/colors.xml 
b/app/src/main/res/values/colors.xml
index 3b6dc88..0fe76cc 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,4 +3,7 @@
     <color name="colorPrimary">#283593</color>
     <color name="colorPrimaryDark">#1A237E</color>
     <color name="colorAccent">#AE1010</color>
+
+    <color name="red">#C62828</color>
+    <color name="green">#558B2F</color>
 </resources>
diff --git a/app/src/main/res/values/strings.xml 
b/app/src/main/res/values/strings.xml
index 82b942b..1caf41e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,7 +16,15 @@
     <string name="servicedesc">my service</string>
     <string name="aiddescription">my aid</string>
 
+    <!-- HistoryEvents -->
+    <string name="history_event_exchange_added">Exchange Added</string>
+    <string name="history_event_exchange_updated">Exchange Updated</string>
+    <string name="history_event_reserve_balance_updated">Reserve Balance 
Updated</string>
+    <string name="history_event_payment_sent">Payment Made</string>
     <string name="history_event_withdrawn">Withdraw</string>
+    <string name="history_event_order_accepted">Order Confirmed</string>
+    <string name="history_event_order_refused">Order Cancelled</string>
+    <string name="history_event_refreshed">Refresh</string>
 
     <!-- TODO: Remove or change this placeholder text -->
     <string name="hello_blank_fragment">Hello blank fragment</string>
diff --git a/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt 
b/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
index bd1ff9b..d6d68f1 100644
--- a/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
+++ b/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
@@ -1,8 +1,25 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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.wallet.history
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
 import com.fasterxml.jackson.module.kotlin.readValue
+import net.taler.wallet.history.RefreshReason.PAY
 import net.taler.wallet.history.ReserveType.MANUAL
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -15,6 +32,13 @@ class HistoryEventTest {
 
     private val timestamp = Random.nextLong()
     private val exchangeBaseUrl = "https://exchange.test.taler.net/";
+    private val orderShortInfo = OrderShortInfo(
+        proposalId = "EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+        orderId = "2019.364-01RAQ68DQ7AWR",
+        merchantBaseUrl = 
"https://backend.demo.taler.net/public/instances/FSF/";,
+        amount = "KUDOS:0.5",
+        summary = "Essay: Foreword"
+    )
 
     @Test
     fun `test ExchangeAddedEvent`() {
@@ -140,6 +164,153 @@ class HistoryEventTest {
         assertEquals(timestamp, event.timestamp.ms)
     }
 
+    @Test
+    fun `test OrderShortInfo`() {
+        val json = """{
+            "amount": "KUDOS:0.5",
+            "orderId": "2019.364-01RAQ68DQ7AWR",
+            "merchantBaseUrl": 
"https:\/\/backend.demo.taler.net\/public\/instances\/FSF\/",
+            "proposalId": 
"EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "summary": "Essay: Foreword"
+        }""".trimIndent()
+        val info: OrderShortInfo = mapper.readValue(json)
+
+        assertEquals("KUDOS:0.5", info.amount)
+        assertEquals("2019.364-01RAQ68DQ7AWR", info.orderId)
+        assertEquals("Essay: Foreword", info.summary)
+    }
+
+    @Test
+    fun `test HistoryOrderAcceptedEvent`() {
+        val json = """{
+            "type": "order-accepted",
+            "eventId": 
"order-accepted;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "timestamp": {
+                "t_ms": $timestamp
+            }
+        }""".trimIndent()
+        val event: HistoryOrderAcceptedEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryOrderRefusedEvent`() {
+        val json = """{
+            "type": "order-refused",
+            "eventId": 
"order-refused;9RJGAYXKWX0Y3V37H66606SXSA7V2CV255EBFS4G1JSH6W1EG7F0",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "timestamp": {
+                "t_ms": $timestamp
+            }
+        }""".trimIndent()
+        val event: HistoryOrderRefusedEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryPaymentSentEvent`() {
+        val json = """{
+            "type": "payment-sent",
+            "eventId": 
"payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "replay": false,
+            "sessionId": "e4f436c4-3c5c-4aee-81d2-26e425c09520",
+            "timestamp": {
+                "t_ms": $timestamp
+            },
+            "numCoins": 6,
+            "amountPaidWithFees": "KUDOS:0.6"
+        }""".trimIndent()
+        val event: HistoryPaymentSentEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(false, event.replay)
+        assertEquals(6, event.numCoins)
+        assertEquals("KUDOS:0.6", event.amountPaidWithFees)
+        assertEquals("e4f436c4-3c5c-4aee-81d2-26e425c09520", event.sessionId)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryPaymentSentEvent without sessionId`() {
+        val json = """{
+            "type": "payment-sent",
+            "eventId": 
"payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG",
+            "orderShortInfo": {
+                "amount": "${orderShortInfo.amount}",
+                "orderId": "${orderShortInfo.orderId}",
+                "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}",
+                "proposalId": "${orderShortInfo.proposalId}",
+                "summary": "${orderShortInfo.summary}"
+            },
+            "replay": true,
+            "timestamp": {
+                "t_ms": $timestamp
+            },
+            "numCoins": 6,
+            "amountPaidWithFees": "KUDOS:0.6"
+        }""".trimIndent()
+        val event: HistoryPaymentSentEvent = mapper.readValue(json)
+
+        assertEquals(orderShortInfo, event.orderShortInfo)
+        assertEquals(true, event.replay)
+        assertEquals(6, event.numCoins)
+        assertEquals("KUDOS:0.6", event.amountPaidWithFees)
+        assertEquals(null, event.sessionId)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
+    @Test
+    fun `test HistoryRefreshedEvent`() {
+        val json = """{
+            "type": "refreshed",
+            "refreshGroupId": 
"8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640",
+            "eventId": 
"refreshed;8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640",
+            "timestamp": {
+                "t_ms": $timestamp
+            },
+            "refreshReason": "pay",
+            "amountRefreshedEffective": "KUDOS:0",
+            "amountRefreshedRaw": "KUDOS:1",
+            "numInputCoins": 6,
+            "numOutputCoins": 0,
+            "numRefreshedInputCoins": 1
+        }""".trimIndent()
+        val event: HistoryRefreshedEvent = mapper.readValue(json)
+
+        assertEquals("KUDOS:0", event.amountRefreshedEffective)
+        assertEquals("KUDOS:1", event.amountRefreshedRaw)
+        assertEquals(6, event.numInputCoins)
+        assertEquals(0, event.numOutputCoins)
+        assertEquals(1, event.numRefreshedInputCoins)
+        assertEquals("8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640", 
event.refreshGroupId)
+        assertEquals(PAY, event.refreshReason)
+        assertEquals(timestamp, event.timestamp.ms)
+    }
+
     @Test
     fun `test list of events as History`() {
         val builtIn = Random.nextBoolean()
diff --git 
a/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt 
b/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt
index b5687c6..208995a 100644
--- a/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt
+++ b/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt
@@ -1,3 +1,19 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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.wallet.history
 
 import com.fasterxml.jackson.databind.ObjectMapper

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]