diff options
author | Torsten Grote <t@grobox.de> | 2020-04-03 14:04:22 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-04-03 14:04:22 -0300 |
commit | bee652834f90fd8abd46b5adcf30adcc587984c2 (patch) | |
tree | 75c66b3ff5c3160ec063cf33d16400a69823a483 /wallet/src/main/java/net/taler | |
parent | e52ee8f55326de402a7ad421c396eb6c81a79a68 (diff) | |
download | taler-android-bee652834f90fd8abd46b5adcf30adcc587984c2.tar.gz taler-android-bee652834f90fd8abd46b5adcf30adcc587984c2.tar.bz2 taler-android-bee652834f90fd8abd46b5adcf30adcc587984c2.zip |
[wallet] add detail page for withdrawal event in history
Also make history accessible by tapping the balance
Diffstat (limited to 'wallet/src/main/java/net/taler')
8 files changed, 198 insertions, 74 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt index d871cfb..3d5364b 100644 --- a/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt @@ -31,6 +31,7 @@ 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 @@ -41,13 +42,17 @@ import com.google.zxing.integration.android.IntentIntegrator.QR_CODE import kotlinx.android.synthetic.main.fragment_show_balance.* import net.taler.wallet.BalanceAdapter.BalanceViewHolder -class BalanceFragment : Fragment() { +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() + private val balancesAdapter = BalanceAdapter(this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -141,9 +146,13 @@ class BalanceFragment : Fragment() { beginDelayedTransition(view as ViewGroup) } + override fun onBalanceClick() { + findNavController().navigate(R.id.walletHistory) + } + } -class BalanceAdapter : Adapter<BalanceViewHolder>() { +class BalanceAdapter(private val listener: BalanceClickListener) : Adapter<BalanceViewHolder>() { private var items = emptyList<BalanceItem>() @@ -169,13 +178,14 @@ class BalanceAdapter : Adapter<BalanceViewHolder>() { this.notifyDataSetChanged() } - class BalanceViewHolder(private val v: View) : ViewHolder(v) { + 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 diff --git a/wallet/src/main/java/net/taler/wallet/Utils.kt b/wallet/src/main/java/net/taler/wallet/Utils.kt new file mode 100644 index 0000000..ae8712f --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/Utils.kt @@ -0,0 +1,21 @@ +/* + * 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 + +fun cleanExchange(exchange: String) = exchange.let { + if (it.startsWith("https://")) it.substring(8) else it +}.trimEnd('/') diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryAdapter.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryAdapter.kt index 43b7bd7..881ada5 100644 --- a/wallet/src/main/java/net/taler/wallet/history/HistoryAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryAdapter.kt @@ -16,23 +16,19 @@ package net.taler.wallet.history -import android.annotation.SuppressLint +import android.content.Context import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG 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.annotation.CallSuper -import androidx.core.net.toUri import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder -import net.taler.common.Amount import net.taler.common.toRelativeTime -import net.taler.wallet.BuildConfig import net.taler.wallet.R +import net.taler.wallet.cleanExchange import net.taler.wallet.history.HistoryAdapter.HistoryEventViewHolder @@ -68,23 +64,20 @@ internal class HistoryAdapter( this.notifyDataSetChanged() } - internal abstract inner class HistoryEventViewHolder(protected val v: View) : ViewHolder(v) { + internal abstract inner class HistoryEventViewHolder(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) @CallSuper open fun bind(event: HistoryEvent) { - if (BuildConfig.DEBUG) { // doesn't produce recycling issues, no need to cover all cases - v.setOnClickListener { listener.onEventClicked(event) } - } else { - v.background = null - } + v.setOnClickListener { listener.onEventClicked(event) } icon.setImageResource(event.icon) if (event.title == 0) title.text = event::class.java.simpleName else title.setText(event.title) - time.text = event.timestamp.ms.toRelativeTime(v.context) + time.text = event.timestamp.ms.toRelativeTime(context) } } @@ -96,8 +89,8 @@ internal class HistoryAdapter( override fun bind(event: HistoryEvent) { super.bind(event) info.text = when (event) { - is ExchangeAddedEvent -> event.exchangeBaseUrl - is ExchangeUpdatedEvent -> event.exchangeBaseUrl + is ExchangeAddedEvent -> cleanExchange(event.exchangeBaseUrl) + is ExchangeUpdatedEvent -> cleanExchange(event.exchangeBaseUrl) is ReserveBalanceUpdatedEvent -> event.amountReserveBalance.toString() is HistoryPaymentSentEvent -> event.orderShortInfo.summary is HistoryOrderAcceptedEvent -> event.orderShortInfo.summary @@ -113,8 +106,7 @@ internal class HistoryAdapter( private val summary: TextView = v.findViewById(R.id.summary) private val amountWithdrawn: TextView = v.findViewById(R.id.amountWithdrawn) - private val feeLabel: TextView = v.findViewById(R.id.feeLabel) - private val fee: TextView = v.findViewById(R.id.fee) + private val paintFlags = amountWithdrawn.paintFlags override fun bind(event: HistoryEvent) { super.bind(event) @@ -127,52 +119,31 @@ internal class HistoryAdapter( } private fun bind(event: HistoryWithdrawnEvent) { - title.text = getHostname(event.exchangeBaseUrl) - summary.setText(event.title) - - showAmounts(event.amountWithdrawnEffective, event.amountWithdrawnRaw) + summary.text = cleanExchange(event.exchangeBaseUrl) + amountWithdrawn.text = + context.getString(R.string.amount_positive, event.amountWithdrawnEffective) + amountWithdrawn.paintFlags = paintFlags } private fun bind(event: HistoryRefundedEvent) { - title.text = event.orderShortInfo.summary - summary.setText(event.title) - - showAmounts(event.amountRefundedEffective, event.amountRefundedRaw) + summary.text = event.orderShortInfo.summary + amountWithdrawn.text = + context.getString(R.string.amount_positive, event.amountRefundedEffective) + amountWithdrawn.paintFlags = paintFlags } private fun bind(event: HistoryTipAcceptedEvent) { - title.setText(event.title) summary.text = null - showAmounts(event.tipRaw, event.tipRaw) + amountWithdrawn.text = context.getString(R.string.amount_positive, event.tipRaw) + amountWithdrawn.paintFlags = paintFlags } private fun bind(event: HistoryTipDeclinedEvent) { - title.setText(event.title) summary.text = null - showAmounts(event.tipAmount, event.tipAmount) + amountWithdrawn.text = context.getString(R.string.amount_positive, event.tipAmount) amountWithdrawn.paintFlags = amountWithdrawn.paintFlags or STRIKE_THRU_TEXT_FLAG } - private fun showAmounts(effective: Amount, raw: Amount) { - @SuppressLint("SetTextI18n") - amountWithdrawn.text = "+$raw" - val calculatedFee = raw - effective - if (calculatedFee.isZero()) { - fee.visibility = GONE - feeLabel.visibility = GONE - } else { - @SuppressLint("SetTextI18n") - fee.text = "-$calculatedFee" - fee.visibility = VISIBLE - feeLabel.visibility = VISIBLE - } - amountWithdrawn.paintFlags = fee.paintFlags - } - - private fun getHostname(url: String): String { - return url.toUri().host!! - } - } internal inner class HistoryPaymentViewHolder(v: View) : HistoryEventViewHolder(v) { @@ -182,7 +153,6 @@ internal class HistoryAdapter( override fun bind(event: HistoryEvent) { super.bind(event) - summary.setText(event.title) when (event) { is HistoryPaymentSentEvent -> bind(event) is HistoryPaymentAbortedEvent -> bind(event) @@ -191,23 +161,29 @@ internal class HistoryAdapter( } private fun bind(event: HistoryPaymentSentEvent) { - title.text = event.orderShortInfo.summary - @SuppressLint("SetTextI18n") - amountPaidWithFees.text = "-${event.amountPaidWithFees}" + summary.text = event.orderShortInfo.summary + amountPaidWithFees.text = + context.getString(R.string.amount_negative, event.amountPaidWithFees) } private fun bind(event: HistoryPaymentAbortedEvent) { - title.text = event.orderShortInfo.summary - @SuppressLint("SetTextI18n") - amountPaidWithFees.text = "-${event.amountLost}" + summary.text = event.orderShortInfo.summary + amountPaidWithFees.text = context.getString(R.string.amount_negative, event.amountLost) } private fun bind(event: HistoryRefreshedEvent) { - title.text = "" + val res = when (event.refreshReason) { + RefreshReason.MANUAL -> R.string.history_event_refresh_reason_manual + RefreshReason.PAY -> R.string.history_event_refresh_reason_pay + RefreshReason.REFUND -> R.string.history_event_refresh_reason_refund + RefreshReason.ABORT_PAY -> R.string.history_event_refresh_reason_abort_pay + RefreshReason.RECOUP -> R.string.history_event_refresh_reason_recoup + RefreshReason.BACKUP_RESTORED -> R.string.history_event_refresh_reason_backup_restored + } + summary.text = context.getString(res) val fee = event.amountRefreshedRaw - event.amountRefreshedEffective - @SuppressLint("SetTextI18n") if (fee.isZero()) amountPaidWithFees.text = null - else amountPaidWithFees.text = "-$fee" + else amountPaidWithFees.text = context.getString(R.string.amount_negative, fee) } } diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt new file mode 100644 index 0000000..f0dec75 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEventFragment.kt @@ -0,0 +1,80 @@ +/* + * 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.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import kotlinx.android.synthetic.main.fragment_history_event.* +import net.taler.common.toAbsoluteTime +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel +import net.taler.wallet.cleanExchange + +class HistoryEventFragment : Fragment() { + + private val model: WalletViewModel by activityViewModels() + private val historyManager by lazy { model.historyManager } + private val event by lazy { requireNotNull(historyManager.selectedEvent) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (model.devMode.value == true) setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_history_event, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val event = event as HistoryWithdrawnEvent + + timeView.text = event.timestamp.ms.toAbsoluteTime(requireContext()) + effectiveAmountLabel.text = getString(R.string.withdraw_total) + effectiveAmountView.text = event.amountWithdrawnEffective.toString() + chosenAmountLabel.text = getString(R.string.amount_chosen) + chosenAmountView.text = + getString(R.string.amount_positive, event.amountWithdrawnRaw.toString()) + val fee = event.amountWithdrawnRaw - event.amountWithdrawnEffective + feeView.text = getString(R.string.amount_negative, fee.toString()) + exchangeView.text = cleanExchange(event.exchangeBaseUrl) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.history_event, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.show_json -> { + JsonDialogFragment.new(event.json.toString(2)).show(parentFragmentManager, null) + true + } + else -> super.onOptionsItemSelected(item) + } + } + +} 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 2586ef8..b0f6728 100644 --- a/wallet/src/main/java/net/taler/wallet/history/HistoryFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryFragment.kt @@ -28,10 +28,14 @@ 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 kotlinx.android.synthetic.main.fragment_show_history.* +import net.taler.common.exhaustive +import net.taler.common.fadeIn +import net.taler.common.fadeOut import net.taler.wallet.R import net.taler.wallet.WalletViewModel @@ -73,8 +77,7 @@ class HistoryFragment : Fragment(), OnEventClickListener { historyProgressBar.visibility = if (show) VISIBLE else INVISIBLE }) historyManager.history.observe(viewLifecycleOwner, Observer { history -> - historyEmptyState.visibility = if (history.isEmpty()) VISIBLE else INVISIBLE - historyAdapter.update(history) + onHistoryResult(history) }) // kicks off initial load, needs to be adapted if showAll state is ever saved @@ -106,9 +109,29 @@ class HistoryFragment : Fragment(), OnEventClickListener { } override fun onEventClicked(event: HistoryEvent) { - if (model.devMode.value != true) return - JsonDialogFragment.new(event.json.toString(4)) - .show(parentFragmentManager, null) + when (event) { + is HistoryWithdrawnEvent -> { + historyManager.selectedEvent = event + findNavController().navigate(R.id.action_walletHistory_to_historyEventFragment) + } + else -> { + if (model.devMode.value != true) return + JsonDialogFragment.new(event.json.toString(2)) + .show(parentFragmentManager, null) + } + }.exhaustive + } + + private fun onHistoryResult(result: HistoryResult) = when (result) { + HistoryResult.Error -> { + historyList.fadeOut() + historyEmptyState.text = getString(R.string.history_error) + historyEmptyState.fadeIn() + } + is HistoryResult.Success -> { + historyEmptyState.visibility = if (result.history.isEmpty()) VISIBLE else INVISIBLE + historyAdapter.update(result.history) + } } } diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt index c350daa..7ce4f5b 100644 --- a/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt @@ -29,6 +29,11 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart import net.taler.wallet.backend.WalletBackendApi +sealed class HistoryResult { + object Error : HistoryResult() + class Success(val history: History) : HistoryResult() +} + @Suppress("EXPERIMENTAL_API_USAGE") class HistoryManager( private val walletBackendApi: WalletBackendApi, @@ -40,7 +45,9 @@ class HistoryManager( val showAll = MutableLiveData<Boolean>() - val history: LiveData<History> = showAll.switchMap { showAll -> + var selectedEvent: HistoryEvent? = null + + val history: LiveData<HistoryResult> = showAll.switchMap { showAll -> loadHistory(showAll) .onStart { mProgress.postValue(true) } .onCompletion { mProgress.postValue(false) } @@ -50,7 +57,7 @@ class HistoryManager( private fun loadHistory(showAll: Boolean) = callbackFlow { walletBackendApi.sendRequest("getHistory", null) { isError, result -> if (isError) { - // TODO show error message in [WalletHistory] fragment + offer(HistoryResult.Error) close() return@sendRequest } @@ -62,7 +69,8 @@ class HistoryManager( history.add(event) } history.reverse() // show latest first - offer(if (showAll) history else history.filter { it.showToUser } as History) + val filtered = if (showAll) history else history.filter { it.showToUser } as History + offer(HistoryResult.Success(filtered)) close() } awaitClose() diff --git a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt index f51dba9..5421db3 100644 --- a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt @@ -20,6 +20,8 @@ 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 @@ -47,4 +49,9 @@ class JsonDialogFragment : DialogFragment() { 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/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt index ea04e24..5d0fe63 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -30,6 +30,7 @@ import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.wallet.R import net.taler.wallet.WalletViewModel +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.Withdrawing @@ -115,9 +116,7 @@ class PromptWithdrawFragment : Fragment() { feeView.fadeIn() exchangeIntroView.fadeIn() - withdrawExchangeUrl.text = exchange.let { - if (it.startsWith("https://")) it.substring(8) else it - }.trimEnd('/') + withdrawExchangeUrl.text = cleanExchange(exchange) withdrawExchangeUrl.fadeIn() withdrawCard.fadeIn() |