From 8b7947a14349877120e74127a3949a73f917c3d2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 15 Apr 2020 09:37:37 -0300 Subject: [wallet] use floating action button for scan action This prepares a layout change for display of balances and transactions. --- .../main/java/net/taler/wallet/BalanceAdapter.kt | 79 ++++++++ .../main/java/net/taler/wallet/BalanceFragment.kt | 205 --------------------- .../src/main/java/net/taler/wallet/MainActivity.kt | 5 +- .../src/main/java/net/taler/wallet/MainFragment.kt | 152 +++++++++++++++ .../main/java/net/taler/wallet/MainViewModel.kt | 122 ++++++++++++ wallet/src/main/java/net/taler/wallet/Settings.kt | 142 -------------- .../main/java/net/taler/wallet/SettingsFragment.kt | 142 ++++++++++++++ .../main/java/net/taler/wallet/WalletViewModel.kt | 122 ------------ .../taler/wallet/history/HistoryEventFragment.kt | 4 +- .../net/taler/wallet/history/HistoryFragment.kt | 4 +- .../taler/wallet/payment/PromptPaymentFragment.kt | 4 +- .../wallet/pending/PendingOperationsFragment.kt | 4 +- .../net/taler/wallet/withdraw/ErrorFragment.kt | 4 +- .../wallet/withdraw/PromptWithdrawFragment.kt | 4 +- .../wallet/withdraw/ReviewExchangeTosFragment.kt | 4 +- .../wallet/withdraw/SelectExchangeFragment.kt | 4 +- wallet/src/main/res/layout/fragment_settings.xml | 2 +- .../src/main/res/layout/fragment_show_balance.xml | 72 ++------ wallet/src/main/res/menu/activity_main_drawer.xml | 4 - wallet/src/main/res/navigation/nav_graph.xml | 4 +- 20 files changed, 534 insertions(+), 549 deletions(-) create mode 100644 wallet/src/main/java/net/taler/wallet/BalanceAdapter.kt delete mode 100644 wallet/src/main/java/net/taler/wallet/BalanceFragment.kt create mode 100644 wallet/src/main/java/net/taler/wallet/MainFragment.kt create mode 100644 wallet/src/main/java/net/taler/wallet/MainViewModel.kt delete mode 100644 wallet/src/main/java/net/taler/wallet/Settings.kt create mode 100644 wallet/src/main/java/net/taler/wallet/SettingsFragment.kt delete mode 100644 wallet/src/main/java/net/taler/wallet/WalletViewModel.kt (limited to 'wallet') diff --git a/wallet/src/main/java/net/taler/wallet/BalanceAdapter.kt b/wallet/src/main/java/net/taler/wallet/BalanceAdapter.kt new file mode 100644 index 0000000..96cfb99 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/BalanceAdapter.kt @@ -0,0 +1,79 @@ +/* + * 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 + +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.TextView +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Adapter +import net.taler.wallet.BalanceAdapter.BalanceViewHolder + +class BalanceAdapter(private val listener: BalanceClickListener) : Adapter() { + + private var items = emptyList() + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalanceViewHolder { + val v = + LayoutInflater.from(parent.context).inflate(R.layout.list_item_balance, parent, false) + return BalanceViewHolder(v) + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder(holder: BalanceViewHolder, position: Int) { + val item = items[position] + holder.bind(item) + } + + fun setItems(items: List) { + this.items = items + this.notifyDataSetChanged() + } + + inner class BalanceViewHolder(private val v: View) : RecyclerView.ViewHolder(v) { + private val currencyView: TextView = v.findViewById(R.id.balance_currency) + private val amountView: TextView = v.findViewById(R.id.balance_amount) + private val balanceInboundAmount: TextView = v.findViewById(R.id.balanceInboundAmount) + private val balanceInboundLabel: TextView = v.findViewById(R.id.balanceInboundLabel) + + fun bind(item: BalanceItem) { + v.setOnClickListener { listener.onBalanceClick() } + currencyView.text = item.available.currency + amountView.text = item.available.amountStr + + val amountIncoming = item.pendingIncoming + if (amountIncoming.isZero()) { + balanceInboundAmount.visibility = GONE + balanceInboundLabel.visibility = GONE + } else { + balanceInboundAmount.visibility = VISIBLE + balanceInboundLabel.visibility = VISIBLE + balanceInboundAmount.text = + v.context.getString(R.string.amount_positive, amountIncoming) + } + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt deleted file mode 100644 index 3d5364b..0000000 --- a/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt +++ /dev/null @@ -1,205 +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 - */ - -package net.taler.wallet - -import android.os.Bundle -import android.transition.TransitionManager.beginDelayedTransition -import android.util.Log -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.view.ViewGroup -import android.widget.TextView -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 androidx.recyclerview.widget.RecyclerView.Adapter -import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.google.zxing.integration.android.IntentIntegrator -import com.google.zxing.integration.android.IntentIntegrator.QR_CODE -import kotlinx.android.synthetic.main.fragment_show_balance.* -import net.taler.wallet.BalanceAdapter.BalanceViewHolder - -interface BalanceClickListener { - fun onBalanceClick() -} - -class BalanceFragment : Fragment(), BalanceClickListener { - - private val model: WalletViewModel by activityViewModels() - private val withdrawManager by lazy { model.withdrawManager } - - private var reloadBalanceMenuItem: MenuItem? = null - private val balancesAdapter = BalanceAdapter(this) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_show_balance, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - balancesList.apply { - layoutManager = LinearLayoutManager(context) - adapter = balancesAdapter - addItemDecoration(DividerItemDecoration(context, VERTICAL)) - } - - model.balances.observe(viewLifecycleOwner, Observer { - onBalancesChanged(it) - }) - - model.devMode.observe(viewLifecycleOwner, Observer { enabled -> - delayedTransition() - testWithdrawButton.visibility = if (enabled) VISIBLE else GONE - reloadBalanceMenuItem?.isVisible = enabled - }) - testWithdrawButton.setOnClickListener { - withdrawManager.withdrawTestkudos() - } - withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, Observer { loading -> - Log.v("taler-wallet", "observing balance loading $loading in show balance") - testWithdrawButton.isEnabled = !loading - model.showProgressBar.value = loading - }) - - scanButton.setOnClickListener { - IntentIntegrator(activity).apply { - setPrompt("") - setBeepEnabled(true) - setOrientationLocked(false) - }.initiateScan(listOf(QR_CODE)) - } - } - - override fun onStart() { - super.onStart() - model.loadBalances() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.reload_balance -> { - model.loadBalances() - true - } - R.id.developer_mode -> { - item.isChecked = !item.isChecked - model.devMode.value = item.isChecked - true - } - else -> super.onOptionsItemSelected(item) - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.balance, menu) - menu.findItem(R.id.developer_mode).isChecked = model.devMode.value!! - reloadBalanceMenuItem = menu.findItem(R.id.reload_balance).apply { - isVisible = model.devMode.value!! - } - super.onCreateOptionsMenu(menu, inflater) - } - - private fun onBalancesChanged(balances: List) { - delayedTransition() - if (balances.isEmpty()) { - balancesEmptyState.visibility = VISIBLE - balancesList.visibility = GONE - } else { - balancesAdapter.setItems(balances) - balancesEmptyState.visibility = GONE - balancesList.visibility = VISIBLE - } - } - - private fun delayedTransition() { - beginDelayedTransition(view as ViewGroup) - } - - override fun onBalanceClick() { - findNavController().navigate(R.id.walletHistory) - } - -} - -class BalanceAdapter(private val listener: BalanceClickListener) : Adapter() { - - private var items = emptyList() - - init { - setHasStableIds(false) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalanceViewHolder { - val v = - LayoutInflater.from(parent.context).inflate(R.layout.list_item_balance, parent, false) - return BalanceViewHolder(v) - } - - override fun getItemCount() = items.size - - override fun onBindViewHolder(holder: BalanceViewHolder, position: Int) { - val item = items[position] - holder.bind(item) - } - - fun setItems(items: List) { - this.items = items - this.notifyDataSetChanged() - } - - inner class BalanceViewHolder(private val v: View) : ViewHolder(v) { - private val currencyView: TextView = v.findViewById(R.id.balance_currency) - private val amountView: TextView = v.findViewById(R.id.balance_amount) - private val balanceInboundAmount: TextView = v.findViewById(R.id.balanceInboundAmount) - private val balanceInboundLabel: TextView = v.findViewById(R.id.balanceInboundLabel) - - fun bind(item: BalanceItem) { - v.setOnClickListener { listener.onBalanceClick() } - currencyView.text = item.available.currency - amountView.text = item.available.amountStr - - val amountIncoming = item.pendingIncoming - if (amountIncoming.isZero()) { - balanceInboundAmount.visibility = GONE - balanceInboundLabel.visibility = GONE - } else { - balanceInboundAmount.visibility = VISIBLE - balanceInboundLabel.visibility = VISIBLE - balanceInboundAmount.text = - v.context.getString(R.string.amount_positive, amountIncoming) - } - } - } - -} diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt index 6f0ed89..a43cbf2 100644 --- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt +++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt @@ -57,7 +57,7 @@ import java.util.Locale.ROOT class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, ResetDialogEventListener { - private val model: WalletViewModel by viewModels() + private val model: MainViewModel by viewModels() private lateinit var nav: NavController @@ -76,7 +76,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, setSupportActionBar(toolbar) val appBarConfiguration = AppBarConfiguration( - setOf(R.id.showBalance, R.id.settings, R.id.walletHistory, R.id.nav_pending_operations), + setOf(R.id.showBalance, R.id.settings, R.id.nav_pending_operations), drawer_layout ) toolbar.setupWithNavController(nav, appBarConfiguration) @@ -116,7 +116,6 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, when (item.itemId) { R.id.nav_home -> nav.navigate(R.id.showBalance) R.id.nav_settings -> nav.navigate(R.id.settings) - R.id.nav_history -> nav.navigate(R.id.walletHistory) R.id.nav_pending_operations -> nav.navigate(R.id.nav_pending_operations) } drawer_layout.closeDrawer(START) diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt b/wallet/src/main/java/net/taler/wallet/MainFragment.kt new file mode 100644 index 0000000..a0eb8ff --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt @@ -0,0 +1,152 @@ +/* + * 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 + +import android.os.Bundle +import android.transition.TransitionManager.beginDelayedTransition +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.View.GONE +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.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL +import com.google.zxing.integration.android.IntentIntegrator +import com.google.zxing.integration.android.IntentIntegrator.QR_CODE +import kotlinx.android.synthetic.main.fragment_show_balance.* + +interface BalanceClickListener { + fun onBalanceClick() +} + +class MainFragment : Fragment(), BalanceClickListener { + + private val model: MainViewModel by activityViewModels() + private val withdrawManager by lazy { model.withdrawManager } + + private var reloadBalanceMenuItem: MenuItem? = null + private val balancesAdapter = BalanceAdapter(this) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_show_balance, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + mainList.apply { + layoutManager = LinearLayoutManager(context) + adapter = balancesAdapter + addItemDecoration(DividerItemDecoration(context, VERTICAL)) + } + + model.balances.observe(viewLifecycleOwner, Observer { + onBalancesChanged(it) + }) + +// model.devMode.observe(viewLifecycleOwner, Observer { enabled -> +// delayedTransition() +// testWithdrawButton.visibility = if (enabled) VISIBLE else GONE +// reloadBalanceMenuItem?.isVisible = enabled +// }) +// testWithdrawButton.setOnClickListener { +// withdrawManager.withdrawTestkudos() +// } +// withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, Observer { loading -> +// Log.v("taler-wallet", "observing balance loading $loading in show balance") +// testWithdrawButton.isEnabled = !loading +// model.showProgressBar.value = loading +// }) + + mainFab.setOnClickListener { + onScanButtonClicked() + } + } + + override fun onStart() { + super.onStart() + model.loadBalances() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.reload_balance -> { + model.loadBalances() + true + } + R.id.developer_mode -> { + item.isChecked = !item.isChecked + model.devMode.value = item.isChecked + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.balance, menu) + menu.findItem(R.id.developer_mode).isChecked = model.devMode.value!! + reloadBalanceMenuItem = menu.findItem(R.id.reload_balance).apply { + isVisible = model.devMode.value!! + } + super.onCreateOptionsMenu(menu, inflater) + } + + private fun onScanButtonClicked() { + IntentIntegrator(activity).apply { + setPrompt("") + setBeepEnabled(true) + setOrientationLocked(false) + }.initiateScan(listOf(QR_CODE)) + } + + private fun onBalancesChanged(balances: List) { + delayedTransition() + if (balances.isEmpty()) { + mainEmptyState.visibility = VISIBLE + mainList.visibility = GONE + } else { + balancesAdapter.setItems(balances) + mainEmptyState.visibility = GONE + mainList.visibility = VISIBLE + } + } + + private fun delayedTransition() { + beginDelayedTransition(view as ViewGroup) + } + + override fun onBalanceClick() { + findNavController().navigate(R.id.walletHistory) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt new file mode 100644 index 0000000..e5f385a --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -0,0 +1,122 @@ +/* + * 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 + +import android.app.Application +import android.util.Log +import androidx.annotation.UiThread +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.distinctUntilChanged +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 net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.history.HistoryManager +import net.taler.wallet.payment.PaymentManager +import net.taler.wallet.pending.PendingOperationsManager +import net.taler.wallet.refund.RefundManager +import net.taler.wallet.withdraw.WithdrawManager +import org.json.JSONObject + +const val TAG = "taler-wallet" + +data class BalanceItem(val available: Amount, val pendingIncoming: Amount) + +class MainViewModel(val app: Application) : AndroidViewModel(app) { + + private val mBalances = MutableLiveData>() + val balances: LiveData> = mBalances.distinctUntilChanged() + + val devMode = MutableLiveData(BuildConfig.DEBUG) + val showProgressBar = MutableLiveData() + + private val walletBackendApi = WalletBackendApi(app, { + // nothing to do when we connect, balance will be requested by BalanceFragment in onStart() + }) { payload -> + if ( + payload.getString("type") != "waiting-for-retry" && // ignore ping + payload.optString("operation") != "init" // ignore init notification + ) { + Log.i(TAG, "Received notification from wallet-core: ${payload.toString(2)}") + loadBalances() + } + } + + private val mapper = ObjectMapper() + .registerModule(KotlinModule()) + .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) + + val withdrawManager = WithdrawManager(walletBackendApi) + val paymentManager = PaymentManager(walletBackendApi, mapper) + val pendingOperationsManager = PendingOperationsManager(walletBackendApi) + val historyManager = HistoryManager(walletBackendApi, mapper) + val refundManager = RefundManager(walletBackendApi) + + override fun onCleared() { + walletBackendApi.destroy() + super.onCleared() + } + + @UiThread + fun loadBalances() { + showProgressBar.value = true + walletBackendApi.sendRequest("getBalances", null) { isError, result -> + if (isError) { + Log.e(TAG, "Error retrieving balances: ${result.toString(2)}") + return@sendRequest + } + val balanceList = mutableListOf() + 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) + balanceList.add(BalanceItem(amount, amountIncoming)) + } + mBalances.postValue(balanceList) + showProgressBar.postValue(false) + } + } + + @UiThread + fun dangerouslyReset() { + walletBackendApi.sendRequest("reset", null) + withdrawManager.testWithdrawalInProgress.value = false + mBalances.value = emptyList() + } + + fun startTunnel() { + walletBackendApi.sendRequest("startTunnel", null) + } + + fun stopTunnel() { + walletBackendApi.sendRequest("stopTunnel", null) + } + + fun tunnelResponse(resp: String) { + val respJson = JSONObject(resp) + walletBackendApi.sendRequest("tunnelResponse", respJson) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/Settings.kt b/wallet/src/main/java/net/taler/wallet/Settings.kt deleted file mode 100644 index 572c036..0000000 --- a/wallet/src/main/java/net/taler/wallet/Settings.kt +++ /dev/null @@ -1,142 +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 - */ - -package net.taler.wallet - -import android.app.Dialog -import android.content.Context -import android.content.Intent -import android.content.Intent.ACTION_CREATE_DOCUMENT -import android.content.Intent.ACTION_OPEN_DOCUMENT -import android.content.Intent.CATEGORY_OPENABLE -import android.content.Intent.EXTRA_TITLE -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer -import kotlinx.android.synthetic.main.fragment_settings.* - - -interface ResetDialogEventListener { - fun onResetConfirmed() - fun onResetCancelled() -} - - -class ResetDialogFragment : DialogFragment() { - private lateinit var listener: ResetDialogEventListener - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return activity?.let { - // Use the Builder class for convenient dialog construction - val builder = AlertDialog.Builder(it) - builder.setMessage("Do you really want to reset the wallet and lose all coins and purchases? Consider making a backup first.") - .setPositiveButton("Reset") { _, _ -> - listener.onResetConfirmed() - } - .setNegativeButton("Cancel") { _, _ -> - listener.onResetCancelled() - } - // Create the AlertDialog object and return it - builder.create() - } ?: throw IllegalStateException("Activity cannot be null") - } - - override fun onAttach(context: Context) { - super.onAttach(context) - // Verify that the host activity implements the callback interface - try { - // Instantiate the NoticeDialogListener so we can send events to the host - listener = context as ResetDialogEventListener - } catch (e: ClassCastException) { - // The activity doesn't implement the interface, throw exception - throw ClassCastException( - (context.toString() + - " must implement ResetDialogEventListener") - ) - } - } -} - -class Settings : Fragment() { - - companion object { - private const val TAG = "taler-wallet" - private const val CREATE_FILE = 1 - private const val PICK_FILE = 2 - } - - private val model: WalletViewModel by activityViewModels() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_settings, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - model.devMode.observe(viewLifecycleOwner, Observer { enabled -> - val visibility = if (enabled) VISIBLE else GONE - devSettingsTitle.visibility = visibility - button_reset_wallet_dangerously.visibility = visibility - }) - - textView4.text = BuildConfig.VERSION_NAME - button_reset_wallet_dangerously.setOnClickListener { - val d = ResetDialogFragment() - d.show(parentFragmentManager, "walletResetDialog") - } - button_backup_export.setOnClickListener { - val intent = Intent(ACTION_CREATE_DOCUMENT).apply { - addCategory(CATEGORY_OPENABLE) - type = "application/json" - putExtra(EXTRA_TITLE, "taler-wallet-backup.json") - - // Optionally, specify a URI for the directory that should be opened in - // the system file picker before your app creates the document. - //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) - } - startActivityForResult(intent, CREATE_FILE) - } - button_backup_import.setOnClickListener { - val intent = Intent(ACTION_OPEN_DOCUMENT).apply { - addCategory(CATEGORY_OPENABLE) - type = "application/json" - - //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) - } - startActivityForResult(intent, PICK_FILE) - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (data == null) return - when (requestCode) { - CREATE_FILE -> Log.i(TAG, "got createFile result with URL ${data.data}") - PICK_FILE -> Log.i(TAG, "got pickFile result with URL ${data.data}") - } - } - -} diff --git a/wallet/src/main/java/net/taler/wallet/SettingsFragment.kt b/wallet/src/main/java/net/taler/wallet/SettingsFragment.kt new file mode 100644 index 0000000..559b162 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/SettingsFragment.kt @@ -0,0 +1,142 @@ +/* + * 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 + +import android.app.Dialog +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_CREATE_DOCUMENT +import android.content.Intent.ACTION_OPEN_DOCUMENT +import android.content.Intent.CATEGORY_OPENABLE +import android.content.Intent.EXTRA_TITLE +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import kotlinx.android.synthetic.main.fragment_settings.* + + +interface ResetDialogEventListener { + fun onResetConfirmed() + fun onResetCancelled() +} + + +class ResetDialogFragment : DialogFragment() { + private lateinit var listener: ResetDialogEventListener + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { + // Use the Builder class for convenient dialog construction + val builder = AlertDialog.Builder(it) + builder.setMessage("Do you really want to reset the wallet and lose all coins and purchases? Consider making a backup first.") + .setPositiveButton("Reset") { _, _ -> + listener.onResetConfirmed() + } + .setNegativeButton("Cancel") { _, _ -> + listener.onResetCancelled() + } + // Create the AlertDialog object and return it + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + + override fun onAttach(context: Context) { + super.onAttach(context) + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + listener = context as ResetDialogEventListener + } catch (e: ClassCastException) { + // The activity doesn't implement the interface, throw exception + throw ClassCastException( + (context.toString() + + " must implement ResetDialogEventListener") + ) + } + } +} + +class SettingsFragment : Fragment() { + + companion object { + private const val TAG = "taler-wallet" + private const val CREATE_FILE = 1 + private const val PICK_FILE = 2 + } + + private val model: MainViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_settings, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + model.devMode.observe(viewLifecycleOwner, Observer { enabled -> + val visibility = if (enabled) VISIBLE else GONE + devSettingsTitle.visibility = visibility + button_reset_wallet_dangerously.visibility = visibility + }) + + textView4.text = BuildConfig.VERSION_NAME + button_reset_wallet_dangerously.setOnClickListener { + val d = ResetDialogFragment() + d.show(parentFragmentManager, "walletResetDialog") + } + button_backup_export.setOnClickListener { + val intent = Intent(ACTION_CREATE_DOCUMENT).apply { + addCategory(CATEGORY_OPENABLE) + type = "application/json" + putExtra(EXTRA_TITLE, "taler-wallet-backup.json") + + // Optionally, specify a URI for the directory that should be opened in + // the system file picker before your app creates the document. + //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) + } + startActivityForResult(intent, CREATE_FILE) + } + button_backup_import.setOnClickListener { + val intent = Intent(ACTION_OPEN_DOCUMENT).apply { + addCategory(CATEGORY_OPENABLE) + type = "application/json" + + //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) + } + startActivityForResult(intent, PICK_FILE) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (data == null) return + when (requestCode) { + CREATE_FILE -> Log.i(TAG, "got createFile result with URL ${data.data}") + PICK_FILE -> Log.i(TAG, "got pickFile result with URL ${data.data}") + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt deleted file mode 100644 index 607ce15..0000000 --- a/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt +++ /dev/null @@ -1,122 +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 - */ - -package net.taler.wallet - -import android.app.Application -import android.util.Log -import androidx.annotation.UiThread -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.distinctUntilChanged -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 net.taler.wallet.backend.WalletBackendApi -import net.taler.wallet.history.HistoryManager -import net.taler.wallet.payment.PaymentManager -import net.taler.wallet.pending.PendingOperationsManager -import net.taler.wallet.refund.RefundManager -import net.taler.wallet.withdraw.WithdrawManager -import org.json.JSONObject - -const val TAG = "taler-wallet" - -data class BalanceItem(val available: Amount, val pendingIncoming: Amount) - -class WalletViewModel(val app: Application) : AndroidViewModel(app) { - - private val mBalances = MutableLiveData>() - val balances: LiveData> = mBalances.distinctUntilChanged() - - val devMode = MutableLiveData(BuildConfig.DEBUG) - val showProgressBar = MutableLiveData() - - private val walletBackendApi = WalletBackendApi(app, { - // nothing to do when we connect, balance will be requested by BalanceFragment in onStart() - }) { payload -> - if ( - payload.getString("type") != "waiting-for-retry" && // ignore ping - payload.optString("operation") != "init" // ignore init notification - ) { - Log.i(TAG, "Received notification from wallet-core: ${payload.toString(2)}") - loadBalances() - } - } - - private val mapper = ObjectMapper() - .registerModule(KotlinModule()) - .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) - - val withdrawManager = WithdrawManager(walletBackendApi) - val paymentManager = PaymentManager(walletBackendApi, mapper) - val pendingOperationsManager = PendingOperationsManager(walletBackendApi) - val historyManager = HistoryManager(walletBackendApi, mapper) - val refundManager = RefundManager(walletBackendApi) - - override fun onCleared() { - walletBackendApi.destroy() - super.onCleared() - } - - @UiThread - fun loadBalances() { - showProgressBar.value = true - walletBackendApi.sendRequest("getBalances", null) { isError, result -> - if (isError) { - Log.e(TAG, "Error retrieving balances: ${result.toString(2)}") - return@sendRequest - } - val balanceList = mutableListOf() - 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) - balanceList.add(BalanceItem(amount, amountIncoming)) - } - mBalances.postValue(balanceList) - showProgressBar.postValue(false) - } - } - - @UiThread - fun dangerouslyReset() { - walletBackendApi.sendRequest("reset", null) - withdrawManager.testWithdrawalInProgress.value = false - mBalances.value = emptyList() - } - - fun startTunnel() { - walletBackendApi.sendRequest("startTunnel", null) - } - - fun stopTunnel() { - walletBackendApi.sendRequest("stopTunnel", null) - } - - fun tunnelResponse(resp: String) { - val respJson = JSONObject(resp) - walletBackendApi.sendRequest("tunnelResponse", respJson) - } - -} diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt index 0093bb5..b6a2a33 100644 --- a/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt @@ -35,12 +35,12 @@ import kotlinx.android.synthetic.main.fragment_event_withdraw.timeView import net.taler.common.Amount import net.taler.common.toAbsoluteTime import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel import net.taler.wallet.cleanExchange class HistoryEventFragment : Fragment() { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val historyManager by lazy { model.historyManager } private val event by lazy { requireNotNull(historyManager.selectedEvent) } diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryFragment.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryFragment.kt index 73dbae0..9f83d5a 100644 --- a/wallet/src/main/java/net/taler/wallet/history/HistoryFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryFragment.kt @@ -36,7 +36,7 @@ import kotlinx.android.synthetic.main.fragment_show_history.* import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel interface OnEventClickListener { fun onEventClicked(event: HistoryEvent) @@ -44,7 +44,7 @@ interface OnEventClickListener { class HistoryFragment : Fragment(), OnEventClickListener { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val historyManager by lazy { model.historyManager } private lateinit var showAllItem: MenuItem private var reloadHistoryItem: MenuItem? = null diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt index 7fab695..6d31879 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -37,14 +37,14 @@ import net.taler.common.ContractTerms import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel /** * Show a payment and ask the user to accept/decline. */ class PromptPaymentFragment : Fragment(), ProductImageClickListener { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val paymentManager by lazy { model.paymentManager } private val adapter = ProductAdapter(this) diff --git a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt index d8b0896..1e776d4 100644 --- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt @@ -39,7 +39,7 @@ import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT import kotlinx.android.synthetic.main.fragment_pending_operations.* import net.taler.wallet.R import net.taler.wallet.TAG -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel import org.json.JSONObject interface PendingOperationClickListener { @@ -49,7 +49,7 @@ interface PendingOperationClickListener { class PendingOperationsFragment : Fragment(), PendingOperationClickListener { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val pendingOperationsManager by lazy { model.pendingOperationsManager } private val pendingAdapter = PendingOperationsAdapter(emptyList(), this) diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt index f0f6610..fa5ab2f 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt @@ -27,11 +27,11 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import kotlinx.android.synthetic.main.fragment_error.* import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel class ErrorFragment : Fragment() { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val withdrawManager by lazy { model.withdrawManager } override fun onCreateView( 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 56a2a8c..747551b 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -29,7 +29,7 @@ import net.taler.common.Amount import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel import net.taler.wallet.cleanExchange import net.taler.wallet.withdraw.WithdrawStatus.Loading import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired @@ -37,7 +37,7 @@ import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing class PromptWithdrawFragment : Fragment() { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val withdrawManager by lazy { model.withdrawManager } override fun onCreateView( 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 af76971..47b6f14 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt @@ -29,11 +29,11 @@ import kotlinx.android.synthetic.main.fragment_review_exchange_tos.* import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel class ReviewExchangeTosFragment : Fragment() { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val withdrawManager by lazy { model.withdrawManager } override fun onCreateView( diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt index 1e7ee3a..fd614c6 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt @@ -32,13 +32,13 @@ import net.taler.common.Amount import net.taler.common.toRelativeTime import net.taler.common.toShortDate import net.taler.wallet.R -import net.taler.wallet.WalletViewModel +import net.taler.wallet.MainViewModel import net.taler.wallet.withdraw.CoinFeeAdapter.CoinFeeViewHolder import net.taler.wallet.withdraw.WireFeeAdapter.WireFeeViewHolder class SelectExchangeFragment : Fragment() { - private val model: WalletViewModel by activityViewModels() + private val model: MainViewModel by activityViewModels() private val withdrawManager by lazy { model.withdrawManager } override fun onCreateView( diff --git a/wallet/src/main/res/layout/fragment_settings.xml b/wallet/src/main/res/layout/fragment_settings.xml index fdd0120..27c5f57 100644 --- a/wallet/src/main/res/layout/fragment_settings.xml +++ b/wallet/src/main/res/layout/fragment_settings.xml @@ -20,7 +20,7 @@ android:layout_height="match_parent" android:layout_margin="10dp" android:orientation="vertical" - tools:context=".Settings"> + tools:context=".SettingsFragment"> --> - - - - -