diff options
author | Torsten Grote <t@grobox.de> | 2020-03-16 15:53:58 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-03-17 11:32:46 -0300 |
commit | 7eebd0754d16eccadeb3d1cb53c1cebffda65d07 (patch) | |
tree | c402456c20863daddec352421d8ad38f10a7709f | |
parent | bca790d381880b3077df0e37cf29b7a2f9e2266c (diff) | |
download | merchant-terminal-android-master.tar.gz merchant-terminal-android-master.tar.bz2 merchant-terminal-android-master.zip |
(still hidden in UI because API incomplete/broken)
13 files changed, 580 insertions, 15 deletions
diff --git a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt index 560ca59..3fe472d 100644 --- a/app/src/main/java/net/taler/merchantpos/MainViewModel.kt +++ b/app/src/main/java/net/taler/merchantpos/MainViewModel.kt @@ -25,6 +25,7 @@ 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 @@ -41,6 +42,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) { } 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/Utils.kt b/app/src/main/java/net/taler/merchantpos/Utils.kt index 507d142..a0c30d6 100644 --- a/app/src/main/java/net/taler/merchantpos/Utils.kt +++ b/app/src/main/java/net/taler/merchantpos/Utils.kt @@ -30,11 +30,13 @@ 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 @@ -107,6 +109,8 @@ 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) { diff --git a/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt b/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt index 1459876..594e7cc 100644 --- a/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt +++ b/app/src/main/java/net/taler/merchantpos/history/HistoryManager.kt @@ -16,10 +16,12 @@ 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 @@ -79,7 +81,7 @@ class HistoryManager( val params = mapOf("instance" to merchantConfig.instance) val req = MerchantRequest(GET, merchantConfig, "history", params, null, Listener { onHistoryResponse(it) }, - ErrorListener { onNetworkError() }) + ErrorListener { onHistoryError() }) queue.add(req) } @@ -96,7 +98,7 @@ class HistoryManager( } @UiThread - private fun onNetworkError() { + 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 index 5299f28..0c53f71 100644 --- a/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt +++ b/app/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt @@ -22,11 +22,11 @@ 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.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL import androidx.recyclerview.widget.LinearLayoutManager @@ -40,14 +40,19 @@ 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() { +class MerchantHistoryFragment : Fragment(), RefundClickListener { companion object { const val TAG = "taler-merchant" @@ -55,8 +60,9 @@ class MerchantHistoryFragment : Fragment() { private val model: MainViewModel by activityViewModels() private val historyManager by lazy { model.historyManager } + private val refundManager by lazy { model.refundManager } - private val historyListAdapter = HistoryItemAdapter() + private val historyListAdapter = HistoryItemAdapter(this) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -91,7 +97,7 @@ class MerchantHistoryFragment : Fragment() { override fun onStart() { super.onStart() if (model.configManager.merchantConfig?.instance == null) { - actionGlobalMerchantSettings().navigate(findNavController()) + navigate(actionGlobalMerchantSettings()) } else { historyManager.fetchHistory() } @@ -101,9 +107,15 @@ class MerchantHistoryFragment : Fragment() { Snackbar.make(view!!, R.string.error_network, LENGTH_SHORT).show() } + override fun onRefundClicked(item: HistoryItem) { + refundManager.startRefund(item) + navigate(actionNavHistoryToRefundFragment()) + } + } -internal class HistoryItemAdapter : Adapter<HistoryItemViewHolder>() { +private class HistoryItemAdapter(private val listener: RefundClickListener) : + Adapter<HistoryItemViewHolder>() { private val items = ArrayList<HistoryItem>() @@ -125,12 +137,13 @@ internal class HistoryItemAdapter : Adapter<HistoryItemViewHolder>() { this.notifyDataSetChanged() } - internal class HistoryItemViewHolder(private val v: View) : ViewHolder(v) { + 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 @@ -139,6 +152,7 @@ internal class HistoryItemAdapter : Adapter<HistoryItemViewHolder>() { 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/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_refund_uri.xml b/app/src/main/res/layout/fragment_refund_uri.xml new file mode 100644 index 0000000..8447d28 --- /dev/null +++ b/app/src/main/res/layout/fragment_refund_uri.xml @@ -0,0 +1,93 @@ +<?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=".payment.ProcessPaymentFragment"> + + <ImageView + android:id="@+id/refundQrcodeView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="32dp" + 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" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.54" /> + + <TextView + android:id="@+id/refundIntroView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="@string/refund_intro_nfc" + android:textAlignment="center" + android:textSize="24sp" + 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" /> + + <TextView + 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/refundRefView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toBottomOf="@+id/refundIntroView" + tools:text="10.49 TESTKUDOS" /> + + <TextView + android:id="@+id/refundRefView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:textAlignment="center" + app:layout_constraintBottom_toTopOf="@id/cancelRefundButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/guideline" + app:layout_constraintTop_toBottomOf="@+id/refundAmountView" + tools:text="@string/refund_order_ref" /> + + <Button + 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/refund_abort" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="@+id/guideline" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/list_item_history.xml b/app/src/main/res/layout/list_item_history.xml index 06ffff2..fe485ba 100644 --- a/app/src/main/res/layout/list_item_history.xml +++ b/app/src/main/res/layout/list_item_history.xml @@ -40,12 +40,12 @@ 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_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintEnd_toStartOf="@+id/refundButton" app:layout_constraintStart_toEndOf="@+id/orderSummaryView" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.0" @@ -72,13 +72,26 @@ 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_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" + 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 0d7d870..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. ~ @@ -57,7 +56,27 @@ android:id="@+id/nav_history" 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 ac84c5d..77c7e03 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,18 @@ <string name="history_label">Payment History</string> <string name="history_received_at">Received at</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> |