gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-wallet-android] branch master updated: withdraw via


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-android] branch master updated: withdraw via QR code, various bug fixes
Date: Sun, 01 Sep 2019 18:33:06 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 6f963cd  withdraw via QR code, various bug fixes
6f963cd is described below

commit 6f963cdfa0e918dbe35c99dafdb648dfa0586d7b
Author: Florian Dold <address@hidden>
AuthorDate: Sun Sep 1 18:33:03 2019 +0200

    withdraw via QR code, various bug fixes
---
 app/build.gradle                                   |   2 +-
 app/src/main/java/net/taler/wallet/MainActivity.kt |  41 +++--
 .../main/java/net/taler/wallet/PromptPayment.kt    |   2 +-
 .../main/java/net/taler/wallet/PromptWithdraw.kt   | 101 +++++++++++
 app/src/main/java/net/taler/wallet/ShowBalance.kt  |  38 +++--
 .../main/java/net/taler/wallet/WalletViewModel.kt  | 162 ++++++++++++++----
 .../java/net/taler/wallet/WithdrawSuccessful.kt    |  26 +++
 app/src/main/res/drawable/ic_scan_qr.xml           | 186 +++++++++++++++++++++
 app/src/main/res/layout/app_bar_main.xml           |  10 ++
 app/src/main/res/layout/balance_row.xml            |  40 +++++
 .../main/res/layout/fragment_prompt_withdraw.xml   |  79 +++++++++
 app/src/main/res/layout/fragment_show_balance.xml  |   7 +-
 .../res/layout/fragment_withdraw_successful.xml    |  53 ++++++
 app/src/main/res/navigation/nav_graph.xml          |  18 ++
 14 files changed, 693 insertions(+), 72 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 0651d3d..4f8ea09 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ android {
     buildToolsVersion "29.0.1"
     defaultConfig {
         applicationId "net.taler.wallet"
-        minSdkVersion 28
+        minSdkVersion 18
         targetSdkVersion 29
         versionCode 1
         versionName "1.0"
diff --git a/app/src/main/java/net/taler/wallet/MainActivity.kt 
b/app/src/main/java/net/taler/wallet/MainActivity.kt
index 44cd8a6..3d07821 100644
--- a/app/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/app/src/main/java/net/taler/wallet/MainActivity.kt
@@ -18,6 +18,7 @@ import androidx.lifecycle.ViewModelProviders
 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.snackbar.Snackbar
 import com.google.zxing.integration.android.IntentIntegrator
@@ -41,6 +42,14 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
 
         navView.menu.getItem(0).isChecked = true
 
+        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.")
+            integrator.initiateScan(listOf("QR_CODE"))
+        }
+        fab.hide()
+
 
         navView.setNavigationItemSelectedListener(this)
 
@@ -166,20 +175,26 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         }
 
         val url = scanResult.contents!!
-        if (!url.startsWith("talerpay:")) {
-            val bar: Snackbar = Snackbar.make(
-                findViewById(R.id.nav_host_fragment),
-                "Scanned QR code doesn't contain Taler payment.",
-                Snackbar.LENGTH_SHORT
-            )
-            bar.show()
-            return
+        when {
+            url.startsWith("taler://pay") -> {
+                Log.v(TAG, "navigating!")
+                
findNavController(R.id.nav_host_fragment).navigate(R.id.action_showBalance_to_promptPayment)
+                model.preparePay(url)
+            }
+            url.startsWith("taler://withdraw") -> {
+                Log.v(TAG, "navigating!")
+                
findNavController(R.id.nav_host_fragment).navigate(R.id.action_showBalance_to_promptWithdraw)
+                model.getWithdrawalInfo(url)
+            }
+            else -> {
+                val bar: Snackbar = Snackbar.make(
+                    findViewById(R.id.nav_host_fragment),
+                    "Scanned QR code doesn't contain Taler payment.",
+                    Snackbar.LENGTH_SHORT
+                )
+                bar.show()
+            }
         }
-
-        Log.v(TAG, "navigating!")
-
-        
findNavController(R.id.nav_host_fragment).navigate(R.id.action_showBalance_to_promptPayment)
-        model.preparePay(url)
     }
 
 
diff --git a/app/src/main/java/net/taler/wallet/PromptPayment.kt 
b/app/src/main/java/net/taler/wallet/PromptPayment.kt
index 07d3dd2..1d828e3 100644
--- a/app/src/main/java/net/taler/wallet/PromptPayment.kt
+++ b/app/src/main/java/net/taler/wallet/PromptPayment.kt
@@ -32,7 +32,7 @@ class PromptPayment : Fragment() {
 
     var fragmentView: View? = null
 
-    fun triggerLoading() {
+    private fun triggerLoading() {
         val loading = model.payStatus.value == null || (model.payStatus.value 
is PayStatus.Loading)
         val myActivity = activity!!
         val progressBar = 
myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar)
diff --git a/app/src/main/java/net/taler/wallet/PromptWithdraw.kt 
b/app/src/main/java/net/taler/wallet/PromptWithdraw.kt
new file mode 100644
index 0000000..785da42
--- /dev/null
+++ b/app/src/main/java/net/taler/wallet/PromptWithdraw.kt
@@ -0,0 +1,101 @@
+package net.taler.wallet
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import androidx.navigation.findNavController
+import com.google.android.material.snackbar.Snackbar
+import me.zhanghai.android.materialprogressbar.MaterialProgressBar
+
+
+class PromptWithdraw : Fragment() {
+
+    private lateinit var model: WalletViewModel
+
+    private fun triggerLoading() {
+        val loading =
+            model.withdrawStatus.value is WithdrawStatus.Loading || 
model.withdrawStatus.value is WithdrawStatus.Withdrawing
+        val myActivity = activity!!
+        val progressBar = 
myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar)
+        if (loading) {
+            progressBar.visibility = View.VISIBLE
+        } else {
+            progressBar.visibility = View.INVISIBLE
+        }
+    }
+
+    private fun showWithdrawStatus(view: View, status: WithdrawStatus) {
+        val confirmButton = 
view.findViewById<Button>(R.id.button_confirm_withdraw)
+        val promptWithdraw = view.findViewById<View>(R.id.prompt_withdraw)
+        when (status) {
+            is WithdrawStatus.ReceivedDetails -> {
+                promptWithdraw.visibility = View.VISIBLE
+                confirmButton.isEnabled = true
+                val promptWithdraw = 
view.findViewById<View>(R.id.prompt_withdraw)
+                promptWithdraw.visibility = View.VISIBLE
+                val amountView = 
view.findViewById<TextView>(R.id.withdraw_amount)
+                val exchangeView = 
view.findViewById<TextView>(R.id.withdraw_exchange)
+                exchangeView.text = status.suggestedExchange
+                @SuppressLint("SetTextI18n")
+                amountView.text = "${status.amount.amount} 
${status.amount.currency}"
+            }
+            is WithdrawStatus.Success -> {
+                this.model.withdrawStatus.value = WithdrawStatus.None()
+                activity!!.findNavController(R.id.nav_host_fragment)
+                    .navigate(R.id.action_promptWithdraw_to_withdrawSuccessful)
+            }
+            is WithdrawStatus.Loading -> {
+                promptWithdraw.visibility = View.INVISIBLE
+                // Wait
+            }
+            is WithdrawStatus.Withdrawing -> {
+                confirmButton.isEnabled = false
+
+            }
+            is WithdrawStatus.None -> {
+
+            }
+            else -> {
+                val bar = Snackbar.make(view, "Bug: Unexpected result", 
Snackbar.LENGTH_SHORT)
+                bar.show()
+            }
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        model = activity?.run {
+            ViewModelProviders.of(this)[WalletViewModel::class.java]
+        } ?: throw Exception("Invalid Activity")
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        val view = inflater.inflate(R.layout.fragment_prompt_withdraw, 
container, false)
+
+        this.model.withdrawStatus.observe(this, Observer {
+            triggerLoading()
+            showWithdrawStatus(view, it)
+        })
+
+        
view.findViewById<Button>(R.id.button_confirm_withdraw).setOnClickListener {
+            val status = this.model.withdrawStatus.value
+            if (status !is WithdrawStatus.ReceivedDetails) {
+                return@setOnClickListener
+            }
+            model.acceptWithdrawal(status.talerWithdrawUri, 
status.suggestedExchange)
+        }
+
+        return view
+    }
+}
diff --git a/app/src/main/java/net/taler/wallet/ShowBalance.kt 
b/app/src/main/java/net/taler/wallet/ShowBalance.kt
index ba1bf14..a45d4fa 100644
--- a/app/src/main/java/net/taler/wallet/ShowBalance.kt
+++ b/app/src/main/java/net/taler/wallet/ShowBalance.kt
@@ -1,7 +1,6 @@
 package net.taler.wallet
 
 
-import android.app.Activity
 import android.os.Bundle
 import android.util.Log
 import androidx.fragment.app.Fragment
@@ -15,7 +14,6 @@ import androidx.lifecycle.ViewModelProviders
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.snackbar.Snackbar
 import com.google.zxing.integration.android.IntentIntegrator
 import me.zhanghai.android.materialprogressbar.MaterialProgressBar
 
@@ -35,10 +33,22 @@ class MyAdapter(private var myDataset: WalletBalances) : 
RecyclerView.Adapter<My
     }
 
     override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
+        val amount = myDataset.byCurrency[position].available
+        val amountIncoming = myDataset.byCurrency[position].pendingIncoming
         val currencyView = 
holder.rowView.findViewById<TextView>(R.id.balance_currency)
-        currencyView.text = myDataset.byCurrency[position].currency
+        currencyView.text = amount.currency
         val amountView = 
holder.rowView.findViewById<TextView>(R.id.balance_amount)
-        amountView.text = myDataset.byCurrency[position].amount
+        amountView.text = amount.amount
+
+        val amountIncomingRow = 
holder.rowView.findViewById<View>(R.id.balance_row_pending)
+
+        val amountIncomingView = 
holder.rowView.findViewById<TextView>(R.id.balance_pending)
+        if (amountIncoming.isZero()) {
+            amountIncomingRow.visibility = View.GONE
+        } else {
+            amountIncomingRow.visibility = View.VISIBLE
+            amountIncomingView.text = "${amountIncoming.amount} 
${amountIncoming.currency}"
+        }
     }
 
     fun update(updatedBalances: WalletBalances) {
@@ -63,7 +73,7 @@ class ShowBalance : Fragment() {
 
     fun triggerLoading() {
         val loading: Boolean =
-            (model.isBalanceLoading.value == true) || (model.balances.value == 
null) || !model.balances.value!!.initialized
+            (model.testWithdrawalInProgress.value == true) || 
(model.balances.value == null) || !model.balances.value!!.initialized
 
         val myActivity = activity!!
         val progressBar = 
myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar)
@@ -87,11 +97,6 @@ class ShowBalance : Fragment() {
             ViewModelProviders.of(this)[WalletViewModel::class.java]
         } ?: throw Exception("Invalid Activity")
 
-
-        model.isBalanceLoading.observe(this, Observer { loading ->
-            Log.v("taler-wallet", "observing balance loading ${loading} in 
show balance")
-            triggerLoading()
-        })
     }
 
 
@@ -129,13 +134,6 @@ class ShowBalance : Fragment() {
             model.withdrawTestkudos()
         }
 
-        val payNfcButton = view.findViewById<Button>(R.id.button_pay_nfc)
-        payNfcButton.setOnClickListener {
-            val bar: Snackbar = Snackbar.make(view, "Sorry, NFC is not 
implemented yet!", Snackbar.LENGTH_SHORT)
-            bar.show()
-        }
-
-
         this.balancesView = view.findViewById(R.id.list_balances)
         this.balancesPlaceholderView = 
view.findViewById(R.id.list_balances_placeholder)
 
@@ -159,6 +157,12 @@ class ShowBalance : Fragment() {
             updateBalances(it)
         })
 
+        model.testWithdrawalInProgress.observe(this, Observer { loading ->
+            Log.v("taler-wallet", "observing balance loading ${loading} in 
show balance")
+            withdrawTestkudosButton.isEnabled = !loading
+            triggerLoading()
+        })
+
         return view
     }
 }
diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt 
b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
index f644c8d..356eb8a 100644
--- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt
+++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt
@@ -9,13 +9,16 @@ import android.util.Log
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.MutableLiveData
 import org.json.JSONObject
+import java.io.File
 import java.io.InputStream
 import java.lang.Exception
-import java.nio.file.Paths
 
 val TAG = "taler-wallet"
 
-class AssetModuleLoader(private val assetManager: AssetManager, private val 
rootPath: String = "node_modules") :
+class AssetModuleLoader(
+    private val assetManager: AssetManager,
+    private val rootPath: String = "node_modules"
+) :
     AkonoJni.LoadModuleHandler {
 
     private fun makeResult(localPath: String, stream: InputStream): 
ModuleResult {
@@ -26,7 +29,8 @@ class AssetModuleLoader(private val assetManager: 
AssetManager, private val root
     }
 
     private fun tryPath(rawAssetPath: String): ModuleResult? {
-        val assetPath = Paths.get(rawAssetPath).normalize().toString()
+        //val assetPath = Paths.get(rawAssetPath).normalize().toString()
+        val assetPath = File(rawAssetPath).normalize().path
         try {
             val moduleStream = assetManager.open(assetPath)
             return makeResult(assetPath, moduleStream)
@@ -54,13 +58,15 @@ class AssetModuleLoader(private val assetManager: 
AssetManager, private val root
             }
             Log.i(TAG, "main field is $mainFile")
             try {
-                val modPath = 
Paths.get("$assetPath/$mainFile").normalize().toString()
+                //val modPath = 
Paths.get("$assetPath/$mainFile").normalize().toString()
+                val modPath = File("$assetPath/$mainFile").normalize().path
                 return makeResult(modPath, assetManager.open(modPath))
             } catch (e: Exception) {
                 // ignore
             }
             try {
-                val modPath = 
Paths.get("$assetPath/$mainFile.js").normalize().toString()
+                //val modPath = 
Paths.get("$assetPath/$mainFile.js").normalize().toString()
+                val modPath = File("$assetPath/$mainFile.js").normalize().path
                 return makeResult(modPath, assetManager.open(modPath))
             } catch (e: Exception) {
             }
@@ -105,7 +111,8 @@ class AssetDataHandler(private val assetManager: 
AssetManager) : AkonoJni.GetDat
     override fun handleGetData(what: String): ByteArray? {
         if (what == "taler-emscripten-lib.wasm") {
             Log.i(TAG, "loading emscripten binary from taler-wallet")
-            val stream = 
assetManager.open("node_modules/taler-wallet/emscripten/taler-emscripten-lib.wasm")
+            val stream =
+                
assetManager.open("node_modules/taler-wallet/emscripten/taler-emscripten-lib.wasm")
             val bytes: ByteArray = stream.readBytes()
             Log.i(TAG, "size of emscripten binary: ${bytes.size}")
             return bytes
@@ -117,16 +124,24 @@ class AssetDataHandler(private val assetManager: 
AssetManager) : AkonoJni.GetDat
 }
 
 data class Amount(val currency: String, val amount: String) {
+    fun isZero(): Boolean {
+        return amount.toDouble() == 0.0
+    }
+
     companion object {
-        val FRACTIONAL_BASE = 1e8;
+        const val FRACTIONAL_BASE = 1e8;
         fun fromJson(jsonAmount: JSONObject): Amount {
             val amountCurrency = jsonAmount.getString("currency")
             val amountValue = jsonAmount.getString("value")
             val amountFraction = jsonAmount.getString("fraction")
             val amountIntValue = Integer.parseInt(amountValue)
             val amountIntFraction = Integer.parseInt(amountFraction)
-            return Amount(amountCurrency, (amountIntValue + amountIntFraction 
/ FRACTIONAL_BASE).toString())
+            return Amount(
+                amountCurrency,
+                (amountIntValue + amountIntFraction / 
FRACTIONAL_BASE).toString()
+            )
         }
+
         fun fromString(strAmount: String): Amount {
             val components = strAmount.split(":")
             return Amount(components[0], components[1])
@@ -134,40 +149,60 @@ data class Amount(val currency: String, val amount: 
String) {
     }
 }
 
+data class BalanceEntry(val available: Amount, val pendingIncoming: Amount)
 
-data class WalletBalances(val initialized: Boolean, val byCurrency: 
List<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: Int, 
val totalFees: Amount) : PayStatus()
+    data class Prepared(
+        val contractTerms: ContractTerms,
+        val proposalId: Int,
+        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()
+    class Success : WithdrawStatus()
+    data class ReceivedDetails(
+        val talerWithdrawUri: String,
+        val amount: Amount,
+        val suggestedExchange: String
+    ) : WithdrawStatus()
+
+    data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
+}
+
 
 class WalletViewModel(val app: Application) : AndroidViewModel(app) {
     private lateinit var myAkono: AkonoJni
     private var initialized = false
 
-    private var withdrawInProgress: Int = 0
-
-    val balances: MutableLiveData<WalletBalances> = MutableLiveData()
-
-    val isBalanceLoading: MutableLiveData<Boolean> = MutableLiveData()
+    val testWithdrawalInProgress: MutableLiveData<Boolean> = 
MutableLiveData<Boolean>().apply {
+        value = false
+    }
 
-    //val isProposalLoading: MutableLiveData<Boolean> = MutableLiveData()
+    val balances: MutableLiveData<WalletBalances> = 
MutableLiveData<WalletBalances>().apply {
+        value = WalletBalances(false, listOf())
+    }
 
-    val payStatus: MutableLiveData<PayStatus> = MutableLiveData()
+    val payStatus: MutableLiveData<PayStatus> = 
MutableLiveData<PayStatus>().apply {
+        value = PayStatus.None()
+    }
 
-    init {
-        isBalanceLoading.value = false
-        balances.value = WalletBalances(false, listOf())
-        payStatus.value = PayStatus.None()
+    val withdrawStatus: MutableLiveData<WithdrawStatus> = 
MutableLiveData<WithdrawStatus>().apply {
+        value = WithdrawStatus.None()
     }
 
     fun init() {
@@ -201,23 +236,50 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
                         Log.v(TAG, "got response for operation $operation")
                         when (operation) {
                             "withdrawTestkudos" -> {
-                                withdrawInProgress--
-                                if (withdrawInProgress == 0) {
-                                    isBalanceLoading.postValue(false)
-                                }
+                                testWithdrawalInProgress.postValue(false)
                             }
                             "getBalances" -> {
-                                val balanceList = mutableListOf<Amount>();
+                                val balanceList = 
mutableListOf<BalanceEntry>();
                                 val result = message.getJSONObject("result")
                                 val byCurrency = 
result.getJSONObject("byCurrency")
                                 val currencyList = 
byCurrency.keys().asSequence().toList().sorted()
                                 for (currency in currencyList) {
-                                    val jsonAmount = 
byCurrency.getJSONObject(currency).getJSONObject("available")
+                                    val jsonAmount = 
byCurrency.getJSONObject(currency)
+                                        .getJSONObject("available")
                                     val amount = Amount.fromJson(jsonAmount)
-                                    balanceList.add(amount)
+                                    val jsonAmountIncoming = 
byCurrency.getJSONObject(currency)
+                                        .getJSONObject("pendingIncoming")
+                                    val amountIncoming = 
Amount.fromJson(jsonAmountIncoming)
+                                    balanceList.add(BalanceEntry(amount, 
amountIncoming))
                                 }
                                 balances.postValue(WalletBalances(true, 
balanceList))
                             }
+                            "getWithdrawalInfo" -> {
+                                Log.v(TAG, "got getWithdrawalInfo result")
+                                val status = withdrawStatus.value
+                                if (status !is WithdrawStatus.Loading) {
+                                    Log.v(TAG, "ignoring withdrawal info 
result, not loading.")
+                                    return
+                                }
+                                val result = message.getJSONObject("result")
+                                val suggestedExchange = 
result.getString("suggestedExchange")
+                                val amount = 
Amount.fromJson(result.getJSONObject("amount"))
+                                withdrawStatus.postValue(
+                                    WithdrawStatus.ReceivedDetails(
+                                        status.talerWithdrawUri,
+                                        amount,
+                                        suggestedExchange
+                                    )
+                                )
+                            }
+                            "acceptWithdrawal" -> {
+                                Log.v(TAG, "got acceptWithdrawal result")
+                                val status = withdrawStatus.value
+                                if (status !is WithdrawStatus.Withdrawing) {
+                                    Log.v(TAG, "ignoring acceptWithdrawal 
result, invalid state")
+                                }
+                                
withdrawStatus.postValue(WithdrawStatus.Success())
+                            }
                             "preparePay" -> {
                                 Log.v(TAG, "got preparePay result")
                                 val result = message.getJSONObject("result")
@@ -238,9 +300,15 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
                                     totalFees = 
Amount.fromJson(result.getJSONObject("totalFees"))
                                 }
                                 val res = when (status) {
-                                    "payment-possible" -> 
PayStatus.Prepared(contractTerms!!, proposalId!!, totalFees!!)
+                                    "payment-possible" -> PayStatus.Prepared(
+                                        contractTerms!!,
+                                        proposalId!!,
+                                        totalFees!!
+                                    )
                                     "paid" -> 
PayStatus.AlreadyPaid(contractTerms!!)
-                                    "insufficient-balance" -> 
PayStatus.InsufficientBalance(contractTerms!!)
+                                    "insufficient-balance" -> 
PayStatus.InsufficientBalance(
+                                        contractTerms!!
+                                    )
                                     "error" -> PayStatus.Error("got some 
error")
                                     else -> PayStatus.Error("unkown status")
                                 }
@@ -257,6 +325,7 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
         })
 
         myAkono.evalNodeCode("console.log('hello world from taler 
wallet-android')")
+        myAkono.evalNodeCode("require('source-map-support').install();")
         myAkono.evalNodeCode("tw = require('taler-wallet');")
         myAkono.evalNodeCode("tw.installAndroidWalletListener();")
 
@@ -296,8 +365,7 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
             return
         }
 
-        withdrawInProgress++
-        this.isBalanceLoading.value = true
+        testWithdrawalInProgress.value = true
 
         val msg = JSONObject()
         msg.put("operation", "withdrawTestkudos")
@@ -337,7 +405,7 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
 
         sendInitMessage()
 
-        isBalanceLoading.value = false
+        testWithdrawalInProgress.value = false
         balances.value = WalletBalances(false, listOf())
 
         getBalances()
@@ -365,6 +433,32 @@ class WalletViewModel(val app: Application) : 
AndroidViewModel(app) {
         msg.put("args", respJson)
 
         myAkono.sendMessage(msg.toString())
+    }
+
+    fun getWithdrawalInfo(talerWithdrawUri: String) {
+        val msg = JSONObject()
+        msg.put("operation", "getWithdrawalInfo")
+
+        val args = JSONObject()
+        msg.put("args", args)
+        args.put("talerWithdrawUri", talerWithdrawUri)
+
+        withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri)
+
+        myAkono.sendMessage(msg.toString())
+    }
+
+    fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String) {
+        val msg = JSONObject()
+        msg.put("operation", "acceptWithdrawal")
 
+        val args = JSONObject()
+        msg.put("args", args)
+        args.put("talerWithdrawUri", talerWithdrawUri)
+        args.put("selectedExchange", selectedExchange)
+
+        withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri)
+
+        myAkono.sendMessage(msg.toString())
     }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/net/taler/wallet/WithdrawSuccessful.kt 
b/app/src/main/java/net/taler/wallet/WithdrawSuccessful.kt
new file mode 100644
index 0000000..c16dced
--- /dev/null
+++ b/app/src/main/java/net/taler/wallet/WithdrawSuccessful.kt
@@ -0,0 +1,26 @@
+package net.taler.wallet
+
+
+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
+
+/**
+ * A simple [Fragment] subclass.
+ */
+class WithdrawSuccessful : Fragment() {
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        val view = inflater.inflate(R.layout.fragment_withdraw_successful, 
container, false)
+        view.findViewById<Button>(R.id.button_success_back).setOnClickListener 
{
+            activity!!.findNavController(R.id.nav_host_fragment).navigateUp()
+        }
+        return view
+    }
+}
diff --git a/app/src/main/res/drawable/ic_scan_qr.xml 
b/app/src/main/res/drawable/ic_scan_qr.xml
new file mode 100644
index 0000000..c99daff
--- /dev/null
+++ b/app/src/main/res/drawable/ic_scan_qr.xml
@@ -0,0 +1,186 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="278dp"
+    android:height="278dp"
+    android:viewportWidth="278"
+    android:viewportHeight="278">
+  <path
+      android:pathData="M103,28l0,77 -76,0 0,-77 76,0zM93,38l-56,0 0,57 56,0 
0,-57z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M251,28l0,77 -76,0 0,-77 76,0zM241,38l-56,0 0,57 56,0 
0,-57z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M103,174l0,76 -76,0 0,-76 76,0zM93,184l-56,0 0,56 56,0 
0,-56z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      
android:pathData="M157,240l0,-12l-10,0l0,12l-12,0l0,-21l20,0l0,-10l-33,0l0,-9l-10,0l1,19l12,0l0,11l-13,0l0,10l13,0l0,10l42,0l0,-10z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M38,123l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M123,69l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M124,39l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M222,215l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      
android:pathData="M50,144l10,0l0,-10l-10,0l0,-10l-10,0l0,9l-12,0l0,24l12,0l0,8l31,0l0,-19l-11,0l0,9l-10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M242,113l0,10 9,0 0,10 -16,0 0,13 -29,0 0,9 16,0 0,23 
-11,0 0,-13 -23,0 0,-10 8,0 0,-9 -6,0 0,-10 33,0 0,-18 -13,0 0,-5c10,0 21,0 
32,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M121,123l-31,0l0,10l-21,0l0,-10l-11,0l0,-10l63,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M123,74l-10,0l0,17l0,17l10,0l11,0l0,-17l-11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M134,85l11,0l0,18l13,0l0,-12l8,0l0,-16l-32,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M153,44l-28,0 0,11 18,0 0,11 13,0 0,-11 11,0c0,-9 
0,-18 0,-27l-24,0 0,11 10,0 0,5z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M138,70l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M112,139l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M157,183l-11,0l0,-18l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M140,180l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M206,189l-10,0l0,-20l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M241,240l-18,0l0,10l28,0l0,-26l-10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M183,237l-11,0l0,-18l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M123,187l-10,0l0,-23l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      
android:pathData="M172,196l-15,0l0,-10l-32,0l0,10l23,0l0,10l12,0l0,11l46,0l0,-11l-13,0l0,-10l-11,0l0,10l-10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M185,179l-13,0l0,11l-10,0l0,-21l23,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M198,220l10,0 0,20 9,0 0,10c-16,0 -13,0 -31,0l0,-10 
12,0 0,-20z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M118,155l-30,0l0,10l-10,0l0,-13l0,0l0,-7l40,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M136,123l0,9l21,0l0,-9l13,0l0,-10l-43,0l0,10z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M145,147l-22,0l0,-10l22,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M158,150l-11,0l0,10l22,0l0,-23l-11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M251,184l-6,0l0,8l-10,0l0,-8l-5,0l0,-22l21,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M222,194l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M228,217c7,0 15,0 23,0l0,-19 -10,0 0,9 -13,0 0,10z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M237,236l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M82,82l-34,0l0,-32l34,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M230,82l-34,0l0,-32l34,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M82,228l-34,0l0,-32l34,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M183,250l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M140,162l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M218,132l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M193,123l-11,0l0,-10l11,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M251,159l-10,0l0,-10l10,0z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M0,53l0,-53l54,0l0,10l-44,0l0,43z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M278,53l0,-53l-54,0l0,10l44,0l0,43z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M0,225l0,53l54,0l0,-10l-44,0l0,-43z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+  <path
+      android:pathData="M278,225l0,53l-54,0l0,-10l44,0l0,-43z"
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"/>
+</vector>
diff --git a/app/src/main/res/layout/app_bar_main.xml 
b/app/src/main/res/layout/app_bar_main.xml
index 0e95cb0..7bf5acd 100644
--- a/app/src/main/res/layout/app_bar_main.xml
+++ b/app/src/main/res/layout/app_bar_main.xml
@@ -38,4 +38,14 @@
 
     <include layout="@layout/content_main" android:id="@+id/include"/>
 
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+            android:id="@+id/fab"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|end"
+            android:layout_margin="@dimen/fab_margin"
+            app:fabSize="normal"
+            android:scaleType="center"
+            app:srcCompat="@drawable/ic_scan_qr" />
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/balance_row.xml 
b/app/src/main/res/layout/balance_row.xml
index 75f143c..9a81489 100644
--- a/app/src/main/res/layout/balance_row.xml
+++ b/app/src/main/res/layout/balance_row.xml
@@ -28,4 +28,44 @@
 
     </LinearLayout>
 
+    <LinearLayout android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:id="@+id/balance_row_pending">
+
+        <Space android:layout_width="5sp"
+                android:layout_height="match_parent"/>
+
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="+"
+                android:textColor="#006600">
+        </TextView>
+
+        <Space android:layout_width="5sp"
+                android:layout_height="match_parent"/>
+
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:id="@+id/balance_pending"
+                android:textColor="#006600"
+                android:textSize="20sp" tools:text="10 TESTKUDOS">
+        </TextView>
+
+        <Space android:layout_width="5sp"
+                android:layout_height="match_parent"/>
+
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="inbound"
+                android:textColor="#006600">
+        </TextView>
+
+    </LinearLayout>
+
+
+
 </LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_prompt_withdraw.xml 
b/app/src/main/res/layout/fragment_prompt_withdraw.xml
new file mode 100644
index 0000000..aa3c860
--- /dev/null
+++ b/app/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -0,0 +1,79 @@
+<?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=".PromptWithdraw">
+
+    <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:id="@+id/prompt_withdraw">
+
+        <Space android:layout_width="match_parent"
+                android:layout_height="15dp"
+                android:layout_weight="1"/>
+
+        <TextView
+                android:text="Do you want to withdraw"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:id="@+id/textView2"/>
+
+        <TextView
+                android:text="(amount)"
+                tools:text="10.00 KUDOS"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="25sp"
+                android:layout_gravity="center"
+                android:id="@+id/withdraw_amount"/>
+
+        <Space android:layout_width="match_parent"
+                android:layout_height="25dp"/>
+
+
+        <TextView
+                android:text="Using the exchange provider"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:id="@+id/textView3"/>
+        <TextView
+                android:text="(exchange base url)"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="25sp"
+                android:layout_gravity="center"
+                android:id="@+id/withdraw_exchange"/>
+
+        <Space android:layout_width="match_parent"
+                android:layout_height="15dp"
+                android:layout_weight="1"/>
+
+
+        <Space android:layout_width="match_parent"
+                android:layout_height="15dp"
+                android:layout_weight="1"/>
+
+        <LinearLayout android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+            <Button android:layout_width="wrap_content" 
android:layout_height="wrap_content"
+                    android:text="Cancel"
+                    android:id="@+id/button_cancel_withdraw"/>
+
+            <Space android:layout_width="15dp" 
android:layout_height="match_parent"
+                    android:layout_weight="1"/>
+
+            <Button android:layout_width="wrap_content" 
android:layout_height="wrap_content"
+                    android:id="@+id/button_confirm_withdraw"
+                    android:text="Confirm Withdraw"/>
+        </LinearLayout>
+
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_show_balance.xml 
b/app/src/main/res/layout/fragment_show_balance.xml
index e4d2e81..b93d2c2 100644
--- a/app/src/main/res/layout/fragment_show_balance.xml
+++ b/app/src/main/res/layout/fragment_show_balance.xml
@@ -38,14 +38,9 @@
                 android:layout_height="wrap_content"
                 android:id="@+id/button_withdraw_testkudos"/>
         <Button
-                android:text="Pay via QR Code"
+                android:text="Scan Taler QR Code"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/button_pay_qr"/>
-        <Button
-                android:text="Pay via NFC"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/button_pay_nfc"/>
     </LinearLayout>
 </androidx.core.widget.NestedScrollView>
diff --git a/app/src/main/res/layout/fragment_withdraw_successful.xml 
b/app/src/main/res/layout/fragment_withdraw_successful.xml
new file mode 100644
index 0000000..717ca20
--- /dev/null
+++ b/app/src/main/res/layout/fragment_withdraw_successful.xml
@@ -0,0 +1,53 @@
+<?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="10dp"
+        tools:context=".WithdrawSuccessful">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+        <Space
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+        <TextView
+                android:layout_width="match_parent"
+                android:layout_height="50dp"
+                android:layout_gravity="center"
+                android:autoSizeTextType="uniform"
+                android:text="Withdrawal confirmed."
+                android:textAlignment="center"
+                android:textColor="@android:color/holo_green_dark" />
+
+        <TextView
+                android:layout_width="match_parent"
+                android:layout_height="50dp"
+                android:layout_gravity="center"
+                android:text="Your bank will now ask you to approve a transfer 
to the selected change.  After you've confirmed the transfer with your bank, 
the digital cash will show in this wallet."
+                android:textAlignment="center" />
+
+        <Space
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+
+        <Space
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+        <Button
+                android:id="@+id/button_success_back"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Go Back" />
+
+    </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/navigation/nav_graph.xml 
b/app/src/main/res/navigation/nav_graph.xml
index 80a94ec..2f9787b 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -7,6 +7,9 @@
     <fragment android:id="@+id/showBalance" 
android:name="net.taler.wallet.ShowBalance"
               android:label="Balances" 
tools:layout="@layout/fragment_show_balance">
         <action android:id="@+id/action_showBalance_to_promptPayment" 
app:destination="@id/promptPayment"/>
+        <action
+                android:id="@+id/action_showBalance_to_promptWithdraw"
+                app:destination="@id/promptWithdraw" />
     </fragment>
     <fragment android:id="@+id/promptPayment" 
android:name="net.taler.wallet.PromptPayment"
               android:label="Review Payment" 
tools:layout="@layout/fragment_prompt_payment">
@@ -28,4 +31,19 @@
             android:name="net.taler.wallet.AlreadyPaid"
             android:label="Already Paid"
             tools:layout="@layout/fragment_already_paid" />
+    <fragment
+            android:id="@+id/promptWithdraw"
+            android:name="net.taler.wallet.PromptWithdraw"
+            android:label="Withdraw Digital Cash"
+            tools:layout="@layout/fragment_prompt_withdraw" >
+        <action
+                android:id="@+id/action_promptWithdraw_to_withdrawSuccessful"
+                app:destination="@id/withdrawSuccessful"
+                app:popUpTo="@id/showBalance"/>
+    </fragment>
+    <fragment
+            android:id="@+id/withdrawSuccessful"
+            android:name="net.taler.wallet.WithdrawSuccessful"
+            android:label="Withdrawal Confirmed"
+            tools:layout="@layout/fragment_withdraw_successful" />
 </navigation>
\ No newline at end of file

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



reply via email to

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