taler-android

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

ConfigFragment.kt (8762B)


      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.config
     18 
     19 import android.os.Bundle
     20 import android.text.method.LinkMovementMethod
     21 import android.view.LayoutInflater
     22 import android.view.View
     23 import android.view.View.INVISIBLE
     24 import android.view.View.VISIBLE
     25 import android.view.ViewGroup
     26 import android.view.inputmethod.InputMethodManager
     27 import androidx.core.content.ContextCompat.getSystemService
     28 import androidx.core.text.HtmlCompat
     29 import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
     30 import androidx.fragment.app.Fragment
     31 import androidx.fragment.app.activityViewModels
     32 import androidx.lifecycle.Observer
     33 import androidx.lifecycle.lifecycleScope
     34 import androidx.navigation.fragment.findNavController
     35 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
     36 import com.google.android.material.snackbar.Snackbar
     37 import kotlinx.coroutines.Dispatchers
     38 import kotlinx.coroutines.launch
     39 import kotlinx.coroutines.withContext
     40 import net.taler.cashier.MainViewModel
     41 import net.taler.cashier.R
     42 import net.taler.cashier.databinding.FragmentConfigBinding
     43 import net.taler.lib.android.exhaustive
     44 import net.taler.lib.android.handleChallengeResponse
     45 import net.taler.lib.android.showError
     46 
     47 private const val URL_BANK_TEST = "https://bank.demo.taler.net"
     48 private const val URL_BANK_TEST_REGISTER = "https://bank.demo.taler.net/webui/#/register"
     49 
     50 class ConfigFragment : Fragment() {
     51 
     52     private val viewModel: MainViewModel by activityViewModels()
     53     private val configManager by lazy { viewModel.configManager }
     54 
     55     private lateinit var ui: FragmentConfigBinding
     56 
     57     override fun onCreateView(
     58         inflater: LayoutInflater,
     59         container: ViewGroup?,
     60         savedInstanceState: Bundle?,
     61     ): View {
     62         ui = FragmentConfigBinding.inflate(inflater, container, false)
     63         return ui.root
     64     }
     65 
     66     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     67         if (savedInstanceState == null) {
     68             if (configManager.config.bankUrl.isBlank()) {
     69                 ui.urlView.editText!!.setText(URL_BANK_TEST)
     70             } else {
     71                 ui.urlView.editText!!.setText(configManager.config.bankUrl)
     72             }
     73             ui.usernameView.editText!!.setText(configManager.config.username)
     74             ui.passwordView.editText!!.setText(configManager.config.password)
     75         } else {
     76             ui.urlView.editText!!.setText(savedInstanceState.getCharSequence("urlView"))
     77             ui.usernameView.editText!!.setText(savedInstanceState.getCharSequence("usernameView"))
     78             ui.passwordView.editText!!.setText(savedInstanceState.getCharSequence("passwordView"))
     79         }
     80         ui.saveButton.setOnClickListener {
     81             val config = Config(
     82                 bankUrl = ui.urlView.editText!!.text.toString().trim(),
     83                 username = ui.usernameView.editText!!.text.toString().trim(),
     84                 password = ui.passwordView.editText!!.text.toString().trim()
     85             )
     86             if (checkConfig(config)) {
     87                 // show progress
     88                 ui.saveButton.visibility = INVISIBLE
     89                 ui.progressBar.visibility = VISIBLE
     90                 // kick off check and observe result
     91                 configManager.checkAndSaveConfig(config)
     92                 configManager.configResult.observe(viewLifecycleOwner, onConfigResult)
     93                 // hide keyboard
     94                 val inputMethodManager =
     95                     getSystemService(requireContext(), InputMethodManager::class.java)!!
     96                 inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
     97             }
     98         }
     99         ui.demoView.text = HtmlCompat.fromHtml(
    100             getString(R.string.config_demo_hint, URL_BANK_TEST_REGISTER), FROM_HTML_MODE_LEGACY
    101         )
    102         ui.demoView.movementMethod = LinkMovementMethod.getInstance()
    103     }
    104 
    105     override fun onStart() {
    106         super.onStart()
    107         // focus on password if it is the only missing value (like after locking)
    108         if (ui.urlView.editText!!.text.isNotBlank()
    109             && ui.usernameView.editText!!.text.isNotBlank()
    110             && ui.passwordView.editText!!.text.isBlank()
    111         ) {
    112             ui.passwordView.editText!!.requestFocus()
    113         }
    114     }
    115 
    116     override fun onSaveInstanceState(outState: Bundle) {
    117         super.onSaveInstanceState(outState)
    118         // for some reason automatic restore isn't working at the moment!?
    119         outState.putCharSequence("urlView", ui.urlView.editText?.text?.trim())
    120         outState.putCharSequence("usernameView", ui.usernameView.editText?.text?.trim())
    121         outState.putCharSequence("passwordView", ui.passwordView.editText?.text?.trim())
    122     }
    123 
    124     private fun checkConfig(config: Config): Boolean {
    125         if (!config.bankUrl.startsWith("https://") &&
    126             !config.bankUrl.startsWith("http://")
    127         ) {
    128             ui.urlView.error = getString(R.string.config_bank_url_error)
    129             ui.urlView.requestFocus()
    130             return false
    131         }
    132         if (config.username.isBlank()) {
    133             ui.usernameView.error = getString(R.string.config_username_error)
    134             ui.usernameView.requestFocus()
    135             return false
    136         }
    137         ui.urlView.isErrorEnabled = false
    138         return true
    139     }
    140 
    141     private val onConfigResult = Observer<ConfigResult?> { result ->
    142         if (result == null) return@Observer
    143         when (result) {
    144             is ConfigResult.Success -> {
    145                 val action = ConfigFragmentDirections.actionConfigFragmentToBalanceFragment()
    146                 findNavController().navigate(action)
    147             }
    148             ConfigResult.Offline -> {
    149                 Snackbar.make(requireView(), R.string.config_error_offline, LENGTH_LONG).show()
    150             }
    151             is ConfigResult.Error -> {
    152                 if (result.authError) {
    153                     Snackbar.make(requireView(), R.string.config_error_auth, LENGTH_LONG).show()
    154                 } else {
    155                     requireActivity().showError(getString(R.string.config_error), result.msg)
    156                 }
    157             }
    158             is ConfigResult.TanRequired -> {
    159                 lifecycleScope.launch {
    160                     val baseUrl = ui.urlView.editText!!.text.toString().trim()
    161                     val username = ui.usernameView.editText!!.text.toString().trim()
    162                     val solvedIds = handleChallengeResponse(
    163                         result.challenges,
    164                         result.combiAnd,
    165                         onRequestChallenge = { challengeId ->
    166                             withContext(Dispatchers.IO) {
    167                                 configManager.requestChallenge(baseUrl, username, challengeId)
    168                             }
    169                         },
    170                         onConfirmChallenge = { challengeId, tan ->
    171                             withContext(Dispatchers.IO) {
    172                                 configManager.confirmChallenge(baseUrl, username, challengeId, tan)
    173                             }
    174                         }
    175                     )
    176                     if (solvedIds.isNotEmpty()) {
    177                         val config = Config(
    178                             bankUrl = baseUrl,
    179                             username = username,
    180                             password = ui.passwordView.editText!!.text.toString().trim()
    181                         )
    182                         configManager.checkAndSaveConfig(config, solvedIds)
    183                     } else {
    184                         ui.saveButton.visibility = VISIBLE
    185                         ui.progressBar.visibility = INVISIBLE
    186                         configManager.configResult.removeObservers(viewLifecycleOwner)
    187                     }
    188                 }
    189                 return@Observer
    190             }
    191             ConfigResult.Unknown -> {
    192                 requireActivity().showError(getString(R.string.config_error), "Unknown error")
    193             }
    194         }.exhaustive
    195         ui.saveButton.visibility = VISIBLE
    196         ui.progressBar.visibility = INVISIBLE
    197         configManager.configResult.removeObservers(viewLifecycleOwner)
    198     }
    199 
    200 }