summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-07-29 14:12:50 -0300
committerTorsten Grote <t@grobox.de>2020-07-29 16:17:46 -0300
commite19ba096d57353db6b1f141da4bf170ef2d2d534 (patch)
tree8ce7d138757a73f999f8e270e69cef44aa2f781b
parentbc35e8924e652c323001f62f6781657545fa378f (diff)
downloadtaler-android-e19ba096d57353db6b1f141da4bf170ef2d2d534.tar.gz
taler-android-e19ba096d57353db6b1f141da4bf170ef2d2d534.tar.bz2
taler-android-e19ba096d57353db6b1f141da4bf170ef2d2d534.zip
[wallet] update to new wallet-core with v8 exchange API
(except payments which are still buggy)
-rw-r--r--wallet/build.gradle4
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainActivity.kt5
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt39
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt9
-rw-r--r--wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt11
-rw-r--r--wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt134
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt (renamed from wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt)2
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt3
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt (renamed from wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt)12
-rw-r--r--wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt110
-rw-r--r--wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt87
-rw-r--r--wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt75
-rw-r--r--wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt199
-rw-r--r--wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt57
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt105
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt62
-rw-r--r--wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt8
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt26
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt227
-rw-r--r--wallet/src/main/res/drawable/ic_directions.xml25
-rw-r--r--wallet/src/main/res/drawable/ic_edit.xml10
-rw-r--r--wallet/src/main/res/drawable/ic_history.xml9
-rw-r--r--wallet/src/main/res/drawable/transaction_payment_aborted.xml25
-rw-r--r--wallet/src/main/res/drawable/transaction_tip_declined.xml25
-rw-r--r--wallet/src/main/res/layout/fragment_json.xml40
-rw-r--r--wallet/src/main/res/layout/fragment_prompt_withdraw.xml6
-rw-r--r--wallet/src/main/res/layout/fragment_transactions.xml2
-rw-r--r--wallet/src/main/res/layout/list_item_history.xml75
-rw-r--r--wallet/src/main/res/menu/activity_main_drawer.xml4
-rw-r--r--wallet/src/main/res/navigation/nav_graph.xml12
-rw-r--r--wallet/src/main/res/values/strings.xml1
-rw-r--r--wallet/src/main/res/xml/settings_backup.xml2
-rw-r--r--wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt36
40 files changed, 295 insertions, 1170 deletions
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 5b28c6c1..8cca8dcb 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,7 +23,7 @@ plugins {
id "de.undercouch.download"
}
-def walletCoreVersion = "v0.7.1-dev.10"
+def walletCoreVersion = "v0.7.1-dev.14"
static def versionCodeEpoch() {
return (new Date().getTime() / 1000).toInteger()
@@ -47,7 +47,7 @@ android {
minSdkVersion 24
targetSdkVersion 29
versionCode 6
- versionName "0.7.1.dev.10"
+ versionName "0.7.1.dev.14"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "WALLET_CORE_VERSION", "\"$walletCoreVersion\""
}
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index fdb8cf83..c7c31caf 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -81,7 +81,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
setSupportActionBar(toolbar)
val appBarConfiguration = AppBarConfiguration(
- setOf(R.id.nav_main, R.id.nav_settings, R.id.nav_pending_operations, R.id.nav_history),
+ setOf(R.id.nav_main, R.id.nav_settings, R.id.nav_pending_operations),
drawer_layout
)
toolbar.setupWithNavController(nav, appBarConfiguration)
@@ -122,7 +122,6 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
R.id.nav_home -> nav.navigate(R.id.nav_main)
R.id.nav_settings -> nav.navigate(R.id.nav_settings)
R.id.nav_pending_operations -> nav.navigate(R.id.nav_pending_operations)
- R.id.nav_history -> nav.navigate(R.id.nav_history)
}
drawer_layout.closeDrawer(START)
return true
@@ -160,7 +159,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
Log.v(TAG, "navigating!")
// there's more than one entry point, so use global action
nav.navigate(R.id.action_global_promptWithdraw)
- model.withdrawManager.getWithdrawalInfo(url)
+ model.withdrawManager.getWithdrawalDetails(url)
}
url.toLowerCase(ROOT).startsWith("taler://refund/") -> {
model.showProgressBar.value = true
diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
index a735987d..d5bd3fcb 100644
--- a/wallet/src/main/java/net/taler/wallet/MainFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
@@ -49,7 +49,7 @@ class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
model.balances.observe(viewLifecycleOwner, Observer {
- onBalancesChanged(it.values.toList())
+ onBalancesChanged(it)
})
model.transactionsEvent.observe(viewLifecycleOwner, EventObserver { currency ->
// we only need to navigate to a dedicated list, when in multi-currency mode
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 46f5021f..3d725d06 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -27,14 +27,13 @@ import androidx.lifecycle.viewModelScope
import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
-import net.taler.common.Amount
+import com.fasterxml.jackson.module.kotlin.readValue
import net.taler.common.Event
import net.taler.common.assertUiThread
import net.taler.common.toEvent
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.balances.BalanceItem
import net.taler.wallet.exchanges.ExchangeManager
-import net.taler.wallet.history.DevHistoryManager
import net.taler.wallet.payment.PaymentManager
import net.taler.wallet.pending.PendingOperationsManager
import net.taler.wallet.refund.RefundManager
@@ -55,8 +54,8 @@ private val transactionNotifications = listOf(
class MainViewModel(val app: Application) : AndroidViewModel(app) {
- private val mBalances = MutableLiveData<Map<String, BalanceItem>>()
- val balances: LiveData<Map<String, BalanceItem>> = mBalances.distinctUntilChanged()
+ private val mBalances = MutableLiveData<List<BalanceItem>>()
+ val balances: LiveData<List<BalanceItem>> = mBalances.distinctUntilChanged()
val devMode = MutableLiveData(BuildConfig.DEBUG)
val showProgressBar = MutableLiveData<Boolean>()
@@ -85,7 +84,6 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
// refresh pending ops and history with each notification
if (devMode.value == true) {
pendingOperationsManager.getPending()
- historyManager.loadHistory()
}
}
}
@@ -94,12 +92,10 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
.registerModule(KotlinModule())
.configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
- val withdrawManager = WithdrawManager(walletBackendApi)
+ val withdrawManager = WithdrawManager(walletBackendApi, mapper)
val paymentManager = PaymentManager(walletBackendApi, mapper)
val pendingOperationsManager: PendingOperationsManager =
PendingOperationsManager(walletBackendApi)
- val historyManager: DevHistoryManager =
- DevHistoryManager(walletBackendApi, viewModelScope, mapper)
val transactionManager: TransactionManager =
TransactionManager(walletBackendApi, viewModelScope, mapper)
val refundManager = RefundManager(walletBackendApi)
@@ -123,26 +119,13 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
@UiThread
fun loadBalances() {
showProgressBar.value = true
- walletBackendApi.sendRequest("getBalances", null) { isError, result ->
+ walletBackendApi.sendRequest("getBalances") { isError, result ->
if (isError) {
Log.e(TAG, "Error retrieving balances: ${result.toString(2)}")
return@sendRequest
}
- val balanceMap = HashMap<String, BalanceItem>()
- val byCurrency = result.getJSONObject("byCurrency")
- val currencyList = byCurrency.keys().asSequence().toList().sorted()
- for (currency in currencyList) {
- val jsonAmount = byCurrency.getJSONObject(currency)
- .getJSONObject("available")
- val amount = Amount.fromJsonObject(jsonAmount)
- val jsonAmountIncoming = byCurrency.getJSONObject(currency)
- .getJSONObject("pendingIncoming")
- val amountIncoming = Amount.fromJsonObject(jsonAmountIncoming)
- val hasPending = transactionManager.hasPending(currency)
- balanceMap[currency] = BalanceItem(amount, amountIncoming, hasPending)
- }
- mBalances.postValue(balanceMap)
- showProgressBar.postValue(false)
+ mBalances.value = mapper.readValue(result.getString("balances"))
+ showProgressBar.value = false
}
}
@@ -156,17 +139,17 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
@UiThread
fun dangerouslyReset() {
- walletBackendApi.sendRequest("reset", null)
+ walletBackendApi.sendRequest("reset")
withdrawManager.testWithdrawalInProgress.value = false
- mBalances.value = emptyMap()
+ mBalances.value = emptyList()
}
fun startTunnel() {
- walletBackendApi.sendRequest("startTunnel", null)
+ walletBackendApi.sendRequest("startTunnel")
}
fun stopTunnel() {
- walletBackendApi.sendRequest("stopTunnel", null)
+ walletBackendApi.sendRequest("stopTunnel")
}
fun tunnelResponse(resp: String) {
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index 3ffcd7b4..51b3419b 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -30,7 +30,7 @@ import android.util.Log
import android.util.SparseArray
import org.json.JSONObject
import java.lang.ref.WeakReference
-import java.util.*
+import java.util.LinkedList
class WalletBackendApi(
private val app: Application,
@@ -115,7 +115,7 @@ class WalletBackendApi(
fun sendRequest(
operation: String,
- args: JSONObject?,
+ args: JSONObject? = null,
onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ -> }
) {
val requestID = nextRequestID++
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
index c8100540..f39a3e7d 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
@@ -30,7 +30,7 @@ import net.taler.wallet.BuildConfig.WALLET_CORE_VERSION
import net.taler.wallet.HostCardEmulatorService
import org.json.JSONObject
import java.lang.ref.WeakReference
-import java.util.*
+import java.util.LinkedList
import java.util.concurrent.ConcurrentHashMap
import kotlin.system.exitProcess
@@ -56,9 +56,10 @@ class WalletBackendService : Service() {
private val subscribers = LinkedList<Messenger>()
override fun onCreate() {
- val talerWalletAndroidCode = assets.open("taler-wallet-android-$WALLET_CORE_VERSION.js").use {
- it.readBytes().toString(Charsets.UTF_8)
- }
+ val talerWalletAndroidCode =
+ assets.open("taler-wallet-android-$WALLET_CORE_VERSION.js").use {
+ it.readBytes().toString(Charsets.UTF_8)
+ }
Log.i(TAG, "onCreate in wallet backend service")
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
index be503649..c090e753 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -28,7 +28,14 @@ import net.taler.common.Amount
import net.taler.wallet.R
import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder
-data class BalanceItem(val available: Amount, val pendingIncoming: Amount, val hasPending: Boolean)
+data class BalanceItem(
+ val available: Amount,
+ val pendingIncoming: Amount,
+ val pendingOutgoing: Amount
+) {
+ val currency: String get() = available.currency
+ val hasPending: Boolean get() = !pendingIncoming.isZero() || !pendingOutgoing.isZero()
+}
class BalanceAdapter(private val listener: BalanceClickListener) : Adapter<BalanceViewHolder>() {
@@ -65,7 +72,7 @@ class BalanceAdapter(private val listener: BalanceClickListener) : Adapter<Balan
fun bind(item: BalanceItem) {
v.setOnClickListener { listener.onBalanceClick(item.available.currency) }
- currencyView.text = item.available.currency
+ currencyView.text = item.currency
amountView.text = item.available.amountStr
val amountIncoming = item.pendingIncoming
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
index 22dd992e..2b4d032e 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
@@ -60,7 +60,7 @@ class BalancesFragment : Fragment(),
}
model.balances.observe(viewLifecycleOwner, Observer {
- onBalancesChanged(it.values.toList())
+ onBalancesChanged(it)
})
}
diff --git a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
deleted file mode 100644
index 25a59bef..00000000
--- a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.crypto
-
-import java.io.ByteArrayOutputStream
-
-class EncodingException : Exception("Invalid encoding")
-
-
-object Base32Crockford {
-
- private fun ByteArray.getIntAt(index: Int): Int {
- val x = this[index].toInt()
- return if (x >= 0) x else (x + 256)
- }
-
- private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
-
- fun encode(data: ByteArray): String {
- val sb = StringBuilder()
- val size = data.size
- var bitBuf = 0
- var numBits = 0
- var pos = 0
- while (pos < size || numBits > 0) {
- if (pos < size && numBits < 5) {
- val d = data.getIntAt(pos++)
- bitBuf = (bitBuf shl 8) or d
- numBits += 8
- }
- if (numBits < 5) {
- // zero-padding
- bitBuf = bitBuf shl (5 - numBits)
- numBits = 5
- }
- val v = bitBuf.ushr(numBits - 5) and 31
- sb.append(encTable[v])
- numBits -= 5
- }
- return sb.toString()
- }
-
- fun decode(encoded: String, out: ByteArrayOutputStream) {
- val size = encoded.length
- var bitpos = 0
- var bitbuf = 0
- var readPosition = 0
-
- while (readPosition < size || bitpos > 0) {
- //println("at position $readPosition with bitpos $bitpos")
- if (readPosition < size) {
- val v = getValue(encoded[readPosition++])
- bitbuf = (bitbuf shl 5) or v
- bitpos += 5
- }
- while (bitpos >= 8) {
- val d = (bitbuf ushr (bitpos - 8)) and 0xFF
- out.write(d)
- bitpos -= 8
- }
- if (readPosition == size && bitpos > 0) {
- bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF
- bitpos = if (bitbuf == 0) 0 else 8
- }
- }
- }
-
- fun decode(encoded: String): ByteArray {
- val out = ByteArrayOutputStream()
- decode(encoded, out)
- return out.toByteArray()
- }
-
- private fun getValue(chr: Char): Int {
- var a = chr
- when (a) {
- 'O', 'o' -> a = '0'
- 'i', 'I', 'l', 'L' -> a = '1'
- 'u', 'U' -> a = 'V'
- }
- if (a in '0'..'9')
- return a - '0'
- if (a in 'a'..'z')
- a = Character.toUpperCase(a)
- var dec = 0
- if (a in 'A'..'Z') {
- if ('I' < a) dec++
- if ('L' < a) dec++
- if ('O' < a) dec++
- if ('U' < a) dec++
- return a - 'A' + 10 - dec
- }
- throw EncodingException()
- }
-
- /**
- * Compute the length of the resulting string when encoding data of the given size
- * in bytes.
- *
- * @param dataSize size of the data to encode in bytes
- * @return size of the string that would result from encoding
- */
- @Suppress("unused")
- fun calculateEncodedStringLength(dataSize: Int): Int {
- return (dataSize * 8 + 4) / 5
- }
-
- /**
- * Compute the length of the resulting data in bytes when decoding a (valid) string of the
- * given size.
- *
- * @param stringSize size of the string to decode
- * @return size of the resulting data in bytes
- */
- @Suppress("unused")
- fun calculateDecodedDataLength(stringSize: Int): Int {
- return stringSize * 5 / 8
- }
-}
-
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index 9c815c92..ae90b98e 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -14,7 +14,7 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.withdraw
+package net.taler.wallet.exchanges
import net.taler.common.Amount
import net.taler.common.Timestamp
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
index 41c8f2c9..9d31b5fb 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
@@ -21,7 +21,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
-import net.taler.common.Amount
import net.taler.common.Event
import net.taler.common.toEvent
import net.taler.wallet.TAG
@@ -46,7 +45,7 @@ class ExchangeManager(
private fun list(): LiveData<List<ExchangeItem>> {
mProgress.value = true
- walletBackendApi.sendRequest("listExchanges", JSONObject()) { isError, result ->
+ walletBackendApi.sendRequest("listExchanges") { isError, result ->
if (isError) {
throw AssertionError("Wallet core failed to return exchanges!")
} else {
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
index 2ade9f22..ef4894dc 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
@@ -14,7 +14,7 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.withdraw
+package net.taler.wallet.exchanges
import android.os.Bundle
import android.view.LayoutInflater
@@ -33,8 +33,8 @@ import net.taler.common.toRelativeTime
import net.taler.common.toShortDate
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
-import net.taler.wallet.withdraw.CoinFeeAdapter.CoinFeeViewHolder
-import net.taler.wallet.withdraw.WireFeeAdapter.WireFeeViewHolder
+import net.taler.wallet.exchanges.CoinFeeAdapter.CoinFeeViewHolder
+import net.taler.wallet.exchanges.WireFeeAdapter.WireFeeViewHolder
class SelectExchangeFragment : Fragment() {
@@ -59,8 +59,10 @@ class SelectExchangeFragment : Fragment() {
overheadView.visibility = GONE
} else overheadView.setAmount(fees.overhead)
expirationView.text = fees.earliestDepositExpiration.ms.toRelativeTime(requireContext())
- coinFeesList.adapter = CoinFeeAdapter(fees.coinFees)
- wireFeesList.adapter = WireFeeAdapter(fees.wireFees)
+ coinFeesList.adapter =
+ CoinFeeAdapter(fees.coinFees)
+ wireFeesList.adapter =
+ WireFeeAdapter(fees.wireFees)
}
private fun TextView.setAmount(amount: Amount) {
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
deleted file mode 100644
index a2684e11..00000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView.Adapter
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import net.taler.common.exhaustive
-import net.taler.common.toRelativeTime
-import net.taler.wallet.R
-import net.taler.wallet.history.DevHistoryAdapter.HistoryViewHolder
-import net.taler.wallet.transactions.AmountType
-
-internal class DevHistoryAdapter(
- private val listener: OnEventClickListener
-) : Adapter<HistoryViewHolder>() {
-
- private var history: List<HistoryEvent> = ArrayList()
-
- init {
- setHasStableIds(false)
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.list_item_history, parent, false)
- return HistoryViewHolder(view)
- }
-
- override fun getItemCount(): Int = history.size
-
- override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
- val transaction = history[position]
- holder.bind(transaction)
- }
-
- fun update(updatedHistory: List<HistoryEvent>) {
- this.history = updatedHistory
- this.notifyDataSetChanged()
- }
-
- internal open inner class HistoryViewHolder(private val v: View) : ViewHolder(v) {
-
- protected val context: Context = v.context
-
- private val icon: ImageView = v.findViewById(R.id.icon)
- protected val title: TextView = v.findViewById(R.id.title)
- private val time: TextView = v.findViewById(R.id.time)
- private val amount: TextView = v.findViewById(R.id.amount)
-
- private val amountColor = amount.currentTextColor
-
- open fun bind(historyEvent: HistoryEvent) {
- v.setOnClickListener { listener.onTransactionClicked(historyEvent) }
- icon.setImageResource(historyEvent.icon)
- title.text = historyEvent.title
- time.text = historyEvent.timestamp.ms.toRelativeTime(context)
- bindAmount(historyEvent.displayAmount)
- }
-
- private fun bindAmount(displayAmount: DisplayAmount?) {
- if (displayAmount == null) {
- amount.visibility = GONE
- } else {
- amount.visibility = VISIBLE
- when (displayAmount.type) {
- AmountType.Positive -> {
- amount.text = context.getString(
- R.string.amount_positive, displayAmount.amount.amountStr
- )
- amount.setTextColor(context.getColor(R.color.green))
- }
- AmountType.Negative -> {
- amount.text = context.getString(
- R.string.amount_negative, displayAmount.amount.amountStr
- )
- amount.setTextColor(context.getColor(R.color.red))
- }
- AmountType.Neutral -> {
- amount.text = displayAmount.amount.amountStr
- amount.setTextColor(amountColor)
- }
- }.exhaustive
- }
- }
-
- }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
deleted file mode 100644
index c3c07a32..00000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-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.Observer
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_transactions.*
-import net.taler.common.fadeIn
-import net.taler.common.fadeOut
-import net.taler.wallet.MainViewModel
-import net.taler.wallet.R
-
-internal interface OnEventClickListener {
- fun onTransactionClicked(historyEvent: HistoryEvent)
-}
-
-class DevHistoryFragment : Fragment(),
- OnEventClickListener {
-
- private val model: MainViewModel by activityViewModels()
- private val historyManager by lazy { model.historyManager }
- private val historyAdapter by lazy { DevHistoryAdapter(this) }
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_transactions, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- if (savedInstanceState == null) historyManager.loadHistory()
-
- list.apply {
- adapter = historyAdapter
- addItemDecoration(DividerItemDecoration(context, VERTICAL))
- }
- historyManager.progress.observe(viewLifecycleOwner, Observer { show ->
- progressBar.visibility = if (show) VISIBLE else INVISIBLE
- })
- historyManager.history.observe(viewLifecycleOwner, Observer { result ->
- onHistoryResult(result)
- })
- }
-
- override fun onTransactionClicked(historyEvent: HistoryEvent) {
- JsonDialogFragment.new(historyEvent.json.toString(2))
- .show(parentFragmentManager, null)
- }
-
- private fun onHistoryResult(result: HistoryResult) = when (result) {
- HistoryResult.Error -> {
- list.fadeOut()
- emptyState.text = getString(R.string.transactions_error)
- emptyState.fadeIn()
- }
- is HistoryResult.Success -> {
- emptyState.visibility = if (result.history.isEmpty()) VISIBLE else INVISIBLE
- historyAdapter.update(result.history)
- list.fadeIn()
- }
- }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
deleted file mode 100644
index 9052d6ee..00000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import androidx.annotation.UiThread
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import net.taler.wallet.backend.WalletBackendApi
-import org.json.JSONObject
-import java.util.*
-
-sealed class HistoryResult {
- object Error : HistoryResult()
- class Success(val history: List<HistoryEvent>) : HistoryResult()
-}
-
-class DevHistoryManager(
- private val walletBackendApi: WalletBackendApi,
- private val scope: CoroutineScope,
- private val mapper: ObjectMapper
-) {
-
- private val mProgress = MutableLiveData<Boolean>()
- val progress: LiveData<Boolean> = mProgress
-
- private val mHistory = MutableLiveData<HistoryResult>()
- val history: LiveData<HistoryResult> = mHistory
-
- @UiThread
- internal fun loadHistory() {
- mProgress.value = true
- walletBackendApi.sendRequest("getHistory", null) { isError, result ->
- scope.launch(Dispatchers.Default) {
- onEventsLoaded(isError, result)
- }
- }
- }
-
- private fun onEventsLoaded(isError: Boolean, result: JSONObject) {
- if (isError) {
- mHistory.postValue(HistoryResult.Error)
- return
- }
- val history = LinkedList<HistoryEvent>()
- val json = result.getJSONArray("history")
- for (i in 0 until json.length()) {
- val event: HistoryEvent = mapper.readValue(json.getString(i))
- event.json = json.getJSONObject(i)
- history.add(event)
- }
- history.reverse() // show latest first
- mProgress.postValue(false)
- mHistory.postValue(HistoryResult.Success(history))
- }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
deleted file mode 100644
index 3cbe7d73..00000000
--- a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import androidx.annotation.DrawableRes
-import com.fasterxml.jackson.annotation.JsonSubTypes
-import com.fasterxml.jackson.annotation.JsonSubTypes.Type
-import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
-import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
-import com.fasterxml.jackson.annotation.JsonTypeName
-import net.taler.common.Amount
-import net.taler.common.Timestamp
-import net.taler.wallet.R
-import net.taler.wallet.transactions.AmountType
-import org.json.JSONObject
-
-class DisplayAmount(
- val amount: Amount,
- val type: AmountType
-)
-
-@JsonTypeInfo(
- use = NAME,
- include = PROPERTY,
- property = "type",
- defaultImpl = UnknownHistoryEvent::class
-)
-/** missing:
-AuditorComplaintSent = "auditor-complained-sent",
-AuditorComplaintProcessed = "auditor-complaint-processed",
-AuditorTrustAdded = "auditor-trust-added",
-AuditorTrustRemoved = "auditor-trust-removed",
-ExchangeTermsAccepted = "exchange-terms-accepted",
-ExchangePolicyChanged = "exchange-policy-changed",
-ExchangeTrustAdded = "exchange-trust-added",
-ExchangeTrustRemoved = "exchange-trust-removed",
-FundsDepositedToSelf = "funds-deposited-to-self",
-FundsRecouped = "funds-recouped",
-ReserveCreated = "reserve-created",
- */
-@JsonSubTypes(
- Type(value = ExchangeAddedEvent::class, name = "exchange-added"),
- Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"),
- Type(value = ReserveBalanceUpdatedHistoryEvent::class, name = "reserve-balance-updated"),
- Type(value = WithdrawHistoryEvent::class, name = "withdrawn"),
- Type(value = OrderAcceptedHistoryEvent::class, name = "order-accepted"),
- Type(value = OrderRefusedHistoryEvent::class, name = "order-refused"),
- Type(value = OrderRedirectedHistoryEvent::class, name = "order-redirected"),
- Type(value = PaymentHistoryEvent::class, name = "payment-sent"),
- Type(value = PaymentAbortedHistoryEvent::class, name = "payment-aborted"),
- Type(value = TipAcceptedHistoryEvent::class, name = "tip-accepted"),
- Type(value = TipDeclinedHistoryEvent::class, name = "tip-declined"),
- Type(value = RefundHistoryEvent::class, name = "refund"),
- Type(value = RefreshHistoryEvent::class, name = "refreshed")
-)
-abstract class HistoryEvent(
- val timestamp: Timestamp,
- val eventId: String,
- @get:DrawableRes
- open val icon: Int = R.drawable.ic_account_balance
-) {
- val title: String get() = this::class.java.simpleName
- open val displayAmount: DisplayAmount? = null
- lateinit var json: JSONObject
-}
-
-class UnknownHistoryEvent(timestamp: Timestamp, eventId: String) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("exchange-added")
-class ExchangeAddedEvent(
- timestamp: Timestamp,
- eventId: String
-) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("exchange-updated")
-class ExchangeUpdatedEvent(
- timestamp: Timestamp,
- eventId: String
-) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("reserve-balance-updated")
-class ReserveBalanceUpdatedHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- val reserveBalance: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val displayAmount = DisplayAmount(reserveBalance, AmountType.Neutral)
-}
-
-@JsonTypeName("withdrawn")
-class WithdrawHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- val amountWithdrawnEffective: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.transaction_withdrawal
- override val displayAmount = DisplayAmount(amountWithdrawnEffective, AmountType.Positive)
-}
-
-@JsonTypeName("order-accepted")
-class OrderAcceptedHistoryEvent(
- timestamp: Timestamp,
- eventId: String
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.ic_add_circle
-}
-
-@JsonTypeName("order-refused")
-class OrderRefusedHistoryEvent(
- timestamp: Timestamp,
- eventId: String
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.ic_cancel
-}
-
-@JsonTypeName("payment-sent")
-class PaymentHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- val amountPaidWithFees: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.ic_cash_usd_outline
- override val displayAmount = DisplayAmount(amountPaidWithFees, AmountType.Negative)
-}
-
-@JsonTypeName("payment-aborted")
-class PaymentAbortedHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- amountLost: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.transaction_payment_aborted
- override val displayAmount = DisplayAmount(amountLost, AmountType.Negative)
-}
-
-@JsonTypeName("refreshed")
-class RefreshHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- val amountRefreshedEffective: Amount,
- val amountRefreshedRaw: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.transaction_refresh
- override val displayAmount =
- DisplayAmount(amountRefreshedRaw - amountRefreshedEffective, AmountType.Negative)
-}
-
-@JsonTypeName("order-redirected")
-class OrderRedirectedHistoryEvent(
- timestamp: Timestamp,
- eventId: String
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.ic_directions
-}
-
-@JsonTypeName("tip-accepted")
-class TipAcceptedHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- tipRaw: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.transaction_tip_accepted
- override val displayAmount = DisplayAmount(tipRaw, AmountType.Positive)
-}
-
-@JsonTypeName("tip-declined")
-class TipDeclinedHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- tipAmount: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.transaction_tip_declined
- override val displayAmount = DisplayAmount(tipAmount, AmountType.Neutral)
-}
-
-@JsonTypeName("refund")
-class RefundHistoryEvent(
- timestamp: Timestamp,
- eventId: String,
- val amountRefundedEffective: Amount
-) : HistoryEvent(timestamp, eventId) {
- override val icon = R.drawable.transaction_refund
- override val displayAmount = DisplayAmount(amountRefundedEffective, AmountType.Positive)
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
deleted file mode 100644
index 31c2b932..00000000
--- a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.fragment.app.DialogFragment
-import kotlinx.android.synthetic.main.fragment_json.*
-import net.taler.wallet.R
-
-class JsonDialogFragment : DialogFragment() {
-
- companion object {
- fun new(json: String): JsonDialogFragment {
- return JsonDialogFragment().apply {
- arguments = Bundle().apply { putString("json", json) }
- }
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_json, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val json = requireArguments().getString("json")
- jsonView.text = json
- }
-
- override fun onStart() {
- super.onStart()
- dialog?.window?.setLayout(MATCH_PARENT, WRAP_CONTENT)
- }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index 5c73d6cc..c6351eed 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -26,11 +26,29 @@ import net.taler.common.Amount
import net.taler.common.ContractTerms
import net.taler.wallet.TAG
import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
+import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
+import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
import org.json.JSONObject
import java.net.MalformedURLException
val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
+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()
+ object AlreadyPaid : PayStatus()
+ data class Error(val error: String) : PayStatus()
+ data class Success(val currency: String) : PayStatus()
+}
+
class PaymentManager(
private val walletBackendApi: WalletBackendApi,
private val mapper: ObjectMapper
@@ -42,52 +60,27 @@ class PaymentManager(
private val mDetailsShown = MutableLiveData<Boolean>()
internal val detailsShown: LiveData<Boolean> = mDetailsShown
- private var currentPayRequestId = 0
-
@UiThread
fun preparePay(url: String) {
mPayStatus.value = PayStatus.Loading
mDetailsShown.value = false
- val args = JSONObject(mapOf("url" to url))
-
- currentPayRequestId += 1
- val payRequestId = currentPayRequestId
-
+ val args = JSONObject(mapOf("talerPayUri" to url))
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")
- try {
- mPayStatus.postValue(getPayStatusUpdate(status, result))
- } catch (e: Exception) {
- Log.e(TAG, "Error getting PayStatusUpdate", e)
- mPayStatus.postValue(PayStatus.Error(e.message ?: "unknown error"))
- }
- }
+ if (isError) {
+ handleError("preparePay", result.toString(2))
+ return@sendRequest
+ }
+ val response: PreparePayResponse = mapper.readValue(result.toString())
+ Log.e(TAG, "PreparePayResponse $response")
+ mPayStatus.value = when (response) {
+ is PaymentPossibleResponse -> TODO()
+ is InsufficientBalanceResponse -> TODO()
+ is AlreadyConfirmedResponse -> TODO()
}
}
}
- private fun getPayStatusUpdate(status: String, json: JSONObject) = when (status) {
- "payment-possible" -> PayStatus.Prepared(
- contractTerms = getContractTerms(json),
- proposalId = json.getString("proposalId"),
- totalFees = Amount.fromJsonObject(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 terms: ContractTerms = mapper.readValue(json.getString("contractTermsRaw"))
// validate product images
@@ -101,16 +94,13 @@ class PaymentManager(
return terms
}
- @UiThread
- fun toggleDetailsShown() {
- val oldValue = mDetailsShown.value ?: false
- mDetailsShown.value = !oldValue
- }
-
fun confirmPay(proposalId: String, currency: String) {
val args = JSONObject(mapOf("proposalId" to proposalId))
-
- walletBackendApi.sendRequest("confirmPay", args) { _, _ ->
+ walletBackendApi.sendRequest("confirmPay", args) { isError, result ->
+ if (isError) {
+ handleError("preparePay", result.toString())
+ return@sendRequest
+ }
mPayStatus.postValue(PayStatus.Success(currency))
}
}
@@ -129,8 +119,9 @@ class PaymentManager(
Log.i(TAG, "aborting proposal")
- walletBackendApi.sendRequest("abortProposal", args) { isError, _ ->
+ walletBackendApi.sendRequest("abortProposal", args) { isError, result ->
if (isError) {
+ handleError("abortProposal", result.toString(2))
Log.e(TAG, "received error response to abortProposal")
return@sendRequest
}
@@ -139,23 +130,19 @@ class PaymentManager(
}
@UiThread
+ fun toggleDetailsShown() {
+ val oldValue = mDetailsShown.value ?: false
+ mDetailsShown.value = !oldValue
+ }
+
+ @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()
+ private fun handleError(operation: String, msg: String) {
+ Log.e(TAG, "got $operation error result $msg")
+ mPayStatus.value = PayStatus.Error(msg)
+ }
- data class InsufficientBalance(val contractTerms: ContractTerms) : PayStatus()
- data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
- data class Error(val error: String) : PayStatus()
- data class Success(val currency: String) : PayStatus()
}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
new file mode 100644
index 00000000..4c5b0105
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -0,0 +1,62 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.payment
+
+import com.fasterxml.jackson.annotation.JsonSubTypes
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type
+import com.fasterxml.jackson.annotation.JsonTypeInfo
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
+import com.fasterxml.jackson.annotation.JsonTypeName
+import net.taler.common.ContractTerms
+import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
+import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
+import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
+
+@JsonTypeInfo(use = NAME, include = PROPERTY, property = "status")
+@JsonSubTypes(
+ Type(value = PaymentPossibleResponse::class, name = "payment-possible"),
+ Type(value = AlreadyConfirmedResponse::class, name = "already-confirmed"),
+ Type(value = InsufficientBalanceResponse::class, name = "insufficient-balance")
+)
+sealed class PreparePayResponse(open val proposalId: String) {
+ @JsonTypeName("payment-possible")
+ data class PaymentPossibleResponse(
+ override val proposalId: String,
+ val contractTerms: ContractTerms
+ ) : PreparePayResponse(proposalId)
+
+ @JsonTypeName("insufficient-balance")
+ data class InsufficientBalanceResponse(
+ override val proposalId: String,
+ val contractTerms: ContractTerms
+ ) : PreparePayResponse(proposalId)
+
+ @JsonTypeName("already-confirmed")
+ data class AlreadyConfirmedResponse(
+ override val proposalId: String,
+ /**
+ * Did the payment succeed?
+ */
+ val paid: Boolean,
+
+ /**
+ * Redirect URL for the fulfillment page, only given if paid==true.
+ */
+ val nextUrl: String?
+ ) : PreparePayResponse(proposalId)
+}
diff --git a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
index 6c58b819..7027687e 100644
--- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
@@ -32,7 +32,7 @@ class PendingOperationsManager(private val walletBackendApi: WalletBackendApi) {
val pendingOperations = MutableLiveData<List<PendingOperationInfo>>()
internal fun getPending() {
- walletBackendApi.sendRequest("getPendingOperations", null) { isError, result ->
+ walletBackendApi.sendRequest("getPendingOperations") { isError, result ->
if (isError) {
Log.i(TAG, "got getPending error result: $result")
return@sendRequest
@@ -51,7 +51,7 @@ class PendingOperationsManager(private val walletBackendApi: WalletBackendApi) {
}
fun retryPendingNow() {
- walletBackendApi.sendRequest("retryPendingNow", null)
+ walletBackendApi.sendRequest("retryPendingNow")
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
index d8204b63..bd37b37b 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -102,12 +102,4 @@ class TransactionManager(
}
}
- @UiThread
- fun hasPending(currency: String): Boolean {
- val result = mTransactions[currency]?.value ?: return false
- return if (result is TransactionsResult.Success) {
- result.transactions.any { it.pending }
- } else false
- }
-
}
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
index 526aa944..2ae58c32 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -112,7 +112,7 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
model.balances.observe(viewLifecycleOwner, Observer { balances ->
- balances[currency]?.available?.let { amount ->
+ balances.find { it.currency == currency }?.available?.let { amount ->
requireActivity().title =
getString(R.string.transactions_detail_title_balance, amount)
}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
index 55f931d4..9788d1c5 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -60,7 +60,7 @@ class ManualWithdrawFragment : Fragment() {
val amount = Amount(exchangeItem.currency, value, 0)
amountView.hideKeyboard()
Toast.makeText(view.context, "Not implemented: $amount", LENGTH_SHORT).show()
- withdrawManager.getWithdrawalDetails(exchangeItem, amount)
+ withdrawManager.getWithdrawalDetails(exchangeItem.exchangeBaseUrl, amount)
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 331554b4..5a98a89c 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -20,6 +20,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
+import android.widget.Toast.LENGTH_SHORT
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
@@ -34,7 +36,7 @@ import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
import net.taler.wallet.withdraw.WithdrawStatus.Loading
-import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired
+import net.taler.wallet.withdraw.WithdrawStatus.TosReviewRequired
import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing
class PromptWithdrawFragment : Fragment() {
@@ -59,17 +61,13 @@ class PromptWithdrawFragment : Fragment() {
private fun showWithdrawStatus(status: WithdrawStatus?): Any = when (status) {
is WithdrawStatus.ReceivedDetails -> {
- showContent(status.amount, status.fee, status.exchange)
+ showContent(status.amountRaw, status.amountEffective, status.exchangeBaseUrl)
confirmWithdrawButton.apply {
text = getString(R.string.withdraw_button_confirm)
setOnClickListener {
it.fadeOut()
confirmProgressBar.fadeIn()
- withdrawManager.acceptWithdrawal(
- status.talerWithdrawUri,
- status.exchange,
- status.amount.currency
- )
+ withdrawManager.acceptWithdrawal()
}
isEnabled = true
}
@@ -87,8 +85,8 @@ class PromptWithdrawFragment : Fragment() {
is Withdrawing -> {
model.showProgressBar.value = true
}
- is TermsOfServiceReviewRequired -> {
- showContent(status.amount, status.fee, status.exchange)
+ is TosReviewRequired -> {
+ showContent(status.amountRaw, status.amountEffective, status.exchangeBaseUrl)
confirmWithdrawButton.apply {
text = getString(R.string.withdraw_button_tos)
setOnClickListener {
@@ -104,20 +102,20 @@ class PromptWithdrawFragment : Fragment() {
null -> model.showProgressBar.value = false
}
- private fun showContent(amount: Amount, fee: Amount, exchange: String) {
+ private fun showContent(amountRaw: Amount, amountEffective: Amount, exchange: String) {
model.showProgressBar.value = false
progressBar.fadeOut()
introView.fadeIn()
- effectiveAmountView.text = (amount - fee).toString()
+ effectiveAmountView.text = amountEffective.toString()
effectiveAmountView.fadeIn()
chosenAmountLabel.fadeIn()
- chosenAmountView.text = amount.toString()
+ chosenAmountView.text = amountRaw.toString()
chosenAmountView.fadeIn()
feeLabel.fadeIn()
- feeView.text = getString(R.string.amount_negative, fee.toString())
+ feeView.text = getString(R.string.amount_negative, (amountRaw - amountEffective).toString())
feeView.fadeIn()
exchangeIntroView.fadeIn()
@@ -125,7 +123,7 @@ class PromptWithdrawFragment : Fragment() {
withdrawExchangeUrl.fadeIn()
selectExchangeButton.fadeIn()
selectExchangeButton.setOnClickListener {
- findNavController().navigate(R.id.action_promptWithdraw_to_selectExchangeFragment)
+ Toast.makeText(context, "Not yet implemented", LENGTH_SHORT).show()
}
withdrawCard.fadeIn()
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
index ffaef5ab..db1f326e 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
@@ -55,7 +55,7 @@ class ReviewExchangeTosFragment : Fragment() {
}
withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
when (it) {
- is WithdrawStatus.TermsOfServiceReviewRequired -> {
+ is WithdrawStatus.TosReviewRequired -> {
val sections = try {
// TODO remove next line once exchange delivers proper markdown
val text = it.tosText.replace("****************", "================")
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index ea65e7c1..e14a747f 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -17,38 +17,57 @@
package net.taler.wallet.withdraw
import android.util.Log
+import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
import net.taler.common.Amount
import net.taler.wallet.TAG
import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.exchanges.ExchangeFees
import net.taler.wallet.exchanges.ExchangeItem
import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
import org.json.JSONObject
sealed class WithdrawStatus {
- data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
- data class TermsOfServiceReviewRequired(
- val talerWithdrawUri: String,
- val exchange: String,
+ data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus()
+ data class TosReviewRequired(
+ val talerWithdrawUri: String? = null,
+ val exchangeBaseUrl: String,
+ val amountRaw: Amount,
+ val amountEffective: Amount,
val tosText: String,
- val tosEtag: String,
- val amount: Amount,
- val fee: Amount
+ val tosEtag: String
) : WithdrawStatus()
data class ReceivedDetails(
- val talerWithdrawUri: String,
- val exchange: String,
- val amount: Amount,
- val fee: Amount
+ val talerWithdrawUri: String? = null,
+ val exchangeBaseUrl: String,
+ val amountRaw: Amount,
+ val amountEffective: Amount
) : WithdrawStatus()
- data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
+ object Withdrawing : WithdrawStatus()
data class Success(val currency: String) : WithdrawStatus()
data class Error(val message: String?) : WithdrawStatus()
}
-class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
+data class WithdrawalDetailsForUri(
+ val amount: Amount,
+ val defaultExchangeBaseUrl: String?,
+ val possibleExchanges: List<ExchangeItem>
+)
+
+data class WithdrawalDetails(
+ val tosAccepted: Boolean,
+ val amountRaw: Amount,
+ val amountEffective: Amount
+)
+
+class WithdrawManager(
+ private val walletBackendApi: WalletBackendApi,
+ private val mapper: ObjectMapper
+) {
val withdrawStatus = MutableLiveData<WithdrawStatus>()
val testWithdrawalInProgress = MutableLiveData(false)
@@ -58,149 +77,127 @@ class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
fun withdrawTestkudos() {
testWithdrawalInProgress.value = true
-
- walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ ->
+ walletBackendApi.sendRequest("withdrawTestkudos") { _, _ ->
testWithdrawalInProgress.postValue(false)
}
}
- fun getWithdrawalDetails(exchangeItem: ExchangeItem, amount: Amount) {
+ fun getWithdrawalDetails(uri: String) {
+ withdrawStatus.value = WithdrawStatus.Loading(uri)
val args = JSONObject().apply {
- put("exchangeBaseUrl", exchangeItem.exchangeBaseUrl)
- put("amount", amount.toJSONString())
+ put("talerWithdrawUri", uri)
}
- walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { isError, result ->
- // {"rawAmount":"TESTKUDOS:5","effectiveAmount":"TESTKUDOS:4.8","paytoUris":["payto:\/\/x-taler-bank\/bank.test.taler.net\/Exchange"],"tosAccepted":false}
+ walletBackendApi.sendRequest("getWithdrawalDetailsForUri", args) { isError, result ->
if (isError) {
- Log.e(TAG, "$result")
+ handleError("getWithdrawalDetailsForUri", result)
+ return@sendRequest
+ }
+ val details: WithdrawalDetailsForUri = mapper.readValue(result.toString())
+ if (details.defaultExchangeBaseUrl == null) {
+ // TODO go to exchange selection screen instead
+ val chosenExchange = details.possibleExchanges[0].exchangeBaseUrl
+ getWithdrawalDetails(chosenExchange, details.amount, uri)
} else {
- Log.e(TAG, "$result")
+ getWithdrawalDetails(details.defaultExchangeBaseUrl, details.amount, uri)
}
}
}
- fun getWithdrawalInfo(talerWithdrawUri: String) {
+ fun getWithdrawalDetails(exchangeBaseUrl: String, amount: Amount, uri: String? = null) {
+ withdrawStatus.value = WithdrawStatus.Loading(uri)
val args = JSONObject().apply {
- put("talerWithdrawUri", talerWithdrawUri)
+ put("exchangeBaseUrl", exchangeBaseUrl)
+ put("amount", amount.toJSONString())
}
- withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri)
-
- walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result ->
+ walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { isError, result ->
if (isError) {
- Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}")
- val message = if (result.has("message")) result.getString("message") else null
- withdrawStatus.postValue(WithdrawStatus.Error(message))
+ handleError("getWithdrawalDetailsForAmount", result)
return@sendRequest
}
- Log.v(TAG, "got getWithdrawDetailsForUri result")
- val status = withdrawStatus.value
- if (status !is WithdrawStatus.Loading) {
- Log.v(TAG, "ignoring withdrawal info result, not loading.")
- return@sendRequest
- }
- val wi = result.getJSONObject("bankWithdrawDetails")
- val suggestedExchange = wi.getString("suggestedExchange")
- // We just use the suggested exchange, in the future there will be
- // a selection dialog.
- getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange)
+ val details: WithdrawalDetails = mapper.readValue(result.toString())
+ if (details.tosAccepted)
+ withdrawStatus.value = ReceivedDetails(
+ talerWithdrawUri = uri,
+ exchangeBaseUrl = exchangeBaseUrl,
+ amountRaw = details.amountRaw,
+ amountEffective = details.amountEffective
+ )
+ else getExchangeTos(exchangeBaseUrl, details, uri)
}
}
- private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, selectedExchange: String) {
+ private fun getExchangeTos(exchangeBaseUrl: String, details: WithdrawalDetails, uri: String?) {
val args = JSONObject().apply {
- put("talerWithdrawUri", talerWithdrawUri)
- put("selectedExchange", selectedExchange)
+ put("exchangeBaseUrl", exchangeBaseUrl)
}
-
- walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result ->
+ walletBackendApi.sendRequest("getExchangeTos", args) { isError, result ->
if (isError) {
- Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}")
- val message = if (result.has("message")) result.getString("message") else null
- withdrawStatus.postValue(WithdrawStatus.Error(message))
- return@sendRequest
- }
- Log.v(TAG, "got getWithdrawDetailsForUri result (with exchange details)")
- val status = withdrawStatus.value
- if (status !is WithdrawStatus.Loading) {
- Log.w(TAG, "ignoring withdrawal info result, not loading.")
+ handleError("getExchangeTos", result)
return@sendRequest
}
- val wi = result.getJSONObject("bankWithdrawDetails")
- val amount = Amount.fromJsonObject(wi.getJSONObject("amount"))
-
- val ei = result.getJSONObject("exchangeWithdrawDetails")
- val termsOfServiceAccepted = ei.getBoolean("termsOfServiceAccepted")
-
- exchangeFees = ExchangeFees.fromExchangeWithdrawDetailsJson(ei)
-
- val withdrawFee = Amount.fromJsonObject(ei.getJSONObject("withdrawFee"))
- val overhead = Amount.fromJsonObject(ei.getJSONObject("overhead"))
- val fee = withdrawFee + overhead
-
- if (!termsOfServiceAccepted) {
- val exchange = ei.getJSONObject("exchangeInfo")
- val tosText = exchange.getString("termsOfServiceText")
- val tosEtag = exchange.optString("termsOfServiceLastEtag", "undefined")
- withdrawStatus.postValue(
- WithdrawStatus.TermsOfServiceReviewRequired(
- status.talerWithdrawUri,
- selectedExchange, tosText, tosEtag,
- amount, fee
- )
- )
- } else {
- withdrawStatus.postValue(
- ReceivedDetails(
- status.talerWithdrawUri,
- selectedExchange, amount,
- fee
- )
- )
- }
+ withdrawStatus.value = WithdrawStatus.TosReviewRequired(
+ talerWithdrawUri = uri,
+ exchangeBaseUrl = exchangeBaseUrl,
+ amountRaw = details.amountRaw,
+ amountEffective = details.amountEffective,
+ tosText = result.getString("tos"),
+ tosEtag = result.getString("currentEtag")
+ )
}
}
- fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String, currency: String) {
- val args = JSONObject()
- args.put("talerWithdrawUri", talerWithdrawUri)
- args.put("selectedExchange", selectedExchange)
-
- withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri)
-
- walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, result ->
+ /**
+ * Accept the currently displayed terms of service.
+ */
+ fun acceptCurrentTermsOfService() {
+ val s = withdrawStatus.value as WithdrawStatus.TosReviewRequired
+ val args = JSONObject().apply {
+ put("exchangeBaseUrl", s.exchangeBaseUrl)
+ put("etag", s.tosEtag)
+ }
+ walletBackendApi.sendRequest("setExchangeTosAccepted", args) { isError, result ->
if (isError) {
- Log.v(TAG, "got acceptWithdrawal error result: ${result.toString(2)}")
- return@sendRequest
- }
- Log.v(TAG, "got acceptWithdrawal result")
- val status = withdrawStatus.value
- if (status !is WithdrawStatus.Withdrawing) {
- Log.w(TAG, "ignoring acceptWithdrawal result, invalid state: $status")
+ handleError("setExchangeTosAccepted", result)
return@sendRequest
}
- withdrawStatus.postValue(WithdrawStatus.Success(currency))
+ withdrawStatus.value = ReceivedDetails(
+ talerWithdrawUri = s.talerWithdrawUri,
+ exchangeBaseUrl = s.exchangeBaseUrl,
+ amountRaw = s.amountRaw,
+ amountEffective = s.amountEffective
+ )
}
}
- /**
- * Accept the currently displayed terms of service.
- */
- fun acceptCurrentTermsOfService() {
- val s = withdrawStatus.value
- check(s is WithdrawStatus.TermsOfServiceReviewRequired)
+ @UiThread
+ fun acceptWithdrawal() {
+ val status = withdrawStatus.value as ReceivedDetails
+ val operation = if (status.talerWithdrawUri == null)
+ "acceptManualWithdrawal" else "acceptBankIntegratedWithdrawal"
val args = JSONObject().apply {
- put("exchangeBaseUrl", s.exchange)
- put("etag", s.tosEtag)
+ put("exchangeBaseUrl", status.exchangeBaseUrl)
+ if (status.talerWithdrawUri == null) {
+ put("amount", status.amountRaw)
+ } else {
+ put("talerWithdrawUri", status.talerWithdrawUri)
+ }
}
- walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { isError, result ->
+ withdrawStatus.value = WithdrawStatus.Withdrawing
+ walletBackendApi.sendRequest(operation, args) { isError, result ->
if (isError) {
- Log.e(TAG, "Error acceptExchangeTermsOfService ${result.toString(4)}")
+ handleError(operation, result)
return@sendRequest
}
- val status = ReceivedDetails(s.talerWithdrawUri, s.exchange, s.amount, s.fee)
- withdrawStatus.postValue(status)
+ withdrawStatus.value = WithdrawStatus.Success(status.amountRaw.currency)
}
}
+ @UiThread
+ private fun handleError(operation: String, result: JSONObject) {
+ Log.e(TAG, "Error $operation ${result.toString(2)}")
+ val message = if (result.has("message")) result.getString("message") else null
+ withdrawStatus.value = WithdrawStatus.Error(message)
+ }
+
}
diff --git a/wallet/src/main/res/drawable/ic_directions.xml b/wallet/src/main/res/drawable/ic_directions.xml
deleted file mode 100644
index 229d69c5..00000000
--- a/wallet/src/main/res/drawable/ic_directions.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- ~ This file is part of GNU Taler
- ~ (C) 2020 Taler Systems S.A.
- ~
- ~ GNU Taler is free software; you can redistribute it and/or modify it under the
- ~ terms of the GNU General Public License as published by the Free Software
- ~ Foundation; either version 3, or (at your option) any later version.
- ~
- ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- ~
- ~ You should have received a copy of the GNU General Public License along with
- ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/ic_edit.xml b/wallet/src/main/res/drawable/ic_edit.xml
new file mode 100644
index 00000000..69d28309
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_edit.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_history.xml b/wallet/src/main/res/drawable/ic_history.xml
deleted file mode 100644
index d9f75ea6..00000000
--- a/wallet/src/main/res/drawable/ic_history.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/transaction_payment_aborted.xml b/wallet/src/main/res/drawable/transaction_payment_aborted.xml
deleted file mode 100644
index 8d47c262..00000000
--- a/wallet/src/main/res/drawable/transaction_payment_aborted.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- ~ This file is part of GNU Taler
- ~ (C) 2020 Taler Systems S.A.
- ~
- ~ GNU Taler is free software; you can redistribute it and/or modify it under the
- ~ terms of the GNU General Public License as published by the Free Software
- ~ Foundation; either version 3, or (at your option) any later version.
- ~
- ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- ~
- ~ You should have received a copy of the GNU General Public License along with
- ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="#000"
- android:pathData="M15.46 18.12L16.88 19.54L19 17.41L21.12 19.54L22.54 18.12L20.41 16L22.54 13.88L21.12 12.46L19 14.59L16.88 12.46L15.46 13.88L17.59 16M14.97 11.62C14.86 10.28 13.58 8.97 12 9C10.3 9.04 9 10.3 9 12C9 13.7 10.3 14.94 12 15C12.39 15 12.77 14.92 13.14 14.77C13.41 13.67 13.86 12.63 14.97 11.62M13 16H7C7 14.9 6.1 14 5 14V10C6.1 10 7 9.1 7 8H17C17 9.1 17.9 10 19 10V10.05C19.67 10.06 20.34 10.18 21 10.4V6H3V18H13.32C13.1 17.33 13 16.66 13 16Z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/transaction_tip_declined.xml b/wallet/src/main/res/drawable/transaction_tip_declined.xml
deleted file mode 100644
index 4bd16332..00000000
--- a/wallet/src/main/res/drawable/transaction_tip_declined.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- ~ This file is part of GNU Taler
- ~ (C) 2020 Taler Systems S.A.
- ~
- ~ GNU Taler is free software; you can redistribute it and/or modify it under the
- ~ terms of the GNU General Public License as published by the Free Software
- ~ Foundation; either version 3, or (at your option) any later version.
- ~
- ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- ~
- ~ You should have received a copy of the GNU General Public License along with
- ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="#000"
- android:pathData="M10 4A4 4 0 0 0 6 8A4 4 0 0 0 10 12A4 4 0 0 0 14 8A4 4 0 0 0 10 4M17.5 13C15 13 13 15 13 17.5C13 20 15 22 17.5 22C20 22 22 20 22 17.5C22 15 20 13 17.5 13M10 14C5.58 14 2 15.79 2 18V20H11.5A6.5 6.5 0 0 1 11 17.5A6.5 6.5 0 0 1 11.95 14.14C11.32 14.06 10.68 14 10 14M17.5 14.5C19.16 14.5 20.5 15.84 20.5 17.5C20.5 18.06 20.35 18.58 20.08 19L16 14.92C16.42 14.65 16.94 14.5 17.5 14.5M14.92 16L19 20.08C18.58 20.35 18.06 20.5 17.5 20.5C15.84 20.5 14.5 19.16 14.5 17.5C14.5 16.94 14.65 16.42 14.92 16Z" />
-</vector>
diff --git a/wallet/src/main/res/layout/fragment_json.xml b/wallet/src/main/res/layout/fragment_json.xml
deleted file mode 100644
index d9bca8f0..00000000
--- a/wallet/src/main/res/layout/fragment_json.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ This file is part of GNU Taler
- ~ (C) 2020 Taler Systems S.A.
- ~
- ~ GNU Taler is free software; you can redistribute it and/or modify it under the
- ~ terms of the GNU General Public License as published by the Free Software
- ~ Foundation; either version 3, or (at your option) any later version.
- ~
- ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- ~
- ~ You should have received a copy of the GNU General Public License along with
- ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- -->
-
-<ScrollView 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="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/jsonView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:layout_marginTop="8dp"
- android:layout_marginEnd="8dp"
- android:layout_marginBottom="8dp"
- android:fontFamily="monospace"
- android:textSize="12sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="[JSON]" />
-
-</ScrollView>
diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
index c9c9402d..6bca4efa 100644
--- a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -162,12 +162,12 @@
<ImageButton
android:id="@+id/selectExchangeButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:backgroundTint="@color/colorPrimary"
android:contentDescription="@string/nav_exchange_fees"
- android:src="@drawable/ic_cash_usd_outline"
+ android:src="@drawable/ic_edit"
android:tint="?attr/colorOnPrimary"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/withdrawExchangeUrl"
diff --git a/wallet/src/main/res/layout/fragment_transactions.xml b/wallet/src/main/res/layout/fragment_transactions.xml
index 547da245..aaf638c3 100644
--- a/wallet/src/main/res/layout/fragment_transactions.xml
+++ b/wallet/src/main/res/layout/fragment_transactions.xml
@@ -27,7 +27,7 @@
android:scrollbars="vertical"
android:visibility="invisible"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- tools:listitem="@layout/list_item_history"
+ tools:listitem="@layout/list_item_transaction"
tools:visibility="visible" />
<TextView
diff --git a/wallet/src/main/res/layout/list_item_history.xml b/wallet/src/main/res/layout/list_item_history.xml
deleted file mode 100644
index bc94738f..00000000
--- a/wallet/src/main/res/layout/list_item_history.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ This file is part of GNU Taler
- ~ (C) 2020 Taler Systems S.A.
- ~
- ~ GNU Taler is free software; you can redistribute it and/or modify it under the
- ~ terms of the GNU General Public License as published by the Free Software
- ~ Foundation; either version 3, or (at your option) any later version.
- ~
- ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- ~
- ~ You should have received a copy of the GNU General Public License along with
- ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- -->
-
-<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="wrap_content"
- android:background="@drawable/selectable_background"
- android:foreground="?attr/selectableItemBackground"
- android:paddingStart="16dp"
- android:paddingTop="8dp"
- android:paddingEnd="16dp"
- android:paddingBottom="8dp">
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="32dp"
- android:layout_height="32dp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:tint="?android:colorControlNormal"
- tools:ignore="ContentDescription"
- tools:src="@drawable/ic_cash_usd_outline" />
-
- <TextView
- android:id="@+id/title"
- style="@style/TransactionTitle"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="8dp"
- app:layout_constraintEnd_toStartOf="@+id/amount"
- app:layout_constraintStart_toEndOf="@+id/icon"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="@string/payment_title" />
-
- <TextView
- android:id="@+id/amount"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="24sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="- 1337.23" />
-
- <TextView
- android:id="@+id/time"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginEnd="8dp"
- android:textSize="14sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/amount"
- app:layout_constraintStart_toStartOf="@+id/title"
- app:layout_constraintTop_toBottomOf="@+id/title"
- tools:text="23 min ago" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml b/wallet/src/main/res/menu/activity_main_drawer.xml
index 62abc32c..d1cc4627 100644
--- a/wallet/src/main/res/menu/activity_main_drawer.xml
+++ b/wallet/src/main/res/menu/activity_main_drawer.xml
@@ -43,10 +43,6 @@
android:id="@+id/nav_pending_operations"
android:icon="@drawable/ic_sync"
android:title="@string/pending_operations_title" />
- <item
- android:id="@+id/nav_history"
- android:icon="@drawable/ic_history"
- android:title="@string/nav_history" />
</group>
</menu>
</item>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml
index 93db5570..285fac99 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -133,7 +133,7 @@
</fragment>
<fragment
android:id="@+id/selectExchangeFragment"
- android:name="net.taler.wallet.withdraw.SelectExchangeFragment"
+ android:name="net.taler.wallet.exchanges.SelectExchangeFragment"
android:label="@string/nav_exchange_fees"
tools:layout="@layout/fragment_select_exchange" />
@@ -144,12 +144,6 @@
tools:layout="@layout/fragment_pending_operations" />
<fragment
- android:id="@+id/nav_history"
- android:name="net.taler.wallet.history.DevHistoryFragment"
- android:label="@string/nav_history"
- tools:layout="@layout/fragment_transactions" />
-
- <fragment
android:id="@+id/nav_uri_input"
android:name="net.taler.wallet.UriInputFragment"
android:label="@string/enter_uri"
@@ -174,10 +168,6 @@
app:destination="@id/nav_pending_operations" />
<action
- android:id="@+id/action_global_history"
- app:destination="@id/nav_history" />
-
- <action
android:id="@+id/action_nav_transaction_detail"
app:destination="@id/nav_transactions_detail" />
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 421d4ab6..1e629a60 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -40,7 +40,6 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="nav_prompt_withdraw">Withdraw Digital Cash</string>
<string name="nav_exchange_tos">Exchange\'s Terms of Service</string>
<string name="nav_exchange_fees">Exchange Fees</string>
- <string name="nav_history">Event History</string>
<string name="nav_error">Error</string>
<string name="button_back">Go Back</string>
diff --git a/wallet/src/main/res/xml/settings_backup.xml b/wallet/src/main/res/xml/settings_backup.xml
index 52b72ac2..f8c58392 100644
--- a/wallet/src/main/res/xml/settings_backup.xml
+++ b/wallet/src/main/res/xml/settings_backup.xml
@@ -14,7 +14,7 @@
~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
diff --git a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
deleted file mode 100644
index 30332fca..00000000
--- a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.crypto
-
-import org.junit.Test
-
-class Base32CrockfordTest {
- @Test
- fun testBasic() {
- val inputStr = "Hello, World"
- val data = inputStr.toByteArray(Charsets.UTF_8)
- val enc = Base32Crockford.encode(data)
- println(enc)
- val dec = Base32Crockford.decode(enc)
- val recoveredInputStr = dec.toString(Charsets.UTF_8)
- println(recoveredInputStr)
-
- val foo =
- Base32Crockford.decode("51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H2365338E9G6RT4AH1N6H13EGHR70RK6H1S6X2M4CSP8CSK8E1G88VKJH25610KGCHR8RWM4DJ47123CH9K89334D1S8N24ACJ48CR3EH256MR3AH1R711KCE9N6S134GSN6RW46D1H6CV3CDHJ6D0KEDHR6D24CD248MWKADHJ6WT34D25712KCD2474V46EA18H2M4GHM6WTK2E216S14CD238GSK0G9G692KCDHM6RW34CT16MV3CG9P60S34C1G70SMCHHQ8CVKJG9K6CVK6GHK70R46HJ26CR4AE9M8523ADHS8RR3EE1R74S32CHP6N1K0GT38D1M6C1R84TM2E9N8MSK2C1J71248E9H6H1MCD9J70VK4GSG6124CCHR6RS4ADSH8N0M4H1G84R4CD1G8D24AG9N6RR48DT1712K6GJ26X232DT36N0K4C9M8H236HJ48N2K4G9H8GVM8E1P8GSM6E9K891K4CSN65348C26611M8DHJ8S1M6H9G8H338CHS6GV3CD9K64S3GCHR8H2M6GJ58MT3EHA26S232GSJ6GTMAGA570W44DA2852KEDSR8MTKEGA460T3CCT18MR48CHK6WWKEGJ460WK4EA568VM6GSJ70T32CA461234DJ66RS34DHM6D242CT46MV3JDA584S4ADSM6S1MAE1P6GTKEGA68N1M8E216WRMAGHM6RR4ADSJ8MR3EDJ2690KAD9H6H346D9R88RKECSN8RRKJC1N74W34DSQ60W48DSJ8S1K0DSH8D1M4E1J6H1M2D1S8S33CG9R6RSMCH9K4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00")
- println(foo.toString(Charsets.UTF_8))
- }
-}