summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-02-12 16:53:03 -0300
committerTorsten Grote <t@grobox.de>2020-02-12 17:09:09 -0300
commitada382885e9c103fe0795817a8585270a3079302 (patch)
tree3da610aa39d2c767307d974d54f2f4eb32be9188
parentc20a7945f2b37863264c3b9bdcc85454018bd4cd (diff)
downloadwallet-android-ada382885e9c103fe0795817a8585270a3079302.tar.gz
wallet-android-ada382885e9c103fe0795817a8585270a3079302.tar.bz2
wallet-android-ada382885e9c103fe0795817a8585270a3079302.zip
Refactor payment code to make it easier to extend
-rw-r--r--.idea/codeStyles/Project.xml2
-rw-r--r--app/build.gradle8
-rw-r--r--app/src/main/java/net/taler/wallet/MainActivity.kt94
-rw-r--r--app/src/main/java/net/taler/wallet/PromptPayment.kt174
-rw-r--r--app/src/main/java/net/taler/wallet/ShowBalance.kt9
-rw-r--r--app/src/main/java/net/taler/wallet/WalletViewModel.kt110
-rw-r--r--app/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt (renamed from app/src/main/java/net/taler/wallet/AlreadyPaid.kt)23
-rw-r--r--app/src/main/java/net/taler/wallet/payment/PaymentManager.kt108
-rw-r--r--app/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt (renamed from app/src/main/java/net/taler/wallet/PaymentSuccessful.kt)24
-rw-r--r--app/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt136
-rw-r--r--app/src/main/res/layout/app_bar_main.xml7
-rw-r--r--app/src/main/res/layout/fragment_already_paid.xml2
-rw-r--r--app/src/main/res/layout/fragment_payment_successful.xml2
-rw-r--r--app/src/main/res/layout/fragment_prompt_payment.xml222
-rw-r--r--app/src/main/res/layout/fragment_prompt_withdraw.xml4
-rw-r--r--app/src/main/res/navigation/nav_graph.xml6
-rw-r--r--app/src/main/res/values/strings.xml10
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
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
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>