diff options
author | Sebastian <sebasjm@gmail.com> | 2022-07-01 18:05:51 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-07-01 18:06:32 -0300 |
commit | c3c43726de9858f42fee9d4051ab3d3245b47099 (patch) | |
tree | 148c263dccacd278f063552fa8ffd4a990c84fad /wallet/src | |
parent | e05bfb78c4dafee6980f5b86196bca4ff302208c (diff) | |
download | taler-android-c3c43726de9858f42fee9d4051ab3d3245b47099.tar.gz taler-android-c3c43726de9858f42fee9d4051ab3d3245b47099.tar.bz2 taler-android-c3c43726de9858f42fee9d4051ab3d3245b47099.zip |
accept tips
Diffstat (limited to 'wallet/src')
12 files changed, 840 insertions, 4 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt index 3b8be4f..40da9a2 100644 --- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt +++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt @@ -166,6 +166,11 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, nav.navigate(R.id.action_nav_main_to_promptPayment) model.paymentManager.preparePay(url) } + action.startsWith("tip/") -> { + Log.v(TAG, "navigating!") + nav.navigate(R.id.action_nav_main_to_promptTip) + model.tipManager.prepareTip(url) + } action.startsWith("withdraw/") -> { Log.v(TAG, "navigating!") // there's more than one entry point, so use global action diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt index 7bb6ad9..5041037 100644 --- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt +++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -36,6 +36,7 @@ import net.taler.wallet.exchanges.ExchangeManager import net.taler.wallet.payment.PaymentManager import net.taler.wallet.pending.PendingOperationsManager import net.taler.wallet.refund.RefundManager +import net.taler.wallet.tip.TipManager import net.taler.wallet.transactions.TransactionManager import net.taler.wallet.withdraw.WithdrawManager import org.json.JSONObject @@ -86,6 +87,7 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) { } val withdrawManager = WithdrawManager(api, viewModelScope) + val tipManager = TipManager(api, viewModelScope) val paymentManager = PaymentManager(api, viewModelScope) val pendingOperationsManager: PendingOperationsManager = PendingOperationsManager(api) val transactionManager: TransactionManager = TransactionManager(api, viewModelScope) diff --git a/wallet/src/main/java/net/taler/wallet/tip/AlreadyAcceptedFragment.kt b/wallet/src/main/java/net/taler/wallet/tip/AlreadyAcceptedFragment.kt new file mode 100644 index 0000000..d76b6a1 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/tip/AlreadyAcceptedFragment.kt @@ -0,0 +1,49 @@ +/* + * 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.wallet.tip + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import net.taler.wallet.databinding.FragmentAlreadyAcceptedBinding + +/** + * Display the message that the user already paid for the order + * that the merchant is proposing. + */ +class AlreadyAcceptedFragment : Fragment() { + + private lateinit var ui: FragmentAlreadyAcceptedBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + ui = FragmentAlreadyAcceptedBinding.inflate(inflater, container, false) + return ui.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + ui.backButton.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/tip/PromptTipFragment.kt b/wallet/src/main/java/net/taler/wallet/tip/PromptTipFragment.kt new file mode 100644 index 0000000..a5c504c --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/tip/PromptTipFragment.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.wallet.tip + +import android.graphics.Bitmap +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.snackbar.Snackbar.LENGTH_LONG +import net.taler.common.Amount +import net.taler.common.ContractTerms +import net.taler.common.fadeIn +import net.taler.common.fadeOut +import net.taler.wallet.MainViewModel +import net.taler.wallet.R +import net.taler.wallet.cleanExchange +import net.taler.wallet.databinding.FragmentPromptPaymentBinding +import net.taler.wallet.databinding.FragmentPromptTipBinding +import net.taler.wallet.withdraw.ExchangeSelection + +/** + * Show a tip and ask the user to accept/decline. + */ +class PromptTipFragment : Fragment() { + + private val model: MainViewModel by activityViewModels() + private val tipManager by lazy { model.tipManager } + + private lateinit var ui: FragmentPromptTipBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + ui = FragmentPromptTipBinding.inflate(inflater, container, false) + ui.introView.fadeIn() + return ui.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + tipManager.tipStatus.observe(viewLifecycleOwner, ::onPaymentStatusChanged) + + } + + override fun onDestroy() { + super.onDestroy() + if (!requireActivity().isChangingConfigurations) { + // tipManager.abortTip() + } + } + + private fun showLoading(show: Boolean) { + model.showProgressBar.value = show + if (show) { + ui.progressBar.fadeIn() + } else { + ui.progressBar.fadeOut() + } + } + + private fun onPaymentStatusChanged(payStatus: TipStatus?) { + when (payStatus) { + is TipStatus.Prepared -> { + showLoading(false) + showContent(payStatus.tipAmountRaw, payStatus.tipAmountEffective, payStatus.exchangeBaseUrl, payStatus.merchantBaseUrl) + //showOrder(payStatus.contractTerms, payStatus.amountRaw, fees) + ui.confirmWithdrawButton.isEnabled = true + ui.confirmWithdrawButton.setOnClickListener { + model.showProgressBar.value = true + tipManager.confirmTip( + payStatus.walletTipId, + payStatus.tipAmountRaw.currency + ) + ui.confirmWithdrawButton.fadeOut() + ui.progressBar.fadeIn() + } + } + is TipStatus.AlreadyAccepted -> { + showLoading(false) + tipManager.resetTipStatus() + findNavController().navigate(R.id.action_promptTip_to_alreadyAccepted) + } + is TipStatus.Success -> { + showLoading(false) + tipManager.resetTipStatus() + findNavController().navigate(R.id.action_promptTip_to_nav_main) + model.showTransactions(payStatus.currency) + Snackbar.make(requireView(), R.string.tip_received, LENGTH_LONG).show() + } + is TipStatus.Error -> { + showLoading(false) + ui.introView.text = getString(R.string.payment_error, payStatus.error) + ui.introView.fadeIn() + } + is TipStatus.None -> { + // No payment active. + showLoading(false) + } + is TipStatus.Loading -> { + // Wait until loaded ... + showLoading(true) + } + } + } + + private fun showContent( + amountRaw: Amount, + amountEffective: Amount, + exchange: String, + merchant: String, + ) { + model.showProgressBar.value = false + ui.progressBar.fadeOut() + + ui.introView.fadeIn() + ui.effectiveAmountView.text = amountEffective.toString() + ui.effectiveAmountView.fadeIn() + + ui.chosenAmountLabel.fadeIn() + ui.chosenAmountView.text = amountRaw.toString() + ui.chosenAmountView.fadeIn() + + ui.feeLabel.fadeIn() + ui.feeView.text = + getString(R.string.amount_negative, (amountRaw - amountEffective).toString()) + ui.feeView.fadeIn() + + ui.exchangeIntroView.fadeIn() + ui.withdrawExchangeUrl.text = cleanExchange(exchange) + ui.withdrawExchangeUrl.fadeIn() + + ui.merchantIntroView.fadeIn() + ui.withdrawMerchantUrl.text = cleanExchange(merchant) + ui.withdrawMerchantUrl.fadeIn() + + ui.withdrawCard.fadeIn() + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/tip/TipManager.kt b/wallet/src/main/java/net/taler/wallet/tip/TipManager.kt new file mode 100644 index 0000000..855feb0 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/tip/TipManager.kt @@ -0,0 +1,124 @@ +/* + * 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.wallet.tip + +import android.util.Log +import androidx.annotation.UiThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.taler.common.Amount +import net.taler.common.Timestamp +import net.taler.wallet.TAG +import net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.backend.TalerErrorInfo +import net.taler.wallet.tip.PrepareTipResponse.TipPossibleResponse +import net.taler.wallet.tip.PrepareTipResponse.AlreadyAcceptedResponse + +sealed class TipStatus { + object None : TipStatus() + object Loading : TipStatus() + data class Prepared( + val walletTipId: String, + val merchantBaseUrl: String, + val exchangeBaseUrl: String, + val expirationTimestamp: Timestamp, + val tipAmountRaw: Amount, + val tipAmountEffective: Amount, + ) : TipStatus() + + data class AlreadyAccepted( + val walletTipId: String, + ) : TipStatus() + + // TODO bring user to fulfilment URI + data class Error(val error: String) : TipStatus() + data class Success(val currency: String) : TipStatus() +} + +class TipManager( + private val api: WalletBackendApi, + private val scope: CoroutineScope, +) { + + private val mTipStatus = MutableLiveData<TipStatus>(TipStatus.None) + internal val tipStatus: LiveData<TipStatus> = mTipStatus + + @UiThread + fun prepareTip(url: String) = scope.launch { + mTipStatus.value = TipStatus.Loading + api.request("prepareTip", PrepareTipResponse.serializer()) { + put("talerTipUri", url) + }.onError { + handleError("prepareTip", it) + }.onSuccess { response -> + mTipStatus.value = when (response) { + is TipPossibleResponse -> response.toTipStatusPrepared() + is AlreadyAcceptedResponse -> TipStatus.AlreadyAccepted( + response.walletTipId + ) + } + } + } + + fun confirmTip(tipId: String, currency: String) = scope.launch { + api.request("acceptTip", ConfirmTipResult.serializer()) { + put("walletTipId", tipId) + }.onError { + handleError("acceptTip", it) + }.onSuccess { + mTipStatus.postValue(TipStatus.Success(currency)) + } + } + +/* + @UiThread + fun abortTip() { + val ps = tipStatus.value + if (ps is TipStatus.Prepared) { + abortProposal(ps.walletTipId) + } + resetTipStatus() + } +*/ + +/* + internal fun abortProposal(proposalId: String) = scope.launch { + Log.i(TAG, "aborting proposal") + api.request<Unit>("abortProposal") { + put("proposalId", proposalId) + }.onError { + Log.e(TAG, "received error response to abortProposal") + handleError("abortProposal", it) + }.onSuccess { + mTipStatus.postValue(TipStatus.None) + } + } +*/ + + @UiThread + fun resetTipStatus() { + mTipStatus.value = TipStatus.None + } + + private fun handleError(operation: String, error: TalerErrorInfo) { + Log.e(TAG, "got $operation error result $error") + mTipStatus.value = TipStatus.Error(error.userFacingMsg) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/tip/TipResponses.kt b/wallet/src/main/java/net/taler/wallet/tip/TipResponses.kt new file mode 100644 index 0000000..aa2da15 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/tip/TipResponses.kt @@ -0,0 +1,63 @@ +/* + * 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.wallet.tip + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonClassDiscriminator +import net.taler.common.Amount +import net.taler.common.ContractTerms +import net.taler.common.Timestamp +import net.taler.wallet.backend.TalerErrorInfo + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +@JsonClassDiscriminator("accepted") +sealed class PrepareTipResponse { + + @Serializable + @SerialName("false") + data class TipPossibleResponse( + val walletTipId: String, + val merchantBaseUrl: String, + val exchangeBaseUrl: String, + val expirationTimestamp: Timestamp, + val tipAmountRaw: Amount, + val tipAmountEffective: Amount, + ) : PrepareTipResponse() { + fun toTipStatusPrepared() = TipStatus.Prepared( + walletTipId = walletTipId, + merchantBaseUrl = merchantBaseUrl, + exchangeBaseUrl = exchangeBaseUrl, + expirationTimestamp = expirationTimestamp, + tipAmountEffective = tipAmountEffective, + tipAmountRaw = tipAmountRaw + ) + } + + @Serializable + @SerialName("true") + data class AlreadyAcceptedResponse( + val walletTipId: String, + ) : PrepareTipResponse() +} + +@Serializable +class ConfirmTipResult { + +} diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt index cca370e..ca01501 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -212,9 +212,8 @@ class TransactionTip( override val transactionId: String, override val timestamp: Timestamp, override val pending: Boolean, - // TODO status: TipStatus, - val exchangeBaseUrl: String, - val merchant: ContractMerchant, + val frozen: Boolean, + val merchantBaseUrl: String, override val error: TalerErrorInfo? = null, override val amountRaw: Amount, override val amountEffective: Amount @@ -225,7 +224,7 @@ class TransactionTip( @Transient override val amountType = AmountType.Positive override fun getTitle(context: Context): String { - return context.getString(R.string.transaction_tip_from, merchant.name) + return context.getString(R.string.transaction_tip_from, merchantBaseUrl) } override val generalTitleRes = R.string.tip_title diff --git a/wallet/src/main/res/layout/fragment_already_accepted.xml b/wallet/src/main/res/layout/fragment_already_accepted.xml new file mode 100644 index 0000000..b1a7bb1 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_already_accepted.xml @@ -0,0 +1,52 @@ +<?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:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="15dp" + android:orientation="vertical" + tools:context=".tip.AlreadyAcceptedFragment"> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="50dp" + android:layout_gravity="center" + android:text="@string/tip_already_accepted" + android:textAlignment="center" + android:textColor="@android:color/holo_green_dark" + app:autoSizeTextType="uniform" /> + + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <Button + android:id="@+id/backButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/button_back" /> + +</LinearLayout> diff --git a/wallet/src/main/res/layout/fragment_prompt_tip.xml b/wallet/src/main/res/layout/fragment_prompt_tip.xml new file mode 100644 index 0000000..d96ef60 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_prompt_tip.xml @@ -0,0 +1,257 @@ +<?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=".tip.PromptTipFragment"> + + <TextView + android:id="@+id/introView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/tip_total" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/effectiveAmountView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:visibility="visible" /> + + <TextView + android:id="@+id/effectiveAmountView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:textColor="@color/green" + android:textSize="24sp" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/chosenAmountLabel" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/introView" + tools:text="9.8 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/chosenAmountLabel" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="32dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:text="@string/amount_chosen" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/chosenAmountView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/effectiveAmountView" + tools:visibility="visible" /> + + <TextView + android:id="@+id/chosenAmountView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:textSize="20sp" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/feeLabel" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/chosenAmountLabel" + tools:text="10 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/feeLabel" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="32dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:text="@string/tip_fees" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/feeView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/chosenAmountView" + tools:visibility="visible" /> + + <TextView + android:id="@+id/feeView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:textColor="@color/red" + android:textSize="20sp" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/exchangeIntroView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/feeLabel" + tools:text="-0.2 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/exchangeIntroView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="32dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/tip_exchange" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawExchangeUrl" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/feeView" + tools:visibility="visible" /> + + <TextView + android:id="@+id/withdrawExchangeUrl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginBottom="8dp" + android:layout_marginEnd="8dp" + android:gravity="center" + android:textSize="24sp" + android:visibility="invisible" + app:layout_constrainedWidth="true" + app:layout_constraintBottom_toTopOf="@+id/merchantIntroView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/exchangeIntroView" + tools:text="demo.taler.net" + tools:visibility="visible" /> + + <TextView + android:id="@+id/merchantIntroView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/tip_merchant_url" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawMerchantUrl" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawExchangeUrl" + tools:visibility="visible" /> + + + <TextView + android:id="@+id/withdrawMerchantUrl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="8dp" + android:gravity="center" + android:textSize="24sp" + android:visibility="invisible" + app:layout_constrainedWidth="true" + app:layout_constraintBottom_toTopOf="@+id/withdrawCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.502" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/merchantIntroView" + tools:text="merchant.demo.taler.net" + 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="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/withdrawCard" + style="@style/BottomCard" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp"> + + <Button + android:id="@+id/confirmWithdrawButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/tip_button_confirm" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintStart_toStartOf="parent" + tools:enabled="true" + tools:text="@string/tip_button_confirm" /> + + <ProgressBar + android:id="@+id/confirmProgressBar" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/confirmWithdrawButton" + app:layout_constraintEnd_toEndOf="@+id/confirmWithdrawButton" + app:layout_constraintStart_toStartOf="@+id/confirmWithdrawButton" + app:layout_constraintTop_toTopOf="@+id/confirmWithdrawButton" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.card.MaterialCardView> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml index 469a399..b3f96c5 100644 --- a/wallet/src/main/res/navigation/nav_graph.xml +++ b/wallet/src/main/res/navigation/nav_graph.xml @@ -34,6 +34,24 @@ <action android:id="@+id/action_nav_main_to_nav_uri_input" app:destination="@id/nav_uri_input" /> + <action + android:id="@+id/action_nav_main_to_promptTip" + app:destination="@id/promptTip" /> + </fragment> + + <fragment + android:id="@+id/promptTip" + android:name="net.taler.wallet.tip.PromptTipFragment" + android:label="Review Tip" + tools:layout="@layout/fragment_prompt_tip"> + <action + android:id="@+id/action_promptTip_to_nav_main" + app:destination="@id/nav_main" + app:popUpTo="@id/nav_main" /> + <action + android:id="@+id/action_promptTip_to_alreadyAccepted" + app:destination="@id/alreadyAccepted" + app:popUpTo="@id/nav_main" /> </fragment> <fragment @@ -131,6 +149,12 @@ tools:layout="@layout/fragment_transaction_withdrawal" /> <fragment + android:id="@+id/alreadyAccepted" + android:name="net.taler.wallet.tip.AlreadyAcceptedFragment" + android:label="@string/tip_already_accepted" + tools:layout="@layout/fragment_already_accepted" /> + + <fragment android:id="@+id/alreadyPaid" android:name="net.taler.wallet.payment.AlreadyPaidFragment" android:label="@string/payment_already_paid_title" diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml index 592070c..efaa7dd 100644 --- a/wallet/src/main/res/values/strings.xml +++ b/wallet/src/main/res/values/strings.xml @@ -187,6 +187,14 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card <string name="refund_success">Refund received: %s</string> <string name="tip_title">Tip</string> + <string name="tip_already_accepted">This tip is already accepted.</string> + <string name="tip_total">Total</string> + <string name="tip_fees">Fee</string> + <string name="tip_exchange">Exchange</string> + <string name="tip_merchant_url">Merchant URL</string> + <string name="tip_button_confirm">Accept tip</string> + <string name="tip_received">Tip received</string> + <string name="wifi_disabled_error">Turn on Wi-Fi to get free Wi-Fi</string> <string name="wifi_connect_error">Could not connect to free Wi-Fi: %s</string> diff --git a/wallet/src/test/java/net/taler/wallet/tip/TipResponsesTest.kt b/wallet/src/test/java/net/taler/wallet/tip/TipResponsesTest.kt new file mode 100644 index 0000000..9267f11 --- /dev/null +++ b/wallet/src/test/java/net/taler/wallet/tip/TipResponsesTest.kt @@ -0,0 +1,93 @@ +/* + * 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.wallet.payment + +import kotlinx.serialization.json.Json +import net.taler.common.Amount +import net.taler.wallet.tip.ConfirmTipResult +import net.taler.wallet.tip.PrepareTipResponse +import org.junit.Test + +class TipResponsesTest { + + private val json = Json { + ignoreUnknownKeys = true + } + + @Test + fun testConfirmTipResult() { + val jsonStr = """ + { + "type": "response", + "operation": "acceptTip", + "id": 47, + "result": {} + } + """.trimIndent() + val response = json.decodeFromString(ConfirmTipResult.serializer(), jsonStr) + response as ConfirmTipResult + assert(response != null) + } + +/* + +*/ + + + @Test + fun testTipPossibleSerializer() { + val jsonStr = """ + { + "accepted": false, + "tipAmountRaw": "ARS:2", + "exchangeBaseUrl": "http://exchange.taler:8081/", + "merchantBaseUrl": "http://merchant-backend.taler:9966/", + "expirationTimestamp": { + "t_s": 1688217455 + }, + "tipAmountEffective": "ARS:1.4", + "walletTipId": "SZH86ATJC4NZ427JHFVQ9M3S1TCQKVWSSZGSBW8MQ8VTVWD4M4GG" + } + """.trimIndent() + val response = json.decodeFromString(PrepareTipResponse.serializer(), jsonStr) + response as PrepareTipResponse.TipPossibleResponse + assert(response.walletTipId == "SZH86ATJC4NZ427JHFVQ9M3S1TCQKVWSSZGSBW8MQ8VTVWD4M4GG") + assert(response.tipAmountEffective == Amount(currency = "ARS", fraction = 40000000, value = 1)) + } + + + @Test + fun testTipAcceptedSerializer() { + val jsonStr = """ + { + "accepted": true, + "tipAmountRaw": "ARS:2", + "exchangeBaseUrl": "http://exchange.taler:8081/", + "merchantBaseUrl": "http://merchant-backend.taler:9966/", + "expirationTimestamp": { + "t_s": 1688217455 + }, + "tipAmountEffective": "ARS:1.4", + "walletTipId": "SZH86ATJC4NZ427JHFVQ9M3S1TCQKVWSSZGSBW8MQ8VTVWD4M4GG" + } + """.trimIndent() + val response = json.decodeFromString(PrepareTipResponse.serializer(), jsonStr) + assert(response is PrepareTipResponse.AlreadyAcceptedResponse) + assert((response as PrepareTipResponse.AlreadyAcceptedResponse).walletTipId == "SZH86ATJC4NZ427JHFVQ9M3S1TCQKVWSSZGSBW8MQ8VTVWD4M4GG") + } + +} |