diff options
Diffstat (limited to 'wallet')
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> |