[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-terminal-android] 04/19: Fetch merchant config from cent
From: |
gnunet |
Subject: |
[taler-merchant-terminal-android] 04/19: Fetch merchant config from central configuration JSON |
Date: |
Fri, 21 Feb 2020 18:59:57 +0100 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a commit to branch master
in repository merchant-terminal-android.
commit 77ee9bc073e596ef1d90cdb4edc54b68df01a4f6
Author: Torsten Grote <address@hidden>
AuthorDate: Fri Jan 31 13:10:51 2020 -0300
Fetch merchant config from central configuration JSON
---
.../java/net/taler/merchantpos/CreatePayment.kt | 22 +--
.../java/net/taler/merchantpos/MainActivity.kt | 17 +-
.../java/net/taler/merchantpos/MainViewModel.kt | 50 ++++++
.../java/net/taler/merchantpos/MerchantHistory.kt | 5 +-
.../java/net/taler/merchantpos/MerchantSettings.kt | 128 --------------
.../net/taler/merchantpos/PosTerminalViewModel.kt | 18 --
.../java/net/taler/merchantpos/ProcessPayment.kt | 13 +-
.../net/taler/merchantpos/config/ConfigManager.kt | 141 ++++++++++++++++
.../merchantpos/{ => config}/MerchantConfig.kt | 15 +-
.../merchantpos/config/MerchantConfigFragment.kt | 101 +++++++++++
.../MerchantRequest.kt} | 6 +-
.../taler/merchantpos/order/CategoriesFragment.kt | 8 +-
.../net/taler/merchantpos/order/OrderFragment.kt | 10 +-
.../order/{OrderViewModel.kt => OrderManager.kt} | 54 ++----
.../taler/merchantpos/order/OrderStateFragment.kt | 8 +-
.../taler/merchantpos/order/ProductsFragment.kt | 8 +-
.../main/res/layout/fragment_merchant_settings.xml | 187 +++++++++++----------
app/src/main/res/layout/fragment_order.xml | 12 +-
app/src/main/res/navigation/nav_graph.xml | 2 +-
app/src/main/res/values/colors.xml | 1 +
app/src/main/res/values/strings.xml | 11 ++
21 files changed, 492 insertions(+), 325 deletions(-)
diff --git a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
index f92bac7..02a2ae7 100644
--- a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
+++ b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
@@ -1,6 +1,5 @@
package net.taler.merchantpos
-import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -17,6 +16,7 @@ import com.android.volley.Response
import com.android.volley.VolleyError
import com.android.volley.toolbox.Volley
import com.google.android.material.snackbar.Snackbar
+import net.taler.merchantpos.config.MerchantRequest
import org.json.JSONObject
@@ -25,7 +25,7 @@ import org.json.JSONObject
*/
class CreatePayment : Fragment() {
private lateinit var queue: RequestQueue
- private val model: PosTerminalViewModel by activityViewModels()
+ private val model: MainViewModel by activityViewModels()
private var paused: Boolean = false
@@ -40,7 +40,6 @@ class CreatePayment : Fragment() {
this.paused = false
val textView =
view!!.findViewById<TextView>(R.id.text_create_payment_amount_label)
- @SuppressLint("SetTextI18n")
textView.text = "Amount (${model.merchantConfig!!.currency})"
}
@@ -65,7 +64,7 @@ class CreatePayment : Fragment() {
val reqBody = JSONObject().also { it.put("order", order) }
- val req = MerchantInternalRequest(
+ val req = MerchantRequest(
Request.Method.POST,
model.merchantConfig!!,
"order",
@@ -88,13 +87,14 @@ class CreatePayment : Fragment() {
val params = mapOf("order_id" to orderId, "instance" to
merchantConfig.instance)
model.activeOrderId = orderId
- val req = MerchantInternalRequest(Request.Method.GET,
- model.merchantConfig!!,
- "check-payment",
- params,
- null,
- Response.Listener { onCheckPayment(it) },
- Response.ErrorListener { onNetworkError(it) })
+ val req =
+ MerchantRequest(Request.Method.GET,
+ model.merchantConfig!!,
+ "check-payment",
+ params,
+ null,
+ Response.Listener { onCheckPayment(it) },
+ Response.ErrorListener { onNetworkError(it) })
queue.add(req)
}
diff --git a/app/src/main/java/net/taler/merchantpos/MainActivity.kt
b/app/src/main/java/net/taler/merchantpos/MainActivity.kt
index 8cc2788..6d2e614 100644
--- a/app/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/app/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -1,6 +1,5 @@
package net.taler.merchantpos
-import android.content.Context
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
@@ -143,7 +142,7 @@ class MainActivity : AppCompatActivity(),
NavigationView.OnNavigationItemSelecte
const val TAG = "taler-merchant"
}
- private val model: PosTerminalViewModel by viewModels()
+ private val model: MainViewModel by viewModels()
private var nfcAdapter: NfcAdapter? = null
private var currentTag: IsoDep? = null
@@ -273,19 +272,7 @@ class MainActivity : AppCompatActivity(),
NavigationView.OnNavigationItemSelecte
R.id.merchantHistory
), drawerLayout
)
-
- findViewById<Toolbar>(R.id.toolbar)
- .setupWithNavController(navController, appBarConfiguration)
-
- val prefs = getSharedPreferences("taler-merchant-terminal",
Context.MODE_PRIVATE)
-
- val baseUrl = prefs.getString("merchantBackendUrl",
"https://backend.test.taler.net")
- val instance = prefs.getString("merchantBackendInstance", "default")
- val apiKey = prefs.getString("merchantBackendApiKey", "sandbox")
- val currency = prefs.getString("merchantBackendCurrency", "TESTKUDOS")
-
- model.merchantConfig =
- MerchantConfig(baseUrl!!, instance!!, apiKey!!, currency!!)
+ toolbar.setupWithNavController(navController, appBarConfiguration)
}
override fun onBackPressed() {
diff --git a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
new file mode 100644
index 0000000..c202f5f
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -0,0 +1,50 @@
+package net.taler.merchantpos
+
+import android.app.Application
+import android.text.Editable
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.volley.toolbox.Volley
+import
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.order.OrderManager
+
+class MainViewModel(app: Application) : AndroidViewModel(app) {
+
+ private val mapper = ObjectMapper()
+ .registerModule(KotlinModule())
+ .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+ private val queue = Volley.newRequestQueue(app)
+
+ val orderManager = OrderManager(mapper)
+ val configManager = ConfigManager(app, viewModelScope, mapper,
queue).apply {
+ addConfigurationReceiver(orderManager)
+ }
+
+ val merchantConfig
+ get() = configManager.merchantConfig
+
+ var activeSubject: Editable? = null
+ var activeOrderId: String? = null
+ var activeAmount: String? = null
+ var activeTalerPayUri: String? = null
+
+ init {
+ if (configManager.merchantConfig == null) {
+ configManager.fetchConfig(configManager.config, false)
+ }
+ }
+
+ override fun onCleared() {
+ queue.cancelAll { !it.isCanceled }
+ }
+
+ fun activeAmountPretty(): String? {
+ val amount = activeAmount ?: return null
+ val components = amount.split(":")
+ return "${components[1]} ${components[0]}"
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
index c389c5f..167bb9d 100644
--- a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
+++ b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
@@ -21,6 +21,7 @@ import com.android.volley.Response
import com.android.volley.VolleyError
import com.android.volley.toolbox.Volley
import com.google.android.material.snackbar.Snackbar
+import net.taler.merchantpos.config.MerchantRequest
import org.json.JSONObject
import java.time.Instant
import java.time.ZoneId
@@ -87,7 +88,7 @@ fun parseTalerTimestamp(s: String): Instant {
*/
class MerchantHistory : Fragment() {
private lateinit var queue: RequestQueue
- private val model: PosTerminalViewModel by activityViewModels()
+ private val model: MainViewModel by activityViewModels()
private val historyListAdapter = MyAdapter(listOf())
private val isLoading = MutableLiveData<Boolean>().apply { value = false }
@@ -125,7 +126,7 @@ class MerchantHistory : Fragment() {
private fun fetchHistory() {
isLoading.value = true
val instance = model.merchantConfig!!.instance
- val req = MerchantInternalRequest(
+ val req = MerchantRequest(
Request.Method.GET,
model.merchantConfig!!,
"history",
diff --git a/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt
b/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt
deleted file mode 100644
index a8f6aa5..0000000
--- a/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-package net.taler.merchantpos
-
-import android.content.Context
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.EditText
-import android.widget.TextView
-import androidx.lifecycle.ViewModelProviders
-import com.android.volley.Request
-import com.android.volley.RequestQueue
-import com.android.volley.Response
-import com.android.volley.VolleyError
-import com.android.volley.toolbox.Volley
-import com.google.android.material.snackbar.Snackbar
-import org.json.JSONObject
-
-
-/**
- * Fragment that displays merchant settings.
- */
-class MerchantSettings : Fragment() {
-
- private lateinit var queue: RequestQueue
- private lateinit var model: PosTerminalViewModel
-
- private var newConfig: MerchantConfig? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- model = activity?.run {
- ViewModelProviders.of(this)[PosTerminalViewModel::class.java]
- } ?: throw Exception("Invalid Activity")
-
- queue = Volley.newRequestQueue(context)
- }
-
- private fun reset(view: View) {
- val backendUrlEdit =
view.findViewById<EditText>(R.id.edit_settings_backend_url)
- backendUrlEdit.setText(model.merchantConfig!!.baseUrl,
TextView.BufferType.EDITABLE)
-
- val backendInstanceEdit =
view.findViewById<EditText>(R.id.edit_settings_instance)
- backendInstanceEdit.setText(model.merchantConfig!!.instance,
TextView.BufferType.EDITABLE)
-
- val backendApiKeyEdit =
view.findViewById<EditText>(R.id.edit_settings_apikey)
- backendApiKeyEdit.setText(model.merchantConfig!!.apiKey,
TextView.BufferType.EDITABLE)
-
- val currencyView =
view.findViewById<TextView>(R.id.text_settings_currency)
- currencyView.text = model.merchantConfig!!.currency
- }
-
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val view = inflater.inflate(R.layout.fragment_merchant_settings,
container, false)
-
- reset(view)
-
- val buttonApply = view.findViewById<Button>(R.id.button_settings_apply)
- buttonApply.setOnClickListener {
-
- val backendUrlEdit =
view.findViewById<EditText>(R.id.edit_settings_backend_url)
- val backendInstanceEdit =
view.findViewById<EditText>(R.id.edit_settings_instance)
- val backendApiKeyEdit =
view.findViewById<EditText>(R.id.edit_settings_apikey)
-
- val config = MerchantConfig(
- backendUrlEdit.text.toString(),
- backendInstanceEdit.text.toString(),
- backendApiKeyEdit.text.toString(),
- "UNKNOWN"
- )
-
- newConfig = config
-
- val req = MerchantInternalRequest(
- Request.Method.GET,
- config,
- "config",
- mapOf("instance" to config.instance),
- null,
- Response.Listener { onConfigReceived(it) },
- Response.ErrorListener { onNetworkError(it) })
-
- queue.add(req)
-
- }
-
- val buttonReset = view.findViewById<Button>(R.id.button_settings_reset)
- buttonReset.setOnClickListener {
- reset(view)
- }
-
- return view
- }
-
- private fun onConfigReceived(it: JSONObject) {
- val currency = it.getString("currency")
- val mySnackbar =
- Snackbar.make(view!!, "Changed to new ${currency} merchant",
Snackbar.LENGTH_SHORT)
-
- val config = this.newConfig!!.copy(currency = currency)
- this.newConfig = null
- model.merchantConfig = config
-
- val currencyView =
view!!.findViewById<TextView>(R.id.text_settings_currency)
- currencyView.text = currency
-
- mySnackbar.show()
-
- val prefs = activity!!.getSharedPreferences("taler-merchant-terminal",
Context.MODE_PRIVATE)
- prefs.edit().putString("merchantBackendUrl", config.baseUrl)
- .putString("merchantBackendInstance", config.instance)
- .putString("merchantBackendApiKey", config.apiKey)
- .putString("merchantBackendCurrency", config.currency).apply()
- }
-
- private fun onNetworkError(it: VolleyError) {
- val mySnackbar =
- Snackbar.make(view!!, "Error: Invalid Configuration",
Snackbar.LENGTH_SHORT)
- mySnackbar.show()
- }
-}
diff --git a/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt
b/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt
deleted file mode 100644
index a6548e4..0000000
--- a/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package net.taler.merchantpos
-
-import android.text.Editable
-import androidx.lifecycle.ViewModel
-
-class PosTerminalViewModel : ViewModel() {
- var activeSubject: Editable? = null
- var merchantConfig: MerchantConfig? = null
- var activeOrderId: String? = null
- var activeAmount: String? = null
- var activeTalerPayUri: String? = null
-
- fun activeAmountPretty(): String? {
- val amount = activeAmount ?: return null
- val components = amount.split(":")
- return "${components[1]} ${components[0]}"
- }
-}
diff --git a/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
b/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
index d78d873..d556b6f 100644
--- a/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
+++ b/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
@@ -22,6 +22,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.zxing.BarcodeFormat
import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter
+import net.taler.merchantpos.config.MerchantRequest
import org.json.JSONObject
@@ -38,7 +39,7 @@ class ProcessPayment : Fragment() {
private var paused: Boolean = true
private lateinit var queue: RequestQueue
- private val model: PosTerminalViewModel by activityViewModels()
+ private val model: MainViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -69,8 +70,14 @@ class ProcessPayment : Fragment() {
}
//Log.v("taler-merchant", "checkig if payment happened")
val params = mapOf("order_id" to model.activeOrderId!!, "instance" to
model.merchantConfig!!.instance)
- var req = MerchantInternalRequest(Request.Method.GET,
model.merchantConfig!!, "check-payment", params, null,
- Response.Listener { onCheckPayment(it) }, Response.ErrorListener {
onNetworkError(it) })
+ var req =
+ MerchantRequest(Request.Method.GET,
+ model.merchantConfig!!,
+ "check-payment",
+ params,
+ null,
+ Response.Listener { onCheckPayment(it) },
+ Response.ErrorListener { onNetworkError(it) })
queue.add(req)
val handler = Handler()
handler.postDelayed({
diff --git a/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
b/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
new file mode 100644
index 0000000..f6d1d30
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -0,0 +1,141 @@
+package net.taler.merchantpos.config
+
+import android.content.Context
+import android.content.Context.MODE_PRIVATE
+import android.util.Base64.NO_WRAP
+import android.util.Base64.encodeToString
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.volley.Request.Method.GET
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.JsonObjectRequest
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.json.JSONObject
+
+private const val SETTINGS_NAME = "taler-merchant-terminal"
+
+private const val SETTINGS_CONFIG_URL = "configUrl"
+private const val SETTINGS_USERNAME = "username"
+private const val SETTINGS_PASSWORD = "password"
+
+private val TAG = ConfigManager::class.java.simpleName
+
+interface ConfigurationReceiver {
+ /**
+ * Returns true if the configuration was valid, false otherwise.
+ */
+ suspend fun onConfigurationReceived(json: JSONObject): Boolean
+}
+
+class ConfigManager(
+ context: Context,
+ private val scope: CoroutineScope,
+ private val mapper: ObjectMapper,
+ private val queue: RequestQueue
+) {
+
+ private val prefs = context.getSharedPreferences(SETTINGS_NAME,
MODE_PRIVATE)
+ private val configurationReceivers = ArrayList<ConfigurationReceiver>()
+
+ var config = Config(
+ configUrl = prefs.getString(SETTINGS_CONFIG_URL, "")!!,
+ username = prefs.getString(SETTINGS_USERNAME, "")!!,
+ password = prefs.getString(SETTINGS_PASSWORD, "")!!
+ )
+ var merchantConfig: MerchantConfig? = null
+
+ private val mConfigUpdateResult = MutableLiveData<ConfigUpdateResult>()
+ val configUpdateResult: LiveData<ConfigUpdateResult> = mConfigUpdateResult
+
+ fun addConfigurationReceiver(receiver: ConfigurationReceiver) {
+ configurationReceivers.add(receiver)
+ }
+
+ @UiThread
+ fun fetchConfig(config: Config, save: Boolean) {
+ mConfigUpdateResult.value = null
+ val configToSave = if (save) config else null
+
+ val stringRequest = object : JsonObjectRequest(GET, config.configUrl,
null,
+ Listener { onConfigReceived(it, configToSave) },
+ ErrorListener { onNetworkError(it) }
+ ) {
+ // send basic auth header
+ override fun getHeaders(): MutableMap<String, String> {
+ val credentials = "${config.username}:${config.password}"
+ val auth = ("Basic ${encodeToString(credentials.toByteArray(),
NO_WRAP)}")
+ return mutableMapOf("Authorization" to auth)
+ }
+ }
+ queue.add(stringRequest)
+ }
+
+ 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)
+ return
+ }
+ this.merchantConfig = merchantConfig
+
+ val params = mapOf("instance" to merchantConfig.instance)
+ val req = MerchantRequest(GET, merchantConfig, "config", params, null,
+ Listener { onMerchantConfigReceived(config, json, it) },
+ ErrorListener { onNetworkError(it) }
+ )
+ queue.add(req)
+ }
+
+ private fun onMerchantConfigReceived(
+ newConfig: Config?,
+ configJson: JSONObject,
+ json: JSONObject
+ ) = scope.launch(Dispatchers.Main) {
+ val currency = json.getString("currency")
+
+ var configValid = true
+ configurationReceivers.forEach {
+ configValid = configValid or it.onConfigurationReceived(configJson)
+ }
+ if (configValid) {
+ newConfig?.let {
+ config = it
+ saveConfig(it)
+ }
+ Log.e("TEST", "set currency to $currency")
+ merchantConfig = merchantConfig!!.copy(currency = currency)
+ mConfigUpdateResult.value = ConfigUpdateResult(currency)
+ } else {
+ mConfigUpdateResult.value = ConfigUpdateResult(null)
+ }
+ }
+
+ private fun saveConfig(config: Config) {
+ prefs.edit()
+ .putString(SETTINGS_CONFIG_URL, config.configUrl)
+ .putString(SETTINGS_USERNAME, config.username)
+ .putString(SETTINGS_PASSWORD, config.password)
+ .apply()
+ }
+
+ private fun onNetworkError(it: VolleyError) {
+ val authError = it.networkResponse.statusCode == 401
+ mConfigUpdateResult.value = ConfigUpdateResult(null, authError)
+ }
+
+}
+
+class ConfigUpdateResult(val currency: String?, val authError: Boolean =
false) {
+ val error: Boolean = currency == null
+}
diff --git a/app/src/main/java/net/taler/merchantpos/MerchantConfig.kt
b/app/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
similarity index 61%
rename from app/src/main/java/net/taler/merchantpos/MerchantConfig.kt
rename to app/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
index 626d60b..63dd487 100644
--- a/app/src/main/java/net/taler/merchantpos/MerchantConfig.kt
+++ b/app/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
@@ -1,12 +1,21 @@
-package net.taler.merchantpos
+package net.taler.merchantpos.config
import android.net.Uri
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Config(
+ val configUrl: String,
+ val username: String,
+ val password: String
+)
data class MerchantConfig(
+ @JsonProperty("base_url")
val baseUrl: String,
val instance: String,
+ @JsonProperty("api_key")
val apiKey: String,
- val currency: String
+ val currency: String?
) {
fun urlFor(endpoint: String, params: Map<String, String>?): String {
val uriBuilder = Uri.parse(baseUrl).buildUpon()
@@ -16,4 +25,4 @@ data class MerchantConfig(
}
return uriBuilder.toString()
}
-}
\ No newline at end of file
+}
diff --git
a/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
b/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
new file mode 100644
index 0000000..b824d38
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
@@ -0,0 +1,101 @@
+package net.taler.merchantpos.config
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.*
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
+import kotlinx.android.synthetic.main.fragment_merchant_settings.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+
+/**
+ * Fragment that displays merchant settings.
+ */
+class MerchantConfigFragment : Fragment() {
+
+ private val model: MainViewModel by activityViewModels()
+ private val configManager by lazy { model.configManager }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_merchant_settings,
container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ okButton.setOnClickListener {
+ if (!checkInput()) return@setOnClickListener
+ configUrlView.error = null
+ progressBar.visibility = VISIBLE
+ okButton.visibility = INVISIBLE
+ val config = Config(
+ configUrl = configUrlView.editText!!.text.toString(),
+ username = usernameView.editText!!.text.toString(),
+ password = passwordView.editText!!.text.toString()
+ )
+ configManager.fetchConfig(config, true)
+ configManager.configUpdateResult.observe(viewLifecycleOwner,
Observer { result ->
+ when {
+ result == null -> return@Observer
+ result.error -> onNetworkError(result.authError)
+ else -> onConfigReceived(result.currency!!)
+ }
+
configManager.configUpdateResult.removeObservers(viewLifecycleOwner)
+ })
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ updateView()
+ }
+
+ private fun updateView() {
+ configUrlView.editText!!.setText(configManager.config.configUrl)
+ usernameView.editText!!.setText(configManager.config.username)
+ passwordView.editText!!.setText(configManager.config.password)
+
+ val currency = configManager.merchantConfig?.currency
+ if (currency == null) {
+ currencyView.visibility = GONE
+ } else {
+ currencyView.text = getString(R.string.config_currency, currency)
+ currencyView.visibility = VISIBLE
+ }
+ }
+
+ private fun checkInput(): Boolean {
+ return if (configUrlView.editText!!.text.startsWith("https://")) {
+ true
+ } else {
+ configUrlView.error = getString(R.string.config_malformed_url)
+ false
+ }
+ }
+
+ private fun onConfigReceived(currency: String) {
+ onResultReceived()
+ updateView()
+ Snackbar.make(view!!, "Changed to new $currency merchant",
LENGTH_SHORT).show()
+ }
+
+ private fun onNetworkError(authError: Boolean) {
+ onResultReceived()
+ val res = if (authError) R.string.config_auth_error else
R.string.config_error
+ Snackbar.make(view!!, res, LENGTH_SHORT).show()
+ }
+
+ private fun onResultReceived() {
+ progressBar.visibility = INVISIBLE
+ okButton.visibility = VISIBLE
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/MerchantInternalRequest.kt
b/app/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
similarity index 92%
rename from app/src/main/java/net/taler/merchantpos/MerchantInternalRequest.kt
rename to app/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
index b5ab98e..e6b96cd 100644
--- a/app/src/main/java/net/taler/merchantpos/MerchantInternalRequest.kt
+++ b/app/src/main/java/net/taler/merchantpos/config/MerchantRequest.kt
@@ -1,4 +1,4 @@
-package net.taler.merchantpos
+package net.taler.merchantpos.config
import android.util.ArrayMap
@@ -6,7 +6,7 @@ import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import org.json.JSONObject
-class MerchantInternalRequest(
+class MerchantRequest(
method: Int,
private val merchantConfig: MerchantConfig,
endpoint: String,
@@ -22,4 +22,4 @@ class MerchantInternalRequest(
headerMap["Authorization"] = "ApiKey " + merchantConfig.apiKey
return headerMap
}
-}
\ No newline at end of file
+}
diff --git
a/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
b/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
index 9d1ac5e..148699c 100644
--- a/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
+++ b/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import kotlinx.android.synthetic.main.fragment_categories.*
+import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder
@@ -22,7 +23,8 @@ interface CategorySelectionListener {
class CategoriesFragment : Fragment(), CategorySelectionListener {
- private val viewModel: OrderViewModel by activityViewModels()
+ private val viewModel: MainViewModel by activityViewModels()
+ private val orderManager by lazy { viewModel.orderManager }
private val adapter = CategoryAdapter(this)
override fun onCreateView(
@@ -39,14 +41,14 @@ class CategoriesFragment : Fragment(),
CategorySelectionListener {
layoutManager = LinearLayoutManager(requireContext())
}
- viewModel.categories.observe(viewLifecycleOwner, Observer { categories
->
+ orderManager.categories.observe(viewLifecycleOwner, Observer {
categories ->
adapter.setItems(categories)
progressBar.visibility = INVISIBLE
})
}
override fun onCategorySelected(category: Category) {
- viewModel.setCurrentCategory(category)
+ orderManager.setCurrentCategory(category)
}
}
diff --git a/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
b/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
index 3743281..1cb89ba 100644
--- a/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
+++ b/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
@@ -9,12 +9,13 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController
import androidx.navigation.Navigation.findNavController
import kotlinx.android.synthetic.main.fragment_order.*
+import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
class OrderFragment : Fragment() {
- private val viewModel: OrderViewModel by activityViewModels()
-
+ private val viewModel: MainViewModel by activityViewModels()
+ private val orderManager by lazy { viewModel.orderManager }
override fun onCreateView(
inflater: LayoutInflater,
@@ -25,13 +26,16 @@ class OrderFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- restartButton.setOnClickListener { viewModel.restart() }
+ // TODO build undo-feature that allows to undo a restart and bring
back old order
+ restartButton.setOnClickListener { orderManager.restart() }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val nav: NavController = findNavController(requireActivity(),
R.id.nav_host_fragment)
+ reconfigureButton.setOnClickListener {
nav.navigate(R.id.action_global_merchantSettings) }
historyButton.setOnClickListener {
nav.navigate(R.id.action_global_merchantHistory) }
+ logoutButton.setOnClickListener {
nav.navigate(R.id.action_global_merchantSettings) }
}
}
diff --git a/app/src/main/java/net/taler/merchantpos/order/OrderViewModel.kt
b/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt
similarity index 68%
rename from app/src/main/java/net/taler/merchantpos/order/OrderViewModel.kt
rename to app/src/main/java/net/taler/merchantpos/order/OrderManager.kt
index 02ee33f..e7928c7 100644
--- a/app/src/main/java/net/taler/merchantpos/order/OrderViewModel.kt
+++ b/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt
@@ -1,38 +1,21 @@
package net.taler.merchantpos.order
-import android.app.Application
import android.util.Log
import androidx.annotation.UiThread
-import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations.map
-import androidx.lifecycle.viewModelScope
-import com.android.volley.Request.Method.GET
-import com.android.volley.Response.ErrorListener
-import com.android.volley.Response.Listener
-import com.android.volley.toolbox.JsonObjectRequest
-import com.android.volley.toolbox.Volley
import com.fasterxml.jackson.core.type.TypeReference
-import
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
+import net.taler.merchantpos.config.ConfigurationReceiver
import org.json.JSONObject
-class OrderViewModel(app: Application) : AndroidViewModel(app) {
+class OrderManager(private val mapper: ObjectMapper) : ConfigurationReceiver {
companion object {
- val TAG = OrderViewModel::class.java.simpleName
+ val TAG = OrderManager::class.java.simpleName
}
- private val url = "https://grobox.de/taler/products.json"
- private val queue = Volley.newRequestQueue(app)
- private val mapper = ObjectMapper()
- .registerModule(KotlinModule())
- .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
-
private val productsByCategory = HashMap<Category, ArrayList<Product>>()
private val mOrder = MutableLiveData<HashMap<Product, Int>>()
@@ -45,23 +28,17 @@ class OrderViewModel(app: Application) :
AndroidViewModel(app) {
private val mCategories = MutableLiveData<List<Category>>()
internal val categories: LiveData<List<Category>> = mCategories
- init {
- val stringRequest = JsonObjectRequest(GET, url, null,
- Listener { response -> onConfigurationReceived(response) },
- ErrorListener { onConfigurationError() }
- )
- queue.add(stringRequest)
- }
-
- override fun onCleared() {
- queue.cancelAll { !it.isCanceled }
- }
-
- private fun onConfigurationReceived(json: JSONObject) =
viewModelScope.launch(Dispatchers.IO) {
+ override suspend fun onConfigurationReceived(json: JSONObject): Boolean {
// 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
+ }
+ // pre-select the first category
+ categories[0].selected = true
mCategories.postValue(categories)
// parse products (live data gets updated in setCurrentCategory())
@@ -77,7 +54,7 @@ class OrderViewModel(app: Application) :
AndroidViewModel(app) {
if (category == null) {
Log.e(TAG, "Product $product has unknown category
$categoryId")
onConfigurationError()
- return@launch
+ return false
}
if (productsByCategory.containsKey(category)) {
productsByCategory[category]?.add(product)
@@ -86,9 +63,12 @@ class OrderViewModel(app: Application) :
AndroidViewModel(app) {
}
}
}
- // pre-select the first category
- if (productsByCategory.size > 0) setCurrentCategory(categories[0])
- else onConfigurationError()
+ return if (productsByCategory.size > 0) {
+ mProducts.postValue(productsByCategory[categories[0]])
+ true
+ } else {
+ false
+ }
}
private fun onConfigurationError() {
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 928b688..b473b5d 100644
--- a/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
+++ b/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
@@ -11,12 +11,14 @@ import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_order_state.*
+import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder
class OrderStateFragment : Fragment() {
- private val viewModel: OrderViewModel by activityViewModels()
+ private val viewModel: MainViewModel by activityViewModels()
+ private val orderManager by lazy { viewModel.orderManager }
private val adapter = OrderAdapter()
override fun onCreateView(
@@ -33,10 +35,10 @@ class OrderStateFragment : Fragment() {
layoutManager = LinearLayoutManager(requireContext())
}
- viewModel.order.observe(viewLifecycleOwner, Observer { order ->
+ orderManager.order.observe(viewLifecycleOwner, Observer { order ->
adapter.setItems(order)
})
- viewModel.orderTotal.observe(viewLifecycleOwner, Observer { orderTotal
->
+ orderManager.orderTotal.observe(viewLifecycleOwner, Observer {
orderTotal ->
if (orderTotal == 0.0) {
totalView.text = null
} else {
diff --git a/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
b/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
index 2a028c0..0fef4bd 100644
--- a/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
+++ b/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import kotlinx.android.synthetic.main.fragment_products.*
+import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
import net.taler.merchantpos.order.ProductAdapter.ProductViewHolder
@@ -22,7 +23,8 @@ interface ProductSelectionListener {
class ProductsFragment : Fragment(), ProductSelectionListener {
- private val viewModel: OrderViewModel by activityViewModels()
+ private val viewModel: MainViewModel by activityViewModels()
+ private val orderManager by lazy { viewModel.orderManager }
private val adapter = ProductAdapter(this)
override fun onCreateView(
@@ -39,7 +41,7 @@ class ProductsFragment : Fragment(), ProductSelectionListener
{
layoutManager = GridLayoutManager(requireContext(), 3)
}
- viewModel.products.observe(viewLifecycleOwner, Observer { products ->
+ orderManager.products.observe(viewLifecycleOwner, Observer { products
->
if (products == null) {
adapter.setItems(emptyList())
} else {
@@ -50,7 +52,7 @@ class ProductsFragment : Fragment(), ProductSelectionListener
{
}
override fun onProductSelected(product: Product) {
- viewModel.addProduct(product)
+ orderManager.addProduct(product)
}
}
diff --git a/app/src/main/res/layout/fragment_merchant_settings.xml
b/app/src/main/res/layout/fragment_merchant_settings.xml
index b6e3707..6f1bcc9 100644
--- a/app/src/main/res/layout/fragment_merchant_settings.xml
+++ b/app/src/main/res/layout/fragment_merchant_settings.xml
@@ -1,108 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="15dp"
- tools:context=".MerchantSettings">
-
-
- <LinearLayout
- android:orientation="vertical"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ tools:context=".config.MerchantConfigFragment">
- <TextView
- android:layout_width="match_parent"
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/configUrlView"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:text="Merchant Backend Base URL" />
-
- <EditText
- android:id="@+id/edit_settings_backend_url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ems="10"
- android:inputType="text"
- android:text="Name" />
-
- <Space
- android:layout_width="match_parent"
- android:layout_height="40dp" />
+ android:layout_margin="16dp"
+ android:hint="@string/config_url"
+ app:boxBackgroundColor="@android:color/transparent"
+ app:boxBackgroundMode="outline"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textUri" />
- <TextView
- android:id="@+id/textView4"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Merchant Instance" />
+ </com.google.android.material.textfield.TextInputLayout>
- <EditText
- android:id="@+id/edit_settings_instance"
- android:layout_width="match_parent"
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/usernameView"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:ems="10"
- android:inputType="text"
- android:text="Name" />
+ android:layout_margin="16dp"
+ android:hint="@string/config_username"
+ app:boxBackgroundColor="@android:color/transparent"
+ app:boxBackgroundMode="outline"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/configUrlView">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="text" />
- <Space
- android:layout_width="match_parent"
- android:layout_height="40dp" />
+ </com.google.android.material.textfield.TextInputLayout>
- <TextView
- android:layout_width="match_parent"
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/passwordView"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:text="API Key" />
-
- <EditText
- android:id="@+id/edit_settings_apikey"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ems="10"
- android:inputType="textPassword" />
+ android:layout_margin="16dp"
+ android:hint="@string/config_password"
+ app:boxBackgroundColor="@android:color/transparent"
+ app:boxBackgroundMode="outline"
+ app:endIconMode="password_toggle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/usernameView">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textWebPassword" />
- <Space
- android:layout_width="match_parent"
- android:layout_height="40dp" />
+ </com.google.android.material.textfield.TextInputLayout>
<TextView
- android:layout_width="match_parent"
+ android:id="@+id/currencyView"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:text="Currency" />
-
- <TextView
- android:id="@+id/text_settings_currency"
- android:layout_width="match_parent"
+ android:layout_margin="16dp"
+ android:textSize="18sp"
+ android:visibility="gone"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/passwordView"
+ tools:text="@string/config_currency"
+ tools:visibility="visible" />
+
+ <Button
+ android:id="@+id/okButton"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="TextView"
-
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
-
- <Space
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <Button
- android:id="@+id/button_settings_reset"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|right"
- android:text="Reset" />
-
- <Space
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_margin="16dp"
+ android:text="@string/config_ok"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/currencyView"
+ app:layout_constraintVertical_bias="1.0" />
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ style="?android:attr/progressBarStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="@+id/okButton"
+ app:layout_constraintEnd_toEndOf="@+id/okButton"
+ app:layout_constraintStart_toStartOf="@+id/okButton"
+ app:layout_constraintTop_toTopOf="@+id/okButton"
+ tools:visibility="visible" />
- <Button
- android:id="@+id/button_settings_apply"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|right"
- android:text="Apply" />
- </LinearLayout>
- </LinearLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
-</FrameLayout>
\ No newline at end of file
+</ScrollView>
diff --git a/app/src/main/res/layout/fragment_order.xml
b/app/src/main/res/layout/fragment_order.xml
index 462264d..968544e 100644
--- a/app/src/main/res/layout/fragment_order.xml
+++ b/app/src/main/res/layout/fragment_order.xml
@@ -82,6 +82,16 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/reconfigureButton" />
+ <Button
+ android:id="@+id/logoutButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:backgroundTint="@color/logoutButton"
+ android:text="@string/button_logout"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/historyButton" />
+
<Button
android:id="@+id/completeButton"
android:layout_width="wrap_content"
@@ -92,6 +102,6 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintStart_toEndOf="@+id/historyButton" />
+ app:layout_constraintStart_toEndOf="@+id/logoutButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/navigation/nav_graph.xml
b/app/src/main/res/navigation/nav_graph.xml
index 5951e77..4eaadda 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -31,7 +31,7 @@
<action android:id="@+id/action_global_merchantHistory"
app:destination="@id/merchantHistory"/>
<action android:id="@+id/action_global_createPayment"
app:destination="@id/createPayment"/>
<action android:id="@+id/action_global_order" app:destination="@id/order"/>
- <fragment android:id="@+id/merchantSettings"
android:name="net.taler.merchantpos.MerchantSettings"
+ <fragment android:id="@+id/merchantSettings"
android:name="net.taler.merchantpos.config.MerchantConfigFragment"
android:label="Merchant Settings"
tools:layout="@layout/fragment_merchant_settings"/>
<action android:id="@+id/action_global_merchantSettings"
app:destination="@id/merchantSettings"/>
<fragment
diff --git a/app/src/main/res/values/colors.xml
b/app/src/main/res/values/colors.xml
index 950c107..10354c5 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -5,4 +5,5 @@
<color name="colorAccent">#FFEB3B</color>
<color name="bottomButtons">#9E9D24</color>
+ <color name="logoutButton">#C62828</color>
</resources>
diff --git a/app/src/main/res/values/strings.xml
b/app/src/main/res/values/strings.xml
index 09c7342..dbfa175 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -21,5 +21,16 @@
<string name="order_restart">Restart</string>
<string name="button_reconfigure">Reconfigure</string>
<string name="button_history">History</string>
+ <string name="button_logout">Logout</string>
<string name="button_complete">Complete</string>
+
+ <string name="config_url">Configuration URL</string>
+ <string name="config_username">Username</string>
+ <string name="config_password">Password</string>
+ <string name="config_currency">Currency: %s</string>
+ <string name="config_ok">Fetch Configuration</string>
+ <string name="config_malformed_url">Invalid URL</string>
+ <string name="config_auth_error">Invalid username or password</string>
+ <string name="config_error">Error: Invalid Configuration</string>
+
</resources>
--
To stop receiving notification emails like this one, please contact
address@hidden.
- [taler-merchant-terminal-android] branch master updated (f463d1b -> 39af919), gnunet, 2020/02/21
- [taler-merchant-terminal-android] 03/19: Fix crash when loading history, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 01/19: Upgrade libraries to latest stable versions, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 05/19: Allow user to undo restarting the order, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 07/19: Add ordered products to order's contract terms, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 08/19: Use actual taler icon for the app, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 09/19: Factor out NFC code from MainActivity, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 06/19: Create payments directly from the order, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 04/19: Fetch merchant config from central configuration JSON,
gnunet <=
- [taler-merchant-terminal-android] 02/19: Add screen to process an order, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 18/19: Don't talk about NFC if it is not supported, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 14/19: Use product categories for order summary, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 11/19: Allow user to decide if they want to save password, add FORGET option, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 13/19: Make NFC and QR code re-useable in another app, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 15/19: Introduce different product classes for re-use in other taler apps, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 17/19: Make order sorting deterministic, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 12/19: Fix invalid product configuration, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 19/19: Check for duplicate product IDs, gnunet, 2020/02/21
- [taler-merchant-terminal-android] 10/19: Require valid configuration before showing UI, gnunet, 2020/02/21