diff options
author | Torsten Grote <t@grobox.de> | 2020-03-09 15:13:18 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-03-09 15:13:18 -0300 |
commit | 96da1b763914d02e283478646e0147c515044ed5 (patch) | |
tree | 58d73d71be01e15e3f22390ea64958081fb15fe3 /app/src/main/java/net/taler/merchantpos | |
parent | b87319c0a437e7a72f52384e689c8e2971060cff (diff) | |
download | merchant-terminal-android-96da1b763914d02e283478646e0147c515044ed5.tar.gz merchant-terminal-android-96da1b763914d02e283478646e0147c515044ed5.tar.bz2 merchant-terminal-android-96da1b763914d02e283478646e0147c515044ed5.zip |
Show Meaningful Configuration Parsing Errors
Closes #0006113
Diffstat (limited to 'app/src/main/java/net/taler/merchantpos')
7 files changed, 76 insertions, 58 deletions
diff --git a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt index 3aa9f9f..c68688c 100644 --- a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt +++ b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt @@ -18,7 +18,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) private val queue = Volley.newRequestQueue(app) - val orderManager = OrderManager(mapper) + val orderManager = OrderManager(app, mapper) val configManager = ConfigManager(app, viewModelScope, mapper, queue).apply { addConfigurationReceiver(orderManager) } diff --git a/app/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt b/app/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt index ccadb8b..b4a566a 100644 --- a/app/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt +++ b/app/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt @@ -32,17 +32,18 @@ class ConfigFetcherFragment : Fragment() { super.onActivityCreated(savedInstanceState) configManager.fetchConfig(configManager.config, false) configManager.configUpdateResult.observe(viewLifecycleOwner, Observer { result -> - when { - result == null -> return@Observer - result.error -> onNetworkError(result.authError) - else -> actionConfigFetcherToOrder().navigate(findNavController()) + when (result) { + null -> return@Observer + is ConfigUpdateResult.Error -> onNetworkError(result.msg) + is ConfigUpdateResult.Success -> { + actionConfigFetcherToOrder().navigate(findNavController()) + } } }) } - private fun onNetworkError(authError: Boolean) { - val res = if (authError) R.string.config_auth_error else R.string.config_error - Snackbar.make(view!!, res, LENGTH_SHORT).show() + private fun onNetworkError(msg: String) { + Snackbar.make(view!!, msg, LENGTH_SHORT).show() actionConfigFetcherToMerchantSettings().navigate(findNavController()) } diff --git a/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt b/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt index f8d5629..fd221f2 100644 --- a/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt +++ b/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt @@ -19,6 +19,7 @@ import com.fasterxml.jackson.module.kotlin.readValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import net.taler.merchantpos.R import org.json.JSONObject private const val SETTINGS_NAME = "taler-merchant-terminal" @@ -35,13 +36,13 @@ private val TAG = ConfigManager::class.java.simpleName interface ConfigurationReceiver { /** - * Returns true if the configuration was valid, false otherwise. + * Returns null if the configuration was valid, or a error string for user display otherwise. */ - suspend fun onConfigurationReceived(json: JSONObject, currency: String): Boolean + suspend fun onConfigurationReceived(json: JSONObject, currency: String): String? } class ConfigManager( - context: Context, + private val context: Context, private val scope: CoroutineScope, private val mapper: ObjectMapper, private val queue: RequestQueue @@ -86,12 +87,14 @@ class ConfigManager( queue.add(stringRequest) } + @UiThread private fun onConfigReceived(json: JSONObject, config: Config?) { val merchantConfig: MerchantConfig = try { mapper.readValue(json.getString("config")) } catch (e: Exception) { Log.e(TAG, "Error parsing merchant config", e) - mConfigUpdateResult.value = ConfigUpdateResult(null) + val msg = context.getString(R.string.config_error_malformed) + mConfigUpdateResult.value = ConfigUpdateResult.Error(msg) return } @@ -108,29 +111,27 @@ class ConfigManager( configJson: JSONObject, merchantConfig: MerchantConfig, json: JSONObject - ) = scope.launch(Dispatchers.Main) { + ) = scope.launch(Dispatchers.Default) { val currency = json.getString("currency") - var configValid = true - configurationReceivers.forEach { + for (receiver in configurationReceivers) { val result = try { - it.onConfigurationReceived(configJson, currency) + receiver.onConfigurationReceived(configJson, currency) } catch (e: Exception) { - Log.e(TAG, "Error handling configuration by ${it::class.java.simpleName}", e) - false + Log.e(TAG, "Error handling configuration by ${receiver::class.java.simpleName}", e) + context.getString(R.string.config_error_unknown) } - configValid = result && configValid - } - if (configValid) { - newConfig?.let { - config = it - saveConfig(it) + if (result != null) { // error + mConfigUpdateResult.postValue(ConfigUpdateResult.Error(result)) + return@launch } - this@ConfigManager.merchantConfig = merchantConfig.copy(currency = currency) - mConfigUpdateResult.value = ConfigUpdateResult(currency) - } else { - mConfigUpdateResult.value = ConfigUpdateResult(null) } + newConfig?.let { + config = it + saveConfig(it) + } + this@ConfigManager.merchantConfig = merchantConfig.copy(currency = currency) + mConfigUpdateResult.postValue(ConfigUpdateResult.Success(currency)) } fun forgetPassword() { @@ -147,13 +148,18 @@ class ConfigManager( .apply() } + @UiThread private fun onNetworkError(it: VolleyError?) { - val authError = it?.networkResponse?.statusCode == 401 - mConfigUpdateResult.value = ConfigUpdateResult(null, authError) + val msg = context.getString( + if (it?.networkResponse?.statusCode == 401) R.string.config_auth_error + else R.string.config_error_network + ) + mConfigUpdateResult.value = ConfigUpdateResult.Error(msg) } } -class ConfigUpdateResult(val currency: String?, val authError: Boolean = false) { - val error: Boolean = currency == null +sealed class ConfigUpdateResult { + data class Error(val msg: String) : ConfigUpdateResult() + data class Success(val currency: String) : ConfigUpdateResult() } diff --git a/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt b/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt index 8bbc70d..19b3ab0 100644 --- a/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt +++ b/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt @@ -54,12 +54,9 @@ class MerchantConfigFragment : Fragment() { ) configManager.fetchConfig(config, true, savePasswordCheckBox.isChecked) configManager.configUpdateResult.observe(viewLifecycleOwner, Observer { result -> - when { - result == null -> return@Observer - result.error -> onNetworkError(result.authError) - else -> onConfigReceived(result.currency!!) + if (onConfigUpdate(result)) { + configManager.configUpdateResult.removeObservers(viewLifecycleOwner) } - configManager.configUpdateResult.removeObservers(viewLifecycleOwner) }) } forgetPasswordButton.setOnClickListener { @@ -99,6 +96,21 @@ class MerchantConfigFragment : Fragment() { forgetPasswordButton.visibility = if (config.hasPassword()) VISIBLE else GONE } + /** + * Processes updated config and returns true, if observer can be removed. + */ + private fun onConfigUpdate(result: ConfigUpdateResult?) = when (result) { + null -> false + is ConfigUpdateResult.Error -> { + onError(result.msg) + true + } + is ConfigUpdateResult.Success -> { + onConfigReceived(result.currency) + true + } + } + private fun onConfigReceived(currency: String) { onResultReceived() updateView() @@ -106,10 +118,9 @@ class MerchantConfigFragment : Fragment() { actionSettingsToOrder().navigate(findNavController()) } - private fun onNetworkError(authError: Boolean) { + private fun onError(msg: String) { onResultReceived() - val res = if (authError) R.string.config_auth_error else R.string.config_error - Snackbar.make(view!!, res, LENGTH_LONG).show() + Snackbar.make(view!!, msg, LENGTH_LONG).show() } private fun onResultReceived() { diff --git a/app/src/main/java/net/taler/merchantpos/order/LiveOrder.kt b/app/src/main/java/net/taler/merchantpos/order/LiveOrder.kt index 206b046..c239f8d 100644 --- a/app/src/main/java/net/taler/merchantpos/order/LiveOrder.kt +++ b/app/src/main/java/net/taler/merchantpos/order/LiveOrder.kt @@ -32,7 +32,7 @@ internal class MutableLiveOrder( get() = productsByCategory.keys.map { it.id to it }.toMap() override val order: MutableLiveData<Order> = MutableLiveData(Order(id, availableCategories)) override val orderTotal: LiveData<Double> = Transformations.map(order) { it.total } - override val restartState = MutableLiveData<RestartState>(DISABLED) + override val restartState = MutableLiveData(DISABLED) private val selectedOrderLine = MutableLiveData<ConfigProduct>() override val selectedProductKey: String? get() = selectedOrderLine.value?.id diff --git a/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt b/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt index ab561e2..b97219b 100644 --- a/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt +++ b/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt @@ -1,5 +1,6 @@ package net.taler.merchantpos.order +import android.content.Context import android.util.Log import androidx.annotation.UiThread import androidx.lifecycle.LiveData @@ -8,11 +9,15 @@ import androidx.lifecycle.Transformations.map import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import net.taler.merchantpos.Amount.Companion.fromString +import net.taler.merchantpos.R import net.taler.merchantpos.config.ConfigurationReceiver import net.taler.merchantpos.order.RestartState.ENABLED import org.json.JSONObject -class OrderManager(private val mapper: ObjectMapper) : ConfigurationReceiver { +class OrderManager( + private val context: Context, + private val mapper: ObjectMapper +) : ConfigurationReceiver { companion object { val TAG = OrderManager::class.java.simpleName @@ -32,15 +37,14 @@ class OrderManager(private val mapper: ObjectMapper) : ConfigurationReceiver { private val mCategories = MutableLiveData<List<Category>>() internal val categories: LiveData<List<Category>> = mCategories - @Suppress("BlockingMethodInNonBlockingContext") // run on Dispatchers.Main - override suspend fun onConfigurationReceived(json: JSONObject, currency: String): Boolean { + override suspend fun onConfigurationReceived(json: JSONObject, currency: String): String? { // parse categories val categoriesStr = json.getJSONArray("categories").toString() val categoriesType = object : TypeReference<List<Category>>() {} val categories: List<Category> = mapper.readValue(categoriesStr, categoriesType) if (categories.isEmpty()) { Log.e(TAG, "No valid category found.") - return false + return context.getString(R.string.config_error_category) } // pre-select the first category categories[0].selected = true @@ -52,23 +56,21 @@ class OrderManager(private val mapper: ObjectMapper) : ConfigurationReceiver { // group products by categories productsByCategory.clear() - val seenIds = ArrayList<String>() products.forEach { product -> val productCurrency = fromString(product.price).currency if (productCurrency != currency) { Log.e(TAG, "Product $product has currency $productCurrency, $currency expected") - return false + return context.getString( + R.string.config_error_currency, product.description, productCurrency, currency + ) } - if (seenIds.contains(product.id)) { - Log.e(TAG, "Product $product has duplicate product_id ${product.id}") - return false - } - seenIds.add(product.id) product.categories.forEach { categoryId -> val category = categories.find { it.id == categoryId } if (category == null) { Log.e(TAG, "Product $product has unknown category $categoryId") - return false + return context.getString( + R.string.config_error_product_category_id, product.description, categoryId + ) } if (productsByCategory.containsKey(category)) { productsByCategory[category]?.add(product) @@ -86,10 +88,8 @@ class OrderManager(private val mapper: ObjectMapper) : ConfigurationReceiver { orders[id] = MutableLiveOrder(id, productsByCategory) mCurrentOrderId.postValue(id) } - true - } else { - false - } + null // success, no error string + } else context.getString(R.string.config_error_product_zero) } @UiThread diff --git a/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt b/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt index 9a40577..3afb2cf 100644 --- a/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt +++ b/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt @@ -128,7 +128,7 @@ private class OrderAdapter : Adapter<OrderViewHolder>() { return oldItem.quantity == newItem.quantity } } - private val differ = AsyncListDiffer<ConfigProduct>(this, itemCallback) + private val differ = AsyncListDiffer(this, itemCallback) override fun getItemCount() = differ.currentList.size |