From d57645954dda2d478fe09dd0135183e1b3526db0 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 22 Apr 2020 15:10:08 -0300 Subject: [cashier] expose internal error messages to UI --- .../main/java/net/taler/cashier/BalanceFragment.kt | 32 ++++++++++++---------- .../main/java/net/taler/cashier/ConfigFragment.kt | 22 ++++++++++----- .../src/main/java/net/taler/cashier/HttpHelper.kt | 27 ++++++++++++++---- .../main/java/net/taler/cashier/MainViewModel.kt | 16 ++++++----- .../net/taler/cashier/withdraw/WithdrawManager.kt | 2 +- cashier/src/main/res/values/strings.xml | 6 ++-- 6 files changed, 68 insertions(+), 37 deletions(-) (limited to 'cashier') diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt index c4802ee..246cba0 100644 --- a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt +++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt @@ -35,11 +35,12 @@ import net.taler.cashier.withdraw.LastTransaction import net.taler.cashier.withdraw.WithdrawStatus import net.taler.common.Amount import net.taler.common.SignedAmount +import net.taler.common.exhaustive import net.taler.common.fadeIn import net.taler.common.fadeOut sealed class BalanceResult { - object Error : BalanceResult() + class Error(val msg: String) : BalanceResult() object Offline : BalanceResult() class Success(val amount: SignedAmount) : BalanceResult() } @@ -62,10 +63,7 @@ class BalanceFragment : Fragment() { onLastTransaction(lastTransaction) }) viewModel.balance.observe(viewLifecycleOwner, Observer { result -> - when (result) { - is BalanceResult.Success -> onBalanceUpdated(result.amount) - else -> onBalanceUpdated(null, result is BalanceResult.Offline) - } + onBalanceUpdated(result) }) button5.setOnClickListener { onAmountButtonPressed(5) } button10.setOnClickListener { onAmountButtonPressed(10) } @@ -121,20 +119,26 @@ class BalanceFragment : Fragment() { else -> super.onOptionsItemSelected(item) } - private fun onBalanceUpdated(amount: SignedAmount?, isOffline: Boolean = false) { + private fun onBalanceUpdated(result: BalanceResult) { val uiList = listOf( introView, button5, button10, button20, button50, amountView, currencyView, confirmWithdrawalButton ) - if (amount == null) { - balanceView.text = - getString(if (isOffline) R.string.balance_offline else R.string.balance_error) - uiList.forEach { it.fadeOut() } - } else { - balanceView.text = amount.toString() - uiList.forEach { it.fadeIn() } - } + when (result) { + is BalanceResult.Success -> { + balanceView.text = result.amount.toString() + uiList.forEach { it.fadeIn() } + } + is BalanceResult.Error -> { + balanceView.text = getString(R.string.balance_error, result.msg) + uiList.forEach { it.fadeOut() } + } + BalanceResult.Offline -> { + balanceView.text = getString(R.string.balance_offline) + uiList.forEach { it.fadeOut() } + } + }.exhaustive progressBar.fadeOut() } diff --git a/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt b/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt index ccbb1a6..f435883 100644 --- a/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt +++ b/cashier/src/main/java/net/taler/cashier/ConfigFragment.kt @@ -34,6 +34,7 @@ import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_config.* +import net.taler.common.exhaustive private const val URL_BANK_TEST = "https://bank.test.taler.net" private const val URL_BANK_TEST_REGISTER = "$URL_BANK_TEST/accounts/register" @@ -125,13 +126,20 @@ class ConfigFragment : Fragment() { private val onConfigResult = Observer { result -> if (result == null) return@Observer - if (result.success) { - val action = ConfigFragmentDirections.actionConfigFragmentToBalanceFragment() - findNavController().navigate(action) - } else { - val res = if (result.authError) R.string.config_error_auth else R.string.config_error - Snackbar.make(view!!, res, LENGTH_LONG).show() - } + when (result) { + is ConfigResult.Success -> { + val action = ConfigFragmentDirections.actionConfigFragmentToBalanceFragment() + findNavController().navigate(action) + } + is ConfigResult.Error -> { + if (result.authError) { + Snackbar.make(view!!, R.string.config_error_auth, LENGTH_LONG).show() + } else { + val str = getString(R.string.config_error, result.msg) + Snackbar.make(view!!, str, LENGTH_LONG).show() + } + } + }.exhaustive saveButton.visibility = VISIBLE progressBar.visibility = INVISIBLE viewModel.configResult.removeObservers(viewLifecycleOwner) diff --git a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt index 63eaddf..49a43d1 100644 --- a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt +++ b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt @@ -23,6 +23,8 @@ import okhttp3.MediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody +import okhttp3.Response +import org.json.JSONException import org.json.JSONObject object HttpHelper { @@ -43,14 +45,14 @@ object HttpHelper { .execute() } catch (e: Exception) { Log.e(TAG, "Error retrieving $url", e) - return HttpJsonResult.Error(500) + return HttpJsonResult.Error(0) } return if (response.code() == 200 && response.body() != null) { val jsonObject = JSONObject(response.body()!!.string()) HttpJsonResult.Success(jsonObject) } else { Log.e(TAG, "Received status ${response.code()} from $url expected 200") - HttpJsonResult.Error(response.code()) + HttpJsonResult.Error(response.code(), getErrorBody(response)) } } @@ -69,14 +71,14 @@ object HttpHelper { .execute() } catch (e: Exception) { Log.e(TAG, "Error retrieving $url", e) - return HttpJsonResult.Error(500) + return HttpJsonResult.Error(0) } return if (response.code() == 200 && response.body() != null) { val jsonObject = JSONObject(response.body()!!.string()) HttpJsonResult.Success(jsonObject) } else { Log.e(TAG, "Received status ${response.code()} from $url expected 200") - HttpJsonResult.Error(response.code()) + HttpJsonResult.Error(response.code(), getErrorBody(response)) } } @@ -94,9 +96,24 @@ object HttpHelper { .build() }.build() + private fun getErrorBody(response: Response): String? { + val body = response.body()?.string() ?: return null + Log.e(TAG, "Response body: $body") + return try { + val json = JSONObject(body) + "${json.optString("ec")} ${json.optString("error")}" + } catch (e: JSONException) { + null + } + } + } sealed class HttpJsonResult { - class Error(val statusCode: Int) : HttpJsonResult() + class Error(val statusCode: Int, private val errorMsg: String? = null) : HttpJsonResult() { + val msg: String + get() = errorMsg?.let { "\n\n$statusCode $it" } ?: ": $statusCode" + } + class Success(val json: JSONObject) : HttpJsonResult() } diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt index 2b2d5f7..3587e95 100644 --- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt +++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt @@ -97,14 +97,13 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) { prefs.edit().putString(PREF_KEY_CURRENCY, amount.amount.currency).apply() // save config saveConfig(config) - ConfigResult(true) + ConfigResult.Success } catch (e: AmountParserException) { - ConfigResult(false) + ConfigResult.Error(false, "Invalid Amount: $balance") } } is HttpJsonResult.Error -> { - val authError = response.statusCode == 401 - ConfigResult(false, authError) + ConfigResult.Error(response.statusCode == 401, response.msg) } } mConfigResult.postValue(result) @@ -132,11 +131,11 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) { try { BalanceResult.Success(SignedAmount.fromJSONString(balance)) } catch (e: AmountParserException) { - BalanceResult.Error + BalanceResult.Error("invalid amount: $balance") } } is HttpJsonResult.Error -> { - if (app.isOnline()) BalanceResult.Error + if (app.isOnline()) BalanceResult.Error(response.msg) else BalanceResult.Offline } } @@ -155,4 +154,7 @@ data class Config( val password: String ) -class ConfigResult(val success: Boolean, val authError: Boolean = false) +sealed class ConfigResult { + class Error(val authError: Boolean, val msg: String) : ConfigResult() + object Success : ConfigResult() +} diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt index 317d1bf..edefa5a 100644 --- a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt +++ b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt @@ -102,7 +102,7 @@ class WithdrawManager( timer.start() } is Error -> { - val errorStr = app.getString(R.string.withdraw_error_fetch) + val errorStr = app.getString(R.string.withdraw_error_fetch, result.msg) mWithdrawResult.postValue(WithdrawResult.Error(errorStr)) } } diff --git a/cashier/src/main/res/values/strings.xml b/cashier/src/main/res/values/strings.xml index 98f686c..43e3573 100644 --- a/cashier/src/main/res/values/strings.xml +++ b/cashier/src/main/res/values/strings.xml @@ -7,12 +7,12 @@ Save The address in invalid. Please enter your username - Error retrieving configuration + Error retrieving configuration%s Invalid username or password For testing, you can create a test account at the demo bank]]>. Current balance - ERROR + ERROR%s Offline. Please connect to the Internet Reconfigure Lock @@ -21,7 +21,7 @@ How much e-cash should be withdrawn? Enter positive amount Insufficient balance - Error communicating with bank + Error communicating with bank%s Withdraw Scan code\nwith the Taler wallet app\nto get -- cgit v1.2.3