[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-terminal-android] branch master updated (1764700 -> 7eeb
From: |
gnunet |
Subject: |
[taler-merchant-terminal-android] branch master updated (1764700 -> 7eebd07) |
Date: |
Tue, 17 Mar 2020 16:30:44 +0100 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a change to branch master
in repository merchant-terminal-android.
from 1764700 Allow configuration Uri with basic auth crentials in user info
new e4750a5 If there's just one product, use its description as order
summary
new bca790d Clean up history code and layout
new 7eebd07 Add refund button to history items and allow to refund orders
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../java/net/taler/merchantpos/MainViewModel.kt | 4 +
.../java/net/taler/merchantpos/MerchantHistory.kt | 190 ---------------------
app/src/main/java/net/taler/merchantpos/Utils.kt | 28 +++
.../taler/merchantpos/history/HistoryManager.kt | 106 ++++++++++++
.../merchantpos/history/MerchantHistoryFragment.kt | 160 +++++++++++++++++
.../taler/merchantpos/history/RefundFragment.kt | 99 +++++++++++
.../net/taler/merchantpos/history/RefundManager.kt | 111 ++++++++++++
.../taler/merchantpos/history/RefundUriFragment.kt | 65 +++++++
.../net/taler/merchantpos/order/Definitions.kt | 10 +-
app/src/main/res/drawable/ic_cash_refund.xml | 9 +
app/src/main/res/layout/fragment_refund.xml | 122 +++++++++++++
...process_payment.xml => fragment_refund_uri.xml} | 49 ++----
app/src/main/res/layout/history_row.xml | 91 ----------
app/src/main/res/layout/list_item_history.xml | 97 +++++++++++
app/src/main/res/navigation/nav_graph.xml | 27 ++-
app/src/main/res/values/strings.xml | 14 +-
16 files changed, 860 insertions(+), 322 deletions(-)
delete mode 100644 app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
create mode 100644
app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
create mode 100644
app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
create mode 100644
app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
create mode 100644
app/src/main/java/net/taler/merchantpos/history/RefundManager.kt
create mode 100644
app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
create mode 100644 app/src/main/res/drawable/ic_cash_refund.xml
create mode 100644 app/src/main/res/layout/fragment_refund.xml
copy app/src/main/res/layout/{fragment_process_payment.xml =>
fragment_refund_uri.xml} (68%)
delete mode 100644 app/src/main/res/layout/history_row.xml
create mode 100644 app/src/main/res/layout/list_item_history.xml
diff --git a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
index 1bb57b9..3fe472d 100644
--- a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
+++ b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt
@@ -24,6 +24,8 @@ import
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PRO
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.history.HistoryManager
+import net.taler.merchantpos.history.RefundManager
import net.taler.merchantpos.order.OrderManager
import net.taler.merchantpos.payment.PaymentManager
@@ -39,6 +41,8 @@ class MainViewModel(app: Application) : AndroidViewModel(app)
{
addConfigurationReceiver(orderManager)
}
val paymentManager = PaymentManager(configManager, queue, mapper)
+ val historyManager = HistoryManager(configManager, queue, mapper)
+ val refundManager = RefundManager(configManager, queue)
override fun onCleared() {
queue.cancelAll { !it.isCanceled }
diff --git a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
deleted file mode 100644
index 997a1e6..0000000
--- a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.merchantpos
-
-import android.annotation.SuppressLint
-import android.os.Bundle
-import android.util.Log
-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.navigation.fragment.findNavController
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView.Adapter
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-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.toolbox.Volley
-import com.google.android.material.snackbar.Snackbar
-import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
-import kotlinx.android.synthetic.main.fragment_merchant_history.*
-import net.taler.merchantpos.HistoryItemAdapter.HistoryItemViewHolder
-import
net.taler.merchantpos.MerchantHistoryDirections.Companion.actionGlobalMerchantSettings
-import net.taler.merchantpos.config.MerchantRequest
-import org.json.JSONObject
-import java.time.Instant
-import java.time.ZoneId
-import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle.SHORT
-import java.util.*
-
-/**
- * Fragment to display the merchant's payment history,
- * received from the backend.
- */
-class MerchantHistory : Fragment() {
-
- companion object {
- const val TAG = "taler-merchant"
- }
-
- private lateinit var queue: RequestQueue
- private val model: MainViewModel by activityViewModels()
- private val historyListAdapter = HistoryItemAdapter(listOf())
-
- private val isLoading = MutableLiveData<Boolean>().apply { value = false }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- queue = Volley.newRequestQueue(context)
- }
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_merchant_history, container,
false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- list_history.apply {
- layoutManager = LinearLayoutManager(requireContext())
- addItemDecoration(DividerItemDecoration(context, VERTICAL))
- adapter = historyListAdapter
- }
-
- swipeRefresh.isRefreshing = false
- swipeRefresh.setOnRefreshListener {
- Log.v(TAG, "refreshing!")
- fetchHistory()
- }
-
- this.isLoading.observe(viewLifecycleOwner, androidx.lifecycle.Observer
{ loading ->
- Log.v(TAG, "setting refreshing to $loading")
- swipeRefresh.isRefreshing = loading
- })
- }
-
- override fun onStart() {
- super.onStart()
- if (model.configManager.merchantConfig?.instance == null) {
- actionGlobalMerchantSettings().navigate(findNavController())
- } else {
- fetchHistory()
- }
- }
-
- private fun fetchHistory() {
- isLoading.value = true
- val merchantConfig = model.configManager.merchantConfig!!
- val params = mapOf("instance" to merchantConfig.instance)
- val req = MerchantRequest(GET, merchantConfig, "history", params, null,
- Listener { onHistoryResponse(it) },
- ErrorListener { onNetworkError() })
- queue.add(req)
- }
-
- private fun onHistoryResponse(body: JSONObject) {
- this.isLoading.value = false
- Log.v(TAG, "got history response $body")
- // TODO use jackson instead of manual parsing
- val data = arrayListOf<HistoryItem>()
- val historyJson = body.getJSONArray("history")
- for (i in 0 until historyJson.length()) {
- val item = historyJson.getJSONObject(i)
- val orderId = item.getString("order_id")
- val summary = item.getString("summary")
- val timestampObj = item.getJSONObject("timestamp")
- val timestamp = Instant.ofEpochSecond(timestampObj.getLong("t_ms"))
- val amount = Amount.fromString(item.getString("amount"))
- data.add(HistoryItem(orderId, amount, summary, timestamp))
- }
- historyListAdapter.setData(data)
- }
-
- private fun onNetworkError() {
- this.isLoading.value = false
- Snackbar.make(view!!, R.string.error_network, LENGTH_SHORT).show()
- }
-
-}
-
-data class HistoryItem(
- val orderId: String,
- val amount: Amount,
- val summary: String,
- val timestamp: Instant
-)
-
-class HistoryItemAdapter(private var items: List<HistoryItem>) :
Adapter<HistoryItemViewHolder>() {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
HistoryItemViewHolder {
- val v =
LayoutInflater.from(parent.context).inflate(R.layout.history_row, parent, false)
- return HistoryItemViewHolder(v)
- }
-
- override fun getItemCount() = items.size
-
- override fun onBindViewHolder(holder: HistoryItemViewHolder, position:
Int) {
- holder.bind(items[position])
- }
-
- fun setData(items: List<HistoryItem>) {
- this.items = items
- this.notifyDataSetChanged()
- }
-
- class HistoryItemViewHolder(v: View) : ViewHolder(v) {
-
- private val summaryTextView: TextView =
v.findViewById(R.id.text_history_summary)
- private val amountTextView: TextView =
v.findViewById(R.id.text_history_amount)
- private val timestampTextView: TextView =
v.findViewById(R.id.text_history_time)
- private val orderIdTextView: TextView =
v.findViewById(R.id.text_history_order_id)
- private val formatter: DateTimeFormatter =
DateTimeFormatter.ofLocalizedDateTime(SHORT)
- .withLocale(Locale.getDefault())
- .withZone(ZoneId.systemDefault())
-
- fun bind(item: HistoryItem) {
- summaryTextView.text = item.summary
- val amount = item.amount
- @SuppressLint("SetTextI18n")
- amountTextView.text = "${amount.amount} ${amount.currency}"
- timestampTextView.text = formatter.format(item.timestamp)
- orderIdTextView.text = item.orderId
- }
- }
-
-}
diff --git a/app/src/main/java/net/taler/merchantpos/Utils.kt
b/app/src/main/java/net/taler/merchantpos/Utils.kt
index 2f6d4f8..a0c30d6 100644
--- a/app/src/main/java/net/taler/merchantpos/Utils.kt
+++ b/app/src/main/java/net/taler/merchantpos/Utils.kt
@@ -16,15 +16,27 @@
package net.taler.merchantpos
+import android.content.Context
+import android.text.format.DateUtils.DAY_IN_MILLIS
+import android.text.format.DateUtils.FORMAT_ABBREV_MONTH
+import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE
+import android.text.format.DateUtils.FORMAT_NO_YEAR
+import android.text.format.DateUtils.FORMAT_SHOW_DATE
+import android.text.format.DateUtils.FORMAT_SHOW_TIME
+import android.text.format.DateUtils.MINUTE_IN_MILLIS
+import android.text.format.DateUtils.formatDateTime
+import android.text.format.DateUtils.getRelativeTimeSpanString
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import androidx.navigation.NavDirections
+import androidx.navigation.fragment.findNavController
import
com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_FADE
import com.google.android.material.snackbar.BaseTransientBottomBar.Duration
import com.google.android.material.snackbar.Snackbar.make
@@ -97,6 +109,16 @@ fun topSnackbar(view: View, @StringRes resId: Int,
@Duration duration: Int) {
fun NavDirections.navigate(nav: NavController) = nav.navigate(this)
+fun Fragment.navigate(directions: NavDirections) =
findNavController().navigate(directions)
+
+fun Long.toRelativeTime(context: Context): CharSequence {
+ val now = System.currentTimeMillis()
+ return if (now - this > DAY_IN_MILLIS * 2) {
+ val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or
FORMAT_ABBREV_MONTH or FORMAT_NO_YEAR
+ formatDateTime(context, this, flags)
+ } else getRelativeTimeSpanString(this, now, MINUTE_IN_MILLIS,
FORMAT_ABBREV_RELATIVE)
+}
+
class CombinedLiveData<T, K, S>(
source1: LiveData<T>,
source2: LiveData<K>,
@@ -125,3 +147,9 @@ class CombinedLiveData<T, K, S>(
throw UnsupportedOperationException()
}
}
+
+/**
+ * Use this with 'when' expressions when you need it to handle all
possibilities/branches.
+ */
+val <T> T.exhaustive: T
+ get() = this
diff --git a/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
b/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
new file mode 100644
index 0000000..594e7cc
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
@@ -0,0 +1,106 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.merchantpos.history
+
+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.Request.Method.POST
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import net.taler.merchantpos.Amount
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.config.MerchantRequest
+import org.json.JSONObject
+
+@JsonInclude(NON_EMPTY)
+class Timestamp(
+ @JsonProperty("t_ms")
+ val ms: Long
+)
+
+data class HistoryItem(
+ @JsonProperty("order_id")
+ val orderId: String,
+ @JsonProperty("amount")
+ val amountStr: String,
+ val summary: String,
+ val timestamp: Timestamp
+) {
+ @get:JsonIgnore
+ val amount: Amount by lazy { Amount.fromString(amountStr) }
+
+ @get:JsonIgnore
+ val time = timestamp.ms
+}
+
+sealed class HistoryResult {
+ object Error : HistoryResult()
+ class Success(val items: List<HistoryItem>) : HistoryResult()
+}
+
+class HistoryManager(
+ private val configManager: ConfigManager,
+ private val queue: RequestQueue,
+ private val mapper: ObjectMapper
+) {
+
+ private val mIsLoading = MutableLiveData(false)
+ val isLoading: LiveData<Boolean> = mIsLoading
+
+ private val mItems = MutableLiveData<HistoryResult>()
+ val items: LiveData<HistoryResult> = mItems
+
+ @UiThread
+ internal fun fetchHistory() {
+ mIsLoading.value = true
+ val merchantConfig = configManager.merchantConfig!!
+ val params = mapOf("instance" to merchantConfig.instance)
+ val req = MerchantRequest(GET, merchantConfig, "history", params, null,
+ Listener { onHistoryResponse(it) },
+ ErrorListener { onHistoryError() })
+ queue.add(req)
+ }
+
+ @UiThread
+ private fun onHistoryResponse(body: JSONObject) {
+ mIsLoading.value = false
+ val items = arrayListOf<HistoryItem>()
+ val historyJson = body.getJSONArray("history")
+ for (i in 0 until historyJson.length()) {
+ val historyItem: HistoryItem =
mapper.readValue(historyJson.getString(i))
+ items.add(historyItem)
+ }
+ mItems.value = HistoryResult.Success(items)
+ }
+
+ @UiThread
+ private fun onHistoryError() {
+ mIsLoading.value = false
+ mItems.value = HistoryResult.Error
+ }
+
+}
diff --git
a/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
b/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
new file mode 100644
index 0000000..0c53f71
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
@@ -0,0 +1,160 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.merchantpos.history
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
+import kotlinx.android.synthetic.main.fragment_merchant_history.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.exhaustive
+import net.taler.merchantpos.history.HistoryItemAdapter.HistoryItemViewHolder
+import
net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings
+import
net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
+import net.taler.merchantpos.navigate
+import net.taler.merchantpos.toRelativeTime
+import java.util.*
+
+private interface RefundClickListener {
+ fun onRefundClicked(item: HistoryItem)
+}
+
+/**
+ * Fragment to display the merchant's payment history, received from the
backend.
+ */
+class MerchantHistoryFragment : Fragment(), RefundClickListener {
+
+ companion object {
+ const val TAG = "taler-merchant"
+ }
+
+ private val model: MainViewModel by activityViewModels()
+ private val historyManager by lazy { model.historyManager }
+ private val refundManager by lazy { model.refundManager }
+
+ private val historyListAdapter = HistoryItemAdapter(this)
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_merchant_history, container,
false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ list_history.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ addItemDecoration(DividerItemDecoration(context, VERTICAL))
+ adapter = historyListAdapter
+ }
+
+ swipeRefresh.setOnRefreshListener {
+ Log.v(TAG, "refreshing!")
+ historyManager.fetchHistory()
+ }
+ historyManager.isLoading.observe(viewLifecycleOwner, Observer {
loading ->
+ Log.v(TAG, "setting refreshing to $loading")
+ swipeRefresh.isRefreshing = loading
+ })
+ historyManager.items.observe(viewLifecycleOwner, Observer { result ->
+ when (result) {
+ is HistoryResult.Error -> onError()
+ is HistoryResult.Success ->
historyListAdapter.setData(result.items)
+ }.exhaustive
+ })
+ }
+
+ override fun onStart() {
+ super.onStart()
+ if (model.configManager.merchantConfig?.instance == null) {
+ navigate(actionGlobalMerchantSettings())
+ } else {
+ historyManager.fetchHistory()
+ }
+ }
+
+ private fun onError() {
+ Snackbar.make(view!!, R.string.error_network, LENGTH_SHORT).show()
+ }
+
+ override fun onRefundClicked(item: HistoryItem) {
+ refundManager.startRefund(item)
+ navigate(actionNavHistoryToRefundFragment())
+ }
+
+}
+
+private class HistoryItemAdapter(private val listener: RefundClickListener) :
+ Adapter<HistoryItemViewHolder>() {
+
+ private val items = ArrayList<HistoryItem>()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
HistoryItemViewHolder {
+ val v =
+
LayoutInflater.from(parent.context).inflate(R.layout.list_item_history, parent,
false)
+ return HistoryItemViewHolder(v)
+ }
+
+ override fun getItemCount() = items.size
+
+ override fun onBindViewHolder(holder: HistoryItemViewHolder, position:
Int) {
+ holder.bind(items[position])
+ }
+
+ fun setData(items: List<HistoryItem>) {
+ this.items.clear()
+ this.items.addAll(items)
+ this.notifyDataSetChanged()
+ }
+
+ private inner class HistoryItemViewHolder(private val v: View) :
ViewHolder(v) {
+
+ private val orderSummaryView: TextView =
v.findViewById(R.id.orderSummaryView)
+ private val orderAmountView: TextView =
v.findViewById(R.id.orderAmountView)
+ private val orderTimeView: TextView =
v.findViewById(R.id.orderTimeView)
+ private val orderIdView: TextView = v.findViewById(R.id.orderIdView)
+ private val refundButton: ImageButton =
v.findViewById(R.id.refundButton)
+
+ fun bind(item: HistoryItem) {
+ orderSummaryView.text = item.summary
+ val amount = item.amount
+ @SuppressLint("SetTextI18n")
+ orderAmountView.text = "${amount.amount} ${amount.currency}"
+ orderIdView.text = v.context.getString(R.string.history_ref_no,
item.orderId)
+ orderTimeView.text = item.time.toRelativeTime(v.context)
+ refundButton.setOnClickListener { listener.onRefundClicked(item) }
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
b/app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
new file mode 100644
index 0000000..1797cea
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
@@ -0,0 +1,99 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.merchantpos.history
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.android.synthetic.main.fragment_refund.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.R
+import net.taler.merchantpos.fadeIn
+import net.taler.merchantpos.fadeOut
+import
net.taler.merchantpos.history.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
+import net.taler.merchantpos.history.RefundResult.Error
+import net.taler.merchantpos.history.RefundResult.PastDeadline
+import net.taler.merchantpos.history.RefundResult.Success
+import net.taler.merchantpos.navigate
+
+class RefundFragment : Fragment() {
+
+ private val model: MainViewModel by activityViewModels()
+ private val refundManager by lazy { model.refundManager }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_refund, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ val item = refundManager.toBeRefunded ?: throw IllegalStateException()
+ amountInputView.setText(item.amount.amount)
+ currencyView.text = item.amount.currency
+ abortButton.setOnClickListener { findNavController().navigateUp() }
+ refundButton.setOnClickListener { onRefundButtonClicked(item) }
+
+ refundManager.refundResult.observe(viewLifecycleOwner, Observer {
result ->
+ onRefundResultChanged(result)
+ })
+ }
+
+ private fun onRefundButtonClicked(item: HistoryItem) {
+ val inputAmount = amountInputView.text.toString().toDouble()
+ if (inputAmount > item.amount.amount.toDouble()) {
+ amountView.error = getString(R.string.refund_error_max_amount,
item.amount.amount)
+ return
+ }
+ if (inputAmount <= 0.0) {
+ amountView.error = getString(R.string.refund_error_zero)
+ return
+ }
+ amountView.error = null
+ refundButton.fadeOut()
+ progressBar.fadeIn()
+ refundManager.refund(item, inputAmount,
reasonInputView.text.toString())
+ }
+
+ private fun onRefundResultChanged(result: RefundResult?): Any = when
(result) {
+ Error -> onError(R.string.refund_error_backend)
+ PastDeadline -> onError(R.string.refund_error_deadline)
+ is Success -> {
+ progressBar.fadeOut()
+ refundButton.fadeIn()
+ navigate(actionRefundFragmentToRefundUriFragment())
+ }
+ null -> { // no-op
+ }
+ }
+
+ private fun onError(@StringRes res: Int) {
+ Snackbar.make(view!!, res, LENGTH_LONG).show()
+ progressBar.fadeOut()
+ refundButton.fadeIn()
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/history/RefundManager.kt
b/app/src/main/java/net/taler/merchantpos/history/RefundManager.kt
new file mode 100644
index 0000000..270b3b8
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/RefundManager.kt
@@ -0,0 +1,111 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.merchantpos.history
+
+import android.util.Log
+import androidx.annotation.UiThread
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.volley.Request.Method.POST
+import com.android.volley.RequestQueue
+import com.android.volley.Response.ErrorListener
+import com.android.volley.Response.Listener
+import net.taler.merchantpos.config.ConfigManager
+import net.taler.merchantpos.config.MerchantRequest
+import org.json.JSONObject
+
+sealed class RefundResult {
+ object Error : RefundResult()
+ object PastDeadline : RefundResult()
+ class Success(
+ val refundUri: String,
+ val item: HistoryItem,
+ val amount: Double,
+ val reason: String
+ ) : RefundResult()
+}
+
+class RefundManager(
+ private val configManager: ConfigManager,
+ private val queue: RequestQueue
+) {
+
+ var toBeRefunded: HistoryItem? = null
+ private set
+
+ private val mRefundResult = MutableLiveData<RefundResult>()
+ internal val refundResult: LiveData<RefundResult> = mRefundResult
+
+ @UiThread
+ internal fun startRefund(item: HistoryItem) {
+ toBeRefunded = item
+ mRefundResult.value = null
+ }
+
+ @UiThread
+ internal fun refund(item: HistoryItem, amount: Double, reason: String) {
+ val merchantConfig = configManager.merchantConfig!!
+ val refundRequest = mapOf(
+ "order_id" to item.orderId,
+ "refund" to "${item.amount.currency}:$amount",
+ "reason" to reason
+ )
+ val body = JSONObject(refundRequest)
+ val req = MerchantRequest(POST, merchantConfig, "refund", null, body,
+ Listener { onRefundResponse(it, item, amount, reason) },
+ ErrorListener { onRefundError() }
+ )
+ queue.add(req)
+ }
+
+ @UiThread
+ private fun onRefundResponse(
+ json: JSONObject,
+ item: HistoryItem,
+ amount: Double,
+ reason: String
+ ) {
+ if (!json.has("contract_terms")) {
+ Log.e("TEST", "json: $json")
+ onRefundError()
+ return
+ }
+
+ val contractTerms = json.getJSONObject("contract_terms")
+ val refundDeadline = if (contractTerms.has("refund_deadline")) {
+ contractTerms.getJSONObject("refund_deadline").getLong("t_ms")
+ } else null
+ val autoRefund = contractTerms.has("auto_refund")
+ val refundUri = json.getString("taler_refund_uri")
+
+ Log.e("TEST", "refundDeadline: $refundDeadline")
+ if (refundDeadline != null) Log.e(
+ "TEST",
+ "refundDeadline passed: ${System.currentTimeMillis() >
refundDeadline}"
+ )
+ Log.e("TEST", "autoRefund: $autoRefund")
+ Log.e("TEST", "refundUri: $refundUri")
+
+ mRefundResult.value = RefundResult.Success(refundUri, item, amount,
reason)
+ }
+
+ @UiThread
+ private fun onRefundError() {
+ mRefundResult.value = RefundResult.Error
+ }
+
+}
diff --git
a/app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
b/app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
new file mode 100644
index 0000000..f2bd569
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
@@ -0,0 +1,65 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.merchantpos.history
+
+import android.annotation.SuppressLint
+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.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_refund_uri.*
+import net.taler.merchantpos.MainViewModel
+import net.taler.merchantpos.NfcManager.Companion.hasNfc
+import net.taler.merchantpos.QrCodeManager.makeQrCode
+import net.taler.merchantpos.R
+
+class RefundUriFragment : Fragment() {
+
+ private val model: MainViewModel by activityViewModels()
+ private val refundManager by lazy { model.refundManager }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_refund_uri, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val result = refundManager.refundResult.value
+ if (result !is RefundResult.Success) throw IllegalStateException()
+
+ refundQrcodeView.setImageBitmap(makeQrCode(result.refundUri))
+
+ val introRes =
+ if (hasNfc(requireContext())) R.string.refund_intro_nfc else
R.string.refund_intro
+ refundIntroView.setText(introRes)
+
+ @SuppressLint("SetTextI18n")
+ refundAmountView.text = "${result.amount}
${result.item.amount.currency}"
+
+ refundRefView.text =
+ getString(R.string.refund_order_ref, result.item.orderId,
result.reason)
+
+ cancelRefundButton.setOnClickListener {
findNavController().navigateUp() }
+ }
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/order/Definitions.kt
b/app/src/main/java/net/taler/merchantpos/order/Definitions.kt
index b22413c..63eda17 100644
--- a/app/src/main/java/net/taler/merchantpos/order/Definitions.kt
+++ b/app/src/main/java/net/taler/merchantpos/order/Definitions.kt
@@ -120,9 +120,12 @@ data class Order(val id: Int, val availableCategories:
Map<Int, Category>) {
val products = ArrayList<ConfigProduct>()
val title: String = id.toString()
val summary: String
- get() = getCategoryQuantities().map { (category: Category, quantity:
Int) ->
- "$quantity x ${category.localizedName}"
- }.joinToString()
+ get() {
+ if (products.size == 1) return products[0].description
+ return getCategoryQuantities().map { (category: Category,
quantity: Int) ->
+ "$quantity x ${category.localizedName}"
+ }.joinToString()
+ }
val total: Double
get() {
var total = 0.0
@@ -175,6 +178,7 @@ data class Order(val id: Int, val availableCategories:
Map<Int, Category>) {
*/
val summaryI18n: Map<String, String>?
get() {
+ if (products.size == 1) return products[0].descriptionI18n
val categoryQuantities = getCategoryQuantities()
// get all available locales
val availableLocales = categoryQuantities.mapNotNull { (category,
_) ->
diff --git a/app/src/main/res/drawable/ic_cash_refund.xml
b/app/src/main/res/drawable/ic_cash_refund.xml
new file mode 100644
index 0000000..7359ca3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cash_refund.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M3,11H21V23H3V11M12,15A2,2 0 0,1 14,17A2,2 0 0,1
12,19A2,2 0 0,1 10,17A2,2 0 0,1 12,15M7,13A2,2 0 0,1 5,15V19A2,2 0 0,1
7,21H17A2,2 0 0,1 19,19V15A2,2 0 0,1
17,13H7M17,5V10H15.5V6.5H9.88L12.3,8.93L11.24,10L7,5.75L11.24,1.5L12.3,2.57L9.88,5H17Z"
/>
+</vector>
diff --git a/app/src/main/res/layout/fragment_refund.xml
b/app/src/main/res/layout/fragment_refund.xml
new file mode 100644
index 0000000..5a78cdd
--- /dev/null
+++ b/app/src/main/res/layout/fragment_refund.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ This file is part of GNU Taler
+ ~ (C) 2020 Taler Systems S.A.
+ ~
+ ~ GNU Taler is free software; you can redistribute it and/or modify it under
the
+ ~ terms of the GNU General Public License as published by the Free Software
+ ~ Foundation; either version 3, or (at your option) any later version.
+ ~
+ ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT
ANY
+ ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License along
with
+ ~ GNU Taler; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>
+ -->
+
+<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"
+ tools:context=".history.RefundFragment">
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/amountView"
+
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:hint="@string/refund_amount"
+ app:boxBackgroundMode="outline"
+ app:endIconMode="clear_text"
+ app:endIconTint="?attr/colorControlNormal"
+ app:layout_constraintBottom_toTopOf="@+id/reasonView"
+ app:layout_constraintEnd_toStartOf="@+id/currencyView"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="spread">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/amountInputView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="6"
+ android:inputType="numberDecimal"
+ tools:text="23.42" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <TextView
+ android:id="@+id/currencyView"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginStart="8dp"
+ android:gravity="start|center_vertical"
+ app:layout_constraintBottom_toBottomOf="@+id/amountView"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/amountView"
+ app:layout_constraintTop_toTopOf="@+id/amountView"
+ tools:text="TESTKUDOS" />
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/reasonView"
+
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:hint="@string/refund_reason"
+ app:endIconMode="clear_text"
+ app:layout_constraintBottom_toTopOf="@+id/abortButton"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/amountView">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/reasonInputView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+
android:inputType="textAutoComplete|textAutoCorrect|textMultiLine" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <Button
+ android:id="@+id/abortButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:backgroundTint="@color/red"
+ android:text="@string/refund_abort"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/refundButton"
+ app:layout_constraintHorizontal_bias="0.76"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <Button
+ android:id="@+id/refundButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:backgroundTint="@color/green"
+ android:text="@string/refund_confirm"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toEndOf="@+id/abortButton" />
+
+ <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/refundButton"
+ app:layout_constraintEnd_toEndOf="@+id/refundButton"
+ app:layout_constraintStart_toStartOf="@+id/refundButton"
+ app:layout_constraintTop_toTopOf="@+id/refundButton"
+ tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_process_payment.xml
b/app/src/main/res/layout/fragment_refund_uri.xml
similarity index 68%
copy from app/src/main/res/layout/fragment_process_payment.xml
copy to app/src/main/res/layout/fragment_refund_uri.xml
index 6cd8ea1..8447d28 100644
--- a/app/src/main/res/layout/fragment_process_payment.xml
+++ b/app/src/main/res/layout/fragment_refund_uri.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of GNU Taler
~ (C) 2020 Taler Systems S.A.
~
@@ -23,28 +22,16 @@
tools:context=".payment.ProcessPaymentFragment">
<ImageView
- android:id="@+id/qrcodeView"
+ android:id="@+id/refundQrcodeView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="32dp"
- android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
- tools:src="@tools:sample/avatars"
- tools:visibility="visible" />
-
- <ProgressBar
- android:id="@+id/progressBar"
- style="?android:attr/progressBarStyleLarge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintBottom_toBottomOf="@+id/qrcodeView"
- app:layout_constraintEnd_toEndOf="@+id/qrcodeView"
- app:layout_constraintStart_toStartOf="@+id/qrcodeView"
- app:layout_constraintTop_toTopOf="@+id/qrcodeView" />
+ tools:src="@tools:sample/avatars" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
@@ -54,54 +41,50 @@
app:layout_constraintGuide_percent="0.54" />
<TextView
- android:id="@+id/payIntroView"
+ android:id="@+id/refundIntroView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
- android:text="@string/payment_intro_nfc"
+ android:text="@string/refund_intro_nfc"
android:textAlignment="center"
android:textSize="24sp"
- android:visibility="invisible"
- app:layout_constraintBottom_toTopOf="@+id/amountView"
+ app:layout_constraintBottom_toTopOf="@+id/refundAmountView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_chainStyle="spread"
- tools:visibility="visible" />
+ app:layout_constraintVertical_chainStyle="spread" />
<TextView
- android:id="@+id/amountView"
+ android:id="@+id/refundAmountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
- app:layout_constraintBottom_toTopOf="@+id/orderRefView"
+ app:layout_constraintBottom_toTopOf="@+id/refundRefView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
- app:layout_constraintTop_toBottomOf="@+id/payIntroView"
+ app:layout_constraintTop_toBottomOf="@+id/refundIntroView"
tools:text="10.49 TESTKUDOS" />
<TextView
- android:id="@+id/orderRefView"
+ android:id="@+id/refundRefView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textAlignment="center"
- android:visibility="invisible"
- app:layout_constraintBottom_toTopOf="@id/cancelPaymentButton"
+ app:layout_constraintBottom_toTopOf="@id/cancelRefundButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
- app:layout_constraintTop_toBottomOf="@+id/amountView"
- tools:text="@string/payment_order_ref"
- tools:visibility="visible" />
+ app:layout_constraintTop_toBottomOf="@+id/refundAmountView"
+ tools:text="@string/refund_order_ref" />
<Button
- android:id="@+id/cancelPaymentButton"
+ android:id="@+id/cancelRefundButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:backgroundTint="@color/red"
- android:text="@string/payment_cancel"
+ android:text="@string/refund_abort"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
diff --git a/app/src/main/res/layout/history_row.xml
b/app/src/main/res/layout/history_row.xml
deleted file mode 100644
index e68e5a0..0000000
--- a/app/src/main/res/layout/history_row.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ This file is part of GNU Taler
- ~ (C) 2020 Taler Systems S.A.
- ~
- ~ GNU Taler is free software; you can redistribute it and/or modify it under
the
- ~ terms of the GNU General Public License as published by the Free Software
- ~ Foundation; either version 3, or (at your option) any later version.
- ~
- ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT
ANY
- ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
- ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- ~
- ~ You should have received a copy of the GNU General Public License along
with
- ~ GNU Taler; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginTop="8dp"
- android:layout_marginEnd="16dp"
- android:layout_marginBottom="8dp"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/text_history_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="24sp"
- android:textStyle="bold"
- tools:text="One Cappuccino" />
-
- <TextView
- android:id="@+id/text_history_amount"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="20sp"
- android:textStyle="bold"
- tools:text="1 Euro" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/history_received_at"
- android:textAllCaps="false"
- android:textSize="20sp"
- android:textStyle="italic" />
-
-
- <TextView
- android:id="@+id/text_history_time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="20sp"
- tools:text="2019-08-31 14:25" />
-
- </LinearLayout>
-
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/history_ref_no"
- android:textAllCaps="false"
- android:textSize="20sp"
- android:textStyle="italic" />
-
- <TextView
- android:id="@+id/text_history_order_id"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAllCaps="false"
- android:textSize="20sp"
- android:textStyle="italic"
- tools:text="2019.242-014B6QPF1M1HC5TY" />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/list_item_history.xml
b/app/src/main/res/layout/list_item_history.xml
new file mode 100644
index 0000000..fe485ba
--- /dev/null
+++ b/app/src/main/res/layout/list_item_history.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ This file is part of GNU Taler
+ ~ (C) 2020 Taler Systems S.A.
+ ~
+ ~ GNU Taler is free software; you can redistribute it and/or modify it under
the
+ ~ terms of the GNU General Public License as published by the Free Software
+ ~ Foundation; either version 3, or (at your option) any later version.
+ ~
+ ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT
ANY
+ ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License along
with
+ ~ GNU Taler; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>
+ -->
+
+<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:padding="16dp">
+
+ <TextView
+ android:id="@+id/orderSummaryView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ app:layout_constraintEnd_toStartOf="@+id/orderAmountView"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="One Cappuccino or another name that can be so long
that it spans more than one line" />
+
+ <TextView
+ android:id="@+id/orderAmountView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="16dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ app:layout_constraintBottom_toBottomOf="@+id/orderSummaryView"
+ app:layout_constraintEnd_toStartOf="@+id/refundButton"
+ app:layout_constraintStart_toEndOf="@+id/orderSummaryView"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.0"
+ tools:text="23.42 TESTKUDOS" />
+
+ <TextView
+ android:id="@+id/orderIdView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="@string/history_ref_no"
+ android:textAllCaps="false"
+ android:textSize="20sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/orderTimeView"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/orderSummaryView" />
+
+ <TextView
+ android:id="@+id/orderTimeView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="16dp"
+ android:textSize="20sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/refundButton"
+ app:layout_constraintStart_toEndOf="@+id/orderIdView"
+ app:layout_constraintTop_toBottomOf="@+id/orderAmountView"
+ app:layout_constraintVertical_bias="1.0"
+ tools:text="3 hrs. ago" />
+
+ <ImageButton
+ android:id="@+id/refundButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:backgroundTint="?colorPrimary"
+ android:contentDescription="@string/history_refund"
+ android:tint="?attr/colorOnPrimary"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:srcCompat="@drawable/ic_cash_refund" />
+
+</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 f00d21c..2e337f2 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of GNU Taler
~ (C) 2020 Taler Systems S.A.
~
@@ -55,9 +54,29 @@
<fragment
android:id="@+id/nav_history"
- android:name="net.taler.merchantpos.MerchantHistory"
+
android:name="net.taler.merchantpos.history.MerchantHistoryFragment"
android:label="@string/history_label"
- tools:layout="@layout/fragment_merchant_history" />
+ tools:layout="@layout/fragment_merchant_history">
+ <action
+ android:id="@+id/action_nav_history_to_refundFragment"
+ app:destination="@id/refundFragment" />
+ </fragment>
+
+ <fragment
+ android:id="@+id/refundFragment"
+ android:name="net.taler.merchantpos.history.RefundFragment"
+ android:label="@string/history_refund"
+ tools:layout="@layout/fragment_refund">
+ <action
+ android:id="@+id/action_refundFragment_to_refundUriFragment"
+ app:destination="@id/refundUriFragment" />
+ </fragment>
+
+ <fragment
+ android:id="@+id/refundUriFragment"
+ android:name="net.taler.merchantpos.history.RefundUriFragment"
+ android:label="@string/history_refund"
+ tools:layout="@layout/fragment_refund_uri" />
<fragment
android:id="@+id/nav_settings"
diff --git a/app/src/main/res/values/strings.xml
b/app/src/main/res/values/strings.xml
index 740a080..77c7e03 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -47,7 +47,19 @@
<string name="history_label">Payment History</string>
<string name="history_received_at">Received at</string>
- <string name="history_ref_no">Ref. No:</string>
+ <string name="history_ref_no">Ref. No: %s</string>
+ <string name="history_refund">Refund Order</string>
+ <string name="refund_amount">Amount</string>
+ <string name="refund_reason">Refund reason</string>
+ <string name="refund_abort">Abort</string>
+ <string name="refund_confirm">Give Refund</string>
+ <string name="refund_error_max_amount">Greater than order amount of
%s</string>
+ <string name="refund_error_zero">Needs to be positive amount</string>
+ <string name="refund_error_backend">Error processing refund</string>
+ <string name="refund_error_deadline">Refund deadline has passed</string>
+ <string name="refund_intro_nfc">Please scan QR Code or use NFC to give
refund</string>
+ <string name="refund_intro">Please scan QR Code to give refund</string>
+ <string name="refund_order_ref">Order Reference: %1$s\n\n%2$s</string>
<string name="error_network">Network Error</string>
--
To stop receiving notification emails like this one, please contact
address@hidden.
- [taler-merchant-terminal-android] branch master updated (1764700 -> 7eebd07),
gnunet <=