From 4f665e694b819f7999bb96919d8b468c2a3de48b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 17 Jul 2020 16:25:14 -0300 Subject: [wallet] add UI for making manual withdrawal via exchange --- .../src/main/java/net/taler/common/Amount.kt | 1 + .../src/main/java/net/taler/common/AndroidUtils.kt | 7 ++ wallet/build.gradle | 4 +- .../net/taler/wallet/exchanges/ExchangeAdapter.kt | 33 +++++- .../taler/wallet/exchanges/ExchangeListFragment.kt | 14 ++- .../net/taler/wallet/exchanges/ExchangeManager.kt | 35 ++++-- .../wallet/exchanges/ManualWithdrawFragment.kt | 61 +++++++++++ .../main/res/drawable/ic_baseline_more_vert.xml | 26 +++++ .../src/main/res/layout/fragment_exchange_list.xml | 2 +- .../main/res/layout/fragment_manual_withdraw.xml | 118 +++++++++++++++++++++ wallet/src/main/res/layout/list_item_exchange.xml | 22 +++- wallet/src/main/res/menu/exchange.xml | 21 ++++ wallet/src/main/res/navigation/nav_graph.xml | 11 +- wallet/src/main/res/values/strings.xml | 7 ++ 14 files changed, 337 insertions(+), 25 deletions(-) create mode 100644 wallet/src/main/java/net/taler/wallet/exchanges/ManualWithdrawFragment.kt create mode 100644 wallet/src/main/res/drawable/ic_baseline_more_vert.xml create mode 100644 wallet/src/main/res/layout/fragment_manual_withdraw.xml create mode 100644 wallet/src/main/res/menu/exchange.xml diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt index bd12a40..76cd294 100644 --- a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt +++ b/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt @@ -159,6 +159,7 @@ data class Amount( @Throws(AmountOverflowException::class) operator fun times(factor: Int): Amount { + if (factor == 0) return zero(currency) var result = this for (i in 1 until factor) result += this return result diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt index fda537b..ba6ee1c 100644 --- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt +++ b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt @@ -38,6 +38,8 @@ import android.text.format.DateUtils.getRelativeTimeSpanString import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE +import android.view.inputmethod.InputMethodManager +import androidx.core.content.ContextCompat.getSystemService import androidx.fragment.app.Fragment import androidx.navigation.NavDirections import androidx.navigation.fragment.findNavController @@ -61,6 +63,11 @@ fun View.fadeOut(endAction: () -> Unit = {}) { }.start() } +fun View.hideKeyboard() { + getSystemService(context, InputMethodManager::class.java) + ?.hideSoftInputFromWindow(windowToken, 0) +} + fun assertUiThread() { check(Looper.getMainLooper().thread == Thread.currentThread()) } diff --git a/wallet/build.gradle b/wallet/build.gradle index aa5fbad..d93b8b9 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -23,7 +23,7 @@ plugins { id "de.undercouch.download" } -def walletCoreVersion = "v0.7.1-dev.9" +def walletCoreVersion = "v0.7.1-dev.10" android { compileSdkVersion 29 @@ -35,7 +35,7 @@ android { minSdkVersion 24 targetSdkVersion 29 versionCode 6 - versionName "0.7.1.dev.9" + versionName "0.7.1.dev.10" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "WALLET_CORE_VERSION", "\"$walletCoreVersion\"" } diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt index f53ce46..189f444 100644 --- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt @@ -19,8 +19,11 @@ package net.taler.wallet.exchanges import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageButton import android.widget.TextView +import androidx.appcompat.widget.PopupMenu import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Adapter import net.taler.wallet.R import net.taler.wallet.cleanExchange import net.taler.wallet.exchanges.ExchangeAdapter.ExchangeItemViewHolder @@ -29,9 +32,16 @@ data class ExchangeItem( val exchangeBaseUrl: String, val currency: String, val paytoUris: List -) +) { + val name: String get() = cleanExchange(exchangeBaseUrl) +} + +interface ExchangeClickListener { + fun onManualWithdraw(item: ExchangeItem) +} -internal class ExchangeAdapter : RecyclerView.Adapter() { +internal class ExchangeAdapter(private val listener: ExchangeClickListener) : + Adapter() { private val items = ArrayList() @@ -57,9 +67,26 @@ internal class ExchangeAdapter : RecyclerView.Adapter() private val context = v.context private val urlView: TextView = v.findViewById(R.id.urlView) private val currencyView: TextView = v.findViewById(R.id.currencyView) + private val overflowIcon: ImageButton = v.findViewById(R.id.overflowIcon) + fun bind(item: ExchangeItem) { - urlView.text = cleanExchange(item.exchangeBaseUrl) + urlView.text = item.name currencyView.text = context.getString(R.string.exchange_list_currency, item.currency) + overflowIcon.setOnClickListener { openMenu(overflowIcon, item) } + } + + private fun openMenu(anchor: View, item: ExchangeItem) = PopupMenu(context, anchor).apply { + inflate(R.menu.exchange) + setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + R.id.action_manual_withdrawal -> { + listener.onManualWithdraw(item) + true + } + else -> false + } + } + show() } } diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt index c844042..c7da205 100644 --- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt @@ -25,8 +25,9 @@ import android.widget.Toast.LENGTH_LONG import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL import kotlinx.android.synthetic.main.fragment_exchange_list.* import net.taler.common.EventObserver import net.taler.common.fadeIn @@ -34,11 +35,11 @@ import net.taler.common.fadeOut import net.taler.wallet.MainViewModel import net.taler.wallet.R -class ExchangeListFragment : Fragment() { +class ExchangeListFragment : Fragment(), ExchangeClickListener { private val model: MainViewModel by activityViewModels() private val exchangeManager by lazy { model.exchangeManager } - private val exchangeAdapter by lazy { ExchangeAdapter() } + private val exchangeAdapter by lazy { ExchangeAdapter(this) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -50,7 +51,7 @@ class ExchangeListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { list.apply { adapter = exchangeAdapter - addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL)) + addItemDecoration(DividerItemDecoration(context, VERTICAL)) } addExchangeFab.setOnClickListener { AddExchangeDialogFragment().show(parentFragmentManager, "ADD_EXCHANGE") @@ -82,4 +83,9 @@ class ExchangeListFragment : Fragment() { Toast.makeText(requireContext(), R.string.exchange_add_error, LENGTH_LONG).show() } + override fun onManualWithdraw(item: ExchangeItem) { + exchangeManager.withdrawalExchange = item + findNavController().navigate(R.id.action_nav_settings_exchanges_to_nav_exchange_manual_withdrawal) + } + } 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 4b93c40..cdd5590 100644 --- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt +++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt @@ -21,6 +21,7 @@ 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 @@ -41,6 +42,23 @@ class ExchangeManager( private val mAddError = MutableLiveData>() val addError: LiveData> = mAddError + var withdrawalExchange: ExchangeItem? = null + + private fun list(): LiveData> { + mProgress.value = true + walletBackendApi.sendRequest("listExchanges", JSONObject()) { isError, result -> + if (isError) { + throw AssertionError("Wallet core failed to return exchanges!") + } else { + val exchanges: List = mapper.readValue(result.getString("exchanges")) + Log.d(TAG, "Exchange list: $exchanges") + mProgress.value = false + mExchanges.value = exchanges + } + } + return mExchanges + } + fun add(exchangeUrl: String) { mProgress.value = true val args = JSONObject().apply { put("exchangeBaseUrl", exchangeUrl) } @@ -56,19 +74,18 @@ class ExchangeManager( } } - private fun list(): LiveData> { - mProgress.value = true - walletBackendApi.sendRequest("listExchanges", JSONObject()) { isError, result -> + fun getWithdrawalDetails(exchangeItem: ExchangeItem, amount: Amount) { + val args = JSONObject().apply { + put("exchangeBaseUrl", exchangeItem.exchangeBaseUrl) + put("amount", amount.toJSONString()) + } + walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { isError, result -> if (isError) { - throw AssertionError("Wallet core failed to return exchanges!") + Log.e(TAG, "$result") } else { - val exchanges: List = mapper.readValue(result.getString("exchanges")) - Log.d(TAG, "Exchange list: $exchanges") - mProgress.value = false - mExchanges.value = exchanges + Log.e(TAG, "$result") } } - return mExchanges } } diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ManualWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ManualWithdrawFragment.kt new file mode 100644 index 0000000..c3f201d --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/exchanges/ManualWithdrawFragment.kt @@ -0,0 +1,61 @@ +/* + * 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 + */ + +package net.taler.wallet.exchanges + +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 kotlinx.android.synthetic.main.fragment_manual_withdraw.* +import net.taler.common.Amount +import net.taler.common.hideKeyboard +import net.taler.wallet.MainViewModel +import net.taler.wallet.R +import net.taler.wallet.scanQrCode + +class ManualWithdrawFragment : Fragment() { + + private val model: MainViewModel by activityViewModels() + private val exchangeManager by lazy { model.exchangeManager } + private val exchangeItem by lazy { requireNotNull(exchangeManager.withdrawalExchange) } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_manual_withdraw, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + qrCodeButton.setOnClickListener { scanQrCode(requireActivity()) } + currencyView.text = exchangeItem.currency + paymentOptionsLabel.text = + getString(R.string.withdraw_manual_payment_options, exchangeItem.name) + checkFeesButton.setOnClickListener { + val value = amountView.text.toString().toLong() + val amount = Amount(exchangeItem.currency, value, 0) + amountView.hideKeyboard() + Toast.makeText(view.context, "Not implemented: $amount", LENGTH_SHORT).show() + exchangeManager.getWithdrawalDetails(exchangeItem, amount) + } + } + +} diff --git a/wallet/src/main/res/drawable/ic_baseline_more_vert.xml b/wallet/src/main/res/drawable/ic_baseline_more_vert.xml new file mode 100644 index 0000000..b19e0f1 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_baseline_more_vert.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/wallet/src/main/res/layout/fragment_exchange_list.xml b/wallet/src/main/res/layout/fragment_exchange_list.xml index c7404ae..29d88c7 100644 --- a/wallet/src/main/res/layout/fragment_exchange_list.xml +++ b/wallet/src/main/res/layout/fragment_exchange_list.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_exchange" tools:visibility="visible" /> + + + +