gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-android] 02/02: Clean up and improve withdraw UI (first pa


From: gnunet
Subject: [taler-wallet-android] 02/02: Clean up and improve withdraw UI (first pass)
Date: Tue, 03 Mar 2020 21:30:03 +0100

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository wallet-android.

commit 30980bc83be99ea85205f44c815b78164b11f7b9
Author: Torsten Grote <address@hidden>
AuthorDate: Tue Mar 3 14:03:45 2020 -0300

    Clean up and improve withdraw UI (first pass)
---
 app/src/main/java/net/taler/wallet/MainActivity.kt |   6 +-
 .../main/java/net/taler/wallet/PromptWithdraw.kt   | 127 ------------
 .../java/net/taler/wallet/ReviewExchangeTOS.kt     |  91 ---------
 app/src/main/java/net/taler/wallet/ShowBalance.kt  | 154 +++++++--------
 app/src/main/java/net/taler/wallet/Utils.kt        |  11 +-
 .../main/java/net/taler/wallet/WalletViewModel.kt  | 176 +----------------
 .../wallet/withdraw/PromptWithdrawFragment.kt      | 107 +++++++++++
 .../wallet/withdraw/ReviewExchangeTosFragment.kt   |  80 ++++++++
 .../WithdrawManager.kt}                            | 213 ++-------------------
 .../WithdrawSuccessfulFragment.kt}                 |  28 +--
 .../main/res/layout-w550dp/payment_bottom_bar.xml  |   3 +-
 app/src/main/res/layout/app_bar_main.xml           |  19 +-
 .../main/res/layout/fragment_prompt_withdraw.xml   | 201 ++++++++++++-------
 .../res/layout/fragment_review_exchange_tos.xml    | 101 ++++++----
 .../res/layout/fragment_withdraw_successful.xml    |  66 ++++---
 app/src/main/res/layout/payment_bottom_bar.xml     |   3 +-
 app/src/main/res/navigation/nav_graph.xml          |  12 +-
 app/src/main/res/values/strings.xml                |   3 +
 app/src/main/res/values/styles.xml                 |   5 +
 19 files changed, 564 insertions(+), 842 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/WalletViewModel.kt 
b/app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
similarity index 51%
copy from app/src/main/java/net/taler/wallet/WalletViewModel.kt
copy to app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index 35f59f1..fa20318 100644
--- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt
+++ b/app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -14,40 +14,17 @@
  * 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.app.Application
 import android.util.Log
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.switchMap
-import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.kotlin.readValue
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
+import net.taler.wallet.Amount
+import net.taler.wallet.TAG
 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 org.json.JSONObject
 
-const val TAG = "taler-wallet"
-
-
-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()
+sealed class WithdrawStatus {
+    object None : WithdrawStatus()
     data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
     data class TermsOfServiceReviewRequired(
         val talerWithdrawUri: String,
@@ -56,7 +33,7 @@ open class WithdrawStatus {
         val tosEtag: String
     ) : WithdrawStatus()
 
-    class Success : WithdrawStatus()
+    object Success : WithdrawStatus()
     data class ReceivedDetails(
         val talerWithdrawUri: String,
         val amount: Amount,
@@ -66,152 +43,13 @@ open class WithdrawStatus {
     data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
 }
 
-open class PendingOperationInfo(
-    val type: String,
-    val detail: JSONObject
-)
-
-open class PendingOperations(
-    val pending: List<PendingOperationInfo>
-)
-
-
-@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())
-    }
-
-    private val mHistoryProgress = MutableLiveData<Boolean>()
-    val historyProgress: LiveData<Boolean> = mHistoryProgress
+class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
 
-    val historyShowAll = MutableLiveData<Boolean>()
-
-    val history: LiveData<History> = historyShowAll.switchMap { showAll ->
-        loadHistory(showAll)
-            .onStart { mHistoryProgress.postValue(true) }
-            .onCompletion { mHistoryProgress.postValue(false) }
-            .asLiveData(Dispatchers.IO)
-    }
-
-    val showProgressBar = MutableLiveData<Boolean>()
-
-    private var activeGetBalance = 0
-    private var activeGetPending = 0
+    val withdrawStatus = MutableLiveData<WithdrawStatus>(WithdrawStatus.None)
+    val testWithdrawalInProgress = MutableLiveData<Boolean>(false)
 
     private var currentWithdrawRequestId = 0
 
-    private val walletBackendApi = WalletBackendApi(app)
-
-    private val mapper = ObjectMapper()
-        .registerModule(KotlinModule())
-        .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
-
-    val paymentManager = PaymentManager(walletBackendApi, mapper)
-
-    init {
-        getBalances()
-        getPending()
-
-        walletBackendApi.notificationHandler = {
-            Log.i(TAG, "got notification from wallet")
-            getBalances()
-            getPending()
-        }
-        walletBackendApi.connectedHandler = {
-            activeGetBalance = 0
-            activeGetPending = 0
-            getBalances()
-            getPending()
-        }
-    }
-
-    fun getBalances() {
-        if (activeGetBalance > 0) {
-            return
-        }
-        activeGetBalance++
-        walletBackendApi.sendRequest("getBalances", null) { isError, result ->
-            activeGetBalance--
-            if (isError) {
-                return@sendRequest
-            }
-            val balanceList = mutableListOf<BalanceEntry>()
-            val byCurrency = result.getJSONObject("byCurrency")
-            val currencyList = byCurrency.keys().asSequence().toList().sorted()
-            for (currency in currencyList) {
-                val jsonAmount = byCurrency.getJSONObject(currency)
-                    .getJSONObject("available")
-                val amount = Amount.fromJson(jsonAmount)
-                val jsonAmountIncoming = byCurrency.getJSONObject(currency)
-                    .getJSONObject("pendingIncoming")
-                val amountIncoming = Amount.fromJson(jsonAmountIncoming)
-                balanceList.add(BalanceEntry(amount, amountIncoming))
-            }
-            balances.postValue(WalletBalances(true, balanceList))
-        }
-    }
-
-    private fun getPending() {
-        if (activeGetPending > 0) {
-            return
-        }
-        activeGetPending++
-        walletBackendApi.sendRequest("getPendingOperations", null) { isError, 
result ->
-            activeGetPending--
-            if (isError) {
-                Log.i(TAG, "got getPending error result")
-                return@sendRequest
-            }
-            Log.i(TAG, "got getPending result")
-            val pendingList = mutableListOf<PendingOperationInfo>()
-            val pendingJson = result.getJSONArray("pendingOperations")
-            for (i in 0 until pendingJson.length()) {
-                val p = pendingJson.getJSONObject(i)
-                val type = p.getString("type")
-                pendingList.add(PendingOperationInfo(type, p))
-            }
-            Log.i(TAG, "Got ${pendingList.size} pending operations")
-            pendingOperations.postValue(PendingOperations((pendingList)))
-        }
-    }
-
-    private fun loadHistory(showAll: Boolean) = callbackFlow {
-        mHistoryProgress.postValue(true)
-        walletBackendApi.sendRequest("getHistory", null) { isError, result ->
-            if (isError) {
-                // TODO show error message in [WalletHistory] fragment
-                close()
-                return@sendRequest
-            }
-            val history = History()
-            val json = result.getJSONArray("history")
-            for (i in 0 until json.length()) {
-                val event: HistoryEvent = mapper.readValue(json.getString(i))
-                event.json = json.getJSONObject(i)
-                history.add(event)
-            }
-            history.reverse()  // show latest first
-            mHistoryProgress.postValue(false)
-            offer(if (showAll) history else history.filter { it.showToUser } 
as History)
-            close()
-        }
-        awaitClose()
-    }
-
     fun withdrawTestkudos() {
         testWithdrawalInProgress.value = true
 
@@ -220,25 +58,6 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
         }
     }
 
-    fun dangerouslyReset() {
-        walletBackendApi.sendRequest("reset", null)
-        testWithdrawalInProgress.value = false
-        balances.value = WalletBalances(false, listOf())
-    }
-
-    fun startTunnel() {
-        walletBackendApi.sendRequest("startTunnel", null)
-    }
-
-    fun stopTunnel() {
-        walletBackendApi.sendRequest("stopTunnel", null)
-    }
-
-    fun tunnelResponse(resp: String) {
-        val respJson = JSONObject(resp)
-        walletBackendApi.sendRequest("tunnelResponse", respJson)
-    }
-
     fun getWithdrawalInfo(talerWithdrawUri: String) {
         val args = JSONObject()
         args.put("talerWithdrawUri", talerWithdrawUri)
@@ -342,19 +161,10 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
             if (status !is WithdrawStatus.Withdrawing) {
                 Log.v(TAG, "ignoring acceptWithdrawal result, invalid state")
             }
-            withdrawStatus.postValue(WithdrawStatus.Success())
+            withdrawStatus.postValue(WithdrawStatus.Success)
         }
     }
 
-    fun retryPendingNow() {
-        walletBackendApi.sendRequest("retryPendingNow", null)
-    }
-
-    override fun onCleared() {
-        walletBackendApi.destroy()
-        super.onCleared()
-    }
-
     /**
      * Accept the currently displayed terms of service.
      */
@@ -377,6 +187,7 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
 
     fun cancelCurrentWithdraw() {
         currentWithdrawRequestId++
-        withdrawStatus.value = WithdrawStatus.None()
+        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
similarity index 65%
rename from app/src/main/java/net/taler/wallet/WithdrawSuccessful.kt
rename to 
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>

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



reply via email to

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