taler-android

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

ExchangeListFragment.kt (10612B)


      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.wallet.exchanges
     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.ViewGroup
     26 import android.view.ViewGroup.MarginLayoutParams
     27 import android.widget.Toast
     28 import android.widget.Toast.LENGTH_LONG
     29 import androidx.core.os.bundleOf
     30 import androidx.core.view.MenuProvider
     31 import androidx.core.view.ViewCompat
     32 import androidx.core.view.WindowInsetsCompat
     33 import androidx.core.view.marginBottom
     34 import androidx.core.view.marginLeft
     35 import androidx.core.view.marginRight
     36 import androidx.core.view.updateLayoutParams
     37 import androidx.core.view.updatePadding
     38 import androidx.fragment.app.Fragment
     39 import androidx.fragment.app.activityViewModels
     40 import androidx.lifecycle.Lifecycle.State.RESUMED
     41 import androidx.lifecycle.lifecycleScope
     42 import androidx.navigation.fragment.findNavController
     43 import androidx.recyclerview.widget.DividerItemDecoration
     44 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
     45 import com.google.android.material.dialog.MaterialAlertDialogBuilder
     46 import com.google.android.material.snackbar.Snackbar
     47 import kotlinx.coroutines.launch
     48 import net.taler.common.Amount
     49 import net.taler.common.EventObserver
     50 import net.taler.common.fadeIn
     51 import net.taler.common.fadeOut
     52 import net.taler.common.showError
     53 import net.taler.wallet.main.MainViewModel
     54 import net.taler.wallet.R
     55 import net.taler.wallet.databinding.FragmentExchangeListBinding
     56 import net.taler.wallet.showError
     57 
     58 open class ExchangeListFragment : Fragment(), ExchangeClickListener {
     59 
     60     protected val model: MainViewModel by activityViewModels()
     61     private val exchangeManager by lazy { model.exchangeManager }
     62     private val transactionManager by lazy { model.transactionManager }
     63     private val balanceManager by lazy { model.balanceManager }
     64 
     65     protected lateinit var ui: FragmentExchangeListBinding
     66     protected open val isSelectOnly = false
     67     private val exchangeAdapter by lazy { ExchangeAdapter(isSelectOnly, this, model.devMode.value == true) }
     68 
     69     override fun onCreateView(
     70         inflater: LayoutInflater,
     71         container: ViewGroup?,
     72         savedInstanceState: Bundle?,
     73     ): View? {
     74         ui = FragmentExchangeListBinding.inflate(inflater, container, false)
     75         return ui.root
     76     }
     77 
     78     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     79         setupInsets()
     80 
     81         requireActivity().addMenuProvider(object : MenuProvider {
     82             override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
     83                 if (model.devMode.value == true) {
     84                     menuInflater.inflate(R.menu.exchange_list, menu)
     85                 }
     86             }
     87 
     88             override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
     89                 if (menuItem.itemId == R.id.action_add_dev_exchanges) {
     90                     exchangeManager.addDevExchanges()
     91                 }
     92                 return true
     93             }
     94         }, viewLifecycleOwner, RESUMED)
     95 
     96         ui.list.apply {
     97             adapter = exchangeAdapter
     98             addItemDecoration(DividerItemDecoration(context, VERTICAL))
     99         }
    100         ui.addExchangeFab.setOnClickListener {
    101             AddExchangeDialogFragment().show(parentFragmentManager, "ADD_EXCHANGE")
    102         }
    103 
    104         // TODO: refactor and unify progress bar handling
    105         // exchangeManager.progress.observe(viewLifecycleOwner) { show ->
    106         //     if (show) ui.progressBar.fadeIn() else ui.progressBar.fadeOut()
    107         // }
    108 
    109         exchangeManager.exchanges.observe(viewLifecycleOwner) { exchanges ->
    110             onExchangeUpdate(exchanges)
    111         }
    112 
    113         exchangeManager.addError.observe(viewLifecycleOwner, EventObserver { error ->
    114             onAddExchangeFailed()
    115             if (model.devMode.value == true) {
    116                 showError(error)
    117             }
    118         })
    119 
    120         exchangeManager.listError.observe(viewLifecycleOwner, EventObserver { error ->
    121             onListExchangeFailed()
    122             if (model.devMode.value == true) {
    123                 showError(error)
    124             }
    125         })
    126 
    127         exchangeManager.deleteError.observe(viewLifecycleOwner, EventObserver { error ->
    128             if (model.devMode.value == true) {
    129                 showError(error)
    130             } else {
    131                 showError(error.userFacingMsg)
    132             }
    133         })
    134 
    135         exchangeManager.reloadError.observe(viewLifecycleOwner, EventObserver { error ->
    136             if (model.devMode.value == true) {
    137                 showError(error)
    138             } else {
    139                 showError(error.userFacingMsg)
    140             }
    141         })
    142     }
    143 
    144     protected open fun onExchangeUpdate(exchanges: List<ExchangeItem>) {
    145         exchangeAdapter.update(exchanges)
    146         if (exchanges.isEmpty()) {
    147             ui.emptyState.fadeIn()
    148             ui.list.fadeOut()
    149         } else {
    150             ui.emptyState.fadeOut()
    151             ui.list.fadeIn()
    152         }
    153     }
    154 
    155     private fun onAddExchangeFailed() {
    156         Toast.makeText(requireContext(), R.string.exchange_add_error, LENGTH_LONG).show()
    157     }
    158 
    159     private fun onListExchangeFailed() {
    160         Toast.makeText(requireContext(), R.string.exchange_list_error, LENGTH_LONG).show()
    161     }
    162 
    163     override fun onExchangeSelected(item: ExchangeItem) {
    164         throw AssertionError("must not get triggered here")
    165     }
    166 
    167     override fun onManualWithdraw(item: ExchangeItem) {
    168         model.withdrawManager.resetWithdrawal()
    169         val args = bundleOf(
    170             "editableCurrency" to false,
    171             "exchangeBaseUrl" to item.exchangeBaseUrl,
    172             "amount" to item.currency?.let { Amount.zero(it).toJSONString() },
    173         )
    174         findNavController().navigate(R.id.action_global_promptWithdraw, args)
    175     }
    176 
    177     override fun onPeerReceive(item: ExchangeItem) {
    178         model.selectScope(item.scopeInfo)
    179         findNavController().navigate(R.id.action_global_outgoingPull)
    180     }
    181 
    182     override fun onExchangeReload(item: ExchangeItem) {
    183         exchangeManager.reload(item.exchangeBaseUrl)
    184     }
    185 
    186     override fun onExchangeDelete(item: ExchangeItem) {
    187         val optionsArray = arrayOf(getString(R.string.exchange_delete_force))
    188         val checkedArray = BooleanArray(1) { false }
    189 
    190         MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
    191             .setTitle(R.string.exchange_delete)
    192             .setMultiChoiceItems(optionsArray, checkedArray) { _, which, isChecked ->
    193                 checkedArray[which] = isChecked
    194             }
    195             .setNegativeButton(R.string.transactions_delete) { _, _ ->
    196                 exchangeManager.delete(item.exchangeBaseUrl, checkedArray[0])
    197             }
    198             .setPositiveButton(R.string.cancel) { _, _ -> }
    199             .show()
    200     }
    201 
    202     override fun onExchangeTosView(item: ExchangeItem) {
    203         val bundle = bundleOf(
    204             "exchangeBaseUrl" to item.exchangeBaseUrl,
    205             "readOnly" to true,
    206         )
    207         findNavController().navigate(R.id.action_global_reviewExchangeTOS, bundle)
    208     }
    209 
    210     override fun onExchangeTosAccept(item: ExchangeItem) {
    211         val bundle = bundleOf("exchangeBaseUrl" to item.exchangeBaseUrl)
    212         findNavController().navigate(R.id.action_global_reviewExchangeTOS, bundle)
    213     }
    214 
    215     override fun onExchangeTosForget(item: ExchangeItem) {
    216         viewLifecycleOwner.lifecycleScope.launch {
    217             exchangeManager.getExchangeTos(item.exchangeBaseUrl)?.let { tos ->
    218                 exchangeManager.forgetCurrentTos(item.exchangeBaseUrl, tos.currentEtag)
    219             }
    220         }
    221     }
    222 
    223     override fun onExchangeGlobalCurrencyAdd(item: ExchangeItem) {
    224         item.currency?.let {
    225             balanceManager.addGlobalCurrencyExchange(
    226                 currency = item.currency,
    227                 exchange = item,
    228                 onSuccess = {
    229                     Snackbar.make(
    230                         requireView(),
    231                         getString(R.string.exchange_global_add_success),
    232                         Snackbar.LENGTH_LONG
    233                     ).show()
    234                 },
    235                 onError = { error ->
    236                     showError(error)
    237                 },
    238             )
    239         }
    240     }
    241 
    242     override fun onExchangeGlobalCurrencyDelete(item: ExchangeItem) {
    243         item.currency?.let {
    244             balanceManager.removeGlobalCurrencyExchange(
    245                 currency = item.currency,
    246                 exchange = item,
    247                 onSuccess = {
    248                     Snackbar.make(
    249                         requireView(),
    250                         getString(R.string.exchange_global_delete_success),
    251                         Snackbar.LENGTH_LONG
    252                     ).show()
    253                 },
    254                 onError = { error ->
    255                     showError(error)
    256                 },
    257             )
    258         }
    259     }
    260 
    261     private fun setupInsets() {
    262         ViewCompat.setOnApplyWindowInsetsListener(ui.list) { v, windowInsets ->
    263             val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
    264             v.updatePadding(
    265                 bottom = insets.bottom,
    266                 left = insets.left,
    267                 right = insets.right,
    268             )
    269             windowInsets
    270         }
    271 
    272         val fabMarginBottom = ui.addExchangeFab.marginBottom
    273         val fabMarginLeft = ui.addExchangeFab.marginLeft
    274         val fabMarginRight = ui.addExchangeFab.marginRight
    275         ViewCompat.setOnApplyWindowInsetsListener(ui.addExchangeFab) { v, windowInsets ->
    276             val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
    277             v.updateLayoutParams<MarginLayoutParams> {
    278                 bottomMargin = fabMarginBottom + insets.bottom
    279                 leftMargin = fabMarginLeft + insets.left
    280                 rightMargin = fabMarginRight + insets.right
    281             }
    282             windowInsets
    283         }
    284     }
    285 }