diff options
author | Torsten Grote <t@grobox.de> | 2020-03-03 14:03:45 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-03-03 14:03:45 -0300 |
commit | 30980bc83be99ea85205f44c815b78164b11f7b9 (patch) | |
tree | a8950120aa57df2839e4d1f40cb84ca841f27ad3 | |
parent | a9fd9aa024d1cafe50be76eb2ca6a818bce38862 (diff) | |
download | wallet-android-30980bc83be99ea85205f44c815b78164b11f7b9.tar.gz wallet-android-30980bc83be99ea85205f44c815b78164b11f7b9.tar.bz2 wallet-android-30980bc83be99ea85205f44c815b78164b11f7b9.zip |
Clean up and improve withdraw UI (first pass)
19 files changed, 745 insertions, 641 deletions
diff --git a/app/src/main/java/net/taler/wallet/MainActivity.kt b/app/src/main/java/net/taler/wallet/MainActivity.kt index 79c3373..ebc7136 100644 --- a/app/src/main/java/net/taler/wallet/MainActivity.kt +++ b/app/src/main/java/net/taler/wallet/MainActivity.kt @@ -24,7 +24,7 @@ import android.content.IntentFilter import android.os.Bundle import android.util.Log import android.view.MenuItem -import android.view.View.INVISIBLE +import android.view.View.GONE import android.view.View.VISIBLE import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -74,7 +74,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, toolbar.setupWithNavController(nav, appBarConfiguration) model.showProgressBar.observe(this, Observer { show -> - progress_bar.visibility = if (show) VISIBLE else INVISIBLE + progress_bar.visibility = if (show) VISIBLE else GONE }) if (intent.action == ACTION_VIEW) intent.dataString?.let { uri -> @@ -131,7 +131,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, url.toLowerCase(ROOT).startsWith("taler://withdraw/") -> { Log.v(TAG, "navigating!") nav.navigate(R.id.action_showBalance_to_promptWithdraw) - model.getWithdrawalInfo(url) + model.withdrawManager.getWithdrawalInfo(url) } url.toLowerCase(ROOT).startsWith("taler://refund/") -> { // TODO implement refunds diff --git a/app/src/main/java/net/taler/wallet/PromptWithdraw.kt b/app/src/main/java/net/taler/wallet/PromptWithdraw.kt deleted file mode 100644 index ec65b0e..0000000 --- a/app/src/main/java/net/taler/wallet/PromptWithdraw.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.TextView -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders -import androidx.navigation.findNavController -import com.google.android.material.snackbar.Snackbar -import me.zhanghai.android.materialprogressbar.MaterialProgressBar - - -class PromptWithdraw : Fragment() { - - private lateinit var model: WalletViewModel - - private fun triggerLoading() { - val loading = - model.withdrawStatus.value is WithdrawStatus.Loading || model.withdrawStatus.value is WithdrawStatus.Withdrawing - val myActivity = activity!! - val progressBar = myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar) - if (loading) { - progressBar.visibility = View.VISIBLE - } else { - progressBar.visibility = View.INVISIBLE - } - } - - private fun showWithdrawStatus(view: View, status: WithdrawStatus) { - val confirmButton = view.findViewById<Button>(R.id.button_confirm_withdraw) - val promptWithdraw = view.findViewById<View>(R.id.prompt_withdraw) - when (status) { - is WithdrawStatus.ReceivedDetails -> { - promptWithdraw.visibility = View.VISIBLE - confirmButton.isEnabled = true - val promptWithdraw = view.findViewById<View>(R.id.prompt_withdraw) - promptWithdraw.visibility = View.VISIBLE - val amountView = view.findViewById<TextView>(R.id.withdraw_amount) - val exchangeView = view.findViewById<TextView>(R.id.withdraw_exchange) - exchangeView.text = status.suggestedExchange - @SuppressLint("SetTextI18n") - amountView.text = "${status.amount.amount} ${status.amount.currency}" - } - is WithdrawStatus.Success -> { - this.model.withdrawStatus.value = WithdrawStatus.None() - activity!!.findNavController(R.id.nav_host_fragment) - .navigate(R.id.action_promptWithdraw_to_withdrawSuccessful) - } - is WithdrawStatus.Loading -> { - promptWithdraw.visibility = View.INVISIBLE - // Wait - } - is WithdrawStatus.Withdrawing -> { - confirmButton.isEnabled = false - - } - is WithdrawStatus.None -> { - - } - is WithdrawStatus.TermsOfServiceReviewRequired -> { - val navController = requireActivity().findNavController(R.id.nav_host_fragment) - navController.navigate(R.id.action_promptWithdraw_to_reviewExchangeTOS) - } - else -> { - val bar = Snackbar.make(view, "Bug: Unexpected result", Snackbar.LENGTH_SHORT) - bar.show() - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - model = activity?.run { - ViewModelProviders.of(this)[WalletViewModel::class.java] - } ?: throw Exception("Invalid Activity") - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_prompt_withdraw, container, false) - - this.model.withdrawStatus.observe(this, Observer { - triggerLoading() - showWithdrawStatus(view, it) - }) - - view.findViewById<Button>(R.id.button_cancel_withdraw).setOnClickListener { - val navController = requireActivity().findNavController(R.id.nav_host_fragment) - model.cancelCurrentWithdraw() - navController.navigateUp() - } - - view.findViewById<Button>(R.id.button_confirm_withdraw).setOnClickListener { - val status = this.model.withdrawStatus.value - if (status !is WithdrawStatus.ReceivedDetails) { - return@setOnClickListener - } - model.acceptWithdrawal(status.talerWithdrawUri, status.suggestedExchange) - } - - return view - } -} diff --git a/app/src/main/java/net/taler/wallet/ReviewExchangeTOS.kt b/app/src/main/java/net/taler/wallet/ReviewExchangeTOS.kt deleted file mode 100644 index 4f5db9e..0000000 --- a/app/src/main/java/net/taler/wallet/ReviewExchangeTOS.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 - - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.CheckBox -import android.widget.TextView -import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders -import androidx.navigation.findNavController - -/** - * A simple [Fragment] subclass. - */ -class ReviewExchangeTOS : Fragment() { - - private lateinit var acceptButton: Button - private lateinit var model: WalletViewModel - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - model = activity?.run { - ViewModelProviders.of(this)[WalletViewModel::class.java] - } ?: throw Exception("Invalid Activity") - } - - private fun onAcceptCheck(checked: Boolean) { - acceptButton.isEnabled = checked - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - val view = inflater.inflate(R.layout.fragment_review_exchange_tos, container, false) - val navController = requireActivity().findNavController(R.id.nav_host_fragment) - view.findViewById<Button>(R.id.button_tos_abort).setOnClickListener { - model.cancelCurrentWithdraw() - navController.navigateUp() - } - acceptButton = view.findViewById(R.id.button_tos_accept) - acceptButton.setOnClickListener { - model.acceptCurrentTermsOfService() - } - val checkbox = view.findViewById<CheckBox>(R.id.checkBox_accept_tos) - checkbox.isChecked = false - checkbox.setOnCheckedChangeListener { buttonView, isChecked -> - onAcceptCheck(isChecked) - } - onAcceptCheck(false) - val tosTextField = view.findViewById<TextView>(R.id.text_tos) - model.withdrawStatus.observe(this, Observer { - when (it) { - is WithdrawStatus.TermsOfServiceReviewRequired -> { - tosTextField.text = it.tosText - } - is WithdrawStatus.Loading -> { - navController.navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw) - } - is WithdrawStatus.ReceivedDetails -> { - navController.navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw) - } - else -> { - } - } - }) - return view - } -} diff --git a/app/src/main/java/net/taler/wallet/ShowBalance.kt b/app/src/main/java/net/taler/wallet/ShowBalance.kt index 26fd050..4b52426 100644 --- a/app/src/main/java/net/taler/wallet/ShowBalance.kt +++ b/app/src/main/java/net/taler/wallet/ShowBalance.kt @@ -16,7 +16,6 @@ package net.taler.wallet - import android.annotation.SuppressLint import android.os.Bundle import android.util.Log @@ -25,20 +24,21 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentIntegrator.QR_CODE_TYPES -import me.zhanghai.android.materialprogressbar.MaterialProgressBar import org.json.JSONObject class WalletBalanceAdapter(private var myDataset: WalletBalances) : @@ -70,9 +70,9 @@ class WalletBalanceAdapter(private var myDataset: WalletBalances) : val amountIncomingView = holder.rowView.findViewById<TextView>(R.id.balance_pending) if (amountIncoming.isZero()) { - amountIncomingRow.visibility = View.GONE + amountIncomingRow.visibility = GONE } else { - amountIncomingRow.visibility = View.VISIBLE + amountIncomingRow.visibility = VISIBLE @SuppressLint("SetTextI18n") amountIncomingView.text = "${amountIncoming.amount} ${amountIncoming.currency}" } @@ -120,7 +120,7 @@ class PendingOperationsAdapter(private var myDataset: PendingOperations) : "proposal-choice" -> { val btn1 = holder.rowView.findViewById<TextView>(R.id.button_pending_action_1) btn1.text = btn1.context.getString(R.string.pending_operations_refuse) - btn1.visibility = View.VISIBLE + btn1.visibility = VISIBLE btn1.setOnClickListener { this.listener?.onPendingOperationActionClick(p.type, p.detail) } @@ -128,7 +128,7 @@ class PendingOperationsAdapter(private var myDataset: PendingOperations) : else -> { val btn1 = holder.rowView.findViewById<TextView>(R.id.button_pending_action_1) btn1.text = btn1.context.getString(R.string.pending_operations_no_action) - btn1.visibility = View.GONE + btn1.visibility = GONE btn1.setOnClickListener {} } } @@ -151,93 +151,21 @@ interface PendingOperationClickListener { fun onPendingOperationActionClick(type: String, detail: JSONObject) } -/** - * A simple [Fragment] subclass. - * - */ class ShowBalance : Fragment(), PendingOperationClickListener { + private val model: WalletViewModel by activityViewModels() + private val withdrawManager by lazy { model.withdrawManager } + private lateinit var pendingOperationsLabel: View private lateinit var balancesView: RecyclerView private lateinit var balancesPlaceholderView: TextView - private lateinit var model: WalletViewModel private lateinit var balancesAdapter: WalletBalanceAdapter private lateinit var pendingAdapter: PendingOperationsAdapter - private fun triggerLoading() { - val loading: Boolean = - (model.testWithdrawalInProgress.value == true) || (model.balances.value == null) || !model.balances.value!!.initialized - - val myActivity = activity!! - val progressBar = myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar) - if (loading) { - progressBar.visibility = View.VISIBLE - } else { - progressBar.visibility = View.INVISIBLE - } - } - - override fun onResume() { - super.onResume() - triggerLoading() - Log.v("taler-wallet", "called onResume on ShowBalance") - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.retry_pending -> { - model.retryPendingNow() - true - } - R.id.reload_balance -> { - triggerLoading() - model.balances.value = WalletBalances(false, listOf()) - model.getBalances() - true - } - else -> super.onOptionsItemSelected(item) - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.balance, menu) - super.onCreateOptionsMenu(menu, inflater) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - - model = activity?.run { - ViewModelProvider(this)[WalletViewModel::class.java] - } ?: throw Exception("Invalid Activity") - - } - - - private fun updateBalances(balances: WalletBalances) { - if (!balances.initialized) { - balancesPlaceholderView.visibility = View.GONE - balancesView.visibility = View.GONE - } else if (balances.byCurrency.isEmpty()) { - balancesPlaceholderView.visibility = View.VISIBLE - balancesView.visibility = View.GONE - } else { - balancesPlaceholderView.visibility = View.GONE - balancesView.visibility = View.VISIBLE - } - Log.v(TAG, "updating balances $balances") - balancesAdapter.update(balances) - } - - private fun updatePending(pendingOperations: PendingOperations) { - if (pendingOperations.pending.isEmpty()) { - pendingOperationsLabel.visibility = View.GONE - } else { - pendingOperationsLabel.visibility = View.VISIBLE - } - pendingAdapter.update(pendingOperations) } override fun onCreateView( @@ -279,10 +207,10 @@ class ShowBalance : Fragment(), PendingOperationClickListener { val withdrawTestkudosButton = view.findViewById<Button>(R.id.button_withdraw_testkudos) withdrawTestkudosButton.setOnClickListener { - model.withdrawTestkudos() + withdrawManager.withdrawTestkudos() } - model.testWithdrawalInProgress.observe(viewLifecycleOwner, Observer { loading -> + withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, Observer { loading -> Log.v("taler-wallet", "observing balance loading $loading in show balance") withdrawTestkudosButton.isEnabled = !loading triggerLoading() @@ -308,6 +236,64 @@ class ShowBalance : Fragment(), PendingOperationClickListener { return view } + override fun onResume() { + super.onResume() + triggerLoading() + Log.v("taler-wallet", "called onResume on ShowBalance") + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.retry_pending -> { + model.retryPendingNow() + true + } + R.id.reload_balance -> { + triggerLoading() + model.balances.value = WalletBalances(false, listOf()) + model.getBalances() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.balance, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + private fun triggerLoading() { + val withdrawInProgress = withdrawManager.testWithdrawalInProgress.value == true + val balances = model.balances.value + val loading: Boolean = (withdrawInProgress) || (balances == null) || !balances.initialized + model.showProgressBar.value = loading + } + + private fun updateBalances(balances: WalletBalances) { + if (!balances.initialized) { + balancesPlaceholderView.visibility = GONE + balancesView.visibility = GONE + } else if (balances.byCurrency.isEmpty()) { + balancesPlaceholderView.visibility = VISIBLE + balancesView.visibility = GONE + } else { + balancesPlaceholderView.visibility = GONE + balancesView.visibility = VISIBLE + } + Log.v(TAG, "updating balances $balances") + balancesAdapter.update(balances) + } + + private fun updatePending(pendingOperations: PendingOperations) { + if (pendingOperations.pending.isEmpty()) { + pendingOperationsLabel.visibility = GONE + } else { + pendingOperationsLabel.visibility = VISIBLE + } + pendingAdapter.update(pendingOperations) + } + override fun onPendingOperationClick(type: String, detail: JSONObject) { val v = view ?: return when { diff --git a/app/src/main/java/net/taler/wallet/Utils.kt b/app/src/main/java/net/taler/wallet/Utils.kt index 673fa2b..fb0b3ae 100644 --- a/app/src/main/java/net/taler/wallet/Utils.kt +++ b/app/src/main/java/net/taler/wallet/Utils.kt @@ -20,16 +20,21 @@ import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -fun View.fadeIn() { +fun View.fadeIn(endAction: () -> Unit = {}) { + if (visibility == VISIBLE) return alpha = 0f visibility = VISIBLE - animate().alpha(1f).start() + animate().alpha(1f).withEndAction { + if (context != null) endAction.invoke() + }.start() } -fun View.fadeOut() { +fun View.fadeOut(endAction: () -> Unit = {}) { if (visibility == INVISIBLE) return animate().alpha(0f).withEndAction { + if (context == null) return@withEndAction visibility = INVISIBLE alpha = 1f + endAction.invoke() }.start() } diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt b/app/src/main/java/net/taler/wallet/WalletViewModel.kt index 35f59f1..d9e730d 100644 --- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt +++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt @@ -18,6 +18,7 @@ package net.taler.wallet import android.app.Application import android.util.Log +import androidx.annotation.UiThread import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -36,6 +37,7 @@ import net.taler.wallet.backend.WalletBackendApi import net.taler.wallet.history.History import net.taler.wallet.history.HistoryEvent import net.taler.wallet.payment.PaymentManager +import net.taler.wallet.withdraw.WithdrawManager import org.json.JSONObject const val TAG = "taler-wallet" @@ -46,26 +48,6 @@ data class BalanceEntry(val available: Amount, val pendingIncoming: Amount) data class WalletBalances(val initialized: Boolean, val byCurrency: List<BalanceEntry>) -open class WithdrawStatus { - class None : WithdrawStatus() - data class Loading(val talerWithdrawUri: String) : WithdrawStatus() - data class TermsOfServiceReviewRequired( - val talerWithdrawUri: String, - val exchangeBaseUrl: String, - val tosText: String, - val tosEtag: String - ) : WithdrawStatus() - - class Success : WithdrawStatus() - data class ReceivedDetails( - val talerWithdrawUri: String, - val amount: Amount, - val suggestedExchange: String - ) : WithdrawStatus() - - data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus() -} - open class PendingOperationInfo( val type: String, val detail: JSONObject @@ -79,18 +61,10 @@ open class PendingOperations( @Suppress("EXPERIMENTAL_API_USAGE") class WalletViewModel(val app: Application) : AndroidViewModel(app) { - val testWithdrawalInProgress = MutableLiveData<Boolean>().apply { - value = false - } - val balances = MutableLiveData<WalletBalances>().apply { value = WalletBalances(false, listOf()) } - val withdrawStatus = MutableLiveData<WithdrawStatus>().apply { - value = WithdrawStatus.None() - } - val pendingOperations = MutableLiveData<PendingOperations>().apply { value = PendingOperations(listOf()) } @@ -112,14 +86,13 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { private var activeGetBalance = 0 private var activeGetPending = 0 - private var currentWithdrawRequestId = 0 - private val walletBackendApi = WalletBackendApi(app) private val mapper = ObjectMapper() .registerModule(KotlinModule()) .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) + val withdrawManager = WithdrawManager(walletBackendApi) val paymentManager = PaymentManager(walletBackendApi, mapper) init { @@ -212,17 +185,10 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { awaitClose() } - fun withdrawTestkudos() { - testWithdrawalInProgress.value = true - - walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ -> - testWithdrawalInProgress.postValue(false) - } - } - + @UiThread fun dangerouslyReset() { walletBackendApi.sendRequest("reset", null) - testWithdrawalInProgress.value = false + withdrawManager.testWithdrawalInProgress.value = false balances.value = WalletBalances(false, listOf()) } @@ -239,113 +205,6 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { walletBackendApi.sendRequest("tunnelResponse", respJson) } - fun getWithdrawalInfo(talerWithdrawUri: String) { - val args = JSONObject() - args.put("talerWithdrawUri", talerWithdrawUri) - - 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)}") - 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) { - Log.v(TAG, "ignoring withdrawal info result, not loading.") - return@sendRequest - } - val wi = result.getJSONObject("bankWithdrawDetails") - val suggestedExchange = wi.getString("suggestedExchange") - // We just use the suggested exchange, in the future there will be - // a selection dialog. - getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange) - } - } - - private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, selectedExchange: String) { - val args = JSONObject() - args.put("talerWithdrawUri", talerWithdrawUri) - args.put("selectedExchange", selectedExchange) - - this.currentWithdrawRequestId++ - val myWithdrawRequestId = this.currentWithdrawRequestId - - walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result -> - if (isError) { - Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}") - 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 (with exchange details)") - val status = withdrawStatus.value - if (status !is WithdrawStatus.Loading) { - Log.v(TAG, "ignoring withdrawal info result, not loading.") - return@sendRequest - } - val ei = result.getJSONObject("exchangeWithdrawDetails") - val termsOfServiceAccepted = ei.getBoolean("termsOfServiceAccepted") - if (!termsOfServiceAccepted) { - val exchange = ei.getJSONObject("exchangeInfo") - val tosText = exchange.getString("termsOfServiceText") - val tosEtag = exchange.optString("termsOfServiceLastEtag", "undefined") - withdrawStatus.postValue( - WithdrawStatus.TermsOfServiceReviewRequired( - status.talerWithdrawUri, - selectedExchange, - tosText, - tosEtag - ) - ) - } else { - val wi = result.getJSONObject("bankWithdrawDetails") - val suggestedExchange = wi.getString("suggestedExchange") - val amount = Amount.fromJson(wi.getJSONObject("amount")) - withdrawStatus.postValue( - WithdrawStatus.ReceivedDetails( - status.talerWithdrawUri, - amount, - suggestedExchange - ) - ) - } - } - } - - fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String) { - val args = JSONObject() - args.put("talerWithdrawUri", talerWithdrawUri) - args.put("selectedExchange", selectedExchange) - - withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri) - - walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, _ -> - if (isError) { - Log.v(TAG, "got acceptWithdrawal error result") - return@sendRequest - } - Log.v(TAG, "got acceptWithdrawal result") - val status = withdrawStatus.value - if (status !is WithdrawStatus.Withdrawing) { - Log.v(TAG, "ignoring acceptWithdrawal result, invalid state") - } - withdrawStatus.postValue(WithdrawStatus.Success()) - } - } - fun retryPendingNow() { walletBackendApi.sendRequest("retryPendingNow", null) } @@ -354,29 +213,4 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { walletBackendApi.destroy() super.onCleared() } - - /** - * Accept the currently displayed terms of service. - */ - fun acceptCurrentTermsOfService() { - when (val s = withdrawStatus.value) { - is WithdrawStatus.TermsOfServiceReviewRequired -> { - val args = JSONObject() - args.put("exchangeBaseUrl", s.exchangeBaseUrl) - args.put("etag", s.tosEtag) - walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { isError, _ -> - if (isError) { - return@sendRequest - } - // Try withdrawing again with accepted ToS - getWithdrawalInfo(s.talerWithdrawUri) - } - } - } - } - - fun cancelCurrentWithdraw() { - currentWithdrawRequestId++ - withdrawStatus.value = WithdrawStatus.None() - } } diff --git a/app/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/app/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt new file mode 100644 index 0000000..0b14e32 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -0,0 +1,107 @@ +/* + * 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.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_prompt_withdraw.* +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel +import net.taler.wallet.fadeIn +import net.taler.wallet.fadeOut +import net.taler.wallet.withdraw.WithdrawStatus.Loading +import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired +import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing + +class PromptWithdrawFragment : 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_prompt_withdraw, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + button_cancel_withdraw.setOnClickListener { + withdrawManager.cancelCurrentWithdraw() + findNavController().navigateUp() + } + + button_confirm_withdraw.setOnClickListener { + val status = withdrawManager.withdrawStatus.value + if (status !is WithdrawStatus.ReceivedDetails) throw AssertionError() + it.fadeOut() + confirmProgressBar.fadeIn() + withdrawManager.acceptWithdrawal(status.talerWithdrawUri, status.suggestedExchange) + } + + withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { + showWithdrawStatus(it) + }) + } + + private fun showWithdrawStatus(status: WithdrawStatus) = when (status) { + is WithdrawStatus.ReceivedDetails -> { + model.showProgressBar.value = false + progressBar.fadeOut() + + introView.fadeIn() + @SuppressLint("SetTextI18n") + withdrawAmountView.text = "${status.amount.amount} ${status.amount.currency}" + withdrawAmountView.fadeIn() + feeView.fadeIn() + + exchangeIntroView.fadeIn() + withdrawExchangeUrl.text = status.suggestedExchange + withdrawExchangeUrl.fadeIn() + + button_confirm_withdraw.isEnabled = true + } + is WithdrawStatus.Success -> { + model.showProgressBar.value = false + withdrawManager.withdrawStatus.value = WithdrawStatus.None + findNavController().navigate(R.id.action_promptWithdraw_to_withdrawSuccessful) + } + is Loading -> { + model.showProgressBar.value = true + } + is Withdrawing -> { + model.showProgressBar.value = true + } + is TermsOfServiceReviewRequired -> { + model.showProgressBar.value = false + findNavController().navigate(R.id.action_promptWithdraw_to_reviewExchangeTOS) + } + is WithdrawStatus.None -> { + model.showProgressBar.value = false + } + } + +} diff --git a/app/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt b/app/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt new file mode 100644 index 0000000..cd01a33 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt @@ -0,0 +1,80 @@ +/* + * 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 androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_review_exchange_tos.* +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel +import net.taler.wallet.fadeIn +import net.taler.wallet.fadeOut + +class ReviewExchangeTosFragment : 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_review_exchange_tos, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + acceptTosCheckBox.isChecked = false + acceptTosCheckBox.setOnCheckedChangeListener { _, isChecked -> + acceptTosButton.isEnabled = isChecked + } + abortTosButton.setOnClickListener { + withdrawManager.cancelCurrentWithdraw() + findNavController().navigateUp() + } + acceptTosButton.setOnClickListener { + withdrawManager.acceptCurrentTermsOfService() + } + withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { + when (it) { + is WithdrawStatus.TermsOfServiceReviewRequired -> { + tosTextView.text = it.tosText + tosTextView.fadeIn() + acceptTosCheckBox.fadeIn() + progressBar.fadeOut() + } + is WithdrawStatus.Loading -> { + findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw) + } + is WithdrawStatus.ReceivedDetails -> { + findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw) + } + else -> { + } + } + }) + } + +} diff --git a/app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt new file mode 100644 index 0000000..fa20318 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt @@ -0,0 +1,193 @@ +/* + * 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.util.Log +import androidx.lifecycle.MutableLiveData +import net.taler.wallet.Amount +import net.taler.wallet.TAG +import net.taler.wallet.backend.WalletBackendApi +import org.json.JSONObject + +sealed class WithdrawStatus { + object None : WithdrawStatus() + data class Loading(val talerWithdrawUri: String) : WithdrawStatus() + data class TermsOfServiceReviewRequired( + val talerWithdrawUri: String, + val exchangeBaseUrl: String, + val tosText: String, + val tosEtag: String + ) : WithdrawStatus() + + object Success : WithdrawStatus() + data class ReceivedDetails( + val talerWithdrawUri: String, + val amount: Amount, + val suggestedExchange: String + ) : WithdrawStatus() + + data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus() +} + +class WithdrawManager(private val walletBackendApi: WalletBackendApi) { + + val withdrawStatus = MutableLiveData<WithdrawStatus>(WithdrawStatus.None) + val testWithdrawalInProgress = MutableLiveData<Boolean>(false) + + private var currentWithdrawRequestId = 0 + + fun withdrawTestkudos() { + testWithdrawalInProgress.value = true + + walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ -> + testWithdrawalInProgress.postValue(false) + } + } + + fun getWithdrawalInfo(talerWithdrawUri: String) { + val args = JSONObject() + args.put("talerWithdrawUri", talerWithdrawUri) + + 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)}") + 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) { + Log.v(TAG, "ignoring withdrawal info result, not loading.") + return@sendRequest + } + val wi = result.getJSONObject("bankWithdrawDetails") + val suggestedExchange = wi.getString("suggestedExchange") + // We just use the suggested exchange, in the future there will be + // a selection dialog. + getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange) + } + } + + private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, selectedExchange: String) { + val args = JSONObject() + args.put("talerWithdrawUri", talerWithdrawUri) + args.put("selectedExchange", selectedExchange) + + this.currentWithdrawRequestId++ + val myWithdrawRequestId = this.currentWithdrawRequestId + + walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result -> + if (isError) { + Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}") + 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 (with exchange details)") + val status = withdrawStatus.value + if (status !is WithdrawStatus.Loading) { + Log.v(TAG, "ignoring withdrawal info result, not loading.") + return@sendRequest + } + val ei = result.getJSONObject("exchangeWithdrawDetails") + val termsOfServiceAccepted = ei.getBoolean("termsOfServiceAccepted") + if (!termsOfServiceAccepted) { + val exchange = ei.getJSONObject("exchangeInfo") + val tosText = exchange.getString("termsOfServiceText") + val tosEtag = exchange.optString("termsOfServiceLastEtag", "undefined") + withdrawStatus.postValue( + WithdrawStatus.TermsOfServiceReviewRequired( + status.talerWithdrawUri, + selectedExchange, + tosText, + tosEtag + ) + ) + } else { + val wi = result.getJSONObject("bankWithdrawDetails") + val suggestedExchange = wi.getString("suggestedExchange") + val amount = Amount.fromJson(wi.getJSONObject("amount")) + withdrawStatus.postValue( + WithdrawStatus.ReceivedDetails( + status.talerWithdrawUri, + amount, + suggestedExchange + ) + ) + } + } + } + + fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String) { + val args = JSONObject() + args.put("talerWithdrawUri", talerWithdrawUri) + args.put("selectedExchange", selectedExchange) + + withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri) + + walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, _ -> + if (isError) { + Log.v(TAG, "got acceptWithdrawal error result") + return@sendRequest + } + Log.v(TAG, "got acceptWithdrawal result") + val status = withdrawStatus.value + if (status !is WithdrawStatus.Withdrawing) { + Log.v(TAG, "ignoring acceptWithdrawal result, invalid state") + } + withdrawStatus.postValue(WithdrawStatus.Success) + } + } + + /** + * Accept the currently displayed terms of service. + */ + fun acceptCurrentTermsOfService() { + when (val s = withdrawStatus.value) { + is WithdrawStatus.TermsOfServiceReviewRequired -> { + val args = JSONObject() + args.put("exchangeBaseUrl", s.exchangeBaseUrl) + args.put("etag", s.tosEtag) + walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { isError, _ -> + if (isError) { + return@sendRequest + } + // Try withdrawing again with accepted ToS + getWithdrawalInfo(s.talerWithdrawUri) + } + } + } + } + + fun cancelCurrentWithdraw() { + currentWithdrawRequestId++ + withdrawStatus.value = WithdrawStatus.None + } + +} diff --git a/app/src/main/java/net/taler/wallet/WithdrawSuccessful.kt b/app/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt index 4ff7478..5daeff1 100644 --- a/app/src/main/java/net/taler/wallet/WithdrawSuccessful.kt +++ b/app/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt @@ -14,29 +14,31 @@ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -package net.taler.wallet - +package net.taler.wallet.withdraw import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Button -import androidx.navigation.findNavController +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_withdraw_successful.* +import net.taler.wallet.R + +class WithdrawSuccessfulFragment : Fragment() { -/** - * A simple [Fragment] subclass. - */ -class WithdrawSuccessful : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - val view = inflater.inflate(R.layout.fragment_withdraw_successful, container, false) - view.findViewById<Button>(R.id.backButton).setOnClickListener { - activity!!.findNavController(R.id.nav_host_fragment).navigateUp() + return inflater.inflate(R.layout.fragment_withdraw_successful, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + backButton.setOnClickListener { + findNavController().navigateUp() } - return view } + } diff --git a/app/src/main/res/layout-w550dp/payment_bottom_bar.xml b/app/src/main/res/layout-w550dp/payment_bottom_bar.xml index f9fa32a..d9e2f59 100644 --- a/app/src/main/res/layout-w550dp/payment_bottom_bar.xml +++ b/app/src/main/res/layout-w550dp/payment_bottom_bar.xml @@ -18,10 +18,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/bottomView" + style="@style/BottomCard" android:layout_width="0dp" android:layout_height="wrap_content" - app:cardCornerRadius="0dp" - app:cardElevation="8dp" tools:showIn="@layout/fragment_prompt_payment"> <androidx.constraintlayout.widget.ConstraintLayout diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml index 834d5ca..e2fa71f 100644 --- a/app/src/main/res/layout/app_bar_main.xml +++ b/app/src/main/res/layout/app_bar_main.xml @@ -26,28 +26,35 @@ android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> - <RelativeLayout + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/relativeLayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" style="@style/AppTheme.Toolbar" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> <me.zhanghai.android.materialprogressbar.MaterialProgressBar android:id="@+id/progress_bar" style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="4dp" - android:layout_alignParentBottom="true" android:indeterminate="true" - android:visibility="invisible" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/toolbar" app:mpb_progressStyle="horizontal" app:mpb_useIntrinsicPadding="false" tools:visibility="visible" /> - </RelativeLayout> + + </androidx.constraintlayout.widget.ConstraintLayout> </com.google.android.material.appbar.AppBarLayout> diff --git a/app/src/main/res/layout/fragment_prompt_withdraw.xml b/app/src/main/res/layout/fragment_prompt_withdraw.xml index dba7450..1114c17 100644 --- a/app/src/main/res/layout/fragment_prompt_withdraw.xml +++ b/app/src/main/res/layout/fragment_prompt_withdraw.xml @@ -14,93 +14,158 @@ ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<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:id="@+id/prompt_withdraw" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="15dp" - android:orientation="vertical" - tools:context=".PromptWithdraw"> - - <Space - android:layout_width="match_parent" - android:layout_height="15dp" - android:layout_weight="1" /> + tools:context=".withdraw.PromptWithdrawFragment"> <TextView - android:id="@+id/order_summary_label" - android:layout_width="wrap_content" + android:id="@+id/introView" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_gravity="center" - android:text="@string/withdraw_do_you_want" /> + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/withdraw_do_you_want" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawAmountView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:visibility="visible" /> <TextView - android:id="@+id/withdraw_amount" - android:layout_width="wrap_content" + android:id="@+id/withdrawAmountView" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_gravity="center" - android:textSize="25sp" - tools:text="10.00 KUDOS" /> + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:textAppearance="@style/TextAppearance.AppCompat.Headline" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/feeView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/introView" + tools:text="10.00 TESTKUDOS" + tools:visibility="visible" /> <TextView - android:layout_width="wrap_content" + android:id="@+id/feeView" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_gravity="center" - android:text="@string/withdraw_fees" /> - - <Space - android:layout_width="match_parent" - android:layout_height="25dp" /> - + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:text="@string/withdraw_fees" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/exchangeIntroView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawAmountView" + tools:visibility="visible" /> <TextView - android:id="@+id/order_amount_label" - android:layout_width="wrap_content" + android:id="@+id/exchangeIntroView" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_gravity="center" - android:text="@string/withdraw_exchange" /> + android:layout_marginStart="16dp" + android:layout_marginTop="32dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/withdraw_exchange" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawExchangeUrl" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/feeView" + tools:visibility="visible" /> <TextView - android:id="@+id/withdraw_exchange" - android:layout_width="wrap_content" + android:id="@+id/withdrawExchangeUrl" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_gravity="center" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" android:textSize="25sp" - tools:text="(exchange base url)" /> - - <Space - android:layout_width="match_parent" - android:layout_height="15dp" - android:layout_weight="1" /> - - - <Space - android:layout_width="match_parent" - android:layout_height="15dp" - android:layout_weight="1" /> - - <LinearLayout - android:layout_width="match_parent" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/exchangeIntroView" + tools:text="(exchange base url)" + tools:visibility="visible" /> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Button - android:id="@+id/button_cancel_withdraw" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/button_cancel" /> - - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" /> + app:layout_constraintBottom_toTopOf="@+id/withdrawCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/withdrawCard" + style="@style/BottomCard" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> - <Button - android:id="@+id/button_confirm_withdraw" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/withdraw_button_confirm" /> - </LinearLayout> - -</LinearLayout> + android:padding="8dp"> + + <Button + android:id="@+id/button_cancel_withdraw" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/red" + android:text="@string/button_cancel" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/button_confirm_withdraw" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/button_confirm_withdraw" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/withdraw_button_confirm" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/button_cancel_withdraw" /> + + <ProgressBar + android:id="@+id/confirmProgressBar" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/button_confirm_withdraw" + app:layout_constraintEnd_toEndOf="@+id/button_confirm_withdraw" + app:layout_constraintStart_toStartOf="@+id/button_confirm_withdraw" + app:layout_constraintTop_toTopOf="@+id/button_confirm_withdraw" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.card.MaterialCardView> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_review_exchange_tos.xml b/app/src/main/res/layout/fragment_review_exchange_tos.xml index 355fe25..61a61f1 100644 --- a/app/src/main/res/layout/fragment_review_exchange_tos.xml +++ b/app/src/main/res/layout/fragment_review_exchange_tos.xml @@ -14,55 +14,92 @@ ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<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="match_parent" - android:layout_margin="15dp" - android:orientation="vertical" - tools:context=".ReviewExchangeTOS"> + tools:context=".withdraw.ReviewExchangeTosFragment"> <ScrollView - android:layout_width="match_parent" + android:id="@+id/tosScrollView" + android:layout_width="0dp" android:layout_height="0dp" - android:layout_weight="1" - android:fillViewport="true" - android:scrollbars="vertical"> + app:layout_constraintBottom_toTopOf="@+id/buttonCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> <TextView - android:id="@+id/text_tos" + android:id="@+id/tosTextView" android:layout_width="match_parent" android:layout_height="wrap_content" - tools:text="TextView" /> + android:padding="16dp" + android:visibility="invisible" + tools:text="@tools:sample/lorem/random" + tools:visibility="visible" /> + </ScrollView> - <CheckBox - android:id="@+id/checkBox_accept_tos" - android:layout_width="match_parent" + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/exchange_tos_accept" /> + app:layout_constraintBottom_toBottomOf="@+id/tosScrollView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - <LinearLayout - android:layout_width="match_parent" + <com.google.android.material.card.MaterialCardView + android:id="@+id/buttonCard" + style="@style/BottomCard" + android:layout_width="0dp" android:layout_height="wrap_content" - android:orientation="horizontal"> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> - <Button - android:id="@+id/button_tos_abort" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/button_cancel" /> + android:padding="8dp"> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1" /> + <CheckBox + android:id="@+id/acceptTosCheckBox" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/exchange_tos_accept" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/acceptTosButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible" /> - <Button - android:id="@+id/button_tos_accept" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/exchange_tos_button_continue" /> - </LinearLayout> + <Button + android:id="@+id/abortTosButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/red" + android:text="@string/button_cancel" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/acceptTosButton" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/acceptTosButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/exchange_tos_button_continue" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/abortTosButton" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.card.MaterialCardView> -</LinearLayout> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_withdraw_successful.xml b/app/src/main/res/layout/fragment_withdraw_successful.xml index 5a48f75..d1b9c90 100644 --- a/app/src/main/res/layout/fragment_withdraw_successful.xml +++ b/app/src/main/res/layout/fragment_withdraw_successful.xml @@ -14,51 +14,49 @@ ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<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="match_parent" - android:layout_margin="10dp" - android:orientation="vertical" - tools:context=".WithdrawSuccessful"> - - <Space - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" /> + tools:context=".withdraw.WithdrawSuccessfulFragment"> <TextView - android:layout_width="match_parent" - android:layout_height="50dp" - android:layout_gravity="center" + android:id="@+id/withdrawHeadlineView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="16dp" + android:gravity="center_horizontal|bottom" android:text="@string/withdraw_accepted" - android:textAlignment="center" - android:textColor="@android:color/holo_green_dark" - app:autoSizeTextType="uniform" /> + android:textColor="@color/green" + app:autoSizeMaxTextSize="40sp" + app:autoSizeTextType="uniform" + app:layout_constraintBottom_toTopOf="@+id/withdrawInfoView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> <TextView - android:layout_width="match_parent" - android:layout_height="50dp" - android:layout_gravity="center" - android:text="@string/withdraw_success_info" - android:textAlignment="center" /> - - <Space - android:layout_width="match_parent" + android:id="@+id/withdrawInfoView" + android:layout_width="0dp" android:layout_height="0dp" - android:layout_weight="1" /> - - - <Space - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" /> + android:layout_margin="16dp" + android:text="@string/withdraw_success_info" + android:textAlignment="center" + app:layout_constraintBottom_toTopOf="@+id/backButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawHeadlineView" /> <Button android:id="@+id/backButton" - android:layout_width="match_parent" + android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@string/button_back" /> - -</LinearLayout> + android:layout_margin="16dp" + android:text="@string/button_back" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawInfoView" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/payment_bottom_bar.xml b/app/src/main/res/layout/payment_bottom_bar.xml index 5b5c9f3..8fdf0f8 100644 --- a/app/src/main/res/layout/payment_bottom_bar.xml +++ b/app/src/main/res/layout/payment_bottom_bar.xml @@ -17,10 +17,9 @@ <com.google.android.material.card.MaterialCardView 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" + style="@style/BottomCard" android:layout_width="0dp" android:layout_height="wrap_content" - app:cardCornerRadius="0dp" - app:cardElevation="8dp" tools:showIn="@layout/fragment_prompt_payment"> <androidx.constraintlayout.widget.ConstraintLayout diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 39068ec..2cc1eaa 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -67,10 +67,11 @@ android:name="net.taler.wallet.payment.AlreadyPaidFragment" android:label="Already Paid" tools:layout="@layout/fragment_already_paid" /> + <fragment android:id="@+id/promptWithdraw" - android:name="net.taler.wallet.PromptWithdraw" - android:label="Withdraw Digital Cash" + android:name="net.taler.wallet.withdraw.PromptWithdrawFragment" + android:label="@string/nav_prompt_withdraw" tools:layout="@layout/fragment_prompt_withdraw"> <action android:id="@+id/action_promptWithdraw_to_withdrawSuccessful" @@ -81,15 +82,16 @@ app:destination="@id/reviewExchangeTOS" app:popUpTo="@id/showBalance" /> </fragment> + <fragment android:id="@+id/withdrawSuccessful" - android:name="net.taler.wallet.WithdrawSuccessful" + android:name="net.taler.wallet.withdraw.WithdrawSuccessfulFragment" android:label="Withdrawal Confirmed" tools:layout="@layout/fragment_withdraw_successful" /> <fragment android:id="@+id/reviewExchangeTOS" - android:name="net.taler.wallet.ReviewExchangeTOS" - android:label="Exchange's Terms of Service" + android:name="net.taler.wallet.withdraw.ReviewExchangeTosFragment" + android:label="@string/nav_exchange_tos" tools:layout="@layout/fragment_review_exchange_tos"> <action android:id="@+id/action_reviewExchangeTOS_to_promptWithdraw" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8307e37..19159b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,6 +21,9 @@ <string name="nav_header_subtitle">Wallet</string> <string name="nav_header_desc">Navigation header</string> + <string name="nav_prompt_withdraw">Withdraw Digital Cash</string> + <string name="nav_exchange_tos">Exchange\'s Terms of Service</string> + <string name="button_back">Go Back</string> <string name="button_cancel">Cancel</string> <string name="button_scan_qr_code">Scan Taler QR Code</string> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 09d7a02..83f3e3a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -38,4 +38,9 @@ <item name="android:textColor">?android:textColorPrimary</item> </style> + <style name="BottomCard"> + <item name="cardCornerRadius">0dp</item> + <item name="cardElevation">8dp</item> + </style> + </resources> |