aboutsummaryrefslogtreecommitdiff
path: root/wallet/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main')
-rw-r--r--wallet/src/main/java/net/taler/wallet/WalletViewModel.kt7
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt99
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt13
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt136
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt42
-rw-r--r--wallet/src/main/res/layout/fragment_prompt_withdraw.xml28
-rw-r--r--wallet/src/main/res/layout/fragment_select_exchange.xml135
-rw-r--r--wallet/src/main/res/layout/list_item_coin_fee.xml78
-rw-r--r--wallet/src/main/res/layout/list_item_wire_fee.xml57
-rw-r--r--wallet/src/main/res/navigation/nav_graph.xml8
-rw-r--r--wallet/src/main/res/values/strings.xml18
11 files changed, 579 insertions, 42 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt
index c16b6fc..607ce15 100644
--- a/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt
@@ -48,9 +48,12 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) {
val showProgressBar = MutableLiveData<Boolean>()
private val walletBackendApi = WalletBackendApi(app, {
- loadBalances()
+ // nothing to do when we connect, balance will be requested by BalanceFragment in onStart()
}) { payload ->
- if (payload.getString("type") != "waiting-for-retry") {
+ if (
+ payload.getString("type") != "waiting-for-retry" && // ignore ping
+ payload.optString("operation") != "init" // ignore init notification
+ ) {
Log.i(TAG, "Received notification from wallet-core: ${payload.toString(2)}")
loadBalances()
}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
new file mode 100644
index 0000000..4494e38
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.withdraw
+
+import net.taler.common.Amount
+import net.taler.common.Timestamp
+import org.json.JSONObject
+
+data class CoinFee(
+ val coin: Amount,
+ val feeDeposit: Amount,
+ val feeRefresh: Amount,
+ val feeRefund: Amount,
+ val feeWithdraw: Amount
+)
+
+data class CoinFees(
+ val quantity: Int,
+ val coinFee: CoinFee
+)
+
+data class WireFee(
+ val start: Timestamp,
+ val end: Timestamp,
+ val wireFee: Amount,
+ val closingFee: Amount
+)
+
+data class ExchangeFees(
+ val withdrawFee: Amount,
+ val overhead: Amount,
+ val earliestDepositExpiration: Timestamp,
+ val coinFees: List<CoinFees>,
+ val wireFees: List<WireFee>
+) {
+ companion object {
+ fun fromExchangeWithdrawDetailsJson(json: JSONObject): ExchangeFees {
+ val earliestDepositExpiration =
+ json.getJSONObject("earliestDepositExpiration").getLong("t_ms")
+
+ val selectedDenoms = json.getJSONArray("selectedDenoms")
+ val coinFees = HashMap<CoinFee, Int>(selectedDenoms.length())
+ for (i in 0 until selectedDenoms.length()) {
+ val denom = selectedDenoms.getJSONObject(i)
+ val coinFee = CoinFee(
+ coin = Amount.fromJsonObject(denom.getJSONObject("value")),
+ feeDeposit = Amount.fromJsonObject(denom.getJSONObject("feeDeposit")),
+ feeRefresh = Amount.fromJsonObject(denom.getJSONObject("feeRefresh")),
+ feeRefund = Amount.fromJsonObject(denom.getJSONObject("feeRefund")),
+ feeWithdraw = Amount.fromJsonObject(denom.getJSONObject("feeWithdraw"))
+ )
+ coinFees[coinFee] = (coinFees[coinFee] ?: 0) + 1
+ }
+
+ val wireFeesJson = json.getJSONObject("wireFees")
+ val feesForType = wireFeesJson.getJSONObject("feesForType")
+ val bankFees = feesForType.getJSONArray("x-taler-bank")
+ val wireFees = ArrayList<WireFee>(bankFees.length())
+ for (i in 0 until bankFees.length()) {
+ val fee = bankFees.getJSONObject(i)
+ val startStamp =
+ fee.getJSONObject("startStamp").getLong("t_ms")
+ val endStamp =
+ fee.getJSONObject("endStamp").getLong("t_ms")
+ val wireFee = WireFee(
+ start = Timestamp(startStamp),
+ end = Timestamp(endStamp),
+ wireFee = Amount.fromJsonObject(fee.getJSONObject("wireFee")),
+ closingFee = Amount.fromJsonObject(fee.getJSONObject("closingFee"))
+ )
+ wireFees.add(wireFee)
+ }
+
+ return ExchangeFees(
+ withdrawFee = Amount.fromJsonObject(json.getJSONObject("withdrawFee")),
+ overhead = Amount.fromJsonObject(json.getJSONObject("overhead")),
+ earliestDepositExpiration = Timestamp(earliestDepositExpiration),
+ coinFees = coinFees.map { (coinFee, quantity) ->
+ CoinFees(quantity, coinFee)
+ },
+ wireFees = wireFees
+ )
+ }
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 5d0fe63..56a2a8c 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -57,16 +57,13 @@ class PromptWithdrawFragment : Fragment() {
private fun showWithdrawStatus(status: WithdrawStatus?): Any = when (status) {
is WithdrawStatus.ReceivedDetails -> {
- showContent(status.amount, status.fee, status.suggestedExchange)
+ showContent(status.amount, status.fee, status.exchange)
confirmWithdrawButton.apply {
text = getString(R.string.withdraw_button_confirm)
setOnClickListener {
it.fadeOut()
confirmProgressBar.fadeIn()
- withdrawManager.acceptWithdrawal(
- status.talerWithdrawUri,
- status.suggestedExchange
- )
+ withdrawManager.acceptWithdrawal(status.talerWithdrawUri, status.exchange)
}
isEnabled = true
}
@@ -83,7 +80,7 @@ class PromptWithdrawFragment : Fragment() {
model.showProgressBar.value = true
}
is TermsOfServiceReviewRequired -> {
- showContent(status.amount, status.fee, status.suggestedExchange)
+ showContent(status.amount, status.fee, status.exchange)
confirmWithdrawButton.apply {
text = getString(R.string.withdraw_button_tos)
setOnClickListener {
@@ -118,6 +115,10 @@ class PromptWithdrawFragment : Fragment() {
exchangeIntroView.fadeIn()
withdrawExchangeUrl.text = cleanExchange(exchange)
withdrawExchangeUrl.fadeIn()
+ selectExchangeButton.fadeIn()
+ selectExchangeButton.setOnClickListener {
+ findNavController().navigate(R.id.action_promptWithdraw_to_selectExchangeFragment)
+ }
withdrawCard.fadeIn()
}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
new file mode 100644
index 0000000..78eba53
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.withdraw
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.core.content.ContextCompat.getColor
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import kotlinx.android.synthetic.main.fragment_select_exchange.*
+import net.taler.common.Amount
+import net.taler.common.toRelativeTime
+import net.taler.common.toShortDate
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+import net.taler.wallet.withdraw.CoinFeeAdapter.CoinFeeViewHolder
+import net.taler.wallet.withdraw.WireFeeAdapter.WireFeeViewHolder
+
+class SelectExchangeFragment : Fragment() {
+
+ private val model: WalletViewModel by activityViewModels()
+ private val withdrawManager by lazy { model.withdrawManager }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_select_exchange, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ val fees = withdrawManager.exchangeFees ?: throw IllegalStateException()
+ withdrawFeeView.setAmount(fees.withdrawFee)
+ overheadView.setAmount(fees.overhead)
+ expirationView.text = fees.earliestDepositExpiration.ms.toRelativeTime(requireContext())
+ coinFeesList.adapter = CoinFeeAdapter(fees.coinFees)
+ wireFeesList.adapter = WireFeeAdapter(fees.wireFees)
+ }
+
+ private fun TextView.setAmount(amount: Amount) {
+ if (amount.isZero()) text = amount.toString()
+ else {
+ text = getString(R.string.amount_negative, amount)
+ setTextColor(getColor(context, R.color.red))
+ }
+ }
+
+}
+
+private class CoinFeeAdapter(private val items: List<CoinFees>) : Adapter<CoinFeeViewHolder>() {
+ override fun getItemCount() = items.size
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoinFeeViewHolder {
+ val v =
+ LayoutInflater.from(parent.context).inflate(R.layout.list_item_coin_fee, parent, false)
+ return CoinFeeViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: CoinFeeViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+
+ private class CoinFeeViewHolder(private val v: View) : ViewHolder(v) {
+ private val res = v.context.resources
+ private val coinView: TextView = v.findViewById(R.id.coinView)
+ private val withdrawFeeView: TextView = v.findViewById(R.id.withdrawFeeView)
+ private val depositFeeView: TextView = v.findViewById(R.id.depositFeeView)
+ private val refreshFeeView: TextView = v.findViewById(R.id.refreshFeeView)
+ private val refundFeeView: TextView = v.findViewById(R.id.refundFeeView)
+ fun bind(item: CoinFees) {
+ val fee = item.coinFee
+ coinView.text = res.getQuantityString(
+ R.plurals.exchange_fee_coin,
+ item.quantity,
+ fee.coin,
+ item.quantity
+ )
+ withdrawFeeView.text =
+ v.context.getString(R.string.exchange_fee_withdraw_fee, fee.feeWithdraw)
+ depositFeeView.text =
+ v.context.getString(R.string.exchange_fee_deposit_fee, fee.feeDeposit)
+ refreshFeeView.text =
+ v.context.getString(R.string.exchange_fee_refresh_fee, fee.feeRefresh)
+ refundFeeView.text =
+ v.context.getString(R.string.exchange_fee_refund_fee, fee.feeRefresh)
+ }
+ }
+}
+
+private class WireFeeAdapter(private val items: List<WireFee>) : Adapter<WireFeeViewHolder>() {
+ override fun getItemCount() = items.size
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WireFeeViewHolder {
+ val v =
+ LayoutInflater.from(parent.context).inflate(R.layout.list_item_wire_fee, parent, false)
+ return WireFeeViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: WireFeeViewHolder, position: Int) {
+ holder.bind(items[position])
+ }
+
+ private class WireFeeViewHolder(private val v: View) : ViewHolder(v) {
+ private val validityView: TextView = v.findViewById(R.id.validityView)
+ private val wireFeeView: TextView = v.findViewById(R.id.wireFeeView)
+ private val closingFeeView: TextView = v.findViewById(R.id.closingFeeView)
+ fun bind(item: WireFee) {
+ validityView.text = v.context.getString(
+ R.string.exchange_fee_wire_fee_timespan,
+ item.start.ms.toShortDate(v.context),
+ item.end.ms.toShortDate(v.context)
+ )
+ wireFeeView.text =
+ v.context.getString(R.string.exchange_fee_wire_fee_wire_fee, item.wireFee)
+ closingFeeView.text =
+ v.context.getString(R.string.exchange_fee_wire_fee_closing_fee, item.closingFee)
+ }
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index 26515a5..6bcd013 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -28,19 +28,18 @@ sealed class WithdrawStatus {
data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
data class TermsOfServiceReviewRequired(
val talerWithdrawUri: String,
- val exchangeBaseUrl: String,
+ val exchange: String,
val tosText: String,
val tosEtag: String,
val amount: Amount,
- val fee: Amount,
- val suggestedExchange: String
+ val fee: Amount
) : WithdrawStatus()
data class ReceivedDetails(
val talerWithdrawUri: String,
+ val exchange: String,
val amount: Amount,
- val fee: Amount,
- val suggestedExchange: String
+ val fee: Amount
) : WithdrawStatus()
data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
@@ -54,7 +53,8 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
val withdrawStatus = MutableLiveData<WithdrawStatus>()
val testWithdrawalInProgress = MutableLiveData(false)
- private var currentWithdrawRequestId = 0
+ var exchangeFees: ExchangeFees? = null
+ private set
fun withdrawTestkudos() {
testWithdrawalInProgress.value = true
@@ -70,9 +70,6 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
}
withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri)
- this.currentWithdrawRequestId++
- val myWithdrawRequestId = this.currentWithdrawRequestId
-
walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result ->
if (isError) {
Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}")
@@ -80,11 +77,6 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
withdrawStatus.postValue(WithdrawStatus.Error(message))
return@sendRequest
}
- if (myWithdrawRequestId != this.currentWithdrawRequestId) {
- val mismatch = "$myWithdrawRequestId != ${this.currentWithdrawRequestId}"
- Log.w(TAG, "Got withdraw result for different request id $mismatch")
- return@sendRequest
- }
Log.v(TAG, "got getWithdrawDetailsForUri result")
val status = withdrawStatus.value
if (status !is WithdrawStatus.Loading) {
@@ -105,9 +97,6 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
put("selectedExchange", selectedExchange)
}
- currentWithdrawRequestId++
- val myWithdrawRequestId = currentWithdrawRequestId
-
walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result ->
if (isError) {
Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}")
@@ -115,11 +104,6 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
withdrawStatus.postValue(WithdrawStatus.Error(message))
return@sendRequest
}
- if (myWithdrawRequestId != currentWithdrawRequestId) {
- val mismatch = "$myWithdrawRequestId != $currentWithdrawRequestId"
- Log.w(TAG, "Got withdraw result for different request id $mismatch")
- return@sendRequest
- }
Log.v(TAG, "got getWithdrawDetailsForUri result (with exchange details)")
val status = withdrawStatus.value
if (status !is WithdrawStatus.Loading) {
@@ -127,12 +111,13 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
return@sendRequest
}
val wi = result.getJSONObject("bankWithdrawDetails")
- val suggestedExchange = wi.getString("suggestedExchange")
val amount = Amount.fromJsonObject(wi.getJSONObject("amount"))
val ei = result.getJSONObject("exchangeWithdrawDetails")
val termsOfServiceAccepted = ei.getBoolean("termsOfServiceAccepted")
+ exchangeFees = ExchangeFees.fromExchangeWithdrawDetailsJson(ei)
+
val withdrawFee = Amount.fromJsonObject(ei.getJSONObject("withdrawFee"))
val overhead = Amount.fromJsonObject(ei.getJSONObject("overhead"))
val fee = withdrawFee + overhead
@@ -145,16 +130,15 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
WithdrawStatus.TermsOfServiceReviewRequired(
status.talerWithdrawUri,
selectedExchange, tosText, tosEtag,
- amount, fee,
- suggestedExchange
+ amount, fee
)
)
} else {
withdrawStatus.postValue(
ReceivedDetails(
status.talerWithdrawUri,
- amount, fee,
- suggestedExchange
+ selectedExchange, amount,
+ fee
)
)
}
@@ -191,7 +175,7 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
check(s is WithdrawStatus.TermsOfServiceReviewRequired)
val args = JSONObject().apply {
- put("exchangeBaseUrl", s.exchangeBaseUrl)
+ put("exchangeBaseUrl", s.exchange)
put("etag", s.tosEtag)
}
walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { isError, result ->
@@ -199,7 +183,7 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
Log.e(TAG, "Error acceptExchangeTermsOfService ${result.toString(4)}")
return@sendRequest
}
- val status = ReceivedDetails(s.talerWithdrawUri, s.amount, s.fee, s.suggestedExchange)
+ val status = ReceivedDetails(s.talerWithdrawUri, s.exchange, s.amount, s.fee)
withdrawStatus.postValue(status)
}
}
diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
index 4372cba..c9c9402 100644
--- a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -64,7 +64,7 @@
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:gravity="center"
- android:text="Chosen Amount"
+ android:text="@string/amount_chosen"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/chosenAmountView"
app:layout_constraintEnd_toEndOf="parent"
@@ -144,18 +144,36 @@
<TextView
android:id="@+id/withdrawExchangeUrl"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginEnd="8dp"
android:gravity="center"
android:textSize="24sp"
android:visibility="invisible"
+ app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/withdrawCard"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/selectExchangeButton"
+ app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/exchangeIntroView"
- tools:text="long.exchange.demo.taler.net"
+ tools:text="demo.taler.net"
+ tools:visibility="visible" />
+
+ <ImageButton
+ android:id="@+id/selectExchangeButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
+ android:backgroundTint="@color/colorPrimary"
+ android:contentDescription="@string/nav_exchange_fees"
+ android:src="@drawable/ic_cash_usd_outline"
+ android:tint="?attr/colorOnPrimary"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/withdrawExchangeUrl"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/withdrawExchangeUrl"
+ app:layout_constraintTop_toTopOf="@+id/withdrawExchangeUrl"
tools:visibility="visible" />
<ProgressBar
diff --git a/wallet/src/main/res/layout/fragment_select_exchange.xml b/wallet/src/main/res/layout/fragment_select_exchange.xml
new file mode 100644
index 0000000..cb8d35a
--- /dev/null
+++ b/wallet/src/main/res/layout/fragment_select_exchange.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>
+ -->
+
+<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/withdrawFeeLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:text="@string/exchange_fee_withdrawal_fee_label"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/withdrawFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toTopOf="@+id/withdrawFeeLabel"
+ tools:text="-0.23 TESTKUDOS"
+ tools:textColor="@color/red" />
+
+ <TextView
+ android:id="@+id/overheadLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/exchange_fee_overhead_label"
+ app:layout_constraintStart_toStartOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toBottomOf="@+id/withdrawFeeLabel" />
+
+ <TextView
+ android:id="@+id/overheadView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ app:layout_constraintEnd_toEndOf="@+id/withdrawFeeView"
+ app:layout_constraintStart_toEndOf="@+id/overheadLabel"
+ app:layout_constraintTop_toTopOf="@+id/overheadLabel"
+ tools:text="-0.42 TESTKUDOS"
+ tools:textColor="@color/red" />
+
+ <TextView
+ android:id="@+id/expirationLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/exchange_fee_coin_expiration_label"
+ app:layout_constraintStart_toStartOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toBottomOf="@+id/overheadLabel" />
+
+ <TextView
+ android:id="@+id/expirationView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ app:layout_constraintEnd_toEndOf="@+id/withdrawFeeView"
+ app:layout_constraintStart_toEndOf="@+id/expirationLabel"
+ app:layout_constraintTop_toTopOf="@+id/expirationLabel"
+ tools:text="in 5 years" />
+
+ <TextView
+ android:id="@+id/coinFeesLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/exchange_fee_coin_fees_label"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ app:layout_constraintStart_toStartOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toBottomOf="@+id/expirationLabel" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/coinFeesList"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:overScrollMode="never"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ app:layout_constraintEnd_toEndOf="@+id/withdrawFeeView"
+ app:layout_constraintStart_toStartOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toBottomOf="@+id/coinFeesLabel"
+ tools:listitem="@layout/list_item_coin_fee" />
+
+ <TextView
+ android:id="@+id/wireFeesLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/exchange_fee_wire_fees_label"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ app:layout_constraintStart_toStartOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toBottomOf="@+id/coinFeesList" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/wireFeesList"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:overScrollMode="never"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ app:layout_constraintEnd_toEndOf="@+id/withdrawFeeView"
+ app:layout_constraintStart_toStartOf="@+id/withdrawFeeLabel"
+ app:layout_constraintTop_toBottomOf="@+id/wireFeesLabel"
+ tools:listitem="@layout/list_item_wire_fee" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.core.widget.NestedScrollView>
diff --git a/wallet/src/main/res/layout/list_item_coin_fee.xml b/wallet/src/main/res/layout/list_item_coin_fee.xml
new file mode 100644
index 0000000..daf2789
--- /dev/null
+++ b/wallet/src/main/res/layout/list_item_coin_fee.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/coinView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="Coin: 2 TESTKUDOS (used 3 times)" />
+
+ <TextView
+ android:id="@+id/withdrawFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/coinView"
+ app:layout_constraintTop_toBottomOf="@+id/coinView"
+ tools:text="Withdraw Fee: 0.01 TESTKUDOS" />
+
+ <TextView
+ android:id="@+id/depositFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/coinView"
+ app:layout_constraintTop_toBottomOf="@+id/withdrawFeeView"
+ tools:text="Deposit Fee: 0.01 TESTKUDOS" />
+
+ <TextView
+ android:id="@+id/refreshFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/coinView"
+ app:layout_constraintTop_toBottomOf="@+id/depositFeeView"
+ tools:text="Change Fee: 0.01 TESTKUDOS" />
+
+ <TextView
+ android:id="@+id/refundFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/coinView"
+ app:layout_constraintTop_toBottomOf="@+id/refreshFeeView"
+ tools:text="Refund Fee: 0.01 TESTKUDOS" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/list_item_wire_fee.xml b/wallet/src/main/res/layout/list_item_wire_fee.xml
new file mode 100644
index 0000000..92ede8b
--- /dev/null
+++ b/wallet/src/main/res/layout/list_item_wire_fee.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/validityView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="Timespan: Jan 1 2020 - Dec 31 2020" />
+
+ <TextView
+ android:id="@+id/wireFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/validityView"
+ tools:text="Wire Fee: 0.01 TESTKUDOS" />
+
+ <TextView
+ android:id="@+id/closingFeeView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/wireFeeView"
+ tools:text="Closing Fee: 0.01 TESTKUDOS" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml
index c39df94..f6d8598 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -96,6 +96,9 @@
android:id="@+id/action_promptWithdraw_to_errorFragment"
app:destination="@id/errorFragment"
app:popUpTo="@id/showBalance" />
+ <action
+ android:id="@+id/action_promptWithdraw_to_selectExchangeFragment"
+ app:destination="@id/selectExchangeFragment" />
</fragment>
<fragment
@@ -113,6 +116,11 @@
app:destination="@id/promptWithdraw"
app:popUpTo="@id/showBalance" />
</fragment>
+ <fragment
+ android:id="@+id/selectExchangeFragment"
+ android:name="net.taler.wallet.withdraw.SelectExchangeFragment"
+ android:label="@string/nav_exchange_fees"
+ tools:layout="@layout/fragment_select_exchange" />
<fragment
android:id="@+id/nav_pending_operations"
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 31aaf14..8cbecb9 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -23,6 +23,7 @@
<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_error">Error</string>
<string name="button_back">Go Back</string>
@@ -103,6 +104,23 @@
<string name="withdraw_error_title">Withdrawal Error</string>
<string name="withdraw_error_message">Withdrawing is currently not possible. Please try again later!</string>
+ <string name="exchange_fee_withdrawal_fee_label">Withdrawal Fee:</string>
+ <string name="exchange_fee_overhead_label">Rounding Loss:</string>
+ <string name="exchange_fee_coin_expiration_label">Earliest Coin Expiry:</string>
+ <string name="exchange_fee_coin_fees_label">Coin Fees</string>
+ <string name="exchange_fee_wire_fees_label">Wire Fees</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="one">Coin: %s (used %d time)</item>
+ <item quantity="other">Coin: %s (used %d times)</item>
+ </plurals>
+ <string name="exchange_fee_withdraw_fee">Withdraw Fee: %s</string>
+ <string name="exchange_fee_deposit_fee">Deposit Fee: %s</string>
+ <string name="exchange_fee_refresh_fee">Change Fee: %s</string>
+ <string name="exchange_fee_refund_fee">Refund Fee: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">Timespan: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">Wire Fee: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">Closing Fee: %s</string>
+
<string name="pending_operations_title">Pending Operations</string>
<string name="pending_operations_refuse">Refuse Proposal</string>
<string name="pending_operations_no_action">(no action)</string>