taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

BalanceFragment.kt (8607B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2020 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.cashier
     18 
     19 import android.os.Bundle
     20 import android.view.LayoutInflater
     21 import android.view.Menu
     22 import android.view.MenuInflater
     23 import android.view.MenuItem
     24 import android.view.View
     25 import android.view.View.VISIBLE
     26 import android.view.ViewGroup
     27 import android.view.inputmethod.EditorInfo
     28 import androidx.fragment.app.Fragment
     29 import androidx.fragment.app.activityViewModels
     30 import androidx.navigation.fragment.findNavController
     31 import net.taler.cashier.BalanceFragmentDirections.Companion.actionBalanceFragmentToTransactionFragment
     32 import net.taler.cashier.databinding.FragmentBalanceBinding
     33 import net.taler.cashier.withdraw.LastTransaction
     34 import net.taler.cashier.withdraw.WithdrawStatus
     35 import net.taler.common.Amount
     36 import net.taler.lib.android.exhaustive
     37 import net.taler.lib.android.fadeIn
     38 import net.taler.lib.android.fadeOut
     39 
     40 sealed class BalanceResult {
     41     data class Error(val msg: String) : BalanceResult()
     42     data object Offline : BalanceResult()
     43     data class Success(
     44         val amount: SignedAmount,
     45         val debitThreshold: Amount,
     46         val minCashout: Amount? = null,
     47     ) : BalanceResult()
     48 }
     49 
     50 class BalanceFragment : Fragment() {
     51 
     52     private val viewModel: MainViewModel by activityViewModels()
     53     private val configManager by lazy { viewModel.configManager }
     54     private val withdrawManager by lazy { viewModel.withdrawManager }
     55 
     56     private lateinit var ui: FragmentBalanceBinding
     57 
     58     override fun onCreateView(
     59         inflater: LayoutInflater, container: ViewGroup?,
     60         savedInstanceState: Bundle?,
     61     ): View {
     62         setHasOptionsMenu(true)
     63         ui = FragmentBalanceBinding.inflate(layoutInflater, container, false)
     64         return ui.root
     65     }
     66 
     67     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     68         withdrawManager.lastTransaction.observe(viewLifecycleOwner) { lastTransaction ->
     69             onLastTransaction(lastTransaction)
     70         }
     71         viewModel.balance.observe(viewLifecycleOwner) { result ->
     72             onBalanceUpdated(result)
     73         }
     74         ui.button5.setOnClickListener { onAmountButtonPressed(5) }
     75         ui.button10.setOnClickListener { onAmountButtonPressed(10) }
     76         ui.button20.setOnClickListener { onAmountButtonPressed(20) }
     77         ui.button50.setOnClickListener { onAmountButtonPressed(50) }
     78 
     79         if (savedInstanceState != null) {
     80             ui.amountView.editText!!.setText(savedInstanceState.getCharSequence("amountView"))
     81         }
     82         ui.amountView.editText!!.setOnEditorActionListener { _, actionId, _ ->
     83             if (actionId == EditorInfo.IME_ACTION_GO) {
     84                 onAmountConfirmed(getAmountFromView())
     85                 true
     86             } else false
     87         }
     88         configManager.currency.observe(viewLifecycleOwner) { currency ->
     89             ui.currencyView.text = currency
     90         }
     91         ui.confirmWithdrawalButton.setOnClickListener { onAmountConfirmed(getAmountFromView()) }
     92     }
     93 
     94     override fun onStart() {
     95         super.onStart()
     96         // update balance if there's a config
     97         if (configManager.hasConfig()) {
     98             viewModel.getBalance()
     99         }
    100     }
    101 
    102     override fun onSaveInstanceState(outState: Bundle) {
    103         super.onSaveInstanceState(outState)
    104         // automatic restore isn't working, maybe because of the different layout in landscape mode
    105         // the ui won't be available after onDestroyView()
    106         if (view != null) ui.amountView.editText?.text.let {
    107             outState.putCharSequence("amountView", it)
    108         }
    109     }
    110 
    111     @Deprecated("Deprecated in Java")
    112     override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    113         inflater.inflate(R.menu.balance, menu)
    114         super.onCreateOptionsMenu(menu, inflater)
    115     }
    116 
    117     @Deprecated("Deprecated in Java")
    118     override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
    119         R.id.action_reconfigure -> {
    120             findNavController().navigate(configManager.configDestination)
    121             true
    122         }
    123         R.id.action_lock -> {
    124             viewModel.lock()
    125             findNavController().navigate(configManager.configDestination)
    126             true
    127         }
    128         R.id.action_about -> {
    129             AboutDialogFragment().show(parentFragmentManager, "ABOUT")
    130             true
    131         }
    132         else -> super.onOptionsItemSelected(item)
    133     }
    134 
    135     private fun onBalanceUpdated(result: BalanceResult) {
    136         val uiList = listOf(
    137             ui.introView,
    138             ui.button5, ui.button10, ui.button20, ui.button50,
    139             ui.amountView, ui.currencyView, ui.confirmWithdrawalButton
    140         )
    141         when (result) {
    142             is BalanceResult.Success -> {
    143                 ui.balanceView.text = result.amount.toString()
    144                 if (!result.debitThreshold.isZero()) {
    145                     ui.debitView.text = getString(
    146                         R.string.balance_debit_threshold,
    147                         result.debitThreshold.toString(),
    148                     )
    149                     ui.debitView.fadeIn()
    150                 }
    151                 uiList.forEach { it.fadeIn() }
    152             }
    153             is BalanceResult.Error -> {
    154                 ui.balanceView.text = getString(R.string.balance_error, result.msg)
    155                 ui.debitView.fadeOut()
    156                 uiList.forEach { it.fadeOut() }
    157             }
    158             BalanceResult.Offline -> {
    159                 ui.balanceView.text = getString(R.string.balance_offline)
    160                 ui.debitView.fadeOut()
    161                 uiList.forEach { it.fadeOut() }
    162             }
    163         }.exhaustive
    164         ui.progressBar.fadeOut()
    165     }
    166 
    167     private fun onAmountButtonPressed(amount: Int) {
    168         ui.amountView.editText!!.setText(amount.toString())
    169         ui.amountView.error = null
    170     }
    171 
    172     private fun getAmountFromView(): Amount {
    173         val str = ui.amountView.editText!!.text.toString()
    174         val currency = configManager.currency.value!!
    175         if (str.isBlank()) return Amount.zero(currency)
    176         return Amount.fromString(currency, str)
    177     }
    178 
    179     private fun onAmountConfirmed(amount: Amount) {
    180         val balance = viewModel.balance.value as? BalanceResult.Success
    181         val minCashout = balance?.minCashout
    182         if (amount.isZero()) {
    183             ui.amountView.error = getString(R.string.withdraw_error_zero)
    184         } else if (minCashout != null && amount < minCashout) {
    185             ui.amountView.error = getString(R.string.withdraw_error_under_min_cashout, minCashout.toString())
    186         } else when (withdrawManager.hasSufficientBalance(amount)) {
    187             true -> {
    188                 ui.amountView.error = null
    189                 withdrawManager.withdraw(amount)
    190                 actionBalanceFragmentToTransactionFragment().let {
    191                     findNavController().navigate(it)
    192                 }
    193             }
    194             false -> {
    195                 ui.amountView.error = getString(R.string.withdraw_error_insufficient_balance)
    196             }
    197             null -> {
    198                 ui.amountView.error = getString(R.string.withdraw_error_currency_mismatch)
    199             }
    200         }
    201     }
    202 
    203     private fun onLastTransaction(lastTransaction: LastTransaction?) {
    204         val status = lastTransaction?.withdrawStatus
    205         val text = when (status) {
    206             is WithdrawStatus.Success -> getString(
    207                 R.string.transaction_last_success, lastTransaction.withdrawAmount
    208             )
    209             is WithdrawStatus.Aborted -> getString(R.string.transaction_last_aborted)
    210             else -> getString(R.string.transaction_last_error)
    211         }
    212         ui.lastTransactionView.text = text
    213         val drawable = if (status == WithdrawStatus.Success)
    214             R.drawable.ic_check_circle
    215         else
    216             R.drawable.ic_error
    217         ui.lastTransactionView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, 0, 0, 0)
    218         ui.lastTransactionView.visibility = VISIBLE
    219     }
    220 
    221 }