gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 01/13: [wallet] separate history and transactions


From: gnunet
Subject: [taler-taler-android] 01/13: [wallet] separate history and transactions UI
Date: Mon, 18 May 2020 14:47:18 +0200

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

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

commit e74f39ee86f32b4e0324405af1f0c7be061fb372
Author: Torsten Grote <address@hidden>
AuthorDate: Tue May 12 15:26:44 2020 -0300

    [wallet] separate history and transactions UI
    
    The history with its JSON payload is only shown in dev mode
    while the transactions are prepared to move to the new API.
---
 .../src/main/java/net/taler/wallet/MainActivity.kt |   5 +-
 .../main/java/net/taler/wallet/MainViewModel.kt    |  19 +--
 .../net/taler/wallet/history/DevHistoryAdapter.kt  | 133 +++++++++++++++++++++
 .../net/taler/wallet/history/DevHistoryFragment.kt |  87 ++++++++++++++
 .../net/taler/wallet/history/DevHistoryManager.kt  |  78 ++++++++++++
 .../Transaction.kt => history/HistoryEvent.kt}     | 115 ++++++++++--------
 .../JsonDialogFragment.kt                          |   2 +-
 .../ReserveTransaction.kt                          |   2 +-
 .../wallet/transactions/TransactionAdapter.kt      |  38 +++---
 .../transactions/TransactionDetailFragment.kt      |  17 ++-
 .../wallet/transactions/TransactionManager.kt      |  72 +++++------
 .../wallet/transactions/TransactionsFragment.kt    |  16 ++-
 wallet/src/main/res/drawable/ic_history.xml        |   9 ++
 .../src/main/res/layout/fragment_transactions.xml  |   2 +-
 ..._item_transaction.xml => list_item_history.xml} |   0
 wallet/src/main/res/menu/activity_main_drawer.xml  |  27 ++++-
 wallet/src/main/res/navigation/nav_graph.xml       |  10 ++
 wallet/src/main/res/values/strings.xml             |   1 +
 .../HistoryEventTest.kt}                           |  54 ++++++---
 .../ReserveHistoryEventTest.kt}                    |   5 +-
 20 files changed, 539 insertions(+), 153 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index b6e9a7a..a6385a9 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -75,7 +75,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
 
         setSupportActionBar(toolbar)
         val appBarConfiguration = AppBarConfiguration(
-            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations),
+            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations, R.id.nav_history),
             drawer_layout
         )
         toolbar.setupWithNavController(nav, appBarConfiguration)
@@ -86,7 +86,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
 
         val versionView: TextView = 
nav_view.getHeaderView(0).findViewById(R.id.versionView)
         model.devMode.observe(this, Observer { enabled ->
-            nav_view.menu.findItem(R.id.nav_pending_operations).isVisible = 
enabled
+            nav_view.menu.findItem(R.id.nav_dev).isVisible = enabled
             if (enabled) {
                 @SuppressLint("SetTextI18n")
                 versionView.text = "$VERSION_NAME ($VERSION_CODE)"
@@ -116,6 +116,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
             R.id.nav_home -> nav.navigate(R.id.nav_main)
             R.id.nav_settings -> nav.navigate(R.id.nav_settings)
             R.id.nav_pending_operations -> 
nav.navigate(R.id.nav_pending_operations)
+            R.id.nav_history -> nav.navigate(R.id.nav_history)
         }
         drawer_layout.closeDrawer(START)
         return true
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 230c310..b880036 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -23,11 +23,13 @@ import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.distinctUntilChanged
+import androidx.lifecycle.viewModelScope
 import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
 import net.taler.common.Amount
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.history.DevHistoryManager
 import net.taler.wallet.payment.PaymentManager
 import net.taler.wallet.pending.PendingOperationsManager
 import net.taler.wallet.refund.RefundManager
@@ -70,13 +72,13 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
             loadBalances()
             if (payload.optString("type") in transactionNotifications) {
                 // update transaction list
-                // TODO do this in a better way
-                transactionManager.showAll.value?.let {
-                    transactionManager.showAll.postValue(it)
-                }
+                transactionManager.loadTransactions()
+            }
+            // refresh pending ops and history with each notification
+            if (devMode.value == true) {
+                pendingOperationsManager.getPending()
+                historyManager.loadHistory()
             }
-            // refresh pending ops with each notification
-            if (devMode.value == true) pendingOperationsManager.getPending()
         }
     }
 
@@ -88,7 +90,10 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     val paymentManager = PaymentManager(walletBackendApi, mapper)
     val pendingOperationsManager: PendingOperationsManager =
         PendingOperationsManager(walletBackendApi)
-    val transactionManager: TransactionManager = 
TransactionManager(walletBackendApi, mapper)
+    val historyManager: DevHistoryManager =
+        DevHistoryManager(walletBackendApi, viewModelScope, mapper)
+    val transactionManager: TransactionManager =
+        TransactionManager(walletBackendApi, viewModelScope, mapper)
     val refundManager = RefundManager(walletBackendApi)
 
     override fun onCleared() {
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
new file mode 100644
index 0000000..88db90c
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
@@ -0,0 +1,133 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.content.Context
+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.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.common.exhaustive
+import net.taler.common.toRelativeTime
+import net.taler.wallet.R
+import net.taler.wallet.history.DevHistoryAdapter.HistoryViewHolder
+
+@Deprecated("Replaced by TransactionAdapter")
+internal class DevHistoryAdapter(
+    private val listener: OnEventClickListener,
+    private var history: History = History()
+) : Adapter<HistoryViewHolder>() {
+
+    init {
+        setHasStableIds(false)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
HistoryViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.list_item_history, parent, false)
+        return HistoryViewHolder(view)
+    }
+
+    override fun getItemCount(): Int = history.size
+
+    override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
+        val transaction = history[position]
+        holder.bind(transaction)
+    }
+
+    fun update(updatedHistory: History) {
+        this.history = updatedHistory
+        this.notifyDataSetChanged()
+    }
+
+    internal open inner class HistoryViewHolder(private val v: View) : 
ViewHolder(v) {
+
+        protected val context: Context = v.context
+
+        private val icon: ImageView = v.findViewById(R.id.icon)
+        protected val title: TextView = v.findViewById(R.id.title)
+        private val time: TextView = v.findViewById(R.id.time)
+        private val amount: TextView = v.findViewById(R.id.amount)
+
+        private val amountColor = amount.currentTextColor
+
+        open fun bind(historyEvent: HistoryEvent) {
+            v.setOnClickListener { listener.onTransactionClicked(historyEvent) 
}
+            icon.setImageResource(historyEvent.icon)
+
+            title.text = if (historyEvent.title == null) {
+                when (historyEvent) {
+                    is RefreshHistoryEvent -> getRefreshTitle(historyEvent)
+                    is OrderAcceptedHistoryEvent -> 
context.getString(R.string.transaction_order_accepted)
+                    is OrderRefusedHistoryEvent -> 
context.getString(R.string.transaction_order_refused)
+                    is TipAcceptedHistoryEvent -> 
context.getString(R.string.transaction_tip_accepted)
+                    is TipDeclinedHistoryEvent -> 
context.getString(R.string.transaction_tip_declined)
+                    is ReserveBalanceUpdatedHistoryEvent -> 
context.getString(R.string.transaction_reserve_balance_updated)
+                    else -> historyEvent::class.java.simpleName
+                }
+            } else historyEvent.title
+
+            time.text = historyEvent.timestamp.ms.toRelativeTime(context)
+            bindAmount(historyEvent.displayAmount)
+        }
+
+        private fun bindAmount(displayAmount: DisplayAmount?) {
+            if (displayAmount == null) {
+                amount.visibility = GONE
+            } else {
+                amount.visibility = VISIBLE
+                when (displayAmount.type) {
+                    AmountType.Positive -> {
+                        amount.text = context.getString(
+                            R.string.amount_positive, 
displayAmount.amount.amountStr
+                        )
+                        amount.setTextColor(context.getColor(R.color.green))
+                    }
+                    AmountType.Negative -> {
+                        amount.text = context.getString(
+                            R.string.amount_negative, 
displayAmount.amount.amountStr
+                        )
+                        amount.setTextColor(context.getColor(R.color.red))
+                    }
+                    AmountType.Neutral -> {
+                        amount.text = displayAmount.amount.amountStr
+                        amount.setTextColor(amountColor)
+                    }
+                }.exhaustive
+            }
+        }
+
+        private fun getRefreshTitle(transaction: RefreshHistoryEvent): String {
+            val res = when (transaction.refreshReason) {
+                RefreshReason.MANUAL -> 
R.string.transaction_refresh_reason_manual
+                RefreshReason.PAY -> R.string.transaction_refresh_reason_pay
+                RefreshReason.REFUND -> 
R.string.transaction_refresh_reason_refund
+                RefreshReason.ABORT_PAY -> 
R.string.transaction_refresh_reason_abort_pay
+                RefreshReason.RECOUP -> 
R.string.transaction_refresh_reason_recoup
+                RefreshReason.BACKUP_RESTORED -> 
R.string.transaction_refresh_reason_backup_restored
+            }
+            return context.getString(R.string.transaction_refresh) + " " + 
context.getString(res)
+        }
+
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
new file mode 100644
index 0000000..c3c07a3
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
@@ -0,0 +1,87 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
+import kotlinx.android.synthetic.main.fragment_transactions.*
+import net.taler.common.fadeIn
+import net.taler.common.fadeOut
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+
+internal interface OnEventClickListener {
+    fun onTransactionClicked(historyEvent: HistoryEvent)
+}
+
+class DevHistoryFragment : Fragment(),
+    OnEventClickListener {
+
+    private val model: MainViewModel by activityViewModels()
+    private val historyManager by lazy { model.historyManager }
+    private val historyAdapter by lazy { DevHistoryAdapter(this) }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_transactions, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        if (savedInstanceState == null) historyManager.loadHistory()
+
+        list.apply {
+            adapter = historyAdapter
+            addItemDecoration(DividerItemDecoration(context, VERTICAL))
+        }
+        historyManager.progress.observe(viewLifecycleOwner, Observer { show ->
+            progressBar.visibility = if (show) VISIBLE else INVISIBLE
+        })
+        historyManager.history.observe(viewLifecycleOwner, Observer { result ->
+            onHistoryResult(result)
+        })
+    }
+
+    override fun onTransactionClicked(historyEvent: HistoryEvent) {
+        JsonDialogFragment.new(historyEvent.json.toString(2))
+            .show(parentFragmentManager, null)
+    }
+
+    private fun onHistoryResult(result: HistoryResult) = when (result) {
+        HistoryResult.Error -> {
+            list.fadeOut()
+            emptyState.text = getString(R.string.transactions_error)
+            emptyState.fadeIn()
+        }
+        is HistoryResult.Success -> {
+            emptyState.visibility = if (result.history.isEmpty()) VISIBLE else 
INVISIBLE
+            historyAdapter.update(result.history)
+            list.fadeIn()
+        }
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
new file mode 100644
index 0000000..72967b2
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
@@ -0,0 +1,78 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import net.taler.wallet.backend.WalletBackendApi
+import org.json.JSONObject
+
+sealed class HistoryResult {
+    object Error : HistoryResult()
+    class Success(val history: History) : HistoryResult()
+}
+
+class DevHistoryManager(
+    private val walletBackendApi: WalletBackendApi,
+    private val scope: CoroutineScope,
+    private val mapper: ObjectMapper
+) {
+
+    private val mProgress = MutableLiveData<Boolean>()
+    val progress: LiveData<Boolean> = mProgress
+
+    private val mHistory = MutableLiveData<HistoryResult>()
+    val history: LiveData<HistoryResult> = mHistory
+
+    @UiThread
+    internal fun loadHistory() {
+        mProgress.value = true
+        walletBackendApi.sendRequest("getHistory", null) { isError, result ->
+            scope.launch(Dispatchers.Default) {
+                onEventsLoaded(isError, result)
+            }
+        }
+    }
+
+    private fun onEventsLoaded(isError: Boolean, result: JSONObject) {
+        if (isError) {
+            mHistory.postValue(HistoryResult.Error)
+            return
+        }
+        val history = History()
+        val json = result.getJSONArray("history")
+        for (i in 0 until json.length()) {
+            val event: HistoryEvent = mapper.readValue(json.getString(i))
+            event.json = json.getJSONObject(i)
+            history.add(event)
+        }
+        history.reverse()  // show latest first
+        mProgress.postValue(false)
+        mHistory.postValue(
+            HistoryResult.Success(
+                history
+            )
+        )
+    }
+
+}
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt 
b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
similarity index 82%
rename from wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt
rename to wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
index 34942d0..acca679 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transaction.kt
+++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.transactions
+package net.taler.wallet.history
 
 import androidx.annotation.DrawableRes
 import androidx.annotation.LayoutRes
@@ -103,13 +103,13 @@ class DisplayAmount(
     val type: AmountType
 )
 
-typealias Transactions = ArrayList<Transaction>
+typealias History = ArrayList<HistoryEvent>
 
 @JsonTypeInfo(
     use = NAME,
     include = PROPERTY,
     property = "type",
-    defaultImpl = UnknownTransaction::class
+    defaultImpl = UnknownHistoryEvent::class
 )
 /** missing:
 AuditorComplaintSent = "auditor-complained-sent",
@@ -127,19 +127,19 @@ ReserveCreated = "reserve-created",
 @JsonSubTypes(
     Type(value = ExchangeAddedEvent::class, name = "exchange-added"),
     Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"),
-    Type(value = ReserveBalanceUpdatedTransaction::class, name = 
"reserve-balance-updated"),
-    Type(value = WithdrawTransaction::class, name = "withdrawn"),
-    Type(value = OrderAcceptedTransaction::class, name = "order-accepted"),
-    Type(value = OrderRefusedTransaction::class, name = "order-refused"),
-    Type(value = OrderRedirectedTransaction::class, name = "order-redirected"),
-    Type(value = PaymentTransaction::class, name = "payment-sent"),
-    Type(value = PaymentAbortedTransaction::class, name = "payment-aborted"),
-    Type(value = TipAcceptedTransaction::class, name = "tip-accepted"),
-    Type(value = TipDeclinedTransaction::class, name = "tip-declined"),
-    Type(value = RefundTransaction::class, name = "refund"),
-    Type(value = RefreshTransaction::class, name = "refreshed")
+    Type(value = ReserveBalanceUpdatedHistoryEvent::class, name = 
"reserve-balance-updated"),
+    Type(value = WithdrawHistoryEvent::class, name = "withdrawn"),
+    Type(value = OrderAcceptedHistoryEvent::class, name = "order-accepted"),
+    Type(value = OrderRefusedHistoryEvent::class, name = "order-refused"),
+    Type(value = OrderRedirectedHistoryEvent::class, name = 
"order-redirected"),
+    Type(value = PaymentHistoryEvent::class, name = "payment-sent"),
+    Type(value = PaymentAbortedHistoryEvent::class, name = "payment-aborted"),
+    Type(value = TipAcceptedHistoryEvent::class, name = "tip-accepted"),
+    Type(value = TipDeclinedHistoryEvent::class, name = "tip-declined"),
+    Type(value = RefundHistoryEvent::class, name = "refund"),
+    Type(value = RefreshHistoryEvent::class, name = "refreshed")
 )
-abstract class Transaction(
+abstract class HistoryEvent(
     val timestamp: Timestamp,
     val eventId: String,
     @get:LayoutRes
@@ -155,7 +155,7 @@ abstract class Transaction(
 }
 
 
-class UnknownTransaction(timestamp: Timestamp, eventId: String) : 
Transaction(timestamp, eventId) {
+class UnknownHistoryEvent(timestamp: Timestamp, eventId: String) : 
HistoryEvent(timestamp, eventId) {
     override val title: String? = null
 }
 
@@ -165,7 +165,7 @@ class ExchangeAddedEvent(
     eventId: String,
     val exchangeBaseUrl: String,
     val builtIn: Boolean
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val title = cleanExchange(exchangeBaseUrl)
 }
 
@@ -174,13 +174,13 @@ class ExchangeUpdatedEvent(
     timestamp: Timestamp,
     eventId: String,
     val exchangeBaseUrl: String
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val title = cleanExchange(exchangeBaseUrl)
 }
 
 
 @JsonTypeName("reserve-balance-updated")
-class ReserveBalanceUpdatedTransaction(
+class ReserveBalanceUpdatedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -200,14 +200,17 @@ class ReserveBalanceUpdatedTransaction(
      * Amount that hasn't been withdrawn yet.
      */
     val reserveUnclaimedAmount: Amount
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val title: String? = null
-    override val displayAmount = DisplayAmount(reserveBalance, 
AmountType.Neutral)
+    override val displayAmount = DisplayAmount(
+        reserveBalance,
+        AmountType.Neutral
+    )
     override fun isCurrency(currency: String) = reserveBalance.currency == 
currency
 }
 
 @JsonTypeName("withdrawn")
-class WithdrawTransaction(
+class WithdrawHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -229,45 +232,48 @@ class WithdrawTransaction(
      * Amount that actually was added to the wallet's balance.
      */
     val amountWithdrawnEffective: Amount
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val detailPageLayout = R.layout.fragment_event_withdraw
     override val title = cleanExchange(exchangeBaseUrl)
     override val icon = R.drawable.transaction_withdrawal
     override val showToUser = true
-    override val displayAmount = DisplayAmount(amountWithdrawnEffective, 
AmountType.Positive)
+    override val displayAmount = DisplayAmount(
+        amountWithdrawnEffective,
+        AmountType.Positive
+    )
     override fun isCurrency(currency: String) = amountWithdrawnRaw.currency == 
currency
 }
 
 @JsonTypeName("order-accepted")
-class OrderAcceptedTransaction(
+class OrderAcceptedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
      * Condensed info about the order.
      */
     val orderShortInfo: OrderShortInfo
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.ic_add_circle
     override val title: String? = null
     override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
 }
 
 @JsonTypeName("order-refused")
-class OrderRefusedTransaction(
+class OrderRefusedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
      * Condensed info about the order.
      */
     val orderShortInfo: OrderShortInfo
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.ic_cancel
     override val title: String? = null
     override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
 }
 
 @JsonTypeName("payment-sent")
-class PaymentTransaction(
+class PaymentHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -291,17 +297,20 @@ class PaymentTransaction(
      * Session ID that the payment was (re-)submitted under.
      */
     val sessionId: String?
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val detailPageLayout = R.layout.fragment_event_paid
     override val title = orderShortInfo.summary
     override val icon = R.drawable.ic_cash_usd_outline
     override val showToUser = true
-    override val displayAmount = DisplayAmount(amountPaidWithFees, 
AmountType.Negative)
+    override val displayAmount = DisplayAmount(
+        amountPaidWithFees,
+        AmountType.Negative
+    )
     override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
 }
 
 @JsonTypeName("payment-aborted")
-class PaymentAbortedTransaction(
+class PaymentAbortedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -312,16 +321,19 @@ class PaymentAbortedTransaction(
      * Amount that was lost due to refund and refreshing fees.
      */
     val amountLost: Amount
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val title = orderShortInfo.summary
     override val icon = R.drawable.transaction_payment_aborted
     override val showToUser = true
-    override val displayAmount = DisplayAmount(amountLost, AmountType.Negative)
+    override val displayAmount = DisplayAmount(
+        amountLost,
+        AmountType.Negative
+    )
     override fun isCurrency(currency: String) = orderShortInfo.amount.currency 
== currency
 }
 
 @JsonTypeName("refreshed")
-class RefreshTransaction(
+class RefreshHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -345,7 +357,7 @@ class RefreshTransaction(
      * more refresh session IDs.
      */
     val refreshGroupId: String
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.transaction_refresh
     override val title: String? = null
     override val showToUser = !(amountRefreshedRaw - 
amountRefreshedEffective).isZero()
@@ -362,7 +374,7 @@ class RefreshTransaction(
 }
 
 @JsonTypeName("order-redirected")
-class OrderRedirectedTransaction(
+class OrderRedirectedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -374,14 +386,14 @@ class OrderRedirectedTransaction(
      * Condensed info about the order that we already paid for.
      */
     val alreadyPaidOrderShortInfo: OrderShortInfo
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.ic_directions
     override val title = newOrderShortInfo.summary
     override fun isCurrency(currency: String) = 
newOrderShortInfo.amount.currency == currency
 }
 
 @JsonTypeName("tip-accepted")
-class TipAcceptedTransaction(
+class TipAcceptedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -392,16 +404,19 @@ class TipAcceptedTransaction(
      * Raw amount of the tip, without extra fees that apply.
      */
     val tipRaw: Amount
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.transaction_tip_accepted
     override val title: String? = null
     override val showToUser = true
-    override val displayAmount = DisplayAmount(tipRaw, AmountType.Positive)
+    override val displayAmount = DisplayAmount(
+        tipRaw,
+        AmountType.Positive
+    )
     override fun isCurrency(currency: String) = tipRaw.currency == currency
 }
 
 @JsonTypeName("tip-declined")
-class TipDeclinedTransaction(
+class TipDeclinedHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     /**
@@ -412,16 +427,19 @@ class TipDeclinedTransaction(
      * Raw amount of the tip, without extra fees that apply.
      */
     val tipAmount: Amount
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.transaction_tip_declined
     override val title: String? = null
     override val showToUser = true
-    override val displayAmount = DisplayAmount(tipAmount, AmountType.Neutral)
+    override val displayAmount = DisplayAmount(
+        tipAmount,
+        AmountType.Neutral
+    )
     override fun isCurrency(currency: String) = tipAmount.currency == currency
 }
 
 @JsonTypeName("refund")
-class RefundTransaction(
+class RefundHistoryEvent(
     timestamp: Timestamp,
     eventId: String,
     val orderShortInfo: OrderShortInfo,
@@ -443,12 +461,15 @@ class RefundTransaction(
      * Amount will be added to the wallet's balance after fees and refreshing.
      */
     val amountRefundedEffective: Amount
-) : Transaction(timestamp, eventId) {
+) : HistoryEvent(timestamp, eventId) {
     override val icon = R.drawable.transaction_refund
     override val title = orderShortInfo.summary
     override val detailPageLayout = R.layout.fragment_event_paid
     override val showToUser = true
-    override val displayAmount = DisplayAmount(amountRefundedEffective, 
AmountType.Positive)
+    override val displayAmount = DisplayAmount(
+        amountRefundedEffective,
+        AmountType.Positive
+    )
     override fun isCurrency(currency: String) = amountRefundedRaw.currency == 
currency
 }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/JsonDialogFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
similarity index 97%
rename from 
wallet/src/main/java/net/taler/wallet/transactions/JsonDialogFragment.kt
rename to wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
index 2337059..31c2b93 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/JsonDialogFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.transactions
+package net.taler.wallet.history
 
 import android.os.Bundle
 import android.view.LayoutInflater
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt 
b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
similarity index 97%
rename from 
wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt
rename to wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
index e497e9a..6c8fdaa 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/ReserveTransaction.kt
+++ b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.transactions
+package net.taler.wallet.history
 
 import com.fasterxml.jackson.annotation.JsonProperty
 import com.fasterxml.jackson.annotation.JsonSubTypes
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
index 440d07f..5aca896 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
@@ -34,13 +34,23 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
 import net.taler.common.exhaustive
 import net.taler.common.toRelativeTime
 import net.taler.wallet.R
+import net.taler.wallet.history.AmountType
+import net.taler.wallet.history.DisplayAmount
+import net.taler.wallet.history.History
+import net.taler.wallet.history.HistoryEvent
+import net.taler.wallet.history.OrderAcceptedHistoryEvent
+import net.taler.wallet.history.OrderRefusedHistoryEvent
+import net.taler.wallet.history.RefreshHistoryEvent
+import net.taler.wallet.history.RefreshReason
+import net.taler.wallet.history.ReserveBalanceUpdatedHistoryEvent
+import net.taler.wallet.history.TipAcceptedHistoryEvent
+import net.taler.wallet.history.TipDeclinedHistoryEvent
 import net.taler.wallet.transactions.TransactionAdapter.TransactionViewHolder
 
 
 internal class TransactionAdapter(
-    private val devMode: Boolean,
-    private val listener: OnEventClickListener,
-    private var transactions: Transactions = Transactions()
+    private val listener: OnTransactionClickListener,
+    private var transactions: History = History()
 ) : Adapter<TransactionViewHolder>() {
 
     lateinit var tracker: SelectionTracker<String>
@@ -52,7 +62,7 @@ internal class TransactionAdapter(
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
TransactionViewHolder {
         val view = LayoutInflater.from(parent.context)
-            .inflate(R.layout.list_item_transaction, parent, false)
+            .inflate(R.layout.list_item_history, parent, false)
         return TransactionViewHolder(view)
     }
 
@@ -63,7 +73,7 @@ internal class TransactionAdapter(
         holder.bind(transaction, tracker.isSelected(transaction.eventId))
     }
 
-    fun update(updatedTransactions: Transactions) {
+    fun update(updatedTransactions: History) {
         this.transactions = updatedTransactions
         this.notifyDataSetChanged()
     }
@@ -84,8 +94,8 @@ internal class TransactionAdapter(
         private val selectableForeground = v.foreground
         private val amountColor = amount.currentTextColor
 
-        open fun bind(transaction: Transaction, selected: Boolean) {
-            if (devMode || transaction.detailPageLayout != 0) {
+        open fun bind(transaction: HistoryEvent, selected: Boolean) {
+            if (transaction.detailPageLayout != 0) {
                 v.foreground = selectableForeground
                 v.setOnClickListener { 
listener.onTransactionClicked(transaction) }
             } else {
@@ -97,12 +107,12 @@ internal class TransactionAdapter(
 
             title.text = if (transaction.title == null) {
                 when (transaction) {
-                    is RefreshTransaction -> getRefreshTitle(transaction)
-                    is OrderAcceptedTransaction -> 
context.getString(R.string.transaction_order_accepted)
-                    is OrderRefusedTransaction -> 
context.getString(R.string.transaction_order_refused)
-                    is TipAcceptedTransaction -> 
context.getString(R.string.transaction_tip_accepted)
-                    is TipDeclinedTransaction -> 
context.getString(R.string.transaction_tip_declined)
-                    is ReserveBalanceUpdatedTransaction -> 
context.getString(R.string.transaction_reserve_balance_updated)
+                    is RefreshHistoryEvent -> getRefreshTitle(transaction)
+                    is OrderAcceptedHistoryEvent -> 
context.getString(R.string.transaction_order_accepted)
+                    is OrderRefusedHistoryEvent -> 
context.getString(R.string.transaction_order_refused)
+                    is TipAcceptedHistoryEvent -> 
context.getString(R.string.transaction_tip_accepted)
+                    is TipDeclinedHistoryEvent -> 
context.getString(R.string.transaction_tip_declined)
+                    is ReserveBalanceUpdatedHistoryEvent -> 
context.getString(R.string.transaction_reserve_balance_updated)
                     else -> transaction::class.java.simpleName
                 }
             } else transaction.title
@@ -137,7 +147,7 @@ internal class TransactionAdapter(
             }
         }
 
-        private fun getRefreshTitle(transaction: RefreshTransaction): String {
+        private fun getRefreshTitle(transaction: RefreshHistoryEvent): String {
             val res = when (transaction.refreshReason) {
                 RefreshReason.MANUAL -> 
R.string.transaction_refresh_reason_manual
                 RefreshReason.PAY -> R.string.transaction_refresh_reason_pay
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
index 909a7bf..bb70b5c 100644
--- 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
@@ -37,6 +37,11 @@ import net.taler.common.toAbsoluteTime
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.cleanExchange
+import net.taler.wallet.history.JsonDialogFragment
+import net.taler.wallet.history.OrderShortInfo
+import net.taler.wallet.history.PaymentHistoryEvent
+import net.taler.wallet.history.RefundHistoryEvent
+import net.taler.wallet.history.WithdrawHistoryEvent
 
 class TransactionDetailFragment : Fragment() {
 
@@ -65,9 +70,9 @@ class TransactionDetailFragment : Fragment() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         timeView.text = event.timestamp.ms.toAbsoluteTime(requireContext())
         when (val e = event) {
-            is WithdrawTransaction -> bind(e)
-            is PaymentTransaction -> bind(e)
-            is RefundTransaction -> bind(e)
+            is WithdrawHistoryEvent -> bind(e)
+            is PaymentHistoryEvent -> bind(e)
+            is RefundHistoryEvent -> bind(e)
             else -> Toast.makeText(
                 requireContext(),
                 "event ${e.javaClass} not implement",
@@ -90,7 +95,7 @@ class TransactionDetailFragment : Fragment() {
         }
     }
 
-    private fun bind(event: WithdrawTransaction) {
+    private fun bind(event: WithdrawHistoryEvent) {
         effectiveAmountLabel.text = getString(R.string.withdraw_total)
         effectiveAmountView.text = event.amountWithdrawnEffective.toString()
         chosenAmountLabel.text = getString(R.string.amount_chosen)
@@ -101,13 +106,13 @@ class TransactionDetailFragment : Fragment() {
         exchangeView.text = cleanExchange(event.exchangeBaseUrl)
     }
 
-    private fun bind(event: PaymentTransaction) {
+    private fun bind(event: PaymentHistoryEvent) {
         amountPaidWithFeesView.text = event.amountPaidWithFees.toString()
         val fee = event.amountPaidWithFees - event.orderShortInfo.amount
         bindOrderAndFee(event.orderShortInfo, fee)
     }
 
-    private fun bind(event: RefundTransaction) {
+    private fun bind(event: RefundHistoryEvent) {
         amountPaidWithFeesLabel.text = getString(R.string.transaction_refund)
         amountPaidWithFeesView.setTextColor(getColor(requireContext(), 
R.color.green))
         amountPaidWithFeesView.text =
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
index 549b2a8..850a3bb 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -18,70 +18,62 @@ package net.taler.wallet.transactions
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.switchMap
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.history.History
+import net.taler.wallet.history.HistoryEvent
+import org.json.JSONObject
 
 sealed class TransactionsResult {
     object Error : TransactionsResult()
-    class Success(val transactions: Transactions) : TransactionsResult()
+    class Success(val transactions: History) : TransactionsResult()
 }
 
-@Suppress("EXPERIMENTAL_API_USAGE")
 class TransactionManager(
     private val walletBackendApi: WalletBackendApi,
+    private val scope: CoroutineScope,
     private val mapper: ObjectMapper
 ) {
 
     private val mProgress = MutableLiveData<Boolean>()
     val progress: LiveData<Boolean> = mProgress
 
-    val showAll = MutableLiveData<Boolean>()
-
     var selectedCurrency: String? = null
-    var selectedEvent: Transaction? = null
+    var selectedEvent: HistoryEvent? = null
 
-    val transactions: LiveData<TransactionsResult> = showAll.switchMap { 
showAll ->
-        loadTransactions(showAll)
-            .onStart { mProgress.postValue(true) }
-            .onCompletion { mProgress.postValue(false) }
-            .asLiveData(Dispatchers.IO)
-    }
+    private val mTransactions = MutableLiveData<TransactionsResult>()
+    val transactions: LiveData<TransactionsResult> = mTransactions
 
-    private fun loadTransactions(showAll: Boolean) = callbackFlow {
+    fun loadTransactions() {
+        mProgress.postValue(true)
         walletBackendApi.sendRequest("getHistory", null) { isError, result ->
-            launch(Dispatchers.Default) {
-                if (isError) {
-                    offer(TransactionsResult.Error)
-                    close()
-                    return@launch
-                }
-                val transactions = Transactions()
-                val json = result.getJSONArray("history")
-                val currency = selectedCurrency
-                for (i in 0 until json.length()) {
-                    val event: Transaction = 
mapper.readValue(json.getString(i))
-                    event.json = json.getJSONObject(i)
-                    if (currency == null || event.isCurrency(currency)) {
-                        transactions.add(event)
-                    }
-                }
-                transactions.reverse()  // show latest first
-                val filtered =
-                    if (showAll) transactions else transactions.filter { 
it.showToUser } as Transactions
-                offer(TransactionsResult.Success(filtered))
-                close()
+            scope.launch(Dispatchers.Default) {
+                onTransactionsLoaded(isError, result)
+            }
+        }
+    }
+
+    private fun onTransactionsLoaded(isError: Boolean, result: JSONObject) {
+        if (isError) {
+            mTransactions.postValue(TransactionsResult.Error)
+            return
+        }
+        val transactions = History()
+        val json = result.getJSONArray("history")
+        val currency = selectedCurrency
+        for (i in 0 until json.length()) {
+            val event: HistoryEvent = mapper.readValue(json.getString(i))
+            if (event.showToUser && (currency == null || 
event.isCurrency(currency))) {
+                transactions.add(event)
             }
         }
-        awaitClose()
+        transactions.reverse()  // show latest first
+        mProgress.postValue(false)
+        mTransactions.postValue(TransactionsResult.Success(transactions))
     }
 
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
index e7adaf1..2b5337f 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -42,17 +42,18 @@ import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
+import net.taler.wallet.history.HistoryEvent
 
-interface OnEventClickListener {
-    fun onTransactionClicked(transaction: Transaction)
+interface OnTransactionClickListener {
+    fun onTransactionClicked(transaction: HistoryEvent)
 }
 
-class TransactionsFragment : Fragment(), OnEventClickListener, 
ActionMode.Callback {
+class TransactionsFragment : Fragment(), OnTransactionClickListener, 
ActionMode.Callback {
 
     private val model: MainViewModel by activityViewModels()
     private val transactionManager by lazy { model.transactionManager }
 
-    private val transactionAdapter by lazy { 
TransactionAdapter(model.devMode.value == true, this) }
+    private val transactionAdapter by lazy { TransactionAdapter(this) }
     private val currency by lazy { transactionManager.selectedCurrency!! }
     private var tracker: SelectionTracker<String>? = null
     private var actionMode: ActionMode? = null
@@ -109,7 +110,7 @@ class TransactionsFragment : Fragment(), 
OnEventClickListener, ActionMode.Callba
         })
 
         // kicks off initial load, needs to be adapted if showAll state is 
ever saved
-        if (savedInstanceState == null) transactionManager.showAll.value = 
model.devMode.value
+        if (savedInstanceState == null) transactionManager.loadTransactions()
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -137,14 +138,11 @@ class TransactionsFragment : Fragment(), 
OnEventClickListener, ActionMode.Callba
         }
     }
 
-    override fun onTransactionClicked(transaction: Transaction) {
+    override fun onTransactionClicked(transaction: HistoryEvent) {
         if (actionMode != null) return // don't react on clicks while in 
action mode
         if (transaction.detailPageLayout != 0) {
             transactionManager.selectedEvent = transaction
             findNavController().navigate(R.id.action_nav_transaction_detail)
-        } else if (model.devMode.value == true) {
-            JsonDialogFragment.new(transaction.json.toString(2))
-                .show(parentFragmentManager, null)
         }
     }
 
diff --git a/wallet/src/main/res/drawable/ic_history.xml 
b/wallet/src/main/res/drawable/ic_history.xml
new file mode 100644
index 0000000..d9f75ea
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_history.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="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 
0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 
-3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 
9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
+</vector>
diff --git a/wallet/src/main/res/layout/fragment_transactions.xml 
b/wallet/src/main/res/layout/fragment_transactions.xml
index aaf638c..547da24 100644
--- a/wallet/src/main/res/layout/fragment_transactions.xml
+++ b/wallet/src/main/res/layout/fragment_transactions.xml
@@ -27,7 +27,7 @@
         android:scrollbars="vertical"
         android:visibility="invisible"
         app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
-        tools:listitem="@layout/list_item_transaction"
+        tools:listitem="@layout/list_item_history"
         tools:visibility="visible" />
 
     <TextView
diff --git a/wallet/src/main/res/layout/list_item_transaction.xml 
b/wallet/src/main/res/layout/list_item_history.xml
similarity index 100%
rename from wallet/src/main/res/layout/list_item_transaction.xml
rename to wallet/src/main/res/layout/list_item_history.xml
diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml 
b/wallet/src/main/res/menu/activity_main_drawer.xml
index 896ff69..62abc32 100644
--- a/wallet/src/main/res/menu/activity_main_drawer.xml
+++ b/wallet/src/main/res/menu/activity_main_drawer.xml
@@ -18,7 +18,9 @@
     xmlns:tools="http://schemas.android.com/tools";
     tools:showIn="@layout/activity_main">
 
-    <group android:checkableBehavior="single">
+    <group
+        android:id="@+id/nav_group_main"
+        android:checkableBehavior="single">
         <item
             android:id="@+id/nav_home"
             android:icon="@drawable/ic_account_balance_wallet"
@@ -28,10 +30,25 @@
             android:id="@+id/nav_settings"
             android:icon="@drawable/ic_settings"
             android:title="@string/menu_settings" />
-        <item
-            android:id="@+id/nav_pending_operations"
-            android:icon="@drawable/ic_sync"
-            android:title="@string/pending_operations_title" />
     </group>
 
+    <item
+        android:id="@+id/nav_dev"
+        android:title="@string/settings_dev_mode">
+        <menu>
+            <group
+                android:id="@+id/nav_group_dev"
+                android:checkableBehavior="single">
+                <item
+                    android:id="@+id/nav_pending_operations"
+                    android:icon="@drawable/ic_sync"
+                    android:title="@string/pending_operations_title" />
+                <item
+                    android:id="@+id/nav_history"
+                    android:icon="@drawable/ic_history"
+                    android:title="@string/nav_history" />
+            </group>
+        </menu>
+    </item>
+
 </menu>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index f8d515e..8e717c1 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -128,6 +128,12 @@
         android:label="@string/pending_operations_title"
         tools:layout="@layout/fragment_pending_operations" />
 
+    <fragment
+        android:id="@+id/nav_history"
+        android:name="net.taler.wallet.history.DevHistoryFragment"
+        android:label="@string/nav_history"
+        tools:layout="@layout/fragment_transactions" />
+
     <fragment
         android:id="@+id/errorFragment"
         android:name="net.taler.wallet.withdraw.ErrorFragment"
@@ -142,6 +148,10 @@
         android:id="@+id/action_global_pending_operations"
         app:destination="@id/nav_pending_operations" />
 
+    <action
+        android:id="@+id/action_global_history"
+        app:destination="@id/nav_history" />
+
     <action
         android:id="@+id/action_nav_transaction_detail"
         app:destination="@id/nav_transactions_detail" />
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index a28545f..56ff2ef 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -40,6 +40,7 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="nav_prompt_withdraw">Withdraw Digital Cash</string>
     <string name="nav_exchange_tos">Exchange\'s Terms of Service</string>
     <string name="nav_exchange_fees">Exchange Fees</string>
+    <string name="nav_history">Event History</string>
     <string name="nav_error">Error</string>
 
     <string name="button_back">Go Back</string>
diff --git 
a/wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt 
b/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
similarity index 89%
rename from 
wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt
rename to wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
index 6549434..109b8dc 100644
--- a/wallet/src/test/java/net/taler/wallet/transactions/TransactionTest.kt
+++ b/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt
@@ -14,20 +14,38 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.transactions
+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.common.Amount
-import net.taler.wallet.transactions.RefreshReason.PAY
-import net.taler.wallet.transactions.ReserveType.MANUAL
+import net.taler.wallet.history.ExchangeAddedEvent
+import net.taler.wallet.history.ExchangeUpdatedEvent
+import net.taler.wallet.history.HistoryEvent
+import net.taler.wallet.history.OrderAcceptedHistoryEvent
+import net.taler.wallet.history.OrderRedirectedHistoryEvent
+import net.taler.wallet.history.OrderRefusedHistoryEvent
+import net.taler.wallet.history.OrderShortInfo
+import net.taler.wallet.history.PaymentAbortedHistoryEvent
+import net.taler.wallet.history.PaymentHistoryEvent
+import net.taler.wallet.history.RefreshHistoryEvent
+import net.taler.wallet.history.RefreshReason.PAY
+import net.taler.wallet.history.RefundHistoryEvent
+import net.taler.wallet.history.ReserveBalanceUpdatedHistoryEvent
+import net.taler.wallet.history.ReserveShortInfo
+import net.taler.wallet.history.ReserveType.MANUAL
+import net.taler.wallet.history.TipAcceptedHistoryEvent
+import net.taler.wallet.history.TipDeclinedHistoryEvent
+import net.taler.wallet.history.UnknownHistoryEvent
+import net.taler.wallet.history.WithdrawHistoryEvent
+import net.taler.wallet.history.WithdrawalSourceReserve
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import kotlin.random.Random
 
-class TransactionTest {
+class HistoryEventTest {
 
     private val mapper = ObjectMapper().registerModule(KotlinModule())
 
@@ -111,7 +129,7 @@ class TransactionTest {
                 "reservePub": 
"BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G"
             }
         }""".trimIndent()
-        val transaction: ReserveBalanceUpdatedTransaction = 
mapper.readValue(json)
+        val transaction: ReserveBalanceUpdatedHistoryEvent = 
mapper.readValue(json)
 
         assertEquals(timestamp, transaction.timestamp.ms)
         assertEquals("TESTKUDOS:23", 
transaction.reserveAwaitedAmount.toJSONString())
@@ -137,7 +155,7 @@ class TransactionTest {
                 "reservePub": 
"BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G"
             }
         }""".trimIndent()
-        val event: WithdrawTransaction = mapper.readValue(json)
+        val event: WithdrawHistoryEvent = mapper.readValue(json)
 
         assertEquals(
             "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0",
@@ -186,7 +204,7 @@ class TransactionTest {
                 "t_ms": $timestamp
             }
         }""".trimIndent()
-        val transaction: OrderAcceptedTransaction = mapper.readValue(json)
+        val transaction: OrderAcceptedHistoryEvent = mapper.readValue(json)
 
         assertEquals(orderShortInfo, transaction.orderShortInfo)
         assertEquals(timestamp, transaction.timestamp.ms)
@@ -208,7 +226,7 @@ class TransactionTest {
                 "t_ms": $timestamp
             }
         }""".trimIndent()
-        val transaction: OrderRefusedTransaction = mapper.readValue(json)
+        val transaction: OrderRefusedHistoryEvent = mapper.readValue(json)
 
         assertEquals(orderShortInfo, transaction.orderShortInfo)
         assertEquals(timestamp, transaction.timestamp.ms)
@@ -234,7 +252,7 @@ class TransactionTest {
             "numCoins": 6,
             "amountPaidWithFees": "KUDOS:0.6"
         }""".trimIndent()
-        val event: PaymentTransaction = mapper.readValue(json)
+        val event: PaymentHistoryEvent = mapper.readValue(json)
 
         assertEquals(orderShortInfo, event.orderShortInfo)
         assertEquals(false, event.replay)
@@ -263,7 +281,7 @@ class TransactionTest {
             "numCoins": 6,
             "amountPaidWithFees": "KUDOS:0.6"
         }""".trimIndent()
-        val event: PaymentTransaction = mapper.readValue(json)
+        val event: PaymentHistoryEvent = mapper.readValue(json)
 
         assertEquals(orderShortInfo, event.orderShortInfo)
         assertEquals(true, event.replay)
@@ -290,7 +308,7 @@ class TransactionTest {
             },
             "amountLost": "KUDOS:0.1"
           }""".trimIndent()
-        val transaction: PaymentAbortedTransaction = mapper.readValue(json)
+        val transaction: PaymentAbortedHistoryEvent = mapper.readValue(json)
 
         assertEquals(orderShortInfo, transaction.orderShortInfo)
         assertEquals("KUDOS:0.1", transaction.amountLost.toJSONString())
@@ -308,7 +326,7 @@ class TransactionTest {
             "tipId": 
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
             "tipRaw": "KUDOS:4"
           }""".trimIndent()
-        val transaction: TipAcceptedTransaction = mapper.readValue(json)
+        val transaction: TipAcceptedHistoryEvent = mapper.readValue(json)
 
         assertEquals(
             
"tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
@@ -329,7 +347,7 @@ class TransactionTest {
             "tipId": 
"tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
             "tipAmount": "KUDOS:4"
           }""".trimIndent()
-        val transaction: TipDeclinedTransaction = mapper.readValue(json)
+        val transaction: TipDeclinedHistoryEvent = mapper.readValue(json)
 
         assertEquals(
             
"tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
@@ -359,7 +377,7 @@ class TransactionTest {
             "amountRefundedInvalid": "KUDOS:0.5",
             "amountRefundedEffective": "KUDOS:0.4"
           }""".trimIndent()
-        val event: RefundTransaction = mapper.readValue(json)
+        val event: RefundHistoryEvent = mapper.readValue(json)
 
         assertEquals("refund;998724", event.refundGroupId)
         assertEquals("KUDOS:1", event.amountRefundedRaw.toJSONString())
@@ -385,7 +403,7 @@ class TransactionTest {
             "numOutputCoins": 0,
             "numRefreshedInputCoins": 1
         }""".trimIndent()
-        val event: RefreshTransaction = mapper.readValue(json)
+        val event: RefreshHistoryEvent = mapper.readValue(json)
 
         assertEquals("KUDOS:0", event.amountRefreshedEffective.toJSONString())
         assertEquals("KUDOS:1", event.amountRefreshedRaw.toJSONString())
@@ -420,7 +438,7 @@ class TransactionTest {
               "t_ms": $timestamp
             }
           }""".trimIndent()
-        val transaction: OrderRedirectedTransaction = mapper.readValue(json)
+        val transaction: OrderRedirectedHistoryEvent = mapper.readValue(json)
 
         assertEquals(
             "898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0",
@@ -461,9 +479,9 @@ class TransactionTest {
             },
             "eventId": 
"does-not-exist;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0"
           }""".trimIndent()
-        val event: Transaction = mapper.readValue(json)
+        val event: HistoryEvent = mapper.readValue(json)
 
-        assertEquals(UnknownTransaction::class.java, event.javaClass)
+        assertEquals(UnknownHistoryEvent::class.java, event.javaClass)
         assertEquals(timestamp, event.timestamp.ms)
     }
 
diff --git 
a/wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt 
b/wallet/src/test/java/net/taler/wallet/history/ReserveHistoryEventTest.kt
similarity index 93%
rename from 
wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt
rename to 
wallet/src/test/java/net/taler/wallet/history/ReserveHistoryEventTest.kt
index 4a3c75b..f09d7b6 100644
--- 
a/wallet/src/test/java/net/taler/wallet/transactions/ReserveTransactionTest.kt
+++ b/wallet/src/test/java/net/taler/wallet/history/ReserveHistoryEventTest.kt
@@ -14,16 +14,17 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.transactions
+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.ReserveDepositTransaction
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import kotlin.random.Random
 
-class ReserveTransactionTest {
+class ReserveHistoryEventTest {
 
     private val mapper = ObjectMapper().registerModule(KotlinModule())
 

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



reply via email to

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