From 1ec44435ab0ab098a04b3f4ee9f2599d99535c41 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 30 Dec 2019 12:18:38 -0300 Subject: 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 ++ app/src/main/res/layout/history_payment_sent.xml | 70 ++++++++ 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, 747 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/net/taler/wallet/Amount.kt create mode 100644 app/src/main/res/drawable/ic_account_balance.xml create mode 100644 app/src/main/res/drawable/ic_add_circle.xml create mode 100644 app/src/main/res/drawable/ic_cancel.xml create mode 100644 app/src/main/res/drawable/ic_cash_usd_outline.xml create mode 100644 app/src/main/res/layout/history_payment_sent.xml (limited to 'app') 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 + */ + +@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 + */ + 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() +class History : ArrayList() @JsonTypeInfo( use = NAME, @@ -56,7 +91,11 @@ class History: ArrayList() 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() ] ) 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 + */ + 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 + */ + 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 @@ + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + diff --git a/app/src/main/res/layout/history_payment_sent.xml b/app/src/main/res/layout/history_payment_sent.xml new file mode 100644 index 0000000..0c39133 --- /dev/null +++ b/app/src/main/res/layout/history_payment_sent.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + 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 @@ - + + + + + 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" /> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/title" + tools:text="3 days ago" /> - \ No newline at end of file + \ 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" /> + app:layout_constraintTop_toBottomOf="@+id/title" + app:layout_constraintVertical_bias="0.0" + tools:text="http://taler.exchange.url" /> + + + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintStart_toEndOf="@+id/title" + app:layout_constraintTop_toTopOf="parent" + tools:text="10 TESTKUDOS" /> 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 @@ #283593 #1A237E #AE1010 + + #C62828 + #558B2F 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 @@ my service my aid + + Exchange Added + Exchange Updated + Reserve Balance Updated + Payment Made Withdraw + Order Confirmed + Order Cancelled + Refresh Hello blank fragment 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 + */ + 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 + */ + package net.taler.wallet.history import com.fasterxml.jackson.databind.ObjectMapper -- cgit v1.2.3