gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-terminal-android] 02/19: Add screen to process an order


From: gnunet
Subject: [taler-merchant-terminal-android] 02/19: Add screen to process an order
Date: Fri, 21 Feb 2020 18:59:55 +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 7d299bf8358c854987aab61d139ca74c83079d17
Author: Torsten Grote <address@hidden>
AuthorDate: Mon Jan 27 17:23:30 2020 -0300

    Add screen to process an order
---
 app/build.gradle                                   |   3 +
 app/src/main/AndroidManifest.xml                   |  21 +++-
 .../java/net/taler/merchantpos/CreatePayment.kt    |  19 +--
 .../java/net/taler/merchantpos/MainActivity.kt     |  23 ++--
 .../java/net/taler/merchantpos/MerchantHistory.kt  |  14 +--
 .../java/net/taler/merchantpos/ProcessPayment.kt   |  28 ++---
 .../taler/merchantpos/order/CategoriesFragment.kt  |  88 ++++++++++++++
 .../net/taler/merchantpos/order/Definitions.kt     |  25 ++++
 .../net/taler/merchantpos/order/OrderFragment.kt   |  37 ++++++
 .../taler/merchantpos/order/OrderStateFragment.kt  |  84 +++++++++++++
 .../net/taler/merchantpos/order/OrderViewModel.kt  | 130 +++++++++++++++++++++
 .../taler/merchantpos/order/ProductsFragment.kt    |  93 +++++++++++++++
 app/src/main/res/layout/fragment_categories.xml    |  28 +++++
 app/src/main/res/layout/fragment_order.xml         |  97 +++++++++++++++
 app/src/main/res/layout/fragment_order_state.xml   |  31 +++++
 app/src/main/res/layout/fragment_products.xml      |  28 +++++
 app/src/main/res/layout/list_item_category.xml     |  17 +++
 app/src/main/res/layout/list_item_order.xml        |  40 +++++++
 app/src/main/res/layout/list_item_product.xml      |  40 +++++++
 app/src/main/res/menu/activity_main_drawer.xml     |  14 ++-
 app/src/main/res/navigation/nav_graph.xml          |  18 ++-
 app/src/main/res/values/colors.xml                 |   2 +
 app/src/main/res/values/strings.xml                |   6 +
 app/src/main/res/values/styles.xml                 |   2 +-
 24 files changed, 821 insertions(+), 67 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 1408c8a..3a6c23b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -60,6 +60,9 @@ dependencies {
 
     implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
 
+    // JSON parsing and serialization
+    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7"
+
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'androidx.test:runner:1.2.0'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0920771..49c9892 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,27 +1,36 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android";
-          xmlns:tools="http://schemas.android.com/tools"; 
package="net.taler.merchantpos">
+        xmlns:tools="http://schemas.android.com/tools";
+        package="net.taler.merchantpos">
 
     <uses-permission android:name="android.permission.NFC" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-feature android:name="android.hardware.nfc"
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <uses-feature
+            android:name="android.hardware.nfc"
             android:required="true" />
 
+    <uses-feature
+            android:name="android.hardware.telephony"
+            android:required="false" />
+
     <application
             android:allowBackup="true"
             android:icon="@mipmap/ic_launcher"
             android:label="@string/app_name"
             android:roundIcon="@mipmap/ic_launcher_round"
             android:supportsRtl="true"
-            android:theme="@style/AppTheme" 
tools:ignore="GoogleAppIndexingWarning">
+            android:theme="@style/AppTheme"
+            tools:ignore="GoogleAppIndexingWarning">
         <activity
                 android:name=".MainActivity"
                 android:label="@string/app_name"
+                android:screenOrientation="landscape"
                 android:theme="@style/AppTheme.NoActionBar">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
+                <action android:name="android.intent.action.MAIN" />
 
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
     </application>
diff --git a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt 
b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
index 83dbed8..f92bac7 100644
--- a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
+++ b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
@@ -9,7 +9,7 @@ import android.widget.Button
 import android.widget.EditText
 import android.widget.TextView
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProviders
+import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
 import com.android.volley.Request
 import com.android.volley.RequestQueue
@@ -25,7 +25,7 @@ import org.json.JSONObject
  */
 class CreatePayment : Fragment() {
     private lateinit var queue: RequestQueue
-    private lateinit var model: PosTerminalViewModel
+    private val model: PosTerminalViewModel by activityViewModels()
 
     private var paused: Boolean = false
 
@@ -47,10 +47,6 @@ class CreatePayment : Fragment() {
     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)
     }
 
@@ -92,8 +88,13 @@ 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 = MerchantInternalRequest(Request.Method.GET,
+            model.merchantConfig!!,
+            "check-payment",
+            params,
+            null,
+            Response.Listener { onCheckPayment(it) },
+            Response.ErrorListener { onNetworkError(it) })
         queue.add(req)
     }
 
@@ -119,7 +120,7 @@ class CreatePayment : Fragment() {
     ): View? {
         // Inflate the layout for this fragment
         val view = inflater.inflate(R.layout.fragment_create_payment, 
container, false)
-        val requestPaymentButton = 
view.findViewById<Button>(R.id.button_request_payment);
+        val requestPaymentButton = 
view.findViewById<Button>(R.id.button_request_payment)
         requestPaymentButton.setOnClickListener {
             onRequestPayment()
         }
diff --git a/app/src/main/java/net/taler/merchantpos/MainActivity.kt 
b/app/src/main/java/net/taler/merchantpos/MainActivity.kt
index 7c8330a..8cc2788 100644
--- a/app/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/app/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -6,18 +6,18 @@ import android.nfc.Tag
 import android.nfc.tech.IsoDep
 import android.os.Bundle
 import android.util.Log
-import androidx.core.view.GravityCompat
+import android.view.Menu
 import android.view.MenuItem
-import androidx.drawerlayout.widget.DrawerLayout
-import com.google.android.material.navigation.NavigationView
+import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.Toolbar
-import android.view.Menu
-import androidx.lifecycle.ViewModelProviders
+import androidx.core.view.GravityCompat
+import androidx.drawerlayout.widget.DrawerLayout
 import androidx.navigation.NavController
 import androidx.navigation.findNavController
 import androidx.navigation.ui.AppBarConfiguration
 import androidx.navigation.ui.setupWithNavController
+import com.google.android.material.navigation.NavigationView
 import net.taler.merchantpos.Utils.Companion.hexStringToByteArray
 import org.json.JSONObject
 import java.io.ByteArrayOutputStream
@@ -143,7 +143,7 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         const val TAG = "taler-merchant"
     }
 
-    private lateinit var model: PosTerminalViewModel
+    private val model: PosTerminalViewModel by viewModels()
     private var nfcAdapter: NfcAdapter? = null
 
     private var currentTag: IsoDep? = null
@@ -265,9 +265,9 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         navView.setNavigationItemSelectedListener(this)
 
         val navController = findNavController(R.id.nav_host_fragment)
-        val appBarConfiguration =
-            AppBarConfiguration(
+        val appBarConfiguration = AppBarConfiguration(
                 setOf(
+                    R.id.order,
                     R.id.createPayment,
                     R.id.merchantSettings,
                     R.id.merchantHistory
@@ -284,10 +284,8 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         val apiKey = prefs.getString("merchantBackendApiKey", "sandbox")
         val currency = prefs.getString("merchantBackendCurrency", "TESTKUDOS")
 
-        model = ViewModelProviders.of(this)[PosTerminalViewModel::class.java]
         model.merchantConfig =
             MerchantConfig(baseUrl!!, instance!!, apiKey!!, currency!!)
-
     }
 
     override fun onBackPressed() {
@@ -319,9 +317,8 @@ class MainActivity : AppCompatActivity(), 
NavigationView.OnNavigationItemSelecte
         // Handle navigation view item clicks here.
         val nav: NavController = findNavController(R.id.nav_host_fragment)
         when (item.itemId) {
-            R.id.nav_home -> {
-                nav.navigate(R.id.action_global_createPayment)
-            }
+            R.id.nav_home -> nav.navigate(R.id.action_global_createPayment)
+            R.id.nav_order -> nav.navigate(R.id.action_global_order)
             R.id.nav_history -> {
                 nav.navigate(R.id.action_global_merchantHistory)
             }
diff --git a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt 
b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
index b79f836..577615b 100644
--- a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
+++ b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
@@ -4,13 +4,13 @@ package net.taler.merchantpos
 import android.annotation.SuppressLint
 import android.os.Bundle
 import android.util.Log
-import androidx.fragment.app.Fragment
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModelProviders
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -87,7 +87,7 @@ fun parseTalerTimestamp(s: String): Instant {
  */
 class MerchantHistory : Fragment() {
     private lateinit var queue: RequestQueue
-    private lateinit var model: PosTerminalViewModel
+    private val model: PosTerminalViewModel by activityViewModels()
     private val historyListAdapter = MyAdapter(listOf())
 
     private val isLoading = MutableLiveData<Boolean>().apply { value = false }
@@ -95,10 +95,6 @@ class MerchantHistory : Fragment() {
     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)
     }
 
@@ -166,7 +162,7 @@ class MerchantHistory : Fragment() {
         }
 
         this.isLoading.observe(this, androidx.lifecycle.Observer { loading ->
-            Log.v(TAG, "setting refreshing to ${loading}")
+            Log.v(TAG, "setting refreshing to $loading")
             refreshLayout.isRefreshing = loading
         })
 
@@ -174,6 +170,6 @@ class MerchantHistory : Fragment() {
     }
 
     companion object {
-        val TAG = "taler-merchant"
+        const val TAG = "taler-merchant"
     }
 }
diff --git a/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt 
b/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
index fbd60c6..d78d873 100644
--- a/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
+++ b/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
@@ -1,28 +1,17 @@
 package net.taler.merchantpos
 
-import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.Color
-import android.net.Uri
 import android.os.Bundle
-import androidx.fragment.app.Fragment
+import android.os.Handler
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import com.google.zxing.BarcodeFormat
-import com.google.zxing.common.BitMatrix
-import com.google.zxing.qrcode.QRCodeWriter
-import android.opengl.ETC1.getWidth
-import android.opengl.ETC1.getHeight
-import android.os.Handler
-import android.util.Log
 import android.widget.Button
 import android.widget.ImageView
 import android.widget.TextView
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.addCallback
-import androidx.lifecycle.ViewModelProviders
-import androidx.navigation.findNavController
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
 import com.android.volley.Request
 import com.android.volley.RequestQueue
@@ -30,8 +19,10 @@ import com.android.volley.Response
 import com.android.volley.VolleyError
 import com.android.volley.toolbox.Volley
 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 org.json.JSONObject
-import java.net.URLEncoder
 
 
 // TODO: Rename parameter arguments, choose names that match
@@ -47,17 +38,12 @@ class ProcessPayment : Fragment() {
 
     private var paused: Boolean = true
     private lateinit var queue: RequestQueue
-    private lateinit var model: PosTerminalViewModel
+    private val model: PosTerminalViewModel by activityViewModels()
 
     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 onCheckPayment(checkPaymentResponse: JSONObject) {
diff --git 
a/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt 
b/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
new file mode 100644
index 0000000..9d1ac5e
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
@@ -0,0 +1,88 @@
+package net.taler.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+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.R
+import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder
+
+interface CategorySelectionListener {
+    fun onCategorySelected(category: Category)
+}
+
+class CategoriesFragment : Fragment(), CategorySelectionListener {
+
+    private val viewModel: OrderViewModel by activityViewModels()
+    private val adapter = CategoryAdapter(this)
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_categories, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        categoriesList.apply {
+            adapter = this@CategoriesFragment.adapter
+            layoutManager = LinearLayoutManager(requireContext())
+        }
+
+        viewModel.categories.observe(viewLifecycleOwner, Observer { categories 
->
+            adapter.setItems(categories)
+            progressBar.visibility = INVISIBLE
+        })
+    }
+
+    override fun onCategorySelected(category: Category) {
+        viewModel.setCurrentCategory(category)
+    }
+
+}
+
+private class CategoryAdapter(
+    private val listener: CategorySelectionListener
+) : Adapter<CategoryViewHolder>() {
+
+    private val categories = ArrayList<Category>()
+
+    override fun getItemCount() = categories.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
CategoryViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_category, 
parent, false)
+        return CategoryViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
+        holder.bind(categories[position])
+    }
+
+    fun setItems(items: List<Category>) {
+        categories.clear()
+        categories.addAll(items)
+        notifyDataSetChanged()
+    }
+
+    private inner class CategoryViewHolder(v: View) : 
RecyclerView.ViewHolder(v) {
+        private val button: Button = v.findViewById(R.id.button)
+
+        fun bind(category: Category) {
+            button.text = category.name
+            button.isPressed = category.selected
+            button.setOnClickListener { listener.onCategorySelected(category) }
+        }
+    }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/order/Definitions.kt 
b/app/src/main/java/net/taler/merchantpos/order/Definitions.kt
new file mode 100644
index 0000000..ec7f77e
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/order/Definitions.kt
@@ -0,0 +1,25 @@
+package net.taler.merchantpos.order
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import net.taler.merchantpos.Amount
+
+data class Category(
+    val id: Int,
+    val name: String
+) {
+    var selected: Boolean = false
+}
+
+data class Product(
+    @JsonProperty("product_id")
+    val id: String,
+    val description: String,
+    val price: String,
+    val categories: List<Int>,
+    @JsonProperty("delivery_location")
+    val location: String
+) {
+    val priceAsDouble by lazy { Amount.fromString(price).amount.toDouble() }
+}
+
+typealias OrderLine = Pair<Product, Int>
diff --git a/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt 
b/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
new file mode 100644
index 0000000..3743281
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
@@ -0,0 +1,37 @@
+package net.taler.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+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.R
+
+class OrderFragment : Fragment() {
+
+    private val viewModel: OrderViewModel by activityViewModels()
+
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_order, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        restartButton.setOnClickListener { viewModel.restart() }
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        val nav: NavController = findNavController(requireActivity(), 
R.id.nav_host_fragment)
+        historyButton.setOnClickListener { 
nav.navigate(R.id.action_global_merchantHistory) }
+    }
+
+}
diff --git 
a/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt 
b/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
new file mode 100644
index 0000000..928b688
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
@@ -0,0 +1,84 @@
+package net.taler.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+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.R
+import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder
+
+class OrderStateFragment : Fragment() {
+
+    private val viewModel: OrderViewModel by activityViewModels()
+    private val adapter = OrderAdapter()
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_order_state, container, 
false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        orderList.apply {
+            adapter = this@OrderStateFragment.adapter
+            layoutManager = LinearLayoutManager(requireContext())
+        }
+
+        viewModel.order.observe(viewLifecycleOwner, Observer { order ->
+            adapter.setItems(order)
+        })
+        viewModel.orderTotal.observe(viewLifecycleOwner, Observer { orderTotal 
->
+            if (orderTotal == 0.0) {
+                totalView.text = null
+            } else {
+                totalView.text = getString(R.string.order_total, orderTotal)
+            }
+        })
+    }
+
+}
+
+private class OrderAdapter : RecyclerView.Adapter<OrderViewHolder>() {
+
+    private val orderLines = ArrayList<OrderLine>()
+
+    override fun getItemCount() = orderLines.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
OrderViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_order, parent, 
false)
+        return OrderViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
+        holder.bind(orderLines[position])
+    }
+
+    fun setItems(items: HashMap<Product, Int>) {
+        orderLines.clear()
+        items.forEach { t -> orderLines.add(t.toPair()) }
+        notifyDataSetChanged()
+    }
+
+    private inner class OrderViewHolder(v: View) : RecyclerView.ViewHolder(v) {
+        private val quantity: TextView = v.findViewById(R.id.quantity)
+        private val name: TextView = v.findViewById(R.id.name)
+        private val price: TextView = v.findViewById(R.id.price)
+
+        fun bind(orderLine: OrderLine) {
+            quantity.text = orderLine.second.toString()
+            name.text = orderLine.first.description
+            price.text = String.format("%.2f", orderLine.first.priceAsDouble * 
orderLine.second)
+        }
+    }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/order/OrderViewModel.kt 
b/app/src/main/java/net/taler/merchantpos/order/OrderViewModel.kt
new file mode 100644
index 0000000..02ee33f
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/order/OrderViewModel.kt
@@ -0,0 +1,130 @@
+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 org.json.JSONObject
+
+class OrderViewModel(app: Application) : AndroidViewModel(app) {
+
+    companion object {
+        val TAG = OrderViewModel::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>>()
+    internal val order: LiveData<HashMap<Product, Int>> = mOrder
+    internal val orderTotal: LiveData<Double> = map(mOrder) { map -> 
getTotal(map) }
+
+    private val mProducts = MutableLiveData<List<Product>>()
+    internal val products: LiveData<List<Product>> = mProducts
+
+    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) {
+        // parse categories
+        val categoriesStr = json.getJSONArray("categories").toString()
+        val categoriesType = object : TypeReference<List<Category>>() {}
+        val categories: List<Category> = mapper.readValue(categoriesStr, 
categoriesType)
+        mCategories.postValue(categories)
+
+        // parse products (live data gets updated in setCurrentCategory())
+        val productsStr = json.getJSONArray("products").toString()
+        val productsType = object : TypeReference<List<Product>>() {}
+        val products: List<Product> = mapper.readValue(productsStr, 
productsType)
+
+        // group products by categories
+        productsByCategory.clear()
+        products.forEach { product ->
+            product.categories.forEach { categoryId ->
+                val category = categories.find { it.id == categoryId }
+                if (category == null) {
+                    Log.e(TAG, "Product $product has unknown category 
$categoryId")
+                    onConfigurationError()
+                    return@launch
+                }
+                if (productsByCategory.containsKey(category)) {
+                    productsByCategory[category]?.add(product)
+                } else {
+                    productsByCategory[category] = ArrayList<Product>().apply 
{ add(product) }
+                }
+            }
+        }
+        // pre-select the first category
+        if (productsByCategory.size > 0) setCurrentCategory(categories[0])
+        else onConfigurationError()
+    }
+
+    private fun onConfigurationError() {
+        Log.e("TEST", "ERROR")
+        // TODO
+    }
+
+    internal fun setCurrentCategory(category: Category) {
+        val newCategories = categories.value?.apply {
+            forEach { if (it.selected) it.selected = false }
+            category.selected = true
+        }
+        mCategories.postValue(newCategories)
+        mProducts.postValue(productsByCategory[category])
+    }
+
+    @UiThread
+    internal fun addProduct(product: Product) {
+        val map = mOrder.value ?: HashMap()
+        val quantity = map[product] ?: 0
+        map[product] = quantity + 1
+        mOrder.value = map
+    }
+
+    @UiThread
+    internal fun restart() {
+        mOrder.value = HashMap()
+    }
+
+    private fun getTotal(map: HashMap<Product, Int>): Double {
+        var total = 0.0
+        map.forEach {
+            val price = it.key.priceAsDouble
+            total += price * it.value
+        }
+        return total
+    }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt 
b/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
new file mode 100644
index 0000000..2a028c0
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
@@ -0,0 +1,93 @@
+package net.taler.merchantpos.order
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.INVISIBLE
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+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.R
+import net.taler.merchantpos.order.ProductAdapter.ProductViewHolder
+
+interface ProductSelectionListener {
+    fun onProductSelected(product: Product)
+}
+
+class ProductsFragment : Fragment(), ProductSelectionListener {
+
+    private val viewModel: OrderViewModel by activityViewModels()
+    private val adapter = ProductAdapter(this)
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_products, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        productsList.apply {
+            adapter = this@ProductsFragment.adapter
+            layoutManager = GridLayoutManager(requireContext(), 3)
+        }
+
+        viewModel.products.observe(viewLifecycleOwner, Observer { products ->
+            if (products == null) {
+                adapter.setItems(emptyList())
+            } else {
+                adapter.setItems(products)
+            }
+            progressBar.visibility = INVISIBLE
+        })
+    }
+
+    override fun onProductSelected(product: Product) {
+        viewModel.addProduct(product)
+    }
+
+}
+
+private class ProductAdapter(
+    private val listener: ProductSelectionListener
+) : Adapter<ProductViewHolder>() {
+
+    private val products = ArrayList<Product>()
+
+    override fun getItemCount() = products.size
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
ProductViewHolder {
+        val view =
+            
LayoutInflater.from(parent.context).inflate(R.layout.list_item_product, parent, 
false)
+        return ProductViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
+        holder.bind(products[position])
+    }
+
+    fun setItems(items: List<Product>) {
+        products.clear()
+        products.addAll(items)
+        notifyDataSetChanged()
+    }
+
+    private inner class ProductViewHolder(private val v: View) : ViewHolder(v) 
{
+        private val name: TextView = v.findViewById(R.id.name)
+        private val price: TextView = v.findViewById(R.id.price)
+
+        fun bind(product: Product) {
+            name.text = product.description
+            price.text = product.priceAsDouble.toString()
+            v.setOnClickListener { listener.onProductSelected(product) }
+        }
+    }
+
+}
diff --git a/app/src/main/res/layout/fragment_categories.xml 
b/app/src/main/res/layout/fragment_categories.xml
new file mode 100644
index 0000000..dcd6bd5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_categories.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        android:layout_width="match_parent"
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/categoriesList"
+            android:layout_width="0dp"
+            tools:listitem="@layout/list_item_category"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_order.xml 
b/app/src/main/res/layout/fragment_order.xml
new file mode 100644
index 0000000..462264d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_order.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 
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">
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/fragment1"
+            android:name="net.taler.merchantpos.order.OrderStateFragment"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/restartButton"
+            app:layout_constraintEnd_toStartOf="@+id/guideline1"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:layout="@layout/fragment_order_state" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.25" />
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/fragment2"
+            android:name="net.taler.merchantpos.order.ProductsFragment"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/restartButton"
+            app:layout_constraintEnd_toStartOf="@+id/guideline2"
+            app:layout_constraintStart_toStartOf="@+id/guideline1"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:layout="@layout/fragment_products" />
+
+    <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            app:layout_constraintGuide_percent="0.75" />
+
+    <androidx.fragment.app.FragmentContainerView
+            android:id="@+id/fragment3"
+            android:name="net.taler.merchantpos.order.CategoriesFragment"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/restartButton"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="@+id/guideline2"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:layout="@layout/fragment_categories" />
+
+    <Button
+            android:id="@+id/restartButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:backgroundTint="@color/bottomButtons"
+            android:text="@string/order_restart"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent" />
+
+    <Button
+            android:id="@+id/reconfigureButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:backgroundTint="@color/bottomButtons"
+            android:text="@string/button_reconfigure"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/restartButton" />
+
+    <Button
+            android:id="@+id/historyButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:backgroundTint="@color/bottomButtons"
+            android:text="@string/button_history"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/reconfigureButton" />
+
+    <Button
+            android:id="@+id/completeButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:backgroundTint="@color/bottomButtons"
+            android:text="@string/button_complete"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1.0"
+            app:layout_constraintStart_toEndOf="@+id/historyButton" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_order_state.xml 
b/app/src/main/res/layout/fragment_order_state.xml
new file mode 100644
index 0000000..0cd9c75
--- /dev/null
+++ b/app/src/main/res/layout/fragment_order_state.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 
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">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/orderList"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toTopOf="@+id/totalView"
+            tools:listitem="@layout/list_item_order"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+            android:id="@+id/totalView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            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/orderList"
+            app:layout_constraintVertical_bias="1.0"
+            tools:text="Total: 23.75" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_products.xml 
b/app/src/main/res/layout/fragment_products.xml
new file mode 100644
index 0000000..909fece
--- /dev/null
+++ b/app/src/main/res/layout/fragment_products.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        android:layout_width="match_parent"
+        xmlns:tools="http://schemas.android.com/tools";
+        android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/productsList"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            tools:listitem="@layout/list_item_product" />
+
+    <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/list_item_category.xml 
b/app/src/main/res/layout/list_item_category.xml
new file mode 100644
index 0000000..496b96b
--- /dev/null
+++ b/app/src/main/res/layout/list_item_category.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 
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="wrap_content">
+
+    <Button
+            android:id="@+id/button"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="Snacks" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/list_item_order.xml 
b/app/src/main/res/layout/list_item_order.xml
new file mode 100644
index 0000000..5dc2f86
--- /dev/null
+++ b/app/src/main/res/layout/list_item_order.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 
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="wrap_content"
+        android:paddingStart="8dp"
+        android:paddingTop="8dp"
+        android:paddingEnd="8dp">
+
+    <TextView
+            android:id="@+id/quantity"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:minWidth="24dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="31" />
+
+    <TextView
+            android:id="@+id/name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintEnd_toStartOf="@+id/price"
+            app:layout_constraintStart_toEndOf="@+id/quantity"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="An order product item that in some cases could have a 
very long name" />
+
+    <TextView
+            android:id="@+id/price"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="23.42" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/list_item_product.xml 
b/app/src/main/res/layout/list_item_product.xml
new file mode 100644
index 0000000..51178a5
--- /dev/null
+++ b/app/src/main/res/layout/list_item_product.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView 
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="wrap_content"
+        android:layout_margin="4dp"
+        android:clickable="true"
+        android:focusable="true"
+        app:cardUseCompatPadding="true">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:padding="8dp">
+
+        <TextView
+                android:id="@+id/name"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:textColor="?android:textColorPrimary"
+                android:textStyle="bold"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:text="Steak and two Eggs" />
+
+        <TextView
+                android:id="@+id/price"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:textColor="?android:textColorSecondary"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/name"
+                tools:text="7.95" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_main_drawer.xml 
b/app/src/main/res/menu/activity_main_drawer.xml
index 9ce4a06..7a70824 100644
--- a/app/src/main/res/menu/activity_main_drawer.xml
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -1,20 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android";
-      xmlns:tools="http://schemas.android.com/tools";
-      tools:showIn="navigation_view">
+        xmlns:tools="http://schemas.android.com/tools";
+        tools:showIn="navigation_view">
 
     <group android:checkableBehavior="single">
         <item
                 android:id="@+id/nav_home"
                 android:icon="@drawable/ic_move_money_24dp"
-                android:title="@string/menu_home"/>
+                android:title="@string/menu_home" />
+        <item
+                android:id="@+id/nav_order"
+                android:icon="@drawable/ic_move_money_24dp"
+                android:title="Order" />
         <item
                 android:id="@+id/nav_history"
                 android:icon="@drawable/ic_history_black_24dp"
-                android:title="History"/>
+                android:title="History" />
         <item
                 android:id="@+id/nav_settings"
                 android:icon="@drawable/ic_menu_manage"
-                android:title="Settings"/>
+                android:title="Settings" />
     </group>
 </menu>
diff --git a/app/src/main/res/navigation/nav_graph.xml 
b/app/src/main/res/navigation/nav_graph.xml
index 3a5b470..5951e77 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -1,8 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <navigation 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:id="@+id/nav_graph"
-            app:startDestination="@id/createPayment">
+        xmlns:app="http://schemas.android.com/apk/res-auto";
+        xmlns:tools="http://schemas.android.com/tools";
+        android:id="@+id/nav_graph"
+        app:startDestination="@id/order">
+
+    <fragment
+            android:id="@+id/order"
+            android:name="net.taler.merchantpos.order.OrderFragment"
+            android:label="Order"
+            tools:layout="@layout/fragment_order">
+        <action
+                android:id="@+id/action_createPayment_to_processPayment"
+                app:destination="@id/processPayment" />
+    </fragment>
 
     <fragment android:id="@+id/createPayment" 
android:name="net.taler.merchantpos.CreatePayment"
               android:label="Request Payment" 
tools:layout="@layout/fragment_create_payment">
@@ -19,6 +30,7 @@
               android:label="Payment History" 
tools:layout="@layout/fragment_merchant_history"/>
     <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"
               android:label="Merchant Settings" 
tools:layout="@layout/fragment_merchant_settings"/>
     <action android:id="@+id/action_global_merchantSettings" 
app:destination="@id/merchantSettings"/>
diff --git a/app/src/main/res/values/colors.xml 
b/app/src/main/res/values/colors.xml
index cbe99a2..950c107 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,4 +3,6 @@
     <color name="colorPrimary">#795548</color>
     <color name="colorPrimaryDark">#5D4037</color>
     <color name="colorAccent">#FFEB3B</color>
+
+    <color name="bottomButtons">#9E9D24</color>
 </resources>
diff --git a/app/src/main/res/values/strings.xml 
b/app/src/main/res/values/strings.xml
index f3103af..09c7342 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,4 +16,10 @@
 
     <!-- TODO: Remove or change this placeholder text -->
     <string name="hello_blank_fragment">Hello blank fragment</string>
+
+    <string name="order_total">Total: %1$.2f</string>
+    <string name="order_restart">Restart</string>
+    <string name="button_reconfigure">Reconfigure</string>
+    <string name="button_history">History</string>
+    <string name="button_complete">Complete</string>
 </resources>
diff --git a/app/src/main/res/values/styles.xml 
b/app/src/main/res/values/styles.xml
index 1eb4629..9eac8b0 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,6 +1,6 @@
 <resources>
     <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+    <style name="AppTheme" 
parent="Theme.MaterialComponents.DayNight.DarkActionBar">
         <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]