gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 01/02: [wallet] add option to see exchange's fee s


From: gnunet
Subject: [taler-taler-android] 01/02: [wallet] add option to see exchange's fee structure
Date: Fri, 10 Apr 2020 14:53:38 +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 874b52c6d5c0d8043f3250e2b80f5091c159ded1
Author: Torsten Grote <address@hidden>
AuthorDate: Wed Apr 8 14:21:11 2020 -0300

    [wallet] add option to see exchange's fee structure
---
 .../src/main/java/net/taler/common/AndroidUtils.kt |   6 +
 .../main/java/net/taler/wallet/WalletViewModel.kt  |   7 +-
 .../java/net/taler/wallet/withdraw/ExchangeFees.kt |  99 +++++++++++++++
 .../wallet/withdraw/PromptWithdrawFragment.kt      |  13 +-
 .../wallet/withdraw/SelectExchangeFragment.kt      | 136 +++++++++++++++++++++
 .../net/taler/wallet/withdraw/WithdrawManager.kt   |  42 ++-----
 .../main/res/layout/fragment_prompt_withdraw.xml   |  28 ++++-
 .../main/res/layout/fragment_select_exchange.xml   | 135 ++++++++++++++++++++
 wallet/src/main/res/layout/list_item_coin_fee.xml  |  78 ++++++++++++
 wallet/src/main/res/layout/list_item_wire_fee.xml  |  57 +++++++++
 wallet/src/main/res/navigation/nav_graph.xml       |   8 ++
 wallet/src/main/res/values/strings.xml             |  18 +++
 12 files changed, 585 insertions(+), 42 deletions(-)

diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt 
b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
index ad9dab9..64d5656 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
@@ -22,6 +22,7 @@ import android.net.ConnectivityManager
 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
 import android.os.Build.VERSION.SDK_INT
 import android.text.format.DateUtils.DAY_IN_MILLIS
+import android.text.format.DateUtils.FORMAT_ABBREV_ALL
 import android.text.format.DateUtils.FORMAT_ABBREV_MONTH
 import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE
 import android.text.format.DateUtils.FORMAT_NO_YEAR
@@ -88,3 +89,8 @@ fun Long.toAbsoluteTime(context: Context): CharSequence {
     val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or FORMAT_SHOW_YEAR
     return formatDateTime(context, this, flags)
 }
+
+fun Long.toShortDate(context: Context): CharSequence {
+    val flags = FORMAT_SHOW_DATE or FORMAT_SHOW_YEAR or FORMAT_ABBREV_ALL
+    return formatDateTime(context, this, flags)
+}
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>

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



reply via email to

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