gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-android] branch master updated (5f57c48 -> 30980bc)


From: gnunet
Subject: [taler-wallet-android] branch master updated (5f57c48 -> 30980bc)
Date: Tue, 03 Mar 2020 21:30:01 +0100

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

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

    from 5f57c48  Fix lint and make all strings translatable
     new a9fd9aa  Clean up MainActivity and unregister receivers to not leak 
them
     new 30980bc  Clean up and improve withdraw UI (first pass)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .idea/codeStyles/Project.xml                       |   3 -
 .idea/copyright/profiles_settings.xml              |   2 +-
 .idea/gradle.xml                                   |   4 +-
 .idea/scopes/Copyright_Files.xml                   |   3 +
 app/build.gradle                                   |   3 +-
 app/src/main/java/net/taler/wallet/MainActivity.kt | 158 +++++++--------
 .../main/java/net/taler/wallet/PromptWithdraw.kt   | 127 ------------
 .../java/net/taler/wallet/ReviewExchangeTOS.kt     |  91 ---------
 app/src/main/java/net/taler/wallet/ShowBalance.kt  | 155 +++++++-------
 app/src/main/java/net/taler/wallet/Utils.kt        |  11 +-
 .../main/java/net/taler/wallet/WalletViewModel.kt  | 187 +----------------
 .../wallet/withdraw/PromptWithdrawFragment.kt      | 107 ++++++++++
 .../wallet/withdraw/ReviewExchangeTosFragment.kt   |  80 ++++++++
 .../WithdrawManager.kt}                            | 222 ++-------------------
 .../WithdrawSuccessfulFragment.kt}                 |  28 +--
 .../main/res/layout-w550dp/payment_bottom_bar.xml  |   3 +-
 app/src/main/res/layout/app_bar_main.xml           |  32 ++-
 .../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 +
 build.gradle                                       |   7 +-
 gradle/wrapper/gradle-wrapper.properties           |  20 +-
 26 files changed, 641 insertions(+), 993 deletions(-)
 create mode 100644 .idea/scopes/Copyright_Files.xml
 delete mode 100644 app/src/main/java/net/taler/wallet/PromptWithdraw.kt
 delete mode 100644 app/src/main/java/net/taler/wallet/ReviewExchangeTOS.kt
 create mode 100644 
app/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
 create mode 100644 
app/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
 copy app/src/main/java/net/taler/wallet/{WalletViewModel.kt => 
withdraw/WithdrawManager.kt} (50%)
 rename app/src/main/java/net/taler/wallet/{WithdrawSuccessful.kt => 
withdraw/WithdrawSuccessfulFragment.kt} (65%)

diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index a705caf..fad1c60 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,8 +1,5 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
-    <AndroidXmlCodeStyleSettings>
-      <option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
-    </AndroidXmlCodeStyleSettings>
     <JetCodeStyleSettings>
       <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
       <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" 
value="2147483647" />
diff --git a/.idea/copyright/profiles_settings.xml 
b/.idea/copyright/profiles_settings.xml
index 0040fda..31766eb 100644
--- a/.idea/copyright/profiles_settings.xml
+++ b/.idea/copyright/profiles_settings.xml
@@ -1,7 +1,7 @@
 <component name="CopyrightManager">
   <settings default="Taler">
     <module2copyright>
-      <element module="Project Files" copyright="Taler" />
+      <element module="Copyright Files" copyright="Taler" />
     </module2copyright>
   </settings>
 </component>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 031c262..69c1925 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,8 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
+        <option name="delegatedBuild" value="false" />
+        <option name="testRunner" value="PLATFORM" />
         <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="modules">
@@ -13,7 +16,6 @@
           </set>
         </option>
         <option name="resolveModulePerSourceSet" value="false" />
-        <option name="testRunner" value="PLATFORM" />
       </GradleProjectSettings>
     </option>
   </component>
diff --git a/.idea/scopes/Copyright_Files.xml b/.idea/scopes/Copyright_Files.xml
new file mode 100644
index 0000000..e74c29b
--- /dev/null
+++ b/.idea/scopes/Copyright_Files.xml
@@ -0,0 +1,3 @@
+<component name="DependencyValidationManager">
+  <scope name="Copyright Files" 
pattern="file[app]:src/*/java//*||file[app]:src/main/res/layout/*||file[app]:src/main/res/layout-w550dp/*||file[app]:src/main/res/navigation//*||file[app]:src/main/res/values//*||file[app]:src/main/res/xml//*"
 />
+</component>
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 921bbb1..2c7807e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,12 +16,11 @@
 
 apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
 apply plugin: 'kotlin-android-extensions'
 
 android {
     compileSdkVersion 29
-    buildToolsVersion "29.0.2"
+    buildToolsVersion "29.0.3"
     defaultConfig {
         applicationId "net.taler.wallet"
         minSdkVersion 21
diff --git a/app/src/main/java/net/taler/wallet/MainActivity.kt 
b/app/src/main/java/net/taler/wallet/MainActivity.kt
index 29a56da..ebc7136 100644
--- a/app/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/app/src/main/java/net/taler/wallet/MainActivity.kt
@@ -19,16 +19,16 @@ package net.taler.wallet
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
+import android.content.Intent.ACTION_VIEW
 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
-import androidx.core.view.GravityCompat
-import androidx.drawerlayout.widget.DrawerLayout
+import androidx.core.view.GravityCompat.START
 import androidx.lifecycle.Observer
 import androidx.navigation.NavController
 import androidx.navigation.fragment.NavHostFragment
@@ -38,9 +38,13 @@ import 
com.google.android.material.navigation.NavigationView.OnNavigationItemSel
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
 import com.google.zxing.integration.android.IntentIntegrator
-import com.google.zxing.integration.android.IntentResult
+import 
com.google.zxing.integration.android.IntentIntegrator.parseActivityResult
 import kotlinx.android.synthetic.main.activity_main.*
 import kotlinx.android.synthetic.main.app_bar_main.*
+import net.taler.wallet.HostCardEmulatorService.Companion.HTTP_TUNNEL_RESPONSE
+import 
net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_CONNECTED
+import 
net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_DISCONNECTED
+import 
net.taler.wallet.HostCardEmulatorService.Companion.TRIGGER_PAYMENT_ACTION
 import java.util.Locale.ROOT
 
 class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
@@ -54,13 +58,6 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
 
-        fab.setOnClickListener {
-            val integrator = IntentIntegrator(this)
-            integrator.setPrompt("Place merchant's QR Code inside the 
viewfinder rectangle to initiate payment.")
-            integrator.initiateScan(listOf("QR_CODE"))
-        }
-        fab.hide()
-
         val navHostFragment =
             supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as 
NavHostFragment
         nav = navHostFragment.navController
@@ -76,106 +73,52 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
         )
         toolbar.setupWithNavController(nav, appBarConfiguration)
 
-        model.init()
-        model.getBalances()
-
         model.showProgressBar.observe(this, Observer { show ->
-            progress_bar.visibility = if (show) VISIBLE else INVISIBLE
+            progress_bar.visibility = if (show) VISIBLE else GONE
         })
 
-        val triggerPaymentFilter = 
IntentFilter(HostCardEmulatorService.TRIGGER_PAYMENT_ACTION)
-        registerReceiver(object : BroadcastReceiver() {
-            override fun onReceive(p0: Context?, p1: Intent?) {
-
-                if (nav.currentDestination?.id == R.id.promptPayment) {
-                    return
-                }
-
-                val url = p1!!.extras!!.get("contractUrl") as String
-
-                nav.navigate(R.id.action_global_promptPayment)
-                model.paymentManager.preparePay(url)
-
-            }
-        }, triggerPaymentFilter)
-
-        val nfcConnectedFilter = 
IntentFilter(HostCardEmulatorService.MERCHANT_NFC_CONNECTED)
-        registerReceiver(object : BroadcastReceiver() {
-            override fun onReceive(p0: Context?, p1: Intent?) {
-                Log.v(TAG, "got MERCHANT_NFC_CONNECTED")
-                //model.startTunnel()
-            }
-        }, nfcConnectedFilter)
-
-        val nfcDisconnectedFilter = 
IntentFilter(HostCardEmulatorService.MERCHANT_NFC_DISCONNECTED)
-        registerReceiver(object : BroadcastReceiver() {
-            override fun onReceive(p0: Context?, p1: Intent?) {
-                Log.v(TAG, "got MERCHANT_NFC_DISCONNECTED")
-                //model.stopTunnel()
-            }
-        }, nfcDisconnectedFilter)
-
-        IntentFilter(HostCardEmulatorService.HTTP_TUNNEL_RESPONSE).also { 
filter ->
-            registerReceiver(object : BroadcastReceiver() {
-                override fun onReceive(p0: Context?, p1: Intent?) {
-                    Log.v("taler-tunnel", "got HTTP_TUNNEL_RESPONSE")
-                    model.tunnelResponse(p1!!.getStringExtra("response"))
-                }
-            }, filter)
-        }
-
-        if (intent.action == Intent.ACTION_VIEW) {
-            val uri = intent.dataString
-            if (uri != null)
-                handleTalerUri(uri, "intent")
+        if (intent.action == ACTION_VIEW) intent.dataString?.let { uri ->
+            handleTalerUri(uri, "intent")
         }
 
         //model.startTunnel()
+
+        registerReceiver(triggerPaymentReceiver, 
IntentFilter(TRIGGER_PAYMENT_ACTION))
+        registerReceiver(nfcConnectedReceiver, 
IntentFilter(MERCHANT_NFC_CONNECTED))
+        registerReceiver(nfcDisconnectedReceiver, 
IntentFilter(MERCHANT_NFC_DISCONNECTED))
+        registerReceiver(tunnelResponseReceiver, 
IntentFilter(HTTP_TUNNEL_RESPONSE))
     }
 
     override fun onBackPressed() {
-        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
-        if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
-            drawerLayout.closeDrawer(GravityCompat.START)
-        } else {
-            super.onBackPressed()
-        }
+        if (drawer_layout.isDrawerOpen(START)) drawer_layout.closeDrawer(START)
+        else super.onBackPressed()
     }
 
     override fun onNavigationItemSelected(item: MenuItem): Boolean {
-        // Handle navigation view item clicks here.
         when (item.itemId) {
-            R.id.nav_home -> {
-                nav.navigate(R.id.showBalance)
-            }
-            R.id.nav_settings -> {
-                nav.navigate(R.id.settings)
-            }
-            R.id.nav_history -> {
-                nav.navigate(R.id.walletHistory)
-            }
+            R.id.nav_home -> nav.navigate(R.id.showBalance)
+            R.id.nav_settings -> nav.navigate(R.id.settings)
+            R.id.nav_history -> nav.navigate(R.id.walletHistory)
         }
-        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
-        drawerLayout.closeDrawer(GravityCompat.START)
+        drawer_layout.closeDrawer(START)
         return true
     }
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: 
Intent?) {
         super.onActivityResult(requestCode, resultCode, data)
-        if (requestCode != IntentIntegrator.REQUEST_CODE) {
-            return
-        }
-
-        val scanResult: IntentResult? =
-            IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
-
-        if (scanResult == null || scanResult.contents == null) {
-            Snackbar.make(nav_view, "QR Code scan canceled.", 
LENGTH_SHORT).show()
-            return
+        if (requestCode == IntentIntegrator.REQUEST_CODE) {
+            parseActivityResult(requestCode, resultCode, data)?.contents?.let 
{ contents ->
+                handleTalerUri(contents, "QR code")
+            }
         }
+    }
 
-        val url = scanResult.contents!!
-        handleTalerUri(url, "QR code")
+    override fun onDestroy() {
+        unregisterReceiver(triggerPaymentReceiver)
+        unregisterReceiver(nfcConnectedReceiver)
+        unregisterReceiver(nfcDisconnectedReceiver)
+        unregisterReceiver(tunnelResponseReceiver)
+        super.onDestroy()
     }
 
     private fun handleTalerUri(url: String, from: String) {
@@ -188,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
@@ -204,6 +147,39 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
         }
     }
 
+    private val triggerPaymentReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (nav.currentDestination?.id == R.id.promptPayment) return
+            intent.extras?.getString("contractUrl")?.let { url ->
+                nav.navigate(R.id.action_global_promptPayment)
+                model.paymentManager.preparePay(url)
+            }
+        }
+    }
+
+    private val nfcConnectedReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.v(TAG, "got MERCHANT_NFC_CONNECTED")
+            //model.startTunnel()
+        }
+    }
+
+    private val nfcDisconnectedReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.v(TAG, "got MERCHANT_NFC_DISCONNECTED")
+            //model.stopTunnel()
+        }
+    }
+
+    private val tunnelResponseReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.v("taler-tunnel", "got HTTP_TUNNEL_RESPONSE")
+            intent.getStringExtra("response")?.let {
+                model.tunnelResponse(it)
+            }
+        }
+    }
+
     override fun onResetConfirmed() {
         model.dangerouslyReset()
         Snackbar.make(nav_view, "Wallet has been reset", LENGTH_SHORT).show()
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 1238903..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,94 +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)
-        Log.e("TEST", "MENU INFLATED!!! ${menu.size()}")
-        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(
@@ -280,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()
@@ -309,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 2942805..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
@@ -78,20 +60,11 @@ open class PendingOperations(
 
 @Suppress("EXPERIMENTAL_API_USAGE")
 class WalletViewModel(val app: Application) : AndroidViewModel(app) {
-    private var initialized = false
-
-    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())
     }
@@ -113,24 +86,16 @@ 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)
 
-    fun init() {
-        if (initialized) {
-            Log.e(TAG, "WalletViewModel already initialized")
-            return
-        }
-
-        this.initialized = true
-
+    init {
         getBalances()
         getPending()
 
@@ -147,7 +112,6 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
         }
     }
 
-
     fun getBalances() {
         if (activeGetBalance > 0) {
             return
@@ -221,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())
     }
 
@@ -248,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)
     }
@@ -363,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 50%
copy from app/src/main/java/net/taler/wallet/WalletViewModel.kt
copy to app/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index 2942805..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,161 +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) {
-    private var initialized = false
-
-    val testWithdrawalInProgress = MutableLiveData<Boolean>().apply {
-        value = false
-    }
+class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
 
-    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
-
-    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)
-
-    fun init() {
-        if (initialized) {
-            Log.e(TAG, "WalletViewModel already initialized")
-            return
-        }
-
-        this.initialized = true
-
-        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
 
@@ -229,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)
@@ -351,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.
      */
@@ -386,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 f2d8571..e2fa71f 100644
--- a/app/src/main/res/layout/app_bar_main.xml
+++ b/app/src/main/res/layout/app_bar_main.xml
@@ -21,34 +21,40 @@
         android:layout_height="match_parent"
         tools:context=".MainActivity">
 
-
     <com.google.android.material.appbar.AppBarLayout
             android:layout_width="match_parent"
             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>
 
@@ -65,14 +71,4 @@
             app:layout_constraintTop_toTopOf="parent"
             app:navGraph="@navigation/nav_graph" />
 
-    <com.google.android.material.floatingactionbutton.FloatingActionButton
-            android:id="@+id/fab"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom|end"
-            android:layout_margin="@dimen/fab_margin"
-            android:scaleType="center"
-            app:fabSize="normal"
-            app:srcCompat="@drawable/ic_scan_qr" />
-
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
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>
diff --git a/build.gradle b/build.gradle
index df21dcd..a9d913c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,20 +14,15 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-// Top-level build file where you can add configuration options common to all 
sub-projects/modules.
-
 buildscript {
     ext.kotlin_version = '1.3.61'
     repositories {
         google()
         jcenter()
-        
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.3'
+        classpath 'com.android.tools.build:gradle:3.6.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
     }
 }
 
diff --git a/gradle/wrapper/gradle-wrapper.properties 
b/gradle/wrapper/gradle-wrapper.properties
index d432222..75a58ba 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,22 +1,6 @@
-#
-# 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/>
-#
-
-#Wed Aug 14 17:39:00 CEST 2019
+#Tue Mar 03 08:42:04 BRT 2020
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

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



reply via email to

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