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 }