aboutsummaryrefslogtreecommitdiff
path: root/wallet/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main')
-rw-r--r--wallet/src/main/AndroidManifest.xml8
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt22
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt217
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt74
-rw-r--r--wallet/src/main/res/layout/fragment_manual_withdraw.xml3
-rw-r--r--wallet/src/main/res/navigation/nav_graph.xml22
-rw-r--r--wallet/src/main/res/values/strings.xml7
9 files changed, 342 insertions, 19 deletions
diff --git a/wallet/src/main/AndroidManifest.xml b/wallet/src/main/AndroidManifest.xml
index b011583..963032c 100644
--- a/wallet/src/main/AndroidManifest.xml
+++ b/wallet/src/main/AndroidManifest.xml
@@ -83,4 +83,12 @@
android:process=":WalletBackendService" />
</application>
+ <queries>
+ <intent>
+ <data
+ android:host="iban"
+ android:scheme="payto" />
+ </intent>
+ </queries>
+
</manifest>
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
index 8a45bec..319aa7e 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
@@ -23,21 +23,27 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
import net.taler.common.startActivitySafe
import net.taler.common.toAbsoluteTime
+import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
import net.taler.wallet.databinding.FragmentTransactionWithdrawalBinding
+import net.taler.wallet.withdraw.createManualTransferRequired
class TransactionWithdrawalFragment : TransactionDetailFragment() {
+ private val model: MainViewModel by activityViewModels()
+ private val withdrawManager by lazy { model.withdrawManager }
private lateinit var ui: FragmentTransactionWithdrawalBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
+ savedInstanceState: Bundle?,
+ ): View {
ui = FragmentTransactionWithdrawalBinding.inflate(inflater, container, false)
return ui.root
}
@@ -55,6 +61,18 @@ class TransactionWithdrawalFragment : TransactionDetailFragment() {
data = Uri.parse(t.withdrawalDetails.bankConfirmationUrl)
}
ui.confirmWithdrawalButton.setOnClickListener { startActivitySafe(i) }
+ } else if (t.pending && !t.confirmed && t.withdrawalDetails is WithdrawalDetails.ManualTransfer) {
+ ui.confirmWithdrawalButton.setText(R.string.withdraw_manual_ready_details_intro)
+ ui.confirmWithdrawalButton.setOnClickListener {
+ val status = createManualTransferRequired(
+ amount = t.amountRaw,
+ exchangeBaseUrl = t.exchangeBaseUrl,
+ // TODO what if there's more than one or no URI?
+ uriStr = t.withdrawalDetails.exchangePaytoUris[0],
+ )
+ withdrawManager.viewManualWithdrawal(status)
+ findNavController().navigate(R.id.action_nav_transactions_detail_withdrawal_to_nav_exchange_manual_withdrawal_success)
+ }
} else ui.confirmWithdrawalButton.visibility = View.GONE
ui.chosenAmountLabel.text = getString(R.string.amount_chosen)
ui.chosenAmountView.text =
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
index 3acb29f..e78ff44 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -44,7 +44,7 @@ class ManualWithdrawFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
- ): View? {
+ ): View {
ui = FragmentManualWithdrawBinding.inflate(inflater, container, false)
return ui.root
}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
new file mode 100644
index 0000000..1f84278
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.withdraw
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment.Companion.CenterHorizontally
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.composethemeadapter.MdcTheme
+import net.taler.common.startActivitySafe
+import net.taler.lib.common.Amount
+import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.cleanExchange
+
+class ManualWithdrawSuccessFragment : Fragment() {
+ private val model: MainViewModel by activityViewModels()
+ private val withdrawManager by lazy { model.withdrawManager }
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View = ComposeView(requireContext()).apply {
+ val status = withdrawManager.withdrawStatus.value as WithdrawStatus.ManualTransferRequired
+ val intent = Intent().apply {
+ data = status.uri
+ }
+ // TODO test if this works with an actual payto:// handling app
+ val componentName = intent.resolveActivity(requireContext().packageManager)
+ val onBankAppClick = if (componentName == null) null else {
+ { startActivitySafe(intent) }
+ }
+ setContent {
+ MdcTheme {
+ Surface {
+ Screen(status, onBankAppClick)
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ activity?.setTitle(R.string.withdraw_title)
+ }
+}
+
+@Composable
+private fun Screen(
+ status: WithdrawStatus.ManualTransferRequired,
+ bankAppClick: (() -> Unit)?,
+) {
+ Column(modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = 16.dp)
+ .wrapContentWidth(CenterHorizontally)
+ ) {
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_title),
+ style = MaterialTheme.typography.h5,
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_intro,
+ status.amountRaw.toString()),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_details_intro),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ Row {
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_iban),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.3f)
+ )
+ Text(
+ text = status.iban,
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.7f)
+ )
+ }
+ Row {
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_subject),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.3f)
+ )
+ Text(
+ text = status.subject,
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.7f)
+ )
+ }
+ Row {
+ Text(
+ text = stringResource(R.string.amount_chosen),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.3f)
+ )
+ Text(
+ text = status.amountRaw.toString(),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.7f)
+ )
+ }
+ Row {
+ Text(
+ text = stringResource(R.string.withdraw_exchange),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.3f)
+ )
+ Text(
+ text = cleanExchange(status.exchangeBaseUrl),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .weight(0.7f)
+ .alpha(0.7f)
+ )
+ }
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_warning),
+ style = MaterialTheme.typography.body2,
+ color = colorResource(R.color.notice_text),
+ modifier = Modifier
+ .padding(all = 8.dp)
+ .background(colorResource(R.color.notice_background))
+ .border(BorderStroke(2.dp, colorResource(R.color.notice_border)))
+ .padding(all = 16.dp)
+ )
+ if (bankAppClick != null) {
+ Button(
+ onClick = bankAppClick,
+ modifier = Modifier
+ .padding(vertical = 16.dp)
+ .align(CenterHorizontally),
+ ) {
+ Text(text = stringResource(R.string.withdraw_manual_ready_bank_button))
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewScreen() {
+ Surface {
+ Screen(WithdrawStatus.ManualTransferRequired(
+ exchangeBaseUrl = "test.exchange.taler.net",
+ uri = Uri.parse("https://taler.net"),
+ iban = "ASDQWEASDZXCASDQWE",
+ subject = "Taler Withdrawal P2T19EXRBY4B145JRNZ8CQTD7TCS03JE9VZRCEVKVWCP930P56WG",
+ amountRaw = Amount("KUDOS", 10, 0)
+ )) {}
+ }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 38e09fa..08cbc2e 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -49,7 +49,7 @@ class PromptWithdrawFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
- ): View? {
+ ): View {
ui = FragmentPromptWithdrawBinding.inflate(inflater, container, false)
return ui.root
}
@@ -80,6 +80,10 @@ class PromptWithdrawFragment : Fragment() {
is TosReviewRequired -> onTosReviewRequired(status)
is ReceivedDetails -> onReceivedDetails(status)
is Withdrawing -> model.showProgressBar.value = true
+ is WithdrawStatus.ManualTransferRequired -> {
+ model.showProgressBar.value = false
+ findNavController().navigate(R.id.action_promptWithdraw_to_nav_exchange_manual_withdrawal_success)
+ }
is WithdrawStatus.Success -> {
model.showProgressBar.value = false
withdrawManager.withdrawStatus.value = null
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index cc4c057..858d63e 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -16,6 +16,7 @@
package net.taler.wallet.withdraw
+import android.net.Uri
import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
@@ -56,6 +57,14 @@ sealed class WithdrawStatus {
object Withdrawing : WithdrawStatus()
data class Success(val currency: String) : WithdrawStatus()
+ data class ManualTransferRequired(
+ val exchangeBaseUrl: String,
+ val uri: Uri,
+ val iban: String,
+ val subject: String,
+ val amountRaw: Amount,
+ ) : WithdrawStatus()
+
data class Error(val message: String?) : WithdrawStatus()
}
@@ -79,6 +88,11 @@ data class WithdrawalDetails(
val amountEffective: Amount,
)
+@Serializable
+data class AcceptManualWithdrawalResponse(
+ val exchangePaytoUris: List<String>,
+)
+
data class ExchangeSelection(
val amount: Amount,
val talerWithdrawUri: String,
@@ -197,31 +211,69 @@ class WithdrawManager(
@UiThread
fun acceptWithdrawal() = scope.launch {
val status = withdrawStatus.value as ReceivedDetails
- val operation = if (status.talerWithdrawUri == null) {
- "acceptManualWithdrawal"
+ withdrawStatus.value = WithdrawStatus.Withdrawing
+ if (status.talerWithdrawUri == null) {
+ acceptManualWithdrawal(status)
} else {
- "acceptBankIntegratedWithdrawal"
+ acceptBankIntegratedWithdrawal(status)
}
- withdrawStatus.value = WithdrawStatus.Withdrawing
+ }
- api.request<Unit>(operation) {
+ private suspend fun acceptBankIntegratedWithdrawal(status: ReceivedDetails) {
+ api.request<Unit>("acceptBankIntegratedWithdrawal") {
put("exchangeBaseUrl", status.exchangeBaseUrl)
- if (status.talerWithdrawUri == null) {
- put("amount", status.amountRaw.toJSONString())
- } else {
- put("talerWithdrawUri", status.talerWithdrawUri)
- }
+ put("talerWithdrawUri", status.talerWithdrawUri)
}.onError {
- handleError(operation, it)
+ handleError("acceptBankIntegratedWithdrawal", it)
}.onSuccess {
withdrawStatus.value = WithdrawStatus.Success(status.amountRaw.currency)
}
}
+ private suspend fun acceptManualWithdrawal(status: ReceivedDetails) {
+ api.request("acceptManualWithdrawal", AcceptManualWithdrawalResponse.serializer()) {
+ put("exchangeBaseUrl", status.exchangeBaseUrl)
+ put("amount", status.amountRaw.toJSONString())
+ }.onError {
+ handleError("acceptManualWithdrawal", it)
+ }.onSuccess { response ->
+ withdrawStatus.value = createManualTransferRequired(
+ amount = status.amountRaw,
+ exchangeBaseUrl = status.exchangeBaseUrl,
+ // TODO what if there's more than one or no URI?
+ uriStr = "payto://iban/ASDQWEASDZXCASDQWE?amount=KUDOS%3A10&message=Taler+Withdrawal+P2T19EXRBY4B145JRNZ8CQTD7TCS03JE9VZRCEVKVWCP930P56WG", // response.exchangePaytoUris[0],
+ // "payto://x-taler-bank/bank.demo.taler.net/Exchange?amount=KUDOS%3A10&message=Taler+Withdrawal+P2T19EXRBY4B145JRNZ8CQTD7TCS03JE9VZRCEVKVWCP930P56WG"
+ )
+ }
+ }
+
@UiThread
private fun handleError(operation: String, error: TalerErrorInfo) {
Log.e(TAG, "Error $operation $error")
withdrawStatus.value = WithdrawStatus.Error(error.userFacingMsg)
}
+ /**
+ * A hack to be able to view bank details for manual withdrawal with the same logic.
+ * Don't call this from ongoing withdrawal processes as it destroys state.
+ */
+ fun viewManualWithdrawal(status: WithdrawStatus.ManualTransferRequired) {
+ withdrawStatus.value = status
+ }
+
+}
+
+fun createManualTransferRequired(
+ amount: Amount,
+ exchangeBaseUrl: String,
+ uriStr: String,
+): WithdrawStatus.ManualTransferRequired {
+ val uri = Uri.parse(uriStr)
+ return WithdrawStatus.ManualTransferRequired(
+ exchangeBaseUrl = exchangeBaseUrl,
+ uri = uri,
+ iban = uri.lastPathSegment!!,
+ subject = uri.getQueryParameter("message")!!,
+ amountRaw = amount,
+ )
}
diff --git a/wallet/src/main/res/layout/fragment_manual_withdraw.xml b/wallet/src/main/res/layout/fragment_manual_withdraw.xml
index 5b37d2a..724c3e2 100644
--- a/wallet/src/main/res/layout/fragment_manual_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_manual_withdraw.xml
@@ -63,7 +63,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
- android:hint="@string/withdraw_amount"
+ android:minWidth="128dp"
app:boxBackgroundMode="outline"
app:endIconDrawable="@drawable/ic_cancel"
app:endIconMode="clear_text"
@@ -76,7 +76,6 @@
android:id="@+id/amountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:ems="10"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml
index e8929c9..469a399 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -82,9 +82,19 @@
</fragment>
<fragment
+ android:id="@+id/nav_exchange_manual_withdrawal_success"
+ android:name="net.taler.wallet.withdraw.ManualWithdrawSuccessFragment"
+ android:label="@string/withdraw_title">
+ <action
+ android:id="@+id/action_nav_exchange_manual_withdrawal_success_to_nav_main"
+ app:destination="@id/nav_main"
+ app:popUpTo="@id/nav_main" />
+ </fragment>
+
+ <fragment
android:id="@+id/nav_settings_backup"
android:name="net.taler.wallet.settings.BackupSettingsFragment"
- android:label="@string/nav_settings_backup"/>
+ android:label="@string/nav_settings_backup" />
<fragment
android:id="@+id/nav_transactions"
@@ -96,7 +106,11 @@
android:id="@+id/nav_transactions_detail_withdrawal"
android:name="net.taler.wallet.transactions.TransactionWithdrawalFragment"
android:label="@string/transactions_detail_title"
- tools:layout="@layout/fragment_transaction_withdrawal" />
+ tools:layout="@layout/fragment_transaction_withdrawal">
+ <action
+ android:id="@+id/action_nav_transactions_detail_withdrawal_to_nav_exchange_manual_withdrawal_success"
+ app:destination="@id/nav_exchange_manual_withdrawal_success" />
+ </fragment>
<fragment
android:id="@+id/nav_transactions_detail_payment"
@@ -138,6 +152,10 @@
app:destination="@id/nav_main"
app:popUpTo="@id/nav_main" />
<action
+ android:id="@+id/action_promptWithdraw_to_nav_exchange_manual_withdrawal_success"
+ app:destination="@id/nav_exchange_manual_withdrawal_success"
+ app:popUpTo="@id/nav_main" />
+ <action
android:id="@+id/action_promptWithdraw_to_errorFragment"
app:destination="@id/errorFragment"
app:popUpTo="@id/nav_main" />
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 5c27eb4..d2f181c 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -116,6 +116,13 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="withdraw_amount_error">Enter valid amount</string>
<string name="withdraw_manual_payment_options">Payment options supported by %1$s:\n\n%2$s</string>
<string name="withdraw_manual_check_fees">Check fees</string>
+ <string name="withdraw_manual_ready_title">Exchange is ready for withdrawal!</string>
+ <string name="withdraw_manual_ready_intro">To complete the process you need to wire %s to the exchange bank account</string>
+ <string name="withdraw_manual_ready_details_intro">Bank transfer details</string>
+ <string name="withdraw_manual_ready_iban">IBAN</string>
+ <string name="withdraw_manual_ready_subject">Subject</string>
+ <string name="withdraw_manual_ready_bank_button">Open in banking app</string>
+ <string name="withdraw_manual_ready_warning">Make sure to use the correct subject, otherwise the money will not arrive in this wallet.</string>
<string name="withdraw_error_title">Withdrawal Error</string>
<string name="withdraw_error_message">Withdrawing is currently not possible. Please try again later!</string>
<string name="withdraw_error_test">Error withdrawing TESTKUDOS</string>