gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-android] 01/06: Refactor payment code to make it easier to


From: gnunet
Subject: [taler-wallet-android] 01/06: Refactor payment code to make it easier to extend
Date: Fri, 21 Feb 2020 19:01:50 +0100

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

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

commit ada382885e9c103fe0795817a8585270a3079302
Author: Torsten Grote <address@hidden>
AuthorDate: Wed Feb 12 16:53:03 2020 -0300

    Refactor payment code to make it easier to extend
---
 .idea/codeStyles/Project.xml                       |   2 +
 app/build.gradle                                   |   8 +-
 app/src/main/java/net/taler/wallet/MainActivity.kt |  94 ++++-----
 .../main/java/net/taler/wallet/PromptPayment.kt    | 174 ----------------
 app/src/main/java/net/taler/wallet/ShowBalance.kt  |   9 +-
 .../main/java/net/taler/wallet/WalletViewModel.kt  | 110 +---------
 .../AlreadyPaidFragment.kt}                        |  23 ++-
 .../net/taler/wallet/payment/PaymentManager.kt     | 108 ++++++++++
 .../PaymentSuccessfulFragment.kt}                  |  24 +--
 .../taler/wallet/payment/PromptPaymentFragment.kt  | 136 +++++++++++++
 app/src/main/res/layout/app_bar_main.xml           |   7 +-
 app/src/main/res/layout/fragment_already_paid.xml  |   2 +-
 .../res/layout/fragment_payment_successful.xml     |   2 +-
 .../main/res/layout/fragment_prompt_payment.xml    | 222 ++++++++++-----------
 .../main/res/layout/fragment_prompt_withdraw.xml   |   4 +-
 app/src/main/res/navigation/nav_graph.xml          |   6 +-
 app/src/main/res/values/strings.xml                |  10 +-
 17 files changed, 462 insertions(+), 479 deletions(-)

diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index a88ded0..a705caf 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -4,6 +4,8 @@
       <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" />
       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
     </JetCodeStyleSettings>
     <codeStyleSettings language="XML">
diff --git a/app/build.gradle b/app/build.gradle
index c40ddef..8c0746e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -35,9 +35,9 @@ dependencies {
     implementation fileTree(dir: 'libs', include: ['*.jar'])
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
     implementation 'androidx.appcompat:appcompat:1.1.0'
-    implementation 'androidx.core:core-ktx:1.1.0'
+    implementation 'androidx.core:core-ktx:1.2.0'
     implementation 'androidx.legacy:legacy-support-v4:1.0.0'
-    implementation 'com.google.android.material:material:1.0.0'
+    implementation 'com.google.android.material:material:1.1.0'
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'androidx.test:runner:1.2.0'
@@ -45,12 +45,12 @@ dependencies {
     implementation project(":akono")
     implementation 'com.google.guava:guava:28.0-android'
 
-    def nav_version = "2.2.0-rc04"
+    def nav_version = "2.2.1"
     implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
     implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
 
     // ViewModel and LiveData
-    def lifecycle_version = "2.2.0-rc03"
+    def lifecycle_version = "2.2.0"
     implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
     implementation 
"androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
     implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
diff --git a/app/src/main/java/net/taler/wallet/MainActivity.kt 
b/app/src/main/java/net/taler/wallet/MainActivity.kt
index 3f38a44..8981b62 100644
--- a/app/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/app/src/main/java/net/taler/wallet/MainActivity.kt
@@ -14,10 +14,8 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-
 package net.taler.wallet
 
-
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -26,41 +24,37 @@ import android.os.Bundle
 import android.util.Log
 import android.view.Menu
 import android.view.MenuItem
-import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.Toolbar
 import androidx.core.view.GravityCompat
 import androidx.drawerlayout.widget.DrawerLayout
-import androidx.lifecycle.ViewModelProviders
-import androidx.navigation.NavController
+import androidx.lifecycle.Observer
 import androidx.navigation.findNavController
 import androidx.navigation.ui.AppBarConfiguration
 import androidx.navigation.ui.setupWithNavController
-import com.google.android.material.floatingactionbutton.FloatingActionButton
-import com.google.android.material.navigation.NavigationView
+import 
com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener
 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 me.zhanghai.android.materialprogressbar.MaterialProgressBar
-import java.util.*
+import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.android.synthetic.main.app_bar_main.*
+import java.util.Locale.ROOT
 
+class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
+    ResetDialogEventListener {
 
-class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelectedListener, ResetDialogEventListener {
-
-    lateinit var model: WalletViewModel
+    private val model: WalletViewModel by viewModels()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
-        val toolbar: Toolbar = findViewById(R.id.toolbar)
-        setSupportActionBar(toolbar)
 
-        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
-        val navView: NavigationView = findViewById(R.id.nav_view)
-
-        navView.menu.getItem(0).isChecked = true
+        nav_view.menu.getItem(0).isChecked = true
+        nav_view.setNavigationItemSelectedListener(this)
 
-        val fab: FloatingActionButton = findViewById(R.id.fab)
         fab.setOnClickListener {
             val integrator = IntentIntegrator(this)
             integrator.setPrompt("Place merchant's QR Code inside the 
viewfinder rectangle to initiate payment.")
@@ -68,24 +62,21 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         }
         fab.hide()
 
-        navView.setNavigationItemSelectedListener(this)
-
+        setSupportActionBar(toolbar)
         val navController = findNavController(R.id.nav_host_fragment)
-
-        val appBarConfiguration =
-            AppBarConfiguration(setOf(R.id.showBalance, R.id.settings, 
R.id.walletHistory), drawerLayout)
-
-        findViewById<Toolbar>(R.id.toolbar)
-            .setupWithNavController(navController, appBarConfiguration)
-
-        model = ViewModelProviders.of(this)[WalletViewModel::class.java]
-
-        val progressBar = findViewById<MaterialProgressBar>(R.id.progress_bar)
-        progressBar.visibility = View.INVISIBLE
+        val appBarConfiguration = AppBarConfiguration(
+            setOf(R.id.showBalance, R.id.settings, R.id.walletHistory),
+            drawer_layout
+        )
+        toolbar.setupWithNavController(navController, appBarConfiguration)
 
         model.init()
         model.getBalances()
 
+        model.showProgressBar.observe(this, Observer { show ->
+            progress_bar.visibility = if (show) VISIBLE else INVISIBLE
+        })
+
         val triggerPaymentFilter = 
IntentFilter(HostCardEmulatorService.TRIGGER_PAYMENT_ACTION)
         registerReceiver(object : BroadcastReceiver() {
             override fun onReceive(p0: Context?, p1: Intent?) {
@@ -97,7 +88,7 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
                 val url = p1!!.extras!!.get("contractUrl") as String
 
                 
findNavController(R.id.nav_host_fragment).navigate(R.id.action_global_promptPayment)
-                model.preparePay(url)
+                model.paymentManager.preparePay(url)
 
             }
         }, triggerPaymentFilter)
@@ -188,12 +179,7 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         val scanResult: IntentResult? = 
IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
 
         if (scanResult == null || scanResult.contents == null) {
-            val bar: Snackbar = Snackbar.make(
-                findViewById(R.id.nav_host_fragment),
-                "QR Code scan canceled.",
-                Snackbar.LENGTH_SHORT
-            )
-            bar.show()
+            Snackbar.make(nav_view, "QR Code scan canceled.", 
LENGTH_SHORT).show()
             return
         }
 
@@ -203,35 +189,37 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
 
     private fun handleTalerUri(url: String, from: String) {
         when {
-            url.toLowerCase(Locale.ROOT).startsWith("taler://pay/") -> {
+            url.toLowerCase(ROOT).startsWith("taler://pay/") -> {
                 Log.v(TAG, "navigating!")
                 
findNavController(R.id.nav_host_fragment).navigate(R.id.action_showBalance_to_promptPayment)
-                model.preparePay(url)
+                model.paymentManager.preparePay(url)
             }
-            url.toLowerCase(Locale.ROOT).startsWith("taler://withdraw/") -> {
+            url.toLowerCase(ROOT).startsWith("taler://withdraw/") -> {
                 Log.v(TAG, "navigating!")
                 
findNavController(R.id.nav_host_fragment).navigate(R.id.action_showBalance_to_promptWithdraw)
                 model.getWithdrawalInfo(url)
             }
+            url.toLowerCase(ROOT).startsWith("taler://refund/") -> {
+                // TODO implement refunds
+                Snackbar.make(nav_view, "Refunds are not yet implemented", 
LENGTH_SHORT).show()
+            }
             else -> {
-                val bar: Snackbar = Snackbar.make(
-                    findViewById(R.id.nav_host_fragment),
-                    "URL from $from doesn't contain Taler payment.",
-                    Snackbar.LENGTH_SHORT
-                )
-                bar.show()
+                Snackbar.make(
+                    nav_view,
+                    "URL from $from doesn't contain a supported Taler Uri.",
+                    LENGTH_SHORT
+                ).show()
             }
         }
     }
 
     override fun onResetConfirmed() {
         model.dangerouslyReset()
-        val snackbar = Snackbar.make(findViewById(R.id.nav_host_fragment), 
"Wallet has been reset", Snackbar.LENGTH_SHORT)
-        snackbar.show()
+        Snackbar.make(nav_view, "Wallet has been reset", LENGTH_SHORT).show()
     }
 
     override fun onResetCancelled() {
-        val snackbar = Snackbar.make(findViewById(R.id.nav_host_fragment), 
"Reset cancelled", Snackbar.LENGTH_SHORT)
-        snackbar.show()
+        Snackbar.make(nav_view, "Reset cancelled", LENGTH_SHORT).show()
     }
+
 }
diff --git a/app/src/main/java/net/taler/wallet/PromptPayment.kt 
b/app/src/main/java/net/taler/wallet/PromptPayment.kt
deleted file mode 100644
index aa7512d..0000000
--- a/app/src/main/java/net/taler/wallet/PromptPayment.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 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 android.util.Log
-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
-
-/**
- * Show a payment and ask the user to accept/decline.
- */
-class PromptPayment : Fragment() {
-
-    lateinit var model: WalletViewModel
-
-    var fragmentView: View? = null
-
-    private fun triggerLoading() {
-        val loading = model.payStatus.value == null || (model.payStatus.value 
is PayStatus.Loading)
-        val progressBar = 
requireActivity().findViewById<MaterialProgressBar>(R.id.progress_bar)
-        if (loading) {
-            progressBar.visibility = View.VISIBLE
-        } else {
-            progressBar.visibility = View.INVISIBLE
-        }
-    }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        model = activity?.run {
-            ViewModelProviders.of(this)[WalletViewModel::class.java]
-        } ?: throw Exception("Invalid Activity")
-    }
-
-    override fun onResume() {
-        super.onResume()
-        Log.v("taler-wallet", "called onResume on PromptPayment")
-        triggerLoading()
-    }
-
-    private fun fillOrderInfo(view: View, contractTerms: ContractTerms, 
totalFees: Amount?) {
-        val feesAmountView = 
view.findViewById<TextView>(R.id.order_fees_amount)
-        val amountView = view.findViewById<TextView>(R.id.order_amount)
-        val summaryView = view.findViewById<TextView>(R.id.order_summary)
-        summaryView.text = contractTerms.summary
-        val amount = contractTerms.amount
-        @SuppressLint("SetTextI18n")
-        amountView.text = "${amount.amount} ${amount.currency}"
-        val feesBox = view.findViewById<View>(R.id.order_fees_box)
-        if (totalFees != null) {
-            @SuppressLint("SetTextI18n")
-            feesAmountView.text = "${totalFees.amount} ${totalFees.currency}"
-            feesBox.visibility = View.VISIBLE
-        } else {
-            feesBox.visibility = View.INVISIBLE
-        }
-
-    }
-
-
-    private fun showPayStatus(view: View, payStatus: PayStatus) {
-        val promptPaymentDetails = 
view.findViewById<View>(R.id.prompt_payment_details)
-        val balanceInsufficientWarning = 
view.findViewById<View>(R.id.balance_insufficient_warning)
-        val errorTextView = view.findViewById<TextView>(R.id.pay_error_text)
-        val confirmPaymentButton = 
view.findViewById<Button>(R.id.button_confirm_payment)
-        when (payStatus) {
-            is PayStatus.Prepared -> {
-                fillOrderInfo(view, payStatus.contractTerms, 
payStatus.totalFees)
-                promptPaymentDetails.visibility = View.VISIBLE
-                balanceInsufficientWarning.visibility = View.GONE
-                errorTextView.visibility = View.GONE
-                confirmPaymentButton.isEnabled = true
-
-                confirmPaymentButton.setOnClickListener {
-                    model.confirmPay(payStatus.proposalId)
-                    confirmPaymentButton.isEnabled = false
-                }
-            }
-            is PayStatus.InsufficientBalance -> {
-                fillOrderInfo(view, payStatus.contractTerms, null)
-                promptPaymentDetails.visibility = View.VISIBLE
-                balanceInsufficientWarning.visibility = View.VISIBLE
-                errorTextView.visibility = View.GONE
-                confirmPaymentButton.isEnabled = false
-            }
-            is PayStatus.Success -> {
-                model.payStatus.value = PayStatus.None()
-                
activity!!.findNavController(R.id.nav_host_fragment).navigate(R.id.action_promptPayment_to_paymentSuccessful)
-            }
-            is PayStatus.AlreadyPaid -> {
-                
activity!!.findNavController(R.id.nav_host_fragment).navigate(R.id.action_promptPayment_to_alreadyPaid)
-                model.payStatus.value = PayStatus.None()
-            }
-            is PayStatus.Error -> {
-                errorTextView.visibility = View.VISIBLE
-                errorTextView.text = "Error: ${payStatus.error}"
-            }
-            is PayStatus.None -> {
-                // No payment active.
-            }
-            is PayStatus.Loading -> {
-                // Wait until loaded ...
-            }
-            else -> {
-                val bar = Snackbar.make(view , "Bug: Unexpected result", 
Snackbar.LENGTH_SHORT)
-                bar.show()
-            }
-        }
-    }
-
-
-    override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // Inflate the layout for this fragment
-
-        val view = inflater.inflate(R.layout.fragment_prompt_payment, 
container, false)
-        fragmentView = view
-
-        val promptPaymentDetails = 
view.findViewById<View>(R.id.prompt_payment_details)
-        // Set invisible until data is loaded.
-        promptPaymentDetails.visibility = View.INVISIBLE
-
-        val abortPaymentButton = 
view.findViewById<Button>(R.id.button_abort_payment)
-
-        val errorTextView = view.findViewById<TextView>(R.id.pay_error_text)
-        errorTextView.visibility = View.GONE
-
-        abortPaymentButton.setOnClickListener {
-            when (val ps = model.payStatus.value) {
-                is PayStatus.Prepared -> {
-                    model.abortProposal(ps.proposalId)
-                }
-            }
-            model.payStatus.value = PayStatus.None()
-            
requireActivity().findNavController(R.id.nav_host_fragment).navigateUp()
-        }
-
-        triggerLoading()
-
-        model.payStatus.observe(viewLifecycleOwner, Observer {
-            triggerLoading()
-            showPayStatus(view, it)
-        })
-        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 c4c96d6..1a31b86 100644
--- a/app/src/main/java/net/taler/wallet/ShowBalance.kt
+++ b/app/src/main/java/net/taler/wallet/ShowBalance.kt
@@ -19,7 +19,12 @@ package net.taler.wallet
 
 import android.os.Bundle
 import android.util.Log
-import android.view.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
 import android.widget.Button
 import android.widget.LinearLayout
 import android.widget.TextView
@@ -320,7 +325,7 @@ class ShowBalance : Fragment(), 
PendingOperationClickListener {
                 if (proposalId == "") {
                     return
                 }
-                model.abortProposal(proposalId)
+                model.paymentManager.abortProposal(proposalId)
             }
         }
     }
diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt 
b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
index ad41e77..af1037e 100644
--- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt
+++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
@@ -18,7 +18,11 @@ package net.taler.wallet
 
 import android.app.Application
 import android.util.Log
-import androidx.lifecycle.*
+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
@@ -31,6 +35,7 @@ import kotlinx.coroutines.flow.onStart
 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"
@@ -41,23 +46,6 @@ data class BalanceEntry(val available: Amount, val 
pendingIncoming: Amount)
 
 data class WalletBalances(val initialized: Boolean, val byCurrency: 
List<BalanceEntry>)
 
-data class ContractTerms(val summary: String, val amount: Amount)
-
-open class PayStatus {
-    class None : PayStatus()
-    class Loading : PayStatus()
-    data class Prepared(
-        val contractTerms: ContractTerms,
-        val proposalId: String,
-        val totalFees: Amount
-    ) : PayStatus()
-
-    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
-    data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
-    data class Error(val error: String) : PayStatus()
-    class Success : PayStatus()
-}
-
 open class WithdrawStatus {
     class None : WithdrawStatus()
     data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
@@ -100,10 +88,6 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
         value = WalletBalances(false, listOf())
     }
 
-    val payStatus = MutableLiveData<PayStatus>().apply {
-        value = PayStatus.None()
-    }
-
     val withdrawStatus = MutableLiveData<WithdrawStatus>().apply {
         value = WithdrawStatus.None()
     }
@@ -124,13 +108,16 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
             .asLiveData(Dispatchers.IO)
     }
 
+    val showProgressBar = MutableLiveData<Boolean>()
+
     private var activeGetBalance = 0
     private var activeGetPending = 0
 
-    private var currentPayRequestId = 0
     private var currentWithdrawRequestId = 0
 
     private val walletBackendApi = WalletBackendApi(app)
+    val paymentManager = PaymentManager(walletBackendApi)
+
     private val mapper = ObjectMapper()
         .registerModule(KotlinModule())
         .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
@@ -241,83 +228,6 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
         }
     }
 
-
-    fun preparePay(url: String) {
-        val args = JSONObject()
-        args.put("url", url)
-
-        this.currentPayRequestId += 1
-        val myPayRequestId = this.currentPayRequestId
-        this.payStatus.value = PayStatus.Loading()
-
-        walletBackendApi.sendRequest("preparePay", args) { isError, result ->
-            if (isError) {
-                Log.v(TAG, "got preparePay error result")
-                payStatus.value = PayStatus.Error(result.toString(0))
-                return@sendRequest
-            }
-            Log.v(TAG, "got preparePay result")
-            if (myPayRequestId != this.currentPayRequestId) {
-                Log.v(TAG, "preparePay result was for old request")
-                return@sendRequest
-            }
-            val status = result.getString("status")
-            var contractTerms: ContractTerms? = null
-            var proposalId: String? = null
-            var totalFees: Amount? = null
-            if (result.has("proposalId")) {
-                proposalId = result.getString("proposalId")
-            }
-            if (result.has("contractTermsRaw")) {
-                val ctJson = JSONObject(result.getString("contractTermsRaw"))
-                val amount = Amount.fromString(ctJson.getString("amount"))
-                val summary = ctJson.getString("summary")
-                contractTerms = ContractTerms(summary, amount)
-            }
-            if (result.has("totalFees")) {
-                totalFees = Amount.fromJson(result.getJSONObject("totalFees"))
-            }
-            val res = when (status) {
-                "payment-possible" -> PayStatus.Prepared(
-                    contractTerms!!,
-                    proposalId!!,
-                    totalFees!!
-                )
-                "paid" -> PayStatus.AlreadyPaid(contractTerms!!)
-                "insufficient-balance" -> PayStatus.InsufficientBalance(
-                    contractTerms!!
-                )
-                "error" -> PayStatus.Error("got some error")
-                else -> PayStatus.Error("unknown status")
-            }
-            payStatus.postValue(res)
-        }
-    }
-
-    fun abortProposal(proposalId: String) {
-        val args = JSONObject()
-        args.put("proposalId", proposalId)
-
-        Log.i(TAG, "aborting proposal")
-
-        walletBackendApi.sendRequest("abortProposal", args) { isError, _ ->
-            if (isError) {
-                Log.e(TAG, "received error response to abortProposal")
-                return@sendRequest
-            }
-            payStatus.postValue(PayStatus.None())
-        }
-    }
-
-    fun confirmPay(proposalId: String) {
-        val args = JSONObject()
-        args.put("proposalId", proposalId)
-
-        walletBackendApi.sendRequest("confirmPay", args) { isError, result ->
-            payStatus.postValue(PayStatus.Success())
-        }
-    }
-
     fun dangerouslyReset() {
         walletBackendApi.sendRequest("reset", null)
         testWithdrawalInProgress.value = false
diff --git a/app/src/main/java/net/taler/wallet/AlreadyPaid.kt 
b/app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
similarity index 70%
rename from app/src/main/java/net/taler/wallet/AlreadyPaid.kt
rename to app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
index 65785b9..62d90a5 100644
--- a/app/src/main/java/net/taler/wallet/AlreadyPaid.kt
+++ b/app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
@@ -14,33 +14,34 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet
+package net.taler.wallet.payment
 
 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_already_paid.*
+import net.taler.wallet.R
 
 /**
  * Display the message that the user already paid for the order
  * that the merchant is proposing.
  */
-class AlreadyPaid : Fragment() {
+class AlreadyPaidFragment : Fragment() {
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        // Inflate the layout for this fragment
-        val view = inflater.inflate(R.layout.fragment_already_paid, container, 
false)
-        view.findViewById<Button>(R.id.button_success_back).setOnClickListener 
{
-            activity!!.findNavController(R.id.nav_host_fragment).navigateUp()
-        }
-        return view
+        return inflater.inflate(R.layout.fragment_already_paid, container, 
false)
     }
 
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        button_success_back.setOnClickListener {
+            findNavController().navigateUp()
+        }
+    }
 
 }
diff --git a/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt 
b/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt
new file mode 100644
index 0000000..2e40250
--- /dev/null
+++ b/app/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -0,0 +1,108 @@
+package net.taler.wallet.payment
+
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import net.taler.wallet.Amount
+import net.taler.wallet.TAG
+import net.taler.wallet.backend.WalletBackendApi
+import org.json.JSONObject
+
+class PaymentManager(private val walletBackendApi: WalletBackendApi) {
+
+    private val mPayStatus = MutableLiveData<PayStatus>(PayStatus.None)
+    internal val payStatus: LiveData<PayStatus> = mPayStatus
+
+    private var currentPayRequestId = 0
+
+    @UiThread
+    fun preparePay(url: String) {
+        mPayStatus.value = PayStatus.Loading
+
+        val args = JSONObject(mapOf("url" to url))
+
+        currentPayRequestId += 1
+        val payRequestId = currentPayRequestId
+
+        walletBackendApi.sendRequest("preparePay", args) { isError, result ->
+            when {
+                isError -> {
+                    Log.v(TAG, "got preparePay error result")
+                    mPayStatus.value = PayStatus.Error(result.toString())
+                }
+                payRequestId != this.currentPayRequestId -> {
+                    Log.v(TAG, "preparePay result was for old request")
+                }
+                else -> {
+                    val status = result.getString("status")
+                    mPayStatus.postValue(getPayStatusUpdate(status, result))
+                }
+            }
+        }
+    }
+
+    private fun getPayStatusUpdate(status: String, json: JSONObject) = when 
(status) {
+        "payment-possible" -> PayStatus.Prepared(
+            contractTerms = getContractTerms(json),
+            proposalId = json.getString("proposalId"),
+            totalFees = Amount.fromJson(json.getJSONObject("totalFees"))
+        )
+        "paid" -> PayStatus.AlreadyPaid(getContractTerms(json))
+        "insufficient-balance" -> 
PayStatus.InsufficientBalance(getContractTerms(json))
+        "error" -> PayStatus.Error("got some error")
+        else -> PayStatus.Error("unknown status")
+    }
+
+    private fun getContractTerms(json: JSONObject): ContractTerms {
+        val ctJson = JSONObject(json.getString("contractTermsRaw"))
+        val amount = Amount.fromString(ctJson.getString("amount"))
+        val summary = ctJson.getString("summary")
+        return ContractTerms(summary, amount)
+    }
+
+    fun confirmPay(proposalId: String) {
+        val args = JSONObject(mapOf("proposalId" to proposalId))
+
+        walletBackendApi.sendRequest("confirmPay", args) { _, _ ->
+            mPayStatus.postValue(PayStatus.Success)
+        }
+    }
+
+    fun abortProposal(proposalId: String) {
+        val args = JSONObject(mapOf("proposalId" to proposalId))
+
+        Log.i(TAG, "aborting proposal")
+
+        walletBackendApi.sendRequest("abortProposal", args) { isError, _ ->
+            if (isError) {
+                Log.e(TAG, "received error response to abortProposal")
+                return@sendRequest
+            }
+            mPayStatus.postValue(PayStatus.None)
+        }
+    }
+
+    @UiThread
+    fun resetPayStatus() {
+        mPayStatus.value = PayStatus.None
+    }
+
+}
+
+sealed class PayStatus {
+    object None : PayStatus()
+    object Loading : PayStatus()
+    data class Prepared(
+        val contractTerms: ContractTerms,
+        val proposalId: String,
+        val totalFees: Amount
+    ) : PayStatus()
+
+    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
+    data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
+    data class Error(val error: String) : PayStatus()
+    object Success : PayStatus()
+}
+
+data class ContractTerms(val summary: String, val amount: Amount)
diff --git a/app/src/main/java/net/taler/wallet/PaymentSuccessful.kt 
b/app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
similarity index 68%
rename from app/src/main/java/net/taler/wallet/PaymentSuccessful.kt
rename to 
app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
index 6332c39..608abfd 100644
--- a/app/src/main/java/net/taler/wallet/PaymentSuccessful.kt
+++ b/app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt
@@ -14,33 +14,33 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-
-package net.taler.wallet
+package net.taler.wallet.payment
 
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.widget.Button
 import androidx.fragment.app.Fragment
-import androidx.navigation.findNavController
-
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_payment_successful.*
+import net.taler.wallet.R
 
 /**
  * Fragment that shows the success message for a payment.
- *
  */
-class PaymentSuccessful : Fragment() {
+class PaymentSuccessfulFragment : Fragment() {
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        // Inflate the layout for this fragment
-        val view = inflater.inflate(R.layout.fragment_payment_successful, 
container, false)
-        view.findViewById<Button>(R.id.button_success_back).setOnClickListener 
{
-            activity!!.findNavController(R.id.nav_host_fragment).navigateUp()
+        return inflater.inflate(R.layout.fragment_payment_successful, 
container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        button_success_back.setOnClickListener {
+            findNavController().navigateUp()
         }
-        return view
     }
+
 }
diff --git 
a/app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt 
b/app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
new file mode 100644
index 0000000..4b4bf01
--- /dev/null
+++ b/app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -0,0 +1,136 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 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.payment
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.observe
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_prompt_payment.*
+import net.taler.wallet.Amount
+import net.taler.wallet.R
+import net.taler.wallet.WalletViewModel
+
+/**
+ * Show a payment and ask the user to accept/decline.
+ */
+class PromptPaymentFragment : Fragment() {
+
+    private val model: WalletViewModel by activityViewModels()
+    private val paymentManager by lazy { model.paymentManager }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_prompt_payment, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        paymentManager.payStatus.observe(viewLifecycleOwner, 
this::onPaymentStatusChanged)
+
+        button_abort_payment.setOnClickListener {
+            when (val ps = paymentManager.payStatus.value) {
+                is PayStatus.Prepared -> {
+                    paymentManager.abortProposal(ps.proposalId)
+                }
+            }
+            paymentManager.resetPayStatus()
+            findNavController().navigateUp()
+        }
+    }
+
+    private fun showLoading(show: Boolean) {
+        model.showProgressBar.value = show
+    }
+
+    private fun onPaymentStatusChanged(payStatus: PayStatus) {
+        when (payStatus) {
+            is PayStatus.Prepared -> {
+                showLoading(false)
+                showOrder(payStatus.contractTerms, payStatus.totalFees)
+                button_confirm_payment.isEnabled = true
+                button_confirm_payment.setOnClickListener {
+                    showLoading(true)
+                    paymentManager.confirmPay(payStatus.proposalId)
+                    button_confirm_payment.isEnabled = false
+                }
+            }
+            is PayStatus.InsufficientBalance -> {
+                showLoading(false)
+                showOrder(payStatus.contractTerms, null)
+                error_text.setText(R.string.payment_balance_insufficient)
+                fadeInView(error_text)
+            }
+            is PayStatus.Success -> {
+                showLoading(false)
+                paymentManager.resetPayStatus()
+                
findNavController().navigate(R.id.action_promptPayment_to_paymentSuccessful)
+            }
+            is PayStatus.AlreadyPaid -> {
+                showLoading(false)
+                paymentManager.resetPayStatus()
+                
findNavController().navigate(R.id.action_promptPayment_to_alreadyPaid)
+            }
+            is PayStatus.Error -> {
+                showLoading(false)
+                error_text.text = getString(R.string.payment_error, 
payStatus.error)
+                fadeInView(error_text)
+            }
+            is PayStatus.None -> {
+                // No payment active.
+                showLoading(false)
+            }
+            is PayStatus.Loading -> {
+                // Wait until loaded ...
+                showLoading(true)
+            }
+        }
+    }
+
+    private fun showOrder(contractTerms: ContractTerms, totalFees: Amount?) {
+        order_summary.text = contractTerms.summary
+        val amount = contractTerms.amount
+        @SuppressLint("SetTextI18n")
+        order_amount.text = "${amount.amount} ${amount.currency}"
+        if (totalFees != null && !totalFees.isZero()) {
+            val fee = "${totalFees.amount} ${totalFees.currency}"
+            order_fees_amount.text = getString(R.string.payment_fee, fee)
+            fadeInView(order_fees_amount)
+        } else {
+            order_fees_amount.visibility = INVISIBLE
+        }
+        fadeInView(order_summary_label)
+        fadeInView(order_summary)
+        fadeInView(order_amount_label)
+        fadeInView(order_amount)
+    }
+
+    private fun fadeInView(v: View) {
+        v.alpha = 0f
+        v.visibility = VISIBLE
+        v.animate().alpha(1f).start()
+    }
+
+}
diff --git a/app/src/main/res/layout/app_bar_main.xml 
b/app/src/main/res/layout/app_bar_main.xml
index 7bf5acd..51d3242 100644
--- a/app/src/main/res/layout/app_bar_main.xml
+++ b/app/src/main/res/layout/app_bar_main.xml
@@ -23,15 +23,18 @@
                     android:layout_height="?attr/actionBarSize"
                     android:background="?attr/colorPrimary"
                     app:popupTheme="@style/AppTheme.PopupOverlay"/>
+
             <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"
                     app:mpb_progressStyle="horizontal"
                     app:mpb_useIntrinsicPadding="false"
-                    android:layout_alignParentBottom="true"
-                    
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"/>
+                    tools:visibility="visible" />
         </RelativeLayout>
 
     </com.google.android.material.appbar.AppBarLayout>
diff --git a/app/src/main/res/layout/fragment_already_paid.xml 
b/app/src/main/res/layout/fragment_already_paid.xml
index 69c949e..f2ff7e7 100644
--- a/app/src/main/res/layout/fragment_already_paid.xml
+++ b/app/src/main/res/layout/fragment_already_paid.xml
@@ -4,7 +4,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="15dp"
-        tools:context=".PaymentSuccessful">
+        tools:context=".payment.PaymentSuccessfulFragment">
 
 
     <LinearLayout
diff --git a/app/src/main/res/layout/fragment_payment_successful.xml 
b/app/src/main/res/layout/fragment_payment_successful.xml
index 64ddad9..af8bed2 100644
--- a/app/src/main/res/layout/fragment_payment_successful.xml
+++ b/app/src/main/res/layout/fragment_payment_successful.xml
@@ -4,7 +4,7 @@
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:layout_margin="15dp"
-             tools:context=".PaymentSuccessful">
+             tools:context=".payment.PaymentSuccessfulFragment">
 
 
     <LinearLayout
diff --git a/app/src/main/res/layout/fragment_prompt_payment.xml 
b/app/src/main/res/layout/fragment_prompt_payment.xml
index 1febf9f..8bc7b07 100644
--- a/app/src/main/res/layout/fragment_prompt_payment.xml
+++ b/app/src/main/res/layout/fragment_prompt_payment.xml
@@ -1,122 +1,120 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android";
-             xmlns:tools="http://schemas.android.com/tools";
-             android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             android:layout_margin="15dp"
-             tools:context=".PromptPayment">
+<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"
+        tools:context=".payment.PromptPaymentFragment">
 
     <TextView
-            android:text="Error: (placeholder)"
+            android:id="@+id/error_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textSize="15sp"
-            android:layout_gravity="center"
-            android:id="@+id/pay_error_text" 
android:textColor="@android:color/holo_red_dark"/>
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:textColor="@android:color/holo_red_dark"
+            android:textSize="22sp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/order_summary_label"
+            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:text="@string/payment_balance_insufficient"
+            tools:visibility="visible" />
 
+    <TextView
+            android:id="@+id/order_summary_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:text="@string/payment_label_order_summary"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/order_summary"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/error_text"
+            tools:visibility="visible" />
 
-    <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:id="@+id/prompt_payment_details">
-
-        <Space android:layout_width="match_parent"
-               android:layout_height="15dp"
-               android:layout_weight="1"/>
-
-        <TextView
-                android:text="Order Summary"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:id="@+id/textView2"/>
-
-        <TextView
-                android:text="One Cappuccino"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textSize="25sp"
-                android:layout_gravity="center"
-                android:id="@+id/order_summary"/>
-
-        <Space android:layout_width="match_parent"
-               android:layout_height="25dp"/>
-
-
-        <TextView
-                android:text="Amount"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:id="@+id/textView3"/>
-        <TextView
-                android:text="10 TESTKUDOS"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textSize="25sp"
-                android:layout_gravity="center"
-                android:id="@+id/order_amount"/>
-
-        <LinearLayout android:layout_width="wrap_content"
-                      android:layout_height="wrap_content"
-                      android:orientation="horizontal"
-                      android:layout_gravity="center"
-                      android:id="@+id/order_fees_box">
-
-            <TextView
-                    android:text="(plus additional "
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"/>
-
-            <TextView
-                    android:text="0.5 TESTKUDOS"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:id="@+id/order_fees_amount"/>
-
-            <TextView
-                    android:text=" payment fees)"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    />
-
-        </LinearLayout>
-
-        <Space android:layout_width="match_parent"
-               android:layout_height="15dp"
-               android:layout_weight="1"/>
-
-        <TextView
-                android:text="Balance Insufficient!"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textSize="25sp"
-                android:layout_gravity="center"
-                android:id="@+id/balance_insufficient_warning" 
android:textColor="@android:color/holo_red_dark"/>
-
-
-        <Space android:layout_width="match_parent"
-               android:layout_height="15dp"
-               android:layout_weight="1"/>
-
-        <LinearLayout android:layout_width="match_parent"
-                      android:layout_height="wrap_content"
-                      android:orientation="horizontal">
-            <Button android:layout_width="wrap_content" 
android:layout_height="wrap_content"
-                    android:text="Abort Payment"
-                    android:id="@+id/button_abort_payment"/>
+    <TextView
+            android:id="@+id/order_summary"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:layout_marginTop="16dp"
+            android:gravity="center_horizontal"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            android:textSize="25sp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/order_amount_label"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/order_summary_label"
+            tools:text="2 x Cappuccino, 1 x Hot Meals, 1 x Dessert"
+            tools:visibility="visible" />
 
-            <Space android:layout_width="15dp" 
android:layout_height="match_parent"
-                   android:layout_weight="1"/>
+    <TextView
+            android:id="@+id/order_amount_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:text="@string/payment_label_amount"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/order_amount"
+            app:layout_constraintEnd_toEndOf="@+id/order_summary"
+            app:layout_constraintStart_toStartOf="@+id/order_summary"
+            app:layout_constraintTop_toBottomOf="@+id/order_summary"
+            tools:visibility="visible" />
 
-            <Button android:layout_width="wrap_content" 
android:layout_height="wrap_content"
-                    android:id="@+id/button_confirm_payment"
-                    android:text="Confirm Payment"/>
-        </LinearLayout>
+    <TextView
+            android:id="@+id/order_amount"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/order_fees_amount"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/order_amount_label"
+            tools:text="10 TESTKUDOS"
+            tools:visibility="visible" />
 
-    </LinearLayout>
-</FrameLayout>
\ No newline at end of file
+    <TextView
+            android:id="@+id/order_fees_amount"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toTopOf="@+id/button_abort_payment"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/order_amount"
+            tools:text="@string/payment_fee"
+            tools:visibility="visible" />
+
+    <Button
+            android:id="@+id/button_abort_payment"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:text="@string/payment_button_abort"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/button_confirm_payment"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintStart_toStartOf="parent" />
+
+    <Button
+            android:id="@+id/button_confirm_payment"
+            style="@style/Widget.AppCompat.Button.Colored"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_horizontal_margin"
+            android:enabled="false"
+            android:text="@string/payment_button_confirm"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/button_abort_payment"
+            tools:enabled="true" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_prompt_withdraw.xml 
b/app/src/main/res/layout/fragment_prompt_withdraw.xml
index fc8046b..4cabfe8 100644
--- a/app/src/main/res/layout/fragment_prompt_withdraw.xml
+++ b/app/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -21,7 +21,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center"
-                android:id="@+id/textView2"/>
+                android:id="@+id/order_summary_label"/>
 
         <TextView
                 android:text="(amount)"
@@ -47,7 +47,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center"
-                android:id="@+id/textView3"/>
+                android:id="@+id/order_amount_label"/>
         <TextView
                 android:text="(exchange base url)"
                 android:layout_width="wrap_content"
diff --git a/app/src/main/res/navigation/nav_graph.xml 
b/app/src/main/res/navigation/nav_graph.xml
index f958b9c..0fa1b4a 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -19,7 +19,7 @@
     </fragment>
     <fragment
             android:id="@+id/promptPayment"
-            android:name="net.taler.wallet.PromptPayment"
+            android:name="net.taler.wallet.payment.PromptPaymentFragment"
             android:label="Review Payment"
             tools:layout="@layout/fragment_prompt_payment">
         <action
@@ -33,7 +33,7 @@
     </fragment>
     <fragment
             android:id="@+id/paymentSuccessful"
-            android:name="net.taler.wallet.PaymentSuccessful"
+            android:name="net.taler.wallet.payment.PaymentSuccessfulFragment"
             android:label="Payment Successful"
             tools:layout="@layout/fragment_payment_successful" />
     <fragment
@@ -48,7 +48,7 @@
             tools:layout="@layout/fragment_show_history" />
     <fragment
             android:id="@+id/alreadyPaid"
-            android:name="net.taler.wallet.AlreadyPaid"
+            android:name="net.taler.wallet.payment.AlreadyPaidFragment"
             android:label="Already Paid"
             tools:layout="@layout/fragment_already_paid" />
     <fragment
diff --git a/app/src/main/res/values/strings.xml 
b/app/src/main/res/values/strings.xml
index 39fd3a6..8602063 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,6 +36,12 @@
     <string name="history_reload">Reload History</string>
     <string name="history_empty">The wallet history is empty</string>
 
-    <!-- TODO: Remove or change this placeholder text -->
-    <string name="hello_blank_fragment">Hello blank fragment</string>
+    <string name="payment_fee">(plus an additional %s payment fee)</string>
+    <string name="payment_button_confirm">Confirm Payment</string>
+    <string name="payment_button_abort">Abort Payment</string>
+    <string name="payment_label_amount">Amount</string>
+    <string name="payment_label_order_summary">Order Summary</string>
+    <string name="payment_error">Error: %s</string>
+    <string name="payment_balance_insufficient">Balance Insufficient!</string>
+
 </resources>

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



reply via email to

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