aboutsummaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main/java/net')
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainActivity.kt5
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/tip/AlreadyAcceptedFragment.kt49
-rw-r--r--wallet/src/main/java/net/taler/wallet/tip/PromptTipFragment.kt160
-rw-r--r--wallet/src/main/java/net/taler/wallet/tip/TipManager.kt124
-rw-r--r--wallet/src/main/java/net/taler/wallet/tip/TipResponses.kt63
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt7
7 files changed, 406 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