summaryrefslogtreecommitdiff
path: root/app/src/main/java/net/taler/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/net/taler/wallet')
-rw-r--r--app/src/main/java/net/taler/wallet/Amount.kt120
-rw-r--r--app/src/main/java/net/taler/wallet/WalletViewModel.kt26
-rw-r--r--app/src/main/java/net/taler/wallet/history/HistoryEvent.kt176
-rw-r--r--app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt16
-rw-r--r--app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt89
5 files changed, 377 insertions, 50 deletions
diff --git a/app/src/main/java/net/taler/wallet/Amount.kt b/app/src/main/java/net/taler/wallet/Amount.kt
new file mode 100644
index 0000000..656228f
--- /dev/null
+++ b/app/src/main/java/net/taler/wallet/Amount.kt
@@ -0,0 +1,120 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.taler.wallet
+
+import org.json.JSONObject
+import kotlin.math.round
+
+private const val FRACTIONAL_BASE = 1e8
+
+data class Amount(val currency: String, val amount: String) {
+ fun isZero(): Boolean {
+ return amount.toDouble() == 0.0
+ }
+
+ companion object {
+ fun fromJson(jsonAmount: JSONObject): Amount {
+ val amountCurrency = jsonAmount.getString("currency")
+ val amountValue = jsonAmount.getString("value")
+ val amountFraction = jsonAmount.getString("fraction")
+ val amountIntValue = Integer.parseInt(amountValue)
+ val amountIntFraction = Integer.parseInt(amountFraction)
+ return Amount(
+ amountCurrency,
+ (amountIntValue + amountIntFraction / FRACTIONAL_BASE).toString()
+ )
+ }
+
+ fun fromString(strAmount: String): Amount {
+ val components = strAmount.split(":")
+ return Amount(components[0], components[1])
+ }
+ }
+}
+
+class ParsedAmount(
+ /**
+ * name of the currency using either a three-character ISO 4217 currency code,
+ * or a regional currency identifier starting with a "*" followed by at most 10 characters.
+ * ISO 4217 exponents in the name are not supported,
+ * although the "fraction" is corresponds to an ISO 4217 exponent of 6.
+ */
+ val currency: String,
+
+ /**
+ * unsigned 32 bit value in the currency,
+ * note that "1" here would correspond to 1 EUR or 1 USD, depending on currency, not 1 cent.
+ */
+ val value: UInt,
+
+ /**
+ * unsigned 32 bit fractional value to be added to value
+ * representing an additional currency fraction,
+ * in units of one millionth (1e-6) of the base currency value.
+ * For example, a fraction of 500,000 would correspond to 50 cents.
+ */
+ val fraction: Double
+) {
+ companion object {
+ fun parseAmount(str: String): ParsedAmount {
+ val split = str.split(":")
+ check(split.size == 2)
+ val currency = split[0]
+ val valueSplit = split[1].split(".")
+ val value = valueSplit[0].toUInt()
+ val fraction: Double = if (valueSplit.size > 1) {
+ round("0.${valueSplit[1]}".toDouble() * FRACTIONAL_BASE)
+ } else 0.0
+ return ParsedAmount(currency, value, fraction)
+ }
+ }
+
+ operator fun minus(other: ParsedAmount): ParsedAmount {
+ check(currency == other.currency) { "Can only subtract from same currency" }
+ var resultValue = value
+ var resultFraction = fraction
+ if (resultFraction < other.fraction) {
+ if (resultValue < 1u) {
+ return ParsedAmount(currency, 0u, 0.0)
+ }
+ resultValue--
+ resultFraction += FRACTIONAL_BASE
+ }
+ check(resultFraction >= other.fraction)
+ resultFraction -= other.fraction
+ if (resultValue < other.value) {
+ return ParsedAmount(currency, 0u, 0.0)
+ }
+ resultValue -= other.value
+ return ParsedAmount(currency, resultValue, resultFraction)
+ }
+
+ fun toJSONString(): String {
+ return "$currency:${getValueString()}"
+ }
+
+ override fun toString(): String {
+ return "${getValueString()} $currency"
+ }
+
+ private fun getValueString(): String {
+ return "$value${(fraction / FRACTIONAL_BASE).toString().substring(1)}"
+ }
+
+}
diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
index 94d2d8a..f556ff3 100644
--- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt
+++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
@@ -30,32 +30,6 @@ import org.json.JSONObject
const val TAG = "taler-wallet"
-data class Amount(val currency: String, val amount: String) {
- fun isZero(): Boolean {
- return amount.toDouble() == 0.0
- }
-
- companion object {
- const val FRACTIONAL_BASE = 1e8
- fun fromJson(jsonAmount: JSONObject): Amount {
- val amountCurrency = jsonAmount.getString("currency")
- val amountValue = jsonAmount.getString("value")
- val amountFraction = jsonAmount.getString("fraction")
- val amountIntValue = Integer.parseInt(amountValue)
- val amountIntFraction = Integer.parseInt(amountFraction)
- return Amount(
- amountCurrency,
- (amountIntValue + amountIntFraction / FRACTIONAL_BASE).toString()
- )
- }
-
- fun fromString(strAmount: String): Amount {
- val components = strAmount.split(":")
- return Amount(components[0], components[1])
- }
- }
-}
-
data class BalanceEntry(val available: Amount, val pendingIncoming: Amount)
diff --git a/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt b/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt
index 34f3164..31473f6 100644
--- a/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt
+++ b/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt
@@ -1,10 +1,30 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
package net.taler.wallet.history
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
+import net.taler.wallet.R
enum class ReserveType {
/**
@@ -20,7 +40,22 @@ enum class ReserveType {
}
@JsonInclude(NON_EMPTY)
-class ReserveCreationDetail(val type: ReserveType)
+class ReserveCreationDetail(val type: ReserveType, val bankUrl: String?)
+
+enum class RefreshReason {
+ @JsonProperty("manual")
+ MANUAL,
+ @JsonProperty("pay")
+ PAY,
+ @JsonProperty("refund")
+ REFUND,
+ @JsonProperty("abort-pay")
+ ABORT_PAY,
+ @JsonProperty("recoup")
+ RECOUP,
+ @JsonProperty("backup-restored")
+ BACKUP_RESTORED
+}
@JsonInclude(NON_EMPTY)
@@ -45,7 +80,7 @@ class ReserveShortInfo(
val reserveCreationDetail: ReserveCreationDetail
)
-class History: ArrayList<HistoryEvent>()
+class History : ArrayList<HistoryEvent>()
@JsonTypeInfo(
use = NAME,
@@ -56,7 +91,11 @@ class History: ArrayList<HistoryEvent>()
Type(value = ExchangeAddedEvent::class, name = "exchange-added"),
Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"),
Type(value = ReserveBalanceUpdatedEvent::class, name = "reserve-balance-updated"),
- Type(value = HistoryWithdrawnEvent::class, name = "withdrawn")
+ Type(value = HistoryWithdrawnEvent::class, name = "withdrawn"),
+ Type(value = HistoryOrderAcceptedEvent::class, name = "order-accepted"),
+ Type(value = HistoryOrderRefusedEvent::class, name = "order-refused"),
+ Type(value = HistoryPaymentSentEvent::class, name = "payment-sent"),
+ Type(value = HistoryRefreshedEvent::class, name = "refreshed")
)
@JsonIgnoreProperties(
value = [
@@ -64,7 +103,13 @@ class History: ArrayList<HistoryEvent>()
]
)
abstract class HistoryEvent(
- val timestamp: Timestamp
+ val timestamp: Timestamp,
+ @get:LayoutRes
+ open val layout: Int = R.layout.history_row,
+ @get:StringRes
+ open val title: Int = 0,
+ @get:DrawableRes
+ open val icon: Int = R.drawable.ic_account_balance
)
@@ -73,13 +118,17 @@ class ExchangeAddedEvent(
timestamp: Timestamp,
val exchangeBaseUrl: String,
val builtIn: Boolean
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+ override val title = R.string.history_event_exchange_added
+}
@JsonTypeName("exchange-updated")
class ExchangeUpdatedEvent(
timestamp: Timestamp,
val exchangeBaseUrl: String
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+ override val title = R.string.history_event_exchange_updated
+}
@JsonTypeName("reserve-balance-updated")
@@ -99,7 +148,9 @@ class ReserveBalanceUpdatedEvent(
* considering ongoing withdrawals from that reserve.
*/
val amountExpected: String
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+ override val title = R.string.history_event_reserve_balance_updated
+}
@JsonTypeName("withdrawn")
class HistoryWithdrawnEvent(
@@ -123,8 +174,94 @@ class HistoryWithdrawnEvent(
* Amount that actually was added to the wallet's balance.
*/
val amountWithdrawnEffective: String
-) : HistoryEvent(timestamp)
+) : HistoryEvent(timestamp) {
+ override val layout = R.layout.history_withdrawn
+ override val title = R.string.history_event_withdrawn
+ override val icon = R.drawable.history_withdrawn
+}
+@JsonTypeName("order-accepted")
+class HistoryOrderAcceptedEvent(
+ timestamp: Timestamp,
+ /**
+ * Condensed info about the order.
+ */
+ val orderShortInfo: OrderShortInfo
+) : HistoryEvent(timestamp) {
+ override val icon = R.drawable.ic_add_circle
+ override val title = R.string.history_event_order_accepted
+}
+
+@JsonTypeName("order-refused")
+class HistoryOrderRefusedEvent(
+ timestamp: Timestamp,
+ /**
+ * Condensed info about the order.
+ */
+ val orderShortInfo: OrderShortInfo
+) : HistoryEvent(timestamp) {
+ override val icon = R.drawable.ic_cancel
+ override val title = R.string.history_event_order_refused
+}
+
+@JsonTypeName("payment-sent")
+class HistoryPaymentSentEvent(
+ timestamp: Timestamp,
+ /**
+ * Condensed info about the order that we already paid for.
+ */
+ val orderShortInfo: OrderShortInfo,
+ /**
+ * Set to true if the payment has been previously sent
+ * to the merchant successfully, possibly with a different session ID.
+ */
+ val replay: Boolean,
+ /**
+ * Number of coins that were involved in the payment.
+ */
+ val numCoins: Int,
+ /**
+ * Amount that was paid, including deposit and wire fees.
+ */
+ val amountPaidWithFees: String,
+ /**
+ * Session ID that the payment was (re-)submitted under.
+ */
+ val sessionId: String?
+) : HistoryEvent(timestamp) {
+ override val layout = R.layout.history_payment_sent
+ override val title = R.string.history_event_payment_sent
+ override val icon = R.drawable.ic_cash_usd_outline
+}
+
+@JsonTypeName("refreshed")
+class HistoryRefreshedEvent(
+ timestamp: Timestamp,
+ /**
+ * Amount that is now available again because it has
+ * been refreshed.
+ */
+ val amountRefreshedEffective: String,
+ /**
+ * Amount that we spent for refreshing.
+ */
+ val amountRefreshedRaw: String,
+ /**
+ * Why was the refreshing done?
+ */
+ val refreshReason: RefreshReason,
+ val numInputCoins: Int,
+ val numRefreshedInputCoins: Int,
+ val numOutputCoins: Int,
+ /**
+ * Identifier for a refresh group, contains one or
+ * more refresh session IDs.
+ */
+ val refreshGroupId: String
+) : HistoryEvent(timestamp) {
+ override val icon = R.drawable.ic_history_black_24dp
+ override val title = R.string.history_event_refreshed
+}
@JsonTypeInfo(
use = NAME,
@@ -145,3 +282,26 @@ class WithdrawalSourceTip(
class WithdrawalSourceReserve(
val reservePub: String
) : WithdrawalSource()
+
+data class OrderShortInfo(
+ /**
+ * Wallet-internal identifier of the proposal.
+ */
+ val proposalId: String,
+ /**
+ * Order ID, uniquely identifies the order within a merchant instance.
+ */
+ val orderId: String,
+ /**
+ * Base URL of the merchant.
+ */
+ val merchantBaseUrl: String,
+ /**
+ * Amount that must be paid for the contract.
+ */
+ val amount: String,
+ /**
+ * Summary of the proposal, given by the merchant.
+ */
+ val summary: String
+)
diff --git a/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt b/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
index 880639f..f4cfcb8 100644
--- a/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
+++ b/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt
@@ -1,3 +1,19 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
package net.taler.wallet.history
import com.fasterxml.jackson.annotation.JsonProperty
diff --git a/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt b/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
index 37dc742..14fc1e9 100644
--- a/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
+++ b/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt
@@ -1,13 +1,31 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
package net.taler.wallet.history
import android.text.format.DateUtils.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.wallet.ParsedAmount.Companion.parseAmount
import net.taler.wallet.R
@@ -18,16 +36,14 @@ internal class WalletHistoryAdapter(private var history: History = History()) :
setHasStableIds(false)
}
- override fun getItemViewType(position: Int): Int = when (history[position]) {
- is HistoryWithdrawnEvent -> R.layout.history_withdrawn
- else -> R.layout.history_row
- }
+ override fun getItemViewType(position: Int): Int = history[position].layout
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryEventViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
- R.layout.history_withdrawn -> HistoryWithdrawnEventViewHolder(view)
- else -> HistoryEventViewHolder(view)
+ R.layout.history_withdrawn -> HistoryWithdrawnViewHolder(view)
+ R.layout.history_payment_sent -> HistoryPaymentSentViewHolder(view)
+ else -> GenericHistoryEventViewHolder(view)
}
}
@@ -45,14 +61,17 @@ internal class WalletHistoryAdapter(private var history: History = History()) :
}
-internal open class HistoryEventViewHolder(protected val v: View) : ViewHolder(v) {
+internal abstract class HistoryEventViewHolder(protected val v: View) : ViewHolder(v) {
- protected val title: TextView = v.findViewById(R.id.title)
+ private val icon: ImageView = v.findViewById(R.id.icon)
+ private val title: TextView = v.findViewById(R.id.title)
private val time: TextView = v.findViewById(R.id.time)
@CallSuper
open fun bind(event: HistoryEvent) {
- title.text = event::class.java.simpleName
+ icon.setImageResource(event.icon)
+ if (event.title == 0) title.text = event::class.java.simpleName
+ else title.setText(event.title)
time.text = getRelativeTime(event.timestamp.ms)
}
@@ -61,22 +80,60 @@ internal open class HistoryEventViewHolder(protected val v: View) : ViewHolder(v
return getRelativeTimeSpanString(timestamp, now, MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE)
}
- protected fun getString(resId: Int) = v.context.getString(resId)
+}
+
+internal class GenericHistoryEventViewHolder(v: View) : HistoryEventViewHolder(v) {
+
+ private val info: TextView = v.findViewById(R.id.info)
+
+ override fun bind(event: HistoryEvent) {
+ super.bind(event)
+ info.text = when (event) {
+ is ExchangeAddedEvent -> event.exchangeBaseUrl
+ is ExchangeUpdatedEvent -> event.exchangeBaseUrl
+ is ReserveBalanceUpdatedEvent -> parseAmount(event.amountReserveBalance).toString()
+ is HistoryPaymentSentEvent -> event.orderShortInfo.summary
+ is HistoryOrderAcceptedEvent -> event.orderShortInfo.summary
+ is HistoryOrderRefusedEvent -> event.orderShortInfo.summary
+ is HistoryRefreshedEvent -> {
+ "${parseAmount(event.amountRefreshedRaw)} - ${parseAmount(event.amountRefreshedEffective)}"
+ }
+ else -> ""
+ }
+ }
}
-internal class HistoryWithdrawnEventViewHolder(v: View) : HistoryEventViewHolder(v) {
+internal class HistoryWithdrawnViewHolder(v: View) : HistoryEventViewHolder(v) {
- private val amountWithdrawnRaw: TextView = v.findViewById(R.id.amountWithdrawnRaw)
- private val amountWithdrawnEffective: TextView = v.findViewById(R.id.amountWithdrawnEffective)
+ private val exchangeUrl: TextView = v.findViewById(R.id.exchangeUrl)
+ private val amountWithdrawn: TextView = v.findViewById(R.id.amountWithdrawn)
+ private val fee: TextView = v.findViewById(R.id.fee)
override fun bind(event: HistoryEvent) {
super.bind(event)
event as HistoryWithdrawnEvent
- title.text = getString(R.string.history_event_withdrawn)
- amountWithdrawnRaw.text = event.amountWithdrawnRaw
- amountWithdrawnEffective.text = event.amountWithdrawnEffective
+ exchangeUrl.text = event.exchangeBaseUrl
+ val parsedEffective = parseAmount(event.amountWithdrawnEffective)
+ val parsedRaw = parseAmount(event.amountWithdrawnRaw)
+ amountWithdrawn.text = parsedRaw.toString()
+ fee.text = (parsedRaw - parsedEffective).toString()
+ }
+
+}
+
+internal class HistoryPaymentSentViewHolder(v: View) : HistoryEventViewHolder(v) {
+
+ private val summary: TextView = v.findViewById(R.id.summary)
+ private val amountPaidWithFees: TextView = v.findViewById(R.id.amountPaidWithFees)
+
+ override fun bind(event: HistoryEvent) {
+ super.bind(event)
+ event as HistoryPaymentSentEvent
+
+ summary.text = event.orderShortInfo.summary
+ amountPaidWithFees.text = parseAmount(event.amountPaidWithFees).toString()
}
}