[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taler-android] branch master updated (bdd47e0 -> 0f32834)
From: |
gnunet |
Subject: |
[taler-taler-android] branch master updated (bdd47e0 -> 0f32834) |
Date: |
Mon, 08 Jan 2024 21:58:33 +0100 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a change to branch master
in repository taler-android.
from bdd47e0 [wallet] Inform user when sending isn't working due to
insufficient balance
new 8936e2d [wallet] Initial implementation of DD36.
new d3d7a4a [wallet] Fixed serialization issues
new e2e6573 [wallet] Manual withdrawal: hide fee when zero and relax
paddings for smaller screens
new a4de5f7 [wallet] Bump qtart to v0.9.4-dev.1
new 325388c [wallet] Layout improvement to manual withdrawal details
new f521001 [wallet] Refactor p2p payments to show tx details when ready
new 6ce528f [wallet] Fix empty/null account list in manual withdrawal
new 90153fa [wallet] Delete unused numCoins field
new a038935 [wallet] ScrollableTabRow nitpick
new 2226c1c [wallet] Re-add disable back navigation
new 22029cd [wallet] Add UX from now-deleted push/pull result screens
into p2p tx details
new 0f32834 [wallet] support withdraw-exchange URI
The 12 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
wallet/build.gradle | 2 +-
.../src/main/java/net/taler/wallet/MainActivity.kt | 32 +++
.../CurrencySpecification.kt} | 28 +-
.../taler/wallet/compose/QrCodeUriComposable.kt | 21 +-
.../java/net/taler/wallet/compose/ShareButton.kt | 26 +-
.../net/taler/wallet/exchanges/ExchangeManager.kt | 18 ++
...ntroComposable.kt => OutgoingPullComposable.kt} | 134 +++++++++-
.../net/taler/wallet/peer/OutgoingPullFragment.kt | 46 ++--
.../wallet/peer/OutgoingPullResultComposable.kt | 150 -----------
...ntroComposable.kt => OutgoingPushComposable.kt} | 81 +++++-
.../net/taler/wallet/peer/OutgoingPushFragment.kt | 48 ++--
.../wallet/peer/OutgoingPushResultComposable.kt | 150 -----------
.../java/net/taler/wallet/peer/OutgoingState.kt | 12 +-
.../main/java/net/taler/wallet/peer/PeerManager.kt | 8 +-
.../taler/wallet/peer/TransactionPeerPullCredit.kt | 38 ++-
.../taler/wallet/peer/TransactionPeerPushDebit.kt | 71 ++++-
.../transactions/TransactionWithdrawalFragment.kt | 10 +-
.../net/taler/wallet/transactions/Transactions.kt | 75 +++++-
.../wallet/withdraw/PromptWithdrawFragment.kt | 10 +-
.../withdraw/TransactionWithdrawalComposable.kt | 42 ++-
.../net/taler/wallet/withdraw/WithdrawManager.kt | 170 ++++++++----
.../manual/ManualWithdrawSuccessFragment.kt | 63 +++--
.../taler/wallet/withdraw/manual/ScreenBitcoin.kt | 169 ------------
.../net/taler/wallet/withdraw/manual/ScreenIBAN.kt | 160 -----------
.../taler/wallet/withdraw/manual/ScreenTransfer.kt | 294 +++++++++++++++++++++
.../wallet/withdraw/manual/TransferBitcoin.kt | 110 ++++++++
.../taler/wallet/withdraw/manual/TransferIBAN.kt | 86 ++++++
.../main/res/layout/fragment_prompt_withdraw.xml | 4 +-
wallet/src/main/res/navigation/nav_graph.xml | 12 +
wallet/src/main/res/values/strings.xml | 6 +
30 files changed, 1180 insertions(+), 896 deletions(-)
copy wallet/src/main/java/net/taler/wallet/{refund/RefundPaymentInfo.kt =>
balances/CurrencySpecification.kt} (66%)
rename
wallet/src/main/java/net/taler/wallet/peer/{OutgoingPullIntroComposable.kt =>
OutgoingPullComposable.kt} (63%)
delete mode 100644
wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
rename
wallet/src/main/java/net/taler/wallet/peer/{OutgoingPushIntroComposable.kt =>
OutgoingPushComposable.kt} (74%)
delete mode 100644
wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
delete mode 100644
wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenBitcoin.kt
delete mode 100644
wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenIBAN.kt
create mode 100644
wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt
create mode 100644
wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt
create mode 100644
wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 78f04f3..23c8a0b 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -19,7 +19,7 @@ plugins {
id "kotlinx-serialization"
}
-def qtart_version = "0.9.3-dev.34"
+def qtart_version = "0.9.4-dev.3"
static def versionCodeEpoch() {
return (new Date().getTime() / 1000).toInteger()
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index 16f0efa..868ebe3 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -55,6 +55,7 @@ import com.journeyapps.barcodescanner.ScanOptions
import com.journeyapps.barcodescanner.ScanOptions.QR_CODE
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import net.taler.common.EventObserver
import net.taler.common.isOnline
import net.taler.common.showError
@@ -283,6 +284,37 @@ class MainActivity : AppCompatActivity(),
OnNavigationItemSelectedListener,
nav.navigate(R.id.action_global_promptWithdraw)
model.withdrawManager.getWithdrawalDetails(u2)
}
+
+ action.startsWith("withdraw-exchange/", ignoreCase = true) -> {
+ model.showProgressBar.value = true
+ lifecycleScope.launch(Dispatchers.IO) {
+ val response =
model.withdrawManager.prepareManualWithdrawal(u2)
+ if (response == null) withContext(Dispatchers.Main) {
+ model.showProgressBar.value = false
+ nav.navigate(R.id.errorFragment)
+ } else {
+ val exchange =
+
model.exchangeManager.findExchangeByUrl(response.exchangeBaseUrl)
+ if (exchange == null)
withContext(Dispatchers.Main) {
+ model.showProgressBar.value = false
+ showError(R.string.exchange_add_error)
+ } else {
+ model.exchangeManager.withdrawalExchange =
exchange
+ withContext(Dispatchers.Main) {
+ model.showProgressBar.value = false
+ val args = Bundle().apply {
+ if (response.amount != null) {
+ putString("amount",
response.amount.toJSONString())
+ }
+ }
+ // there's more than one entry point, so
use global action
+
nav.navigate(R.id.action_global_manual_withdrawal, args)
+ }
+ }
+ }
+ }
+ }
+
action.startsWith("refund/", ignoreCase = true) -> {
model.showProgressBar.value = true
model.refundManager.refund(u2).observe(this,
Observer(::onRefundResponse))
diff --git a/wallet/src/main/java/net/taler/wallet/refund/RefundPaymentInfo.kt
b/wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt
similarity index 66%
copy from wallet/src/main/java/net/taler/wallet/refund/RefundPaymentInfo.kt
copy to wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt
index d5f59be..5001db4 100644
--- a/wallet/src/main/java/net/taler/wallet/refund/RefundPaymentInfo.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt
@@ -14,26 +14,20 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.refund
+package net.taler.wallet.balances
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
-class RefundPaymentInfo(
- val summary: String,
- @SerialName("summary_i18n")
- val summaryI18n: Map<String, String>? = null,
- /**
- * More information about the merchant
- */
- val merchant: MerchantInfo,
-)
-
-@Serializable
-class MerchantInfo(
+data class CurrencySpecification(
val name: String,
- val logo: String? = null,
- val website: String? = null,
- val email: String? = null,
-)
+ @SerialName("num_fractional_input_digits")
+ val numFractionalInputDigits: Int,
+ @SerialName("num_fractional_normal_digits")
+ val numFractionalNormalDigits: Int,
+ @SerialName("num_fractional_trailing_zero_digits")
+ val numFractionalTrailingZeroDigits: Int,
+ @SerialName("alt_unit_names")
+ val altUnitNames: Map<String, String>,
+)
\ No newline at end of file
diff --git
a/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt
b/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt
index 2d7ffa1..4991094 100644
--- a/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt
@@ -25,21 +25,21 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.runtime.Composable
import androidx.compose.runtime.produceState
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
@@ -136,14 +136,13 @@ fun CopyToClipboardButton(
colors = colors,
onClick = { copyToClipBoard(context, label, content) },
) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(Icons.Default.ContentCopy, buttonText)
- Text(
- modifier = Modifier.padding(start = 8.dp),
- text = buttonText,
- style = MaterialTheme.typography.bodyLarge,
- )
- }
+ Icon(
+ Icons.Default.ContentCopy,
+ buttonText,
+ modifier = Modifier.size(ButtonDefaults.IconSize),
+ )
+ Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+ Text(buttonText)
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/compose/ShareButton.kt
b/wallet/src/main/java/net/taler/wallet/compose/ShareButton.kt
index ebf2a2f..f3a84dd 100644
--- a/wallet/src/main/java/net/taler/wallet/compose/ShareButton.kt
+++ b/wallet/src/main/java/net/taler/wallet/compose/ShareButton.kt
@@ -19,22 +19,19 @@ package net.taler.wallet.compose
import android.content.Intent
import android.content.Intent.ACTION_SEND
import android.content.Intent.EXTRA_TEXT
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Share
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import net.taler.wallet.R
@@ -59,13 +56,12 @@ fun ShareButton(
startActivity(context, shareIntent, null)
},
) {
- Row(verticalAlignment = CenterVertically) {
- Icon(Icons.Default.Share, buttonText)
- Text(
- modifier = Modifier.padding(start = 8.dp),
- text = buttonText,
- style = MaterialTheme.typography.bodyLarge,
- )
- }
+ Icon(
+ Icons.Default.Share,
+ buttonText,
+ modifier = Modifier.size(ButtonDefaults.IconSize),
+ )
+ Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+ Text(buttonText)
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
index 2048b7c..0e16d7a 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
@@ -37,6 +37,11 @@ data class ExchangeListResponse(
val exchanges: List<ExchangeItem>,
)
+@Serializable
+data class ExchangeDetailedResponse(
+ val exchange: ExchangeItem,
+)
+
class ExchangeManager(
private val api: WalletBackendApi,
private val scope: CoroutineScope,
@@ -104,6 +109,19 @@ class ExchangeManager(
return exchange
}
+ @WorkerThread
+ suspend fun findExchangeByUrl(exchangeUrl: String): ExchangeItem? {
+ var exchange: ExchangeItem? = null
+ api.request("getExchangeDetailedInfo",
ExchangeDetailedResponse.serializer()) {
+ put("exchangeBaseUrl", exchangeUrl)
+ }.onError {
+ Log.e(TAG, "Error getExchangeDetailedInfo: $it")
+ }.onSuccess {
+ exchange = it.exchange
+ }
+ return exchange
+ }
+
fun addDevExchanges() {
scope.launch {
listOf(
diff --git
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt
similarity index 63%
rename from
wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
rename to wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt
index 92bc72e..d58b0b8 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt
@@ -16,15 +16,18 @@
package net.taler.wallet.peer
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -33,6 +36,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -42,15 +46,51 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import kotlinx.serialization.json.JsonPrimitive
import net.taler.common.Amount
import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorCode
+import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
+import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.exchanges.ExchangeItem
import net.taler.wallet.transactions.AmountType
import net.taler.wallet.transactions.TransactionAmountComposable
import net.taler.wallet.transactions.TransactionInfoComposable
import kotlin.random.Random
+@Composable
+fun OutgoingPullComposable(
+ amount: Amount,
+ state: OutgoingState,
+ onCreateInvoice: (amount: Amount, subject: String, hours: Long, exchange:
ExchangeItem) -> Unit,
+ onClose: () -> Unit,
+) {
+ when(state) {
+ is OutgoingChecking, is OutgoingCreating, is OutgoingResponse ->
PeerCreatingComposable()
+ is OutgoingIntro, is OutgoingChecked -> OutgoingPullIntroComposable(
+ amount = amount,
+ state = state,
+ onCreateInvoice = onCreateInvoice,
+ )
+ is OutgoingError -> PeerErrorComposable(state, onClose)
+ }
+}
+
+@Composable
+fun PeerCreatingComposable() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .padding(32.dp)
+ .align(Center),
+ )
+ }
+}
+
@Composable
fun OutgoingPullIntroComposable(
amount: Amount,
@@ -67,6 +107,7 @@ fun OutgoingPullIntroComposable(
) {
var subject by rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
+
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
@@ -87,9 +128,11 @@ fun OutgoingPullIntroComposable(
)
}
)
+
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
+
Text(
modifier = Modifier
.fillMaxWidth()
@@ -98,11 +141,13 @@ fun OutgoingPullIntroComposable(
text = stringResource(R.string.char_count, subject.length,
MAX_LENGTH_SUBJECT),
textAlign = TextAlign.End,
)
+
TransactionAmountComposable(
label = stringResource(id = R.string.amount_chosen),
amount = amount,
amountType = AmountType.Positive,
)
+
if (state is OutgoingChecked) {
val fee = state.amountRaw - state.amountEffective
if (!fee.isZero()) TransactionAmountComposable(
@@ -111,16 +156,19 @@ fun OutgoingPullIntroComposable(
amountType = AmountType.Negative,
)
}
+
val exchangeItem = (state as? OutgoingChecked)?.exchangeItem
TransactionInfoComposable(
label = stringResource(id = R.string.withdraw_exchange),
info = if (exchangeItem == null) "" else
cleanExchange(exchangeItem.exchangeBaseUrl),
)
+
Text(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end =
16.dp),
text = stringResource(R.string.send_peer_expiration_period),
style = MaterialTheme.typography.bodyMedium,
)
+
var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
var hours by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY.hours) }
ExpirationComposable(
@@ -129,6 +177,7 @@ fun OutgoingPullIntroComposable(
hours = hours,
onOptionChange = { option = it }
) { hours = it }
+
Button(
modifier = Modifier.padding(16.dp),
enabled = subject.isNotBlank() && state is OutgoingChecked,
@@ -146,27 +195,86 @@ fun OutgoingPullIntroComposable(
}
}
+@Composable
+fun PeerErrorComposable(state: OutgoingError, onClose: () -> Unit) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ Text(
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodyLarge,
+ text = state.info.userFacingMsg,
+ )
+
+ Button(
+ modifier = Modifier.padding(16.dp),
+ onClick = onClose,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.error,
+ contentColor = MaterialTheme.colorScheme.onError,
+ ),
+ ) {
+ Text(text = stringResource(R.string.close))
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PeerPullComposableCreatingPreview() {
+ TalerSurface {
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = OutgoingCreating,
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
+ }
+}
+
@Preview
@Composable
-fun PreviewReceiveFundsCheckingIntro() {
- Surface {
- OutgoingPullIntroComposable(
- Amount.fromString("TESTKUDOS", "42.23"),
- if (Random.nextBoolean()) OutgoingIntro else OutgoingChecking,
- ) { _, _, _, _ -> }
+fun PeerPullComposableCheckingPreview() {
+ TalerSurface {
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = if (Random.nextBoolean()) OutgoingIntro else
OutgoingChecking,
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
}
}
@Preview
@Composable
-fun PreviewReceiveFundsCheckedIntro() {
- Surface {
+fun PeerPullComposableCheckedPreview() {
+ TalerSurface {
val amountRaw = Amount.fromString("TESTKUDOS", "42.42")
val amountEffective = Amount.fromString("TESTKUDOS", "42.23")
val exchangeItem = ExchangeItem("https://example.org", "TESTKUDOS",
emptyList())
- OutgoingPullIntroComposable(
- Amount.fromString("TESTKUDOS", "42.23"),
- OutgoingChecked(amountRaw, amountEffective, exchangeItem)
- ) { _, _, _, _ -> }
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = OutgoingChecked(amountRaw, amountEffective, exchangeItem),
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
}
}
+
+@Preview
+@Composable
+fun PeerPullComposableErrorPreview() {
+ TalerSurface {
+ val json = mapOf("foo" to JsonPrimitive("bar"))
+ val state =
OutgoingError(TalerErrorInfo(TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
"hint", "message", json))
+ OutgoingPullComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = state,
+ onCreateInvoice = { _, _, _, _ -> },
+ onClose = {},
+ )
+ }
+}
\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
index 7b1eee8..0205ae0 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullFragment.kt
@@ -23,8 +23,11 @@ import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import androidx.navigation.findNavController
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.findNavController
+import kotlinx.coroutines.launch
import net.taler.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
@@ -35,8 +38,8 @@ import net.taler.wallet.showError
class OutgoingPullFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
- private val exchangeManager get() = model.exchangeManager
private val peerManager get() = model.peerManager
+ private val transactionManager get() = model.transactionManager
override fun onCreateView(
inflater: LayoutInflater,
@@ -49,20 +52,15 @@ class OutgoingPullFragment : Fragment() {
return ComposeView(requireContext()).apply {
setContent {
TalerSurface {
- when (val state =
peerManager.pullState.collectAsStateLifecycleAware().value) {
- is OutgoingIntro, OutgoingChecking, is OutgoingChecked
-> {
- OutgoingPullIntroComposable(
- amount = amount,
- state = state,
- onCreateInvoice =
this@OutgoingPullFragment::onCreateInvoice,
- )
+ val state =
peerManager.pullState.collectAsStateLifecycleAware().value
+ OutgoingPullComposable(
+ amount = amount,
+ state = state,
+ onCreateInvoice =
this@OutgoingPullFragment::onCreateInvoice,
+ onClose = {
+
findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
}
- OutgoingCreating, is OutgoingResponse, is
OutgoingError -> {
- OutgoingPullResultComposable(state) {
-
findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
- }
- }
- }
+ )
}
}
}
@@ -70,10 +68,20 @@ class OutgoingPullFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- lifecycleScope.launchWhenStarted {
- peerManager.pullState.collect {
- if (it is OutgoingError && model.devMode.value == true) {
- showError(it.info)
+ lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ peerManager.pullState.collect {
+ if (it is OutgoingResponse) {
+ if
(transactionManager.selectTransaction(it.transactionId)) {
+
findNavController().navigate(R.id.action_nav_peer_pull_to_nav_transactions_detail_peer)
+ } else {
+
findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
+ }
+ }
+
+ if (it is OutgoingError && model.devMode.value == true) {
+ showError(it.info)
+ }
}
}
}
diff --git
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
deleted file mode 100644
index de62cda..0000000
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 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.peer
-
-import android.content.res.Configuration.UI_MODE_NIGHT_YES
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment.Companion.CenterHorizontally
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import kotlinx.serialization.json.JsonPrimitive
-import net.taler.common.QrCodeManager
-import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
-import net.taler.wallet.backend.TalerErrorInfo
-import net.taler.wallet.compose.QrCodeUriComposable
-import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.compose.getQrCodeSize
-
-@Composable
-fun OutgoingPullResultComposable(state: OutgoingState, onClose: () -> Unit) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .verticalScroll(scrollState),
- ) {
- Text(
- modifier = Modifier.padding(top = 16.dp, start = 16.dp, end =
16.dp),
- style = MaterialTheme.typography.titleLarge,
- text = stringResource(id =
R.string.receive_peer_invoice_instruction),
- )
- when (state) {
- OutgoingIntro, OutgoingChecking, is OutgoingChecked -> {
- error("Result composable with ${state::class.simpleName}")
- }
- is OutgoingCreating -> PeerPullCreatingComposable()
- is OutgoingResponse -> PeerPullResponseComposable(state)
- is OutgoingError -> PeerPullErrorComposable(state)
- }
- Button(modifier = Modifier
- .padding(16.dp)
- .align(CenterHorizontally),
- onClick = onClose) {
- Text(text = stringResource(R.string.close))
- }
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPullCreatingComposable() {
- val qrCodeSize = getQrCodeSize()
- CircularProgressIndicator(
- modifier = Modifier
- .padding(32.dp)
- .size(qrCodeSize)
- .align(CenterHorizontally),
- )
-}
-
-@Composable
-private fun ColumnScope.PeerPullResponseComposable(state: OutgoingResponse) {
- QrCodeUriComposable(
- talerUri = state.talerUri,
- clipBoardLabel = "Invoice",
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- style = MaterialTheme.typography.bodyLarge,
- text = stringResource(id = R.string.receive_peer_invoice_uri),
- )
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPullErrorComposable(state: OutgoingError) {
- Text(
- modifier = Modifier
- .align(CenterHorizontally)
- .padding(16.dp),
- color = MaterialTheme.colorScheme.error,
- style = MaterialTheme.typography.bodyLarge,
- text = state.info.userFacingMsg,
- )
-}
-
-@Preview
-@Composable
-fun PeerPullCreatingPreview() {
- Surface {
- OutgoingPullResultComposable(OutgoingCreating) {}
- }
-}
-
-@Preview(uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPullResponsePreview() {
- TalerSurface {
- val talerUri =
"https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri,
QrCodeManager.makeQrCode(talerUri))
- OutgoingPullResultComposable(response) {}
- }
-}
-
-@Preview(widthDp = 720, uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPullResponseLandscapePreview() {
- TalerSurface {
- val talerUri =
"https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri,
QrCodeManager.makeQrCode(talerUri))
- OutgoingPullResultComposable(response) {}
- }
-}
-
-@Preview
-@Composable
-fun PeerPullErrorPreview() {
- Surface {
- val json = mapOf("foo" to JsonPrimitive("bar"))
- val response =
OutgoingError(TalerErrorInfo(WALLET_WITHDRAWAL_KYC_REQUIRED, "hint", "message",
json))
- OutgoingPullResultComposable(response) {}
- }
-}
diff --git
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt
similarity index 74%
rename from
wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
rename to wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt
index 98391be..df61634 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushIntroComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt
@@ -22,10 +22,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -44,10 +42,32 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import kotlinx.serialization.json.JsonPrimitive
import net.taler.common.Amount
import net.taler.wallet.R
+import net.taler.wallet.backend.TalerErrorCode
+import net.taler.wallet.backend.TalerErrorInfo
+import net.taler.wallet.compose.TalerSurface
import kotlin.random.Random
+@Composable
+fun OutgoingPushComposable(
+ state: OutgoingState,
+ amount: Amount,
+ onSend: (amount: Amount, summary: String, hours: Long) -> Unit,
+ onClose: () -> Unit,
+) {
+ when(state) {
+ is OutgoingChecking, is OutgoingCreating, is OutgoingResponse ->
PeerCreatingComposable()
+ is OutgoingIntro, is OutgoingChecked -> OutgoingPushIntroComposable(
+ amount = amount,
+ state = state,
+ onSend = onSend,
+ )
+ is OutgoingError -> PeerErrorComposable(state, onClose)
+ }
+}
+
@Composable
fun OutgoingPushIntroComposable(
state: OutgoingState,
@@ -68,6 +88,7 @@ fun OutgoingPushIntroComposable(
softWrap = false,
style = MaterialTheme.typography.titleLarge,
)
+
if (state is OutgoingChecked) {
val fee = state.amountEffective - state.amountRaw
Text(
@@ -100,9 +121,11 @@ fun OutgoingPushIntroComposable(
)
}
)
+
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
+
Text(
modifier = Modifier
.fillMaxWidth()
@@ -111,11 +134,13 @@ fun OutgoingPushIntroComposable(
text = stringResource(R.string.char_count, subject.length,
MAX_LENGTH_SUBJECT),
textAlign = TextAlign.End,
)
+
Text(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end =
16.dp),
text = stringResource(R.string.send_peer_expiration_period),
style = MaterialTheme.typography.bodyMedium,
)
+
var option by rememberSaveable { mutableStateOf(DEFAULT_EXPIRY) }
var hours by rememberSaveable {
mutableLongStateOf(DEFAULT_EXPIRY.hours) }
ExpirationComposable(
@@ -124,10 +149,12 @@ fun OutgoingPushIntroComposable(
hours = hours,
onOptionChange = { option = it }
) { hours = it }
+
Text(
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
text = stringResource(R.string.send_peer_warning),
)
+
Button(
enabled = state is OutgoingChecked && subject.isNotBlank(),
onClick = { onSend(amount, subject, hours) },
@@ -139,20 +166,58 @@ fun OutgoingPushIntroComposable(
@Preview
@Composable
-fun PeerPushIntroComposableCheckingPreview() {
- Surface {
+fun PeerPushComposableCreatingPreview() {
+ TalerSurface {
+ OutgoingPushComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = OutgoingCreating,
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
+ }
+}
+
+@Preview
+@Composable
+fun PeerPushComposableCheckingPreview() {
+ TalerSurface {
val state = if (Random.nextBoolean()) OutgoingIntro else
OutgoingChecking
- OutgoingPushIntroComposable(state, Amount.fromString("TESTKUDOS",
"42.23")) { _, _, _ -> }
+ OutgoingPushComposable(
+ state = state,
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
}
}
@Preview
@Composable
-fun PeerPushIntroComposableCheckedPreview() {
- Surface {
+fun PeerPushComposableCheckedPreview() {
+ TalerSurface {
val amountEffective = Amount.fromString("TESTKUDOS", "42.42")
val amountRaw = Amount.fromString("TESTKUDOS", "42.23")
val state = OutgoingChecked(amountRaw, amountEffective)
- OutgoingPushIntroComposable(state, amountEffective) { _, _, _ -> }
+ OutgoingPushComposable(
+ state = state,
+ amount = amountEffective,
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
}
}
+
+@Preview
+@Composable
+fun PeerPushComposableErrorPreview() {
+ TalerSurface {
+ val json = mapOf("foo" to JsonPrimitive("bar"))
+ val state =
OutgoingError(TalerErrorInfo(TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
"hint", "message", json))
+ OutgoingPushComposable(
+ amount = Amount.fromString("TESTKUDOS", "42.23"),
+ state = state,
+ onSend = { _, _, _ -> },
+ onClose = {},
+ )
+ }
+}
\ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
index c586a1d..97dbcc2 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushFragment.kt
@@ -24,9 +24,12 @@ import androidx.activity.OnBackPressedCallback
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
+import kotlinx.coroutines.launch
import net.taler.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
@@ -37,6 +40,7 @@ import net.taler.wallet.showError
class OutgoingPushFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
private val peerManager get() = model.peerManager
+ private val transactionManager get() = model.transactionManager
// hacky way to change back action until we have navigation for compose
private val backPressedCallback = object : OnBackPressedCallback(false) {
@@ -61,22 +65,15 @@ class OutgoingPushFragment : Fragment() {
return ComposeView(requireContext()).apply {
setContent {
TalerSurface {
- when (val state =
peerManager.pushState.collectAsStateLifecycleAware().value) {
- is OutgoingIntro, OutgoingChecking, is OutgoingChecked
-> {
- backPressedCallback.isEnabled = false
- OutgoingPushIntroComposable(
- state = state,
- amount = amount,
- onSend = this@OutgoingPushFragment::onSend,
- )
+ val state =
peerManager.pushState.collectAsStateLifecycleAware().value
+ OutgoingPushComposable(
+ amount = amount,
+ state = state,
+ onSend = this@OutgoingPushFragment::onSend,
+ onClose = {
+
findNavController().navigate(R.id.action_nav_peer_pull_to_nav_main)
}
- OutgoingCreating, is OutgoingResponse, is
OutgoingError -> {
- backPressedCallback.isEnabled = true
- OutgoingPushResultComposable(state) {
-
findNavController().navigate(R.id.action_nav_peer_push_to_nav_main)
- }
- }
- }
+ )
}
}
}
@@ -84,10 +81,23 @@ class OutgoingPushFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- lifecycleScope.launchWhenStarted {
- peerManager.pushState.collect {
- if (it is OutgoingError && model.devMode.value == true) {
- showError(it.info)
+ lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ peerManager.pushState.collect {
+ if (it is OutgoingResponse) {
+ if
(transactionManager.selectTransaction(it.transactionId)) {
+
findNavController().navigate(R.id.action_nav_peer_push_to_nav_transactions_detail_peer)
+ } else {
+
findNavController().navigate(R.id.action_nav_peer_push_to_nav_main)
+ }
+ }
+
+ if (it is OutgoingError && model.devMode.value == true) {
+ showError(it.info)
+ }
+
+ // Disable back navigation when tx is being created
+ backPressedCallback.isEnabled = it !is OutgoingCreating
}
}
}
diff --git
a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
deleted file mode 100644
index 0a4ee70..0000000
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 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.peer
-
-import android.content.res.Configuration.UI_MODE_NIGHT_YES
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment.Companion.CenterHorizontally
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import kotlinx.serialization.json.JsonPrimitive
-import net.taler.common.QrCodeManager
-import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
-import net.taler.wallet.backend.TalerErrorInfo
-import net.taler.wallet.compose.QrCodeUriComposable
-import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.compose.getQrCodeSize
-
-@Composable
-fun OutgoingPushResultComposable(state: OutgoingState, onClose: () -> Unit) {
- val scrollState = rememberScrollState()
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .verticalScroll(scrollState),
- ) {
- Text(
- modifier = Modifier.padding(top = 16.dp, start = 16.dp, end =
16.dp),
- style = MaterialTheme.typography.titleLarge,
- text = stringResource(id = R.string.send_peer_payment_instruction),
- )
- when (state) {
- OutgoingIntro, OutgoingChecking, is OutgoingChecked -> {
- error("Result composable with ${state::class.simpleName}")
- }
- is OutgoingCreating -> PeerPushCreatingComposable()
- is OutgoingResponse -> PeerPushResponseComposable(state)
- is OutgoingError -> PeerPushErrorComposable(state)
- }
- Button(modifier = Modifier
- .padding(16.dp)
- .align(CenterHorizontally),
- onClick = onClose) {
- Text(text = stringResource(R.string.close))
- }
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPushCreatingComposable() {
- val qrCodeSize = getQrCodeSize()
- CircularProgressIndicator(
- modifier = Modifier
- .padding(32.dp)
- .size(qrCodeSize)
- .align(CenterHorizontally),
- )
-}
-
-@Composable
-private fun ColumnScope.PeerPushResponseComposable(state: OutgoingResponse) {
- QrCodeUriComposable(
- talerUri = state.talerUri,
- clipBoardLabel = "Invoice",
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- style = MaterialTheme.typography.bodyLarge,
- text = stringResource(id = R.string.receive_peer_invoice_uri),
- )
- }
-}
-
-@Composable
-private fun ColumnScope.PeerPushErrorComposable(state: OutgoingError) {
- Text(
- modifier = Modifier
- .align(CenterHorizontally)
- .padding(16.dp),
- color = MaterialTheme.colorScheme.error,
- style = MaterialTheme.typography.bodyLarge,
- text = state.info.userFacingMsg,
- )
-}
-
-@Preview
-@Composable
-fun PeerPushCreatingPreview() {
- Surface {
- OutgoingPushResultComposable(OutgoingCreating) {}
- }
-}
-
-@Preview(uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPushResponsePreview() {
- TalerSurface {
- val talerUri =
"https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri,
QrCodeManager.makeQrCode(talerUri))
- OutgoingPushResultComposable(response) {}
- }
-}
-
-@Preview(widthDp = 720, uiMode = UI_MODE_NIGHT_YES)
-@Composable
-fun PeerPushResponseLandscapePreview() {
- TalerSurface {
- val talerUri =
"https://example.org/foo/bar/can/be/very/long/url/so/fit/it/on/screen"
- val response = OutgoingResponse(talerUri,
QrCodeManager.makeQrCode(talerUri))
- OutgoingPushResultComposable(response) {}
- }
-}
-
-@Preview
-@Composable
-fun PeerPushErrorPreview() {
- Surface {
- val json = mapOf("foo" to JsonPrimitive("bar"))
- val response =
OutgoingError(TalerErrorInfo(WALLET_WITHDRAWAL_KYC_REQUIRED, "hint", "message",
json))
- OutgoingPushResultComposable(response) {}
- }
-}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
b/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
index e53dd40..05da294 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingState.kt
@@ -16,7 +16,6 @@
package net.taler.wallet.peer
-import android.graphics.Bitmap
import kotlinx.serialization.Serializable
import net.taler.common.Amount
import net.taler.wallet.backend.TalerErrorInfo
@@ -32,8 +31,7 @@ data class OutgoingChecked(
) : OutgoingState()
object OutgoingCreating : OutgoingState()
data class OutgoingResponse(
- val talerUri: String,
- val qrCode: Bitmap,
+ val transactionId: String,
) : OutgoingState()
data class OutgoingError(
@@ -49,10 +47,7 @@ data class CheckPeerPullCreditResponse(
@Serializable
data class InitiatePeerPullPaymentResponse(
- /**
- * Taler URI for the other party to make the payment that was requested.
- */
- val talerUri: String,
+ val transactionId: String,
)
@Serializable
@@ -64,8 +59,5 @@ data class CheckPeerPushDebitResponse(
@Serializable
data class InitiatePeerPushDebitResponse(
val exchangeBaseUrl: String,
- @Deprecated("Will be removed in future version")
- val talerUri: String,
- // TODO bring the user to that transaction and only show QR when in
Pending/Ready state
val transactionId: String,
)
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
index 6e65e0b..5bd2b0b 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
@@ -25,7 +25,6 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import net.taler.common.Amount
-import net.taler.common.QrCodeManager
import net.taler.common.Timestamp
import net.taler.wallet.TAG
import net.taler.wallet.backend.TalerErrorCode.UNKNOWN
@@ -95,8 +94,7 @@ class PeerManager(
put("purse_expiration",
JSONObject(Json.encodeToString(expiry)))
})
}.onSuccess {
- val qrCode = QrCodeManager.makeQrCode(it.talerUri)
- _outgoingPullState.value = OutgoingResponse(it.talerUri,
qrCode)
+ _outgoingPullState.value = OutgoingResponse(it.transactionId)
}.onError { error ->
Log.e(TAG, "got initiatePeerPullCredit error result $error")
_outgoingPullState.value = OutgoingError(error)
@@ -138,9 +136,7 @@ class PeerManager(
put("purse_expiration",
JSONObject(Json.encodeToString(expiry)))
})
}.onSuccess { response ->
- // TODO bring the user to that transaction and only show QR
when in Pending/Ready state
- val qrCode = QrCodeManager.makeQrCode(response.talerUri)
- _outgoingPushState.value = OutgoingResponse(response.talerUri,
qrCode)
+ _outgoingPushState.value =
OutgoingResponse(response.transactionId)
}.onError { error ->
Log.e(TAG, "got initiatePeerPushDebit error result $error")
_outgoingPushState.value = OutgoingError(error)
diff --git
a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
index b04a756..de377fc 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
@@ -17,21 +17,15 @@
package net.taler.wallet.peer
import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.wallet.R
import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
-import net.taler.wallet.compose.QrCodeUriComposable
import net.taler.wallet.transactions.AmountType
import net.taler.wallet.transactions.PeerInfoShort
import net.taler.wallet.transactions.TransactionAction.Abort
@@ -40,6 +34,7 @@ import net.taler.wallet.transactions.TransactionAction.Suspend
import net.taler.wallet.transactions.TransactionAmountComposable
import net.taler.wallet.transactions.TransactionInfoComposable
import net.taler.wallet.transactions.TransactionMajorState.Pending
+import net.taler.wallet.transactions.TransactionMinorState.CreatePurse
import net.taler.wallet.transactions.TransactionMinorState.Ready
import net.taler.wallet.transactions.TransactionPeerComposable
import net.taler.wallet.transactions.TransactionPeerPullCredit
@@ -47,16 +42,23 @@ import net.taler.wallet.transactions.TransactionState
@Composable
fun ColumnScope.TransactionPeerPullCreditComposable(t:
TransactionPeerPullCredit) {
+ if (t.error == null) PeerQrCode(
+ state = t.txState,
+ talerUri = t.talerUri,
+ )
+
TransactionAmountComposable(
label = stringResource(id = R.string.receive_amount),
amount = t.amountEffective,
amountType = AmountType.Positive,
)
+
TransactionAmountComposable(
label = stringResource(id = R.string.amount_chosen),
amount = t.amountRaw,
amountType = AmountType.Neutral,
)
+
val fee = t.amountRaw - t.amountEffective
if (!fee.isZero()) {
TransactionAmountComposable(
@@ -65,32 +67,20 @@ fun ColumnScope.TransactionPeerPullCreditComposable(t:
TransactionPeerPullCredit
amountType = AmountType.Negative,
)
}
+
TransactionInfoComposable(
label = stringResource(id = R.string.send_peer_purpose),
info = t.info.summary ?: "",
)
- if (t.txState == TransactionState(Pending, Ready)) {
- QrCodeUriComposable(
- talerUri = t.talerUri,
- clipBoardLabel = "Invoice",
- buttonText = stringResource(id = R.string.copy),
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- style = MaterialTheme.typography.bodyLarge,
- text = stringResource(id = R.string.receive_peer_invoice_uri),
- )
- }
- }
}
@Preview
@Composable
-fun TransactionPeerPullCreditPreview() {
+fun TransactionPeerPullCreditPreview(loading: Boolean = false) {
val t = TransactionPeerPullCredit(
transactionId = "transactionId",
timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60
* 1000),
- txState = TransactionState(Pending),
+ txState = TransactionState(Pending, if (loading) CreatePurse else
Ready),
txActions = listOf(Retry, Suspend, Abort),
exchangeBaseUrl = "https://exchange.example.org/",
amountRaw = Amount.fromString("TESTKUDOS", "42.23"),
@@ -106,3 +96,9 @@ fun TransactionPeerPullCreditPreview() {
TransactionPeerComposable(t, true) {}
}
}
+
+@Preview
+@Composable
+fun TransactionPeerPullCreditLoadingPreview() {
+ TransactionPeerPullCreditPreview(loading = true)
+}
\ No newline at end of file
diff --git
a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
index 2587ea9..d83d8fe 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
@@ -18,12 +18,15 @@ package net.taler.wallet.peer
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.taler.common.Amount
@@ -32,6 +35,8 @@ import net.taler.wallet.R
import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.QrCodeUriComposable
+import net.taler.wallet.compose.TalerSurface
+import net.taler.wallet.compose.getQrCodeSize
import net.taler.wallet.transactions.AmountType
import net.taler.wallet.transactions.PeerInfoShort
import net.taler.wallet.transactions.TransactionAction.Abort
@@ -40,6 +45,7 @@ import net.taler.wallet.transactions.TransactionAction.Suspend
import net.taler.wallet.transactions.TransactionAmountComposable
import net.taler.wallet.transactions.TransactionInfoComposable
import net.taler.wallet.transactions.TransactionMajorState.Pending
+import net.taler.wallet.transactions.TransactionMinorState.CreatePurse
import net.taler.wallet.transactions.TransactionMinorState.Ready
import net.taler.wallet.transactions.TransactionPeerComposable
import net.taler.wallet.transactions.TransactionPeerPushDebit
@@ -47,16 +53,23 @@ import net.taler.wallet.transactions.TransactionState
@Composable
fun ColumnScope.TransactionPeerPushDebitComposable(t:
TransactionPeerPushDebit) {
+ if (t.error == null) PeerQrCode(
+ state = t.txState,
+ talerUri = t.talerUri,
+ )
+
TransactionAmountComposable(
label = stringResource(id = R.string.transaction_paid),
amount = t.amountEffective,
amountType = AmountType.Negative,
)
+
TransactionAmountComposable(
label = stringResource(id = R.string.transaction_order_total),
amount = t.amountRaw,
amountType = AmountType.Neutral,
)
+
val fee = t.amountEffective - t.amountRaw
if (!fee.isZero()) {
TransactionAmountComposable(
@@ -65,32 +78,55 @@ fun ColumnScope.TransactionPeerPushDebitComposable(t:
TransactionPeerPushDebit)
amountType = AmountType.Negative,
)
}
+
TransactionInfoComposable(
label = stringResource(id = R.string.send_peer_purpose),
info = t.info.summary ?: "",
)
- if (t.txState == TransactionState(Pending, Ready)) {
- QrCodeUriComposable(
- talerUri = t.talerUri,
- clipBoardLabel = "Push payment",
- buttonText = stringResource(id = R.string.copy),
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 16.dp),
- style = MaterialTheme.typography.bodyLarge,
- text = stringResource(id = R.string.receive_peer_invoice_uri),
+}
+
+@Composable
+fun ColumnScope.PeerQrCode(state: TransactionState, talerUri: String?) {
+ if (state == TransactionState(Pending)) {
+ Text(
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp, end =
16.dp),
+ style = MaterialTheme.typography.titleLarge,
+ text = stringResource(id = R.string.send_peer_payment_instruction),
+ textAlign = TextAlign.Center,
+ )
+
+ if (state.minor == Ready && talerUri != null) {
+ QrCodeUriComposable(
+ talerUri = talerUri,
+ clipBoardLabel = "Push payment",
+ buttonText = stringResource(id = R.string.copy),
+ ) {
+ Text(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.bodyLarge,
+ text = stringResource(id =
R.string.receive_peer_invoice_uri),
+ )
+ }
+ } else {
+ val qrCodeSize = getQrCodeSize()
+ CircularProgressIndicator(
+ modifier = Modifier
+ .padding(32.dp)
+ .size(qrCodeSize)
+ .align(CenterHorizontally),
)
}
}
+
}
@Preview
@Composable
-fun TransactionPeerPushDebitPreview() {
+fun TransactionPeerPushDebitPreview(loading: Boolean = false) {
val t = TransactionPeerPushDebit(
transactionId = "transactionId",
timestamp = Timestamp.fromMillis(System.currentTimeMillis() - 360 * 60
* 1000),
- txState = TransactionState(Pending),
+ txState = TransactionState(Pending, if (loading) CreatePurse else
Ready),
txActions = listOf(Retry, Suspend, Abort),
exchangeBaseUrl = "https://exchange.example.org/",
amountRaw = Amount.fromString("TESTKUDOS", "42.1337"),
@@ -102,7 +138,14 @@ fun TransactionPeerPushDebitPreview() {
talerUri = "https://exchange.example.org/peer/pull/credit",
error = TalerErrorInfo(code = EXCHANGE_GENERIC_KYC_REQUIRED),
)
- Surface {
+
+ TalerSurface {
TransactionPeerComposable(t, true) {}
}
}
+
+@Preview
+@Composable
+fun TransactionPeerPushDebitLoadingPreview() {
+ TransactionPeerPushDebitPreview(loading = true)
+}
\ No newline at end of file
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 0cd6d60..969db13 100644
---
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
+++
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
@@ -77,13 +77,13 @@ class TransactionWithdrawalFragment :
TransactionDetailFragment(), ActionListene
ActionListener.Type.CONFIRM_MANUAL -> {
if (tx !is TransactionWithdrawal) return
if (tx.withdrawalDetails !is ManualTransfer) return
- // TODO what if there's more than one or no URI?
- if (tx.withdrawalDetails.exchangePaytoUris.isEmpty()) return
+ if
(tx.withdrawalDetails.exchangeCreditAccountDetails.isNullOrEmpty()) return
val status = createManualTransferRequired(
- amount = tx.amountRaw,
- exchangeBaseUrl = tx.exchangeBaseUrl,
- uriStr = tx.withdrawalDetails.exchangePaytoUris[0],
transactionId = tx.transactionId,
+ exchangeBaseUrl = tx.exchangeBaseUrl,
+ amountRaw = tx.amountRaw,
+ amountEffective = tx.amountEffective,
+ withdrawalAccountList =
tx.withdrawalDetails.exchangeCreditAccountDetails,
)
withdrawManager.viewManualWithdrawal(status)
findNavController().navigate(
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 e7f17c0..de47f68 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -42,6 +42,7 @@ import net.taler.wallet.TAG
import net.taler.wallet.backend.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
+import net.taler.wallet.balances.CurrencySpecification
import net.taler.wallet.refund.RefundPaymentInfo
import net.taler.wallet.transactions.TransactionMajorState.None
import net.taler.wallet.transactions.TransactionMajorState.Pending
@@ -182,12 +183,7 @@ sealed class WithdrawalDetails {
@Serializable
@SerialName("manual-transfer")
class ManualTransfer(
- /**
- * Payto URIs that the exchange supports.
- *
- * Already contains the amount and message.
- */
- val exchangePaytoUris: List<String>,
+ val exchangeCreditAccountDetails:
List<WithdrawalExchangeAccountDetails>? = null,
) : WithdrawalDetails()
@Serializable
@@ -208,6 +204,71 @@ sealed class WithdrawalDetails {
) : WithdrawalDetails()
}
+@Serializable
+data class WithdrawalExchangeAccountDetails (
+ /**
+ * Payto URI to credit the exchange.
+ *
+ * Depending on whether the (manual!) withdrawal is accepted or just
+ * being checked, this already includes the subject with the
+ * reserve public key.
+ */
+ val paytoUri: String,
+
+ /**
+ * Transfer amount. Might be in a different currency than the requested
+ * amount for withdrawal.
+ *
+ * Redundant with the amount in paytoUri, just included to avoid parsing.
+ */
+ val transferAmount: Amount? = null,
+
+ /**
+ * Currency specification for the external currency.
+ *
+ * Only included if this account requires a currency conversion.
+ */
+ val currencySpecification: CurrencySpecification? = null,
+
+ /**
+ * Further restrictions for sending money to the
+ * exchange.
+ */
+ val creditRestrictions: List<AccountRestriction>? = null,
+)
+
+@Serializable
+sealed class AccountRestriction {
+ @Serializable
+ @SerialName("deny")
+ data object DenyAllAccount: AccountRestriction()
+
+ @Serializable
+ @SerialName("regex")
+ data class RegexAccount(
+ // Regular expression that the payto://-URI of the
+ // partner account must follow. The regular expression
+ // should follow posix-egrep, but without support for character
+ // classes, GNU extensions, back-references or intervals. See
+ //
https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html
+ // for a description of the posix-egrep syntax. Applications
+ // may support regexes with additional features, but exchanges
+ // must not use such regexes.
+ @SerialName("payto_regex")
+ val paytoRegex: String,
+
+ // Hint for a human to understand the restriction
+ // (that is hopefully easier to comprehend than the regex itself).
+ @SerialName("human_hint")
+ val humanHint: String,
+
+ // Map from IETF BCP 47 language tags to localized
+ // human hints.
+ @SerialName("human_hint_i18n")
+ val humanHintI18n: Map<String, String>? = null,
+ ): AccountRestriction()
+}
+
@Serializable
@SerialName("payment")
class TransactionPayment(
@@ -425,7 +486,7 @@ class TransactionPeerPushDebit(
override val amountRaw: Amount,
override val amountEffective: Amount,
val info: PeerInfoShort,
- val talerUri: String,
+ val talerUri: String? = null,
// val completed: Boolean, definitely
) : Transaction() {
override val icon = R.drawable.ic_cash_usd_outline
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 fd67e71..caad7b6 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -164,10 +164,12 @@ class PromptWithdrawFragment : Fragment() {
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()
+ val fee = amountRaw - amountEffective
+ if (!fee.isZero()) {
+ ui.feeLabel.fadeIn()
+ ui.feeView.text = getString(R.string.amount_negative,
fee.toString())
+ ui.feeView.fadeIn()
+ }
ui.exchangeIntroView.fadeIn()
ui.withdrawExchangeUrl.text = cleanExchange(exchange)
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
index 378e283..fda1815 100644
---
a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
+++
b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
@@ -38,6 +38,7 @@ import net.taler.wallet.R
import net.taler.wallet.backend.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
+import net.taler.wallet.balances.CurrencySpecification
import net.taler.wallet.transactions.ActionButton
import net.taler.wallet.transactions.ActionListener
import net.taler.wallet.transactions.AmountType
@@ -54,6 +55,7 @@ import net.taler.wallet.transactions.TransactionState
import net.taler.wallet.transactions.TransactionWithdrawal
import net.taler.wallet.transactions.TransitionsComposable
import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer
+import net.taler.wallet.transactions.WithdrawalExchangeAccountDetails
@Composable
fun TransactionWithdrawalComposable(
@@ -75,17 +77,15 @@ fun TransactionWithdrawalComposable(
text = t.timestamp.ms.toAbsoluteTime(context).toString(),
style = MaterialTheme.typography.bodyLarge,
)
- TransactionAmountComposable(
- label = stringResource(id = R.string.withdraw_total),
- amount = t.amountEffective,
- amountType = AmountType.Positive,
- )
+
ActionButton(tx = t, listener = actionListener)
+
TransactionAmountComposable(
- label = stringResource(id = R.string.amount_chosen),
+ label = stringResource(R.string.amount_chosen),
amount = t.amountRaw,
amountType = AmountType.Neutral,
)
+
val fee = t.amountRaw - t.amountEffective
if (!fee.isZero()) {
TransactionAmountComposable(
@@ -94,11 +94,20 @@ fun TransactionWithdrawalComposable(
amountType = AmountType.Negative,
)
}
+
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.withdraw_total),
+ amount = t.amountEffective,
+ amountType = AmountType.Positive,
+ )
+
TransactionInfoComposable(
label = stringResource(id = R.string.withdraw_exchange),
info = cleanExchange(t.exchangeBaseUrl),
)
+
TransitionsComposable(t, devMode, onTransition)
+
if (devMode && t.error != null) {
ErrorTransactionButton(error = t.error)
}
@@ -114,15 +123,30 @@ fun TransactionWithdrawalComposablePreview() {
txState = TransactionState(Pending),
txActions = listOf(Retry, Suspend, Abort),
exchangeBaseUrl = "https://exchange.demo.taler.net/",
- withdrawalDetails = ManualTransfer(exchangePaytoUris = emptyList()),
+ withdrawalDetails = ManualTransfer(
+ exchangeCreditAccountDetails = listOf(
+ WithdrawalExchangeAccountDetails(
+ paytoUri = "payto://IBAN/1231231231",
+ transferAmount = Amount.fromJSONString("NETZBON:42.23"),
+ currencySpecification = CurrencySpecification(
+ name = "NETZBON",
+ numFractionalInputDigits = 2,
+ numFractionalNormalDigits = 2,
+ numFractionalTrailingZeroDigits = 2,
+ altUnitNames = mapOf("0" to "NETZBON"),
+ ),
+ ),
+ ),
+ ),
amountRaw = Amount.fromString("TESTKUDOS", "42.23"),
amountEffective = Amount.fromString("TESTKUDOS", "42.1337"),
error = TalerErrorInfo(code =
TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED),
)
+
val listener = object : ActionListener {
- override fun onActionButtonClicked(tx: Transaction, type:
ActionListener.Type) {
- }
+ override fun onActionButtonClicked(tx: Transaction, type:
ActionListener.Type) {}
}
+
Surface {
TransactionWithdrawalComposable(t, true, listener) {}
}
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 90b8570..ceae7e1 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -19,6 +19,7 @@ package net.taler.wallet.withdraw
import android.net.Uri
import android.util.Log
import androidx.annotation.UiThread
+import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
@@ -33,10 +34,12 @@ import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.exchanges.ExchangeFees
import net.taler.wallet.exchanges.ExchangeItem
+import net.taler.wallet.transactions.WithdrawalExchangeAccountDetails
import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
sealed class WithdrawStatus {
data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus()
+
data class NeedsExchange(val exchangeSelection: Event<ExchangeSelection>)
: WithdrawStatus()
data class TosReviewRequired(
@@ -44,6 +47,7 @@ sealed class WithdrawStatus {
val exchangeBaseUrl: String,
val amountRaw: Amount,
val amountEffective: Amount,
+ val withdrawalAccountList: List<WithdrawalExchangeAccountDetails>,
val ageRestrictionOptions: List<Int>? = null,
val tosText: String,
val tosEtag: String,
@@ -55,36 +59,49 @@ sealed class WithdrawStatus {
val exchangeBaseUrl: String,
val amountRaw: Amount,
val amountEffective: Amount,
+ val withdrawalAccountList: List<WithdrawalExchangeAccountDetails>,
val ageRestrictionOptions: List<Int>? = null,
) : WithdrawStatus()
- object Withdrawing : WithdrawStatus()
+ data object Withdrawing : WithdrawStatus()
+
data class Success(val currency: String, val transactionId: String) :
WithdrawStatus()
- sealed class ManualTransferRequired : WithdrawStatus() {
- abstract val uri: Uri
- abstract val transactionId: String?
- }
- data class ManualTransferRequiredIBAN(
+ class ManualTransferRequired(
+ val transactionId: String?,
+ val transactionAmountRaw: Amount,
+ val transactionAmountEffective: Amount,
val exchangeBaseUrl: String,
- override val uri: Uri,
+ val withdrawalTransfers: List<TransferData>,
+ ) : WithdrawStatus()
+
+ data class Error(val message: String?) : WithdrawStatus()
+}
+
+sealed class TransferData {
+ abstract val subject: String
+ abstract val amountRaw: Amount
+ abstract val amountEffective: Amount
+ abstract val withdrawalAccount: WithdrawalExchangeAccountDetails
+
+ val currency get() = withdrawalAccount.transferAmount?.currency
+
+ data class IBAN(
+ override val subject: String,
+ override val amountRaw: Amount,
+ override val amountEffective: Amount,
+ override val withdrawalAccount: WithdrawalExchangeAccountDetails,
val iban: String,
- val subject: String,
- val amountRaw: Amount,
- override val transactionId: String?,
- ) : ManualTransferRequired()
+ ): TransferData()
- data class ManualTransferRequiredBitcoin(
- val exchangeBaseUrl: String,
- override val uri: Uri,
+ data class Bitcoin(
+ override val subject: String,
+ override val amountRaw: Amount,
+ override val amountEffective: Amount,
+ override val withdrawalAccount: WithdrawalExchangeAccountDetails,
val account: String,
- val segwitAddrs: List<String>,
- val subject: String,
- val amountRaw: Amount,
- override val transactionId: String?,
- ) : ManualTransferRequired()
-
- data class Error(val message: String?) : WithdrawStatus()
+ val segwitAddresses: List<String>,
+ ): TransferData()
}
sealed class WithdrawTestStatus {
@@ -101,10 +118,17 @@ data class WithdrawalDetailsForUri(
)
@Serializable
-data class WithdrawalDetails(
+data class WithdrawExchangeResponse(
+ val exchangeBaseUrl: String,
+ val amount: Amount? = null,
+)
+
+@Serializable
+data class ManualWithdrawalDetails(
val tosAccepted: Boolean,
val amountRaw: Amount,
val amountEffective: Amount,
+ val withdrawalAccountsList: List<WithdrawalExchangeAccountDetails>,
val ageRestrictionOptions: List<Int>? = null,
)
@@ -115,7 +139,9 @@ data class AcceptWithdrawalResponse(
@Serializable
data class AcceptManualWithdrawalResponse(
- val exchangePaytoUris: List<String>,
+ val reservePub: String,
+ val withdrawalAccountsList: List<WithdrawalExchangeAccountDetails>,
+ val transactionId: String,
)
data class ExchangeSelection(
@@ -176,7 +202,7 @@ class WithdrawManager(
uri: String? = null,
) = scope.launch {
withdrawStatus.value = WithdrawStatus.Loading(uri)
- api.request("getWithdrawalDetailsForAmount",
WithdrawalDetails.serializer()) {
+ api.request("getWithdrawalDetailsForAmount",
ManualWithdrawalDetails.serializer()) {
put("exchangeBaseUrl", exchangeBaseUrl)
put("amount", amount.toJSONString())
}.onError { error ->
@@ -188,15 +214,30 @@ class WithdrawManager(
exchangeBaseUrl = exchangeBaseUrl,
amountRaw = details.amountRaw,
amountEffective = details.amountEffective,
+ withdrawalAccountList = details.withdrawalAccountsList,
ageRestrictionOptions = details.ageRestrictionOptions,
)
} else getExchangeTos(exchangeBaseUrl, details,
showTosImmediately, uri)
}
}
+ @WorkerThread
+ suspend fun prepareManualWithdrawal(uri: String):
WithdrawExchangeResponse? {
+ withdrawStatus.postValue(WithdrawStatus.Loading(uri))
+ var response: WithdrawExchangeResponse? = null
+ api.request("prepareWithdrawExchange",
WithdrawExchangeResponse.serializer()) {
+ put("talerUri", uri)
+ }.onError {
+ handleError("prepareWithdrawExchange", it)
+ }.onSuccess {
+ response = it
+ }
+ return response
+ }
+
private fun getExchangeTos(
exchangeBaseUrl: String,
- details: WithdrawalDetails,
+ details: ManualWithdrawalDetails,
showImmediately: Boolean,
uri: String?,
) = scope.launch {
@@ -210,6 +251,7 @@ class WithdrawManager(
exchangeBaseUrl = exchangeBaseUrl,
amountRaw = details.amountRaw,
amountEffective = details.amountEffective,
+ withdrawalAccountList = details.withdrawalAccountsList,
ageRestrictionOptions = details.ageRestrictionOptions,
tosText = it.content,
tosEtag = it.currentEtag,
@@ -234,6 +276,7 @@ class WithdrawManager(
exchangeBaseUrl = s.exchangeBaseUrl,
amountRaw = s.amountRaw,
amountEffective = s.amountEffective,
+ withdrawalAccountList = s.withdrawalAccountList,
ageRestrictionOptions = s.ageRestrictionOptions,
)
}
@@ -275,10 +318,8 @@ class WithdrawManager(
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 = response.exchangePaytoUris[0],
+ status = status,
+ response = response,
)
}
}
@@ -301,33 +342,48 @@ class WithdrawManager(
}
fun createManualTransferRequired(
- amount: Amount,
+ transactionId: String,
exchangeBaseUrl: String,
- uriStr: String,
- transactionId: String? = null,
-): WithdrawStatus.ManualTransferRequired {
- val uri = Uri.parse(uriStr.replace("receiver-name=", "receiver_name="))
- if ("bitcoin".equals(uri.authority, true)) {
- val msg = uri.getQueryParameter("message").orEmpty()
- val reg = "\\b([A-Z0-9]{52})\\b".toRegex().find(msg)
- val reserve = reg?.value ?: uri.getQueryParameter("subject")!!
- val segwitAddrs = Bech32.generateFakeSegwitAddress(reserve,
uri.pathSegments.first())
- return WithdrawStatus.ManualTransferRequiredBitcoin(
- exchangeBaseUrl = exchangeBaseUrl,
- uri = uri,
- account = uri.lastPathSegment!!,
- segwitAddrs = segwitAddrs,
- subject = reserve,
- amountRaw = amount,
- transactionId = transactionId,
+ amountRaw: Amount,
+ amountEffective: Amount,
+ withdrawalAccountList: List<WithdrawalExchangeAccountDetails>,
+) = WithdrawStatus.ManualTransferRequired(
+ transactionId = transactionId,
+ transactionAmountRaw = amountRaw,
+ transactionAmountEffective = amountEffective,
+ exchangeBaseUrl = exchangeBaseUrl,
+ withdrawalTransfers = withdrawalAccountList.map {
+ val uri = Uri.parse(it.paytoUri.replace("receiver-name=",
"receiver_name="))
+ if ("bitcoin".equals(uri.authority, true)) {
+ val msg = uri.getQueryParameter("message").orEmpty()
+ val reg = "\\b([A-Z0-9]{52})\\b".toRegex().find(msg)
+ val reserve = reg?.value ?: uri.getQueryParameter("subject")!!
+ val segwitAddresses = Bech32.generateFakeSegwitAddress(reserve,
uri.pathSegments.first())
+ TransferData.Bitcoin(
+ account = uri.lastPathSegment!!,
+ segwitAddresses = segwitAddresses,
+ subject = reserve,
+ amountRaw = amountRaw,
+ amountEffective = amountEffective,
+ withdrawalAccount = it.copy(paytoUri = uri.toString())
+ )
+ } else TransferData.IBAN(
+ iban = uri.lastPathSegment!!,
+ subject = uri.getQueryParameter("message") ?: "Error: No message
in URI",
+ amountRaw = amountRaw,
+ amountEffective = amountEffective,
+ withdrawalAccount = it.copy(paytoUri = uri.toString())
)
- }
- return WithdrawStatus.ManualTransferRequiredIBAN(
- exchangeBaseUrl = exchangeBaseUrl,
- uri = uri,
- iban = uri.lastPathSegment!!,
- subject = uri.getQueryParameter("message") ?: "Error: No message in
URI",
- amountRaw = amount,
- transactionId = transactionId,
- )
-}
+ },
+)
+
+fun createManualTransferRequired(
+ status: ReceivedDetails,
+ response: AcceptManualWithdrawalResponse,
+): WithdrawStatus.ManualTransferRequired = createManualTransferRequired(
+ transactionId = response.transactionId,
+ exchangeBaseUrl = status.exchangeBaseUrl,
+ amountRaw = status.amountRaw,
+ amountEffective = status.amountEffective,
+ withdrawalAccountList = response.withdrawalAccountsList,
+)
\ No newline at end of file
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt
index fa3f38b..8d83427 100644
---
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt
+++
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt
@@ -17,11 +17,12 @@
package net.taler.wallet.withdraw.manual
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@@ -29,53 +30,57 @@ import androidx.navigation.fragment.findNavController
import net.taler.common.startActivitySafe
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
-import net.taler.wallet.TAG
import net.taler.wallet.compose.TalerSurface
-import net.taler.wallet.showError
+import net.taler.wallet.withdraw.TransferData
import net.taler.wallet.withdraw.WithdrawStatus
class ManualWithdrawSuccessFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
- private val transactionManager by lazy { model.transactionManager }
private val withdrawManager by lazy { model.withdrawManager }
+
+ private lateinit var status: WithdrawStatus.ManualTransferRequired
+
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 {
- { requireContext().startActivitySafe(intent) }
- }
- val tid = status.transactionId
- val onCancelClick = if (tid == null) null else {
- {
- transactionManager.deleteTransaction(tid) {
- Log.e(TAG, "Error deleteTransaction $it")
- showError(it)
+ status = withdrawManager.withdrawStatus.value as
WithdrawStatus.ManualTransferRequired
+
+ // Set action bar subtitle and unset on exit
+ if (status.withdrawalTransfers.size > 1) {
+ val activity = requireActivity() as AppCompatActivity
+
+ activity.apply {
+ supportActionBar?.subtitle =
getString(R.string.withdraw_subtitle)
+ }
+
+ findNavController().addOnDestinationChangedListener { controller,
destination, args ->
+ if (destination.id !=
R.id.nav_exchange_manual_withdrawal_success) {
+ activity.apply {
+ supportActionBar?.subtitle = null
+ }
}
-
findNavController().navigate(R.id.action_nav_exchange_manual_withdrawal_success_to_nav_main)
}
}
+
setContent {
TalerSurface {
- when (status) {
- is WithdrawStatus.ManualTransferRequiredBitcoin -> {
- ScreenBitcoin(status, onBankAppClick, onCancelClick)
- }
-
- is WithdrawStatus.ManualTransferRequiredIBAN -> {
- ScreenIBAN(status, onBankAppClick, onCancelClick)
- }
- }
+ ScreenTransfer(
+ status = status,
+ bankAppClick = { onBankAppClick(it) },
+ )
}
}
}
+ private fun onBankAppClick(transfer: TransferData) {
+ val intent = Intent().apply { data =
Uri.parse(transfer.withdrawalAccount.paytoUri) }
+ val componentName =
intent.resolveActivity(requireContext().packageManager)
+ if (componentName != null) {
+ requireContext().startActivitySafe(intent)
+ }
+ }
+
override fun onStart() {
super.onStart()
activity?.setTitle(R.string.withdraw_title)
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenBitcoin.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenBitcoin.kt
deleted file mode 100644
index fa20072..0000000
--- a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenBitcoin.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 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.manual
-
-import android.net.Uri
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Alignment.Companion.End
-import androidx.compose.ui.Modifier
-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.compose.ui.unit.em
-import net.taler.common.Amount
-import net.taler.wallet.CURRENCY_BTC
-import net.taler.wallet.R
-import net.taler.wallet.compose.CopyToClipboardButton
-import net.taler.wallet.withdraw.WithdrawStatus
-
-@Composable
-fun ScreenBitcoin(
- status: WithdrawStatus.ManualTransferRequiredBitcoin,
- bankAppClick: (() -> Unit)?,
- onCancelClick: (() -> Unit)?,
-) {
- val scrollState = rememberScrollState()
- Column(modifier = Modifier
- .wrapContentWidth(Alignment.CenterHorizontally)
- .verticalScroll(scrollState)
- .padding(all = 16.dp)
- ) {
- Text(
- text = stringResource(R.string.withdraw_manual_bitcoin_title),
- style = MaterialTheme.typography.headlineSmall,
- )
- Text(
- text = stringResource(R.string.withdraw_manual_bitcoin_intro),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier
- .padding(vertical = 8.dp)
- )
- BitcoinSegwitAddrs(
- amount = status.amountRaw,
- addr = status.account,
- segwitAddresses = status.segwitAddrs
- )
- if (bankAppClick != null) {
- Button(
- onClick = bankAppClick,
- modifier = Modifier
- .padding(vertical = 16.dp)
- .align(Alignment.CenterHorizontally),
- ) {
- Text(text =
stringResource(R.string.withdraw_manual_ready_bank_button))
- }
- }
- if (onCancelClick != null) {
- Button(
- onClick = onCancelClick,
- colors = ButtonDefaults.buttonColors(containerColor =
MaterialTheme.colorScheme.error),
- modifier = Modifier
- .padding(vertical = 16.dp)
- .align(End),
- ) {
- Text(
- text =
stringResource(R.string.withdraw_manual_ready_cancel),
- color = MaterialTheme.colorScheme.onError,
- )
- }
- }
- }
-}
-
-@Composable
-fun BitcoinSegwitAddrs(amount: Amount, addr: String, segwitAddresses:
List<String>) {
- Column {
- CopyToClipboardButton(
- modifier = Modifier.align(End),
- label = "Bitcoin",
- content = getCopyText(amount, addr, segwitAddresses),
- )
- Row(modifier = Modifier.padding(vertical = 8.dp)) {
- Column(modifier = Modifier.weight(0.3f)) {
- Text(
- text = addr,
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.Normal,
- fontSize = 3.em
- )
- Text(
- text = amount.withCurrency("BTC").toString(),
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.Bold,
- )
- }
- }
- for (segwitAddress in segwitAddresses) {
- Row(modifier = Modifier.padding(vertical = 8.dp)) {
- Column(modifier = Modifier.weight(0.3f)) {
- Text(
- text = segwitAddress,
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.Normal,
- fontSize = 3.em,
- )
- Text(
- text = SEGWIT_MIN.toString(),
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.Bold,
- )
- }
- }
- }
- }
-}
-
-private val SEGWIT_MIN = Amount("BTC", 0, 294)
-
-private fun getCopyText(amount: Amount, addr: String, segwitAddresses:
List<String>): String {
- val sr = segwitAddresses.joinToString(separator = "\n") { s ->
- "\n$s ${SEGWIT_MIN}\n"
- }
- return "$addr ${amount.withCurrency("BTC")}\n$sr"
-}
-
-@Preview
-@Composable
-fun PreviewScreenBitcoin() {
- Surface {
- ScreenBitcoin(WithdrawStatus.ManualTransferRequiredBitcoin(
- exchangeBaseUrl = "bitcoin.ice.bfh.ch",
- uri = Uri.parse("https://taler.net"),
- account = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
- segwitAddrs = listOf(
- "bc1qqleages8702xvg9qcyu02yclst24xurdrynvxq",
- "bc1qsleagehks96u7jmqrzcf0fw80ea5g57qm3m84c"
- ),
- subject = "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
- amountRaw = Amount(CURRENCY_BTC, 0, 14000000),
- transactionId = "",
- ), {}) {}
- }
-}
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenIBAN.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenIBAN.kt
deleted file mode 100644
index 537f3ad..0000000
--- a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenIBAN.kt
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 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.manual
-
-import android.net.Uri
-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.padding
-import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ContentCopy
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.platform.LocalContext
-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 net.taler.common.Amount
-import net.taler.wallet.R
-import net.taler.wallet.compose.copyToClipBoard
-import net.taler.wallet.withdraw.WithdrawStatus
-
-@Composable
-fun ScreenIBAN(
- status: WithdrawStatus.ManualTransferRequiredIBAN,
- bankAppClick: (() -> Unit)?,
- onCancelClick: (() -> Unit)?,
-) {
- val scrollState = rememberScrollState()
- Column(modifier = Modifier
- .wrapContentWidth(Alignment.CenterHorizontally)
- .verticalScroll(scrollState)
- .padding(all = 16.dp)
- ) {
- Text(
- text = stringResource(R.string.withdraw_manual_ready_title),
- style = MaterialTheme.typography.headlineSmall,
- )
- Text(
- text = stringResource(R.string.withdraw_manual_ready_intro,
- status.amountRaw.toString()),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier
- .padding(vertical = 8.dp)
- )
- DetailRow(stringResource(R.string.withdraw_manual_ready_iban),
status.iban)
- DetailRow(stringResource(R.string.withdraw_manual_ready_subject),
status.subject)
- DetailRow(stringResource(R.string.amount_chosen),
status.amountRaw.toString())
- DetailRow(stringResource(R.string.withdraw_exchange),
status.exchangeBaseUrl, false)
- Text(
- text = stringResource(R.string.withdraw_manual_ready_warning),
- style = MaterialTheme.typography.bodyMedium,
- color = colorResource(R.color.notice_text),
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .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(Alignment.CenterHorizontally),
- ) {
- Text(text =
stringResource(R.string.withdraw_manual_ready_bank_button))
- }
- }
- if (onCancelClick != null) {
- Button(
- onClick = onCancelClick,
- colors = ButtonDefaults.buttonColors(containerColor =
MaterialTheme.colorScheme.error),
- modifier = Modifier
- .padding(vertical = 16.dp)
- .align(Alignment.End),
- ) {
- Text(
- text =
stringResource(R.string.withdraw_manual_ready_cancel),
- color = MaterialTheme.colorScheme.onError,
- )
- }
- }
- }
-}
-
-@Composable
-fun DetailRow(label: String, content: String, copy: Boolean = true) {
- val context = LocalContext.current
- Row {
- Column(
- modifier = Modifier
- .weight(0.3f)) {
- Text(
- text = label,
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = if (copy) FontWeight.Bold else FontWeight.Normal,
- )
- if (copy) {
- IconButton(
- onClick = { copyToClipBoard(context, label, content) },
- ) { Icon(Icons.Default.ContentCopy,
stringResource(R.string.copy)) }
- }
- }
- Text(
- text = content,
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier
- .padding(bottom = 8.dp)
- .weight(0.7f)
- .then(if (copy) Modifier else Modifier.alpha(0.7f))
- )
- }
-}
-
-@Preview
-@Composable
-fun PreviewScreenIBAN() {
- Surface {
- ScreenIBAN(WithdrawStatus.ManualTransferRequiredIBAN(
- exchangeBaseUrl = "test.exchange.taler.net",
- uri = Uri.parse("https://taler.net"),
- iban = "ASDQWEASDZXCASDQWE",
- subject = "Taler Withdrawal
P2T19EXRBY4B145JRNZ8CQTD7TCS03JE9VZRCEVKVWCP930P56WG",
- amountRaw = Amount("KUDOS", 10, 0),
- transactionId = "",
- ), {}) {}
- }
-}
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt
new file mode 100644
index 0000000..19cfbdb
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt
@@ -0,0 +1,294 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2023 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.manual
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ContentCopy
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ScrollableTabRow
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import net.taler.common.Amount
+import net.taler.wallet.CURRENCY_BTC
+import net.taler.wallet.R
+import net.taler.wallet.balances.CurrencySpecification
+import net.taler.wallet.compose.copyToClipBoard
+import net.taler.wallet.transactions.AmountType
+import net.taler.wallet.transactions.TransactionAmountComposable
+import net.taler.wallet.transactions.WithdrawalExchangeAccountDetails
+import net.taler.wallet.withdraw.TransferData
+import net.taler.wallet.withdraw.WithdrawStatus
+
+@Composable
+fun ScreenTransfer(
+ status: WithdrawStatus.ManualTransferRequired,
+ bankAppClick: ((transfer: TransferData) -> Unit)?,
+) {
+ // TODO: show some placeholder
+ if (status.withdrawalTransfers.isEmpty()) return
+
+ val defaultTransfer = status.withdrawalTransfers[0]
+ var selectedTransfer by remember { mutableStateOf(defaultTransfer) }
+
+ Column {
+ if (status.withdrawalTransfers.size > 1) {
+ TransferAccountChooser(
+ accounts = status.withdrawalTransfers.map {
it.withdrawalAccount },
+ selectedAccount = selectedTransfer.withdrawalAccount,
+ onSelectAccount = { account ->
+ status.withdrawalTransfers.find {
+ it.withdrawalAccount.paytoUri == account.paytoUri
+ }?.let { selectedTransfer = it }
+ }
+ )
+ }
+
+ val scrollState = rememberScrollState()
+ Column(
+ modifier = Modifier
+ .verticalScroll(scrollState),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ when (val transfer = selectedTransfer) {
+ is TransferData.IBAN -> TransferIBAN(
+ transfer = transfer,
+ exchangeBaseUrl = status.exchangeBaseUrl,
+ transactionAmountRaw = status.transactionAmountRaw,
+ transactionAmountEffective =
status.transactionAmountEffective,
+ )
+
+ is TransferData.Bitcoin -> TransferBitcoin(
+ transfer = transfer,
+ transactionAmountRaw = status.transactionAmountRaw,
+ transactionAmountEffective =
status.transactionAmountEffective,
+ )
+ }
+
+ if (bankAppClick != null) {
+ Button(
+ onClick = { bankAppClick(selectedTransfer) },
+ modifier = Modifier
+ .padding(bottom = 16.dp)
+ ) {
+ Text(text =
stringResource(R.string.withdraw_manual_ready_bank_button))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun DetailRow(
+ label: String,
+ content: String,
+ copy: Boolean = true,
+) {
+ val context = LocalContext.current
+
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier.padding(top = 16.dp, start = 6.dp, end = 6.dp),
+ text = label,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+
+ Text(
+ modifier = Modifier.padding(
+ top = 8.dp,
+ start = 6.dp,
+ end = 6.dp,
+ ),
+ text = content,
+ style = MaterialTheme.typography.bodyLarge,
+ fontFamily = if (copy) FontFamily.Monospace else
FontFamily.Default,
+ textAlign = TextAlign.Center,
+ )
+
+ if (copy) {
+ IconButton(
+ onClick = { copyToClipBoard(context, label, content) },
+ ) {
+ Icon(
+ imageVector = Icons.Default.ContentCopy,
+ contentDescription = stringResource(R.string.copy),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun WithdrawalAmountTransfer(
+ amountRaw: Amount,
+ amountEffective: Amount,
+ conversionAmountRaw: Amount,
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ TransactionAmountComposable(
+ label = stringResource(R.string.withdraw_transfer),
+ amount = conversionAmountRaw,
+ amountType = AmountType.Neutral,
+ )
+
+ if (amountRaw.currency != conversionAmountRaw.currency) {
+ TransactionAmountComposable(
+ label = stringResource(R.string.withdraw_conversion),
+ amount = amountRaw,
+ amountType = AmountType.Neutral,
+ )
+ }
+
+ val fee = amountRaw - amountEffective
+ if (!fee.isZero()) {
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.withdraw_fees),
+ amount = fee,
+ amountType = AmountType.Negative,
+ )
+ }
+
+ TransactionAmountComposable(
+ label = stringResource(id = R.string.withdraw_total),
+ amount = amountEffective,
+ amountType = AmountType.Positive,
+ )
+ }
+}
+
+@Composable
+fun TransferAccountChooser(
+ modifier: Modifier = Modifier,
+ accounts: List<WithdrawalExchangeAccountDetails>,
+ selectedAccount: WithdrawalExchangeAccountDetails,
+ onSelectAccount: (account: WithdrawalExchangeAccountDetails) -> Unit,
+) {
+ val selectedIndex = accounts.indexOfFirst {
+ it.paytoUri == selectedAccount.paytoUri
+ }
+
+ ScrollableTabRow(
+ selectedTabIndex = selectedIndex,
+ modifier = modifier,
+ edgePadding = 8.dp,
+ ) {
+ accounts.forEachIndexed { index, account ->
+ Tab(
+ selected = selectedAccount.paytoUri == account.paytoUri,
+ onClick = { onSelectAccount(account) },
+ text = {
+ if (account.currencySpecification?.name != null) {
+ Text(stringResource(
+ R.string.withdraw_account_currency,
+ index + 1,
+ account.currencySpecification.name,
+ ))
+ } else if (account.transferAmount?.currency != null) {
+ Text(stringResource(
+ R.string.withdraw_account_currency,
+ index + 1,
+ account.transferAmount.currency,
+ ))
+ } else Text(stringResource(R.string.withdraw_account,
index + 1))
+ },
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+fun ScreenTransferPreview() {
+ Surface {
+ ScreenTransfer(
+ status = WithdrawStatus.ManualTransferRequired(
+ transactionId = "",
+ transactionAmountRaw = Amount.fromJSONString("KUDOS:10"),
+ transactionAmountEffective =
Amount.fromJSONString("KUDOS:9.5"),
+ exchangeBaseUrl = "test.exchange.taler.net",
+ withdrawalTransfers = listOf(
+ TransferData.IBAN(
+ iban = "ASDQWEASDZXCASDQWE",
+ subject = "Taler Withdrawal
P2T19EXRBY4B145JRNZ8CQTD7TCS03JE9VZRCEVKVWCP930P56WG",
+ amountRaw = Amount("KUDOS", 10, 0),
+ amountEffective = Amount("KUDOS", 9, 5),
+ withdrawalAccount = WithdrawalExchangeAccountDetails(
+ paytoUri = "https://taler.net/kudos",
+ transferAmount = Amount("KUDOS", 10, 0),
+ currencySpecification = CurrencySpecification(
+ "KUDOS",
+ numFractionalInputDigits = 2,
+ numFractionalNormalDigits = 2,
+ numFractionalTrailingZeroDigits = 2,
+ altUnitNames = emptyMap(),
+ ),
+ ),
+ ),
+ TransferData.Bitcoin(
+ account = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
+ segwitAddresses = listOf(
+ "bc1qqleages8702xvg9qcyu02yclst24xurdrynvxq",
+ "bc1qsleagehks96u7jmqrzcf0fw80ea5g57qm3m84c"
+ ),
+ subject =
"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
+ amountRaw = Amount(CURRENCY_BTC, 0, 14000000),
+ amountEffective = Amount(CURRENCY_BTC, 0, 14000000),
+ withdrawalAccount = WithdrawalExchangeAccountDetails(
+ paytoUri = "https://taler.net/btc",
+ transferAmount = Amount("BTC", 0, 14000000),
+ currencySpecification = CurrencySpecification(
+ "Bitcoin",
+ numFractionalInputDigits = 2,
+ numFractionalNormalDigits = 2,
+ numFractionalTrailingZeroDigits = 2,
+ altUnitNames = emptyMap(),
+ ),
+ ),
+ )
+ ),
+ ),
+ bankAppClick = {},
+ )
+ }
+}
\ No newline at end of file
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt
new file mode 100644
index 0000000..292f1d5
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt
@@ -0,0 +1,110 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2023 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.manual
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment.Companion.CenterHorizontally
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import net.taler.common.Amount
+import net.taler.wallet.R
+import net.taler.wallet.compose.CopyToClipboardButton
+import net.taler.wallet.withdraw.TransferData
+
+@Composable
+fun TransferBitcoin(
+ transfer: TransferData.Bitcoin,
+ transactionAmountRaw: Amount,
+ transactionAmountEffective: Amount,
+) {
+ Column(
+ modifier = Modifier.padding(all = 16.dp),
+ horizontalAlignment = CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(R.string.withdraw_manual_bitcoin_intro),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+
+ BitcoinSegwitAddresses(
+ amount = transfer.amountRaw,
+ address = transfer.account,
+ segwitAddresses = transfer.segwitAddresses,
+ )
+
+ transfer.withdrawalAccount.transferAmount?.let { amount ->
+ WithdrawalAmountTransfer(
+ amountRaw = transactionAmountRaw,
+ amountEffective = transactionAmountEffective,
+ conversionAmountRaw = amount,
+ )
+ }
+ }
+}
+
+@Composable
+fun BitcoinSegwitAddresses(amount: Amount, address: String, segwitAddresses:
List<String>) {
+ Column {
+ val allSegwitAddresses = listOf(address) + segwitAddresses
+ for (segwitAddress in allSegwitAddresses) {
+ Row(modifier = Modifier.padding(vertical = 8.dp)) {
+ Column(modifier = Modifier.weight(0.3f)) {
+ Text(
+ text = segwitAddress,
+ fontWeight = FontWeight.Normal,
+ fontFamily = FontFamily.Monospace,
+ style = MaterialTheme.typography.bodySmall,
+ )
+ Text(
+ text = if (segwitAddress == address)
+ amount.withCurrency("BTC").toString()
+ else SEGWIT_MIN.toString(),
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ }
+ }
+
+ CopyToClipboardButton(
+ modifier = Modifier
+ .padding(top = 16.dp, start = 6.dp, end = 6.dp)
+ .align(CenterHorizontally),
+ label = "Bitcoin",
+ content = getCopyText(amount, address, segwitAddresses),
+ )
+ }
+}
+
+private val SEGWIT_MIN = Amount("BTC", 0, 294)
+
+private fun getCopyText(amount: Amount, addr: String, segwitAddresses:
List<String>): String {
+ val sr = segwitAddresses.joinToString(separator = "\n") { s ->
+ "\n$s ${SEGWIT_MIN}\n"
+ }
+ return "$addr ${amount.withCurrency("BTC")}\n$sr"
+}
\ No newline at end of file
diff --git
a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt
b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt
new file mode 100644
index 0000000..a9e5f59
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt
@@ -0,0 +1,86 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2023 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.manual
+
+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.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import net.taler.common.Amount
+import net.taler.wallet.R
+import net.taler.wallet.cleanExchange
+import net.taler.wallet.transactions.TransactionInfoComposable
+import net.taler.wallet.withdraw.TransferData
+
+@Composable
+fun TransferIBAN(
+ transfer: TransferData.IBAN,
+ exchangeBaseUrl: String,
+ transactionAmountRaw: Amount,
+ transactionAmountEffective: Amount,
+) {
+ Column(
+ modifier = Modifier.padding(all = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(
+ R.string.withdraw_manual_ready_intro,
+ transfer.amountRaw.toString()),
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_warning),
+ style = MaterialTheme.typography.bodyMedium,
+ color = colorResource(R.color.notice_text),
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .padding(all = 8.dp)
+ .background(colorResource(R.color.notice_background))
+ .border(BorderStroke(2.dp,
colorResource(R.color.notice_border)))
+ .padding(all = 16.dp)
+ )
+
+ DetailRow(stringResource(R.string.withdraw_manual_ready_iban),
transfer.iban)
+ DetailRow(stringResource(R.string.withdraw_manual_ready_subject),
transfer.subject)
+
+ TransactionInfoComposable(
+ label = stringResource(R.string.withdraw_exchange),
+ info = cleanExchange(exchangeBaseUrl),
+ )
+
+ transfer.withdrawalAccount.transferAmount?.let { amount ->
+ WithdrawalAmountTransfer(
+ amountRaw = transactionAmountRaw,
+ amountEffective = transactionAmountEffective,
+ conversionAmountRaw = amount,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
index 7b63d1b..748a51a 100644
--- a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -98,7 +98,7 @@
android:layout_marginEnd="16dp"
android:gravity="center"
android:text="@string/withdraw_fees"
- android:visibility="invisible"
+ android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/feeView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
@@ -116,7 +116,7 @@
android:gravity="center"
android:textColor="?colorError"
android:textSize="20sp"
- android:visibility="invisible"
+ android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/exchangeIntroView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/wallet/src/main/res/navigation/nav_graph.xml
b/wallet/src/main/res/navigation/nav_graph.xml
index c0bd330..672bb77 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -177,6 +177,10 @@
android:id="@+id/action_nav_peer_pull_to_nav_main"
app:destination="@id/nav_main"
app:popUpTo="@id/nav_main" />
+ <action
+
android:id="@+id/action_nav_peer_pull_to_nav_transactions_detail_peer"
+ app:destination="@id/nav_transactions_detail_peer"
+ app:popUpTo="@id/nav_main" />
</fragment>
<fragment
@@ -192,6 +196,10 @@
android:id="@+id/action_nav_peer_push_to_nav_main"
app:destination="@id/nav_main"
app:popUpTo="@id/nav_main" />
+ <action
+
android:id="@+id/action_nav_peer_push_to_nav_transactions_detail_peer"
+ app:destination="@id/nav_transactions_detail_peer"
+ app:popUpTo="@id/nav_main" />
</fragment>
<fragment
@@ -368,6 +376,10 @@
android:id="@+id/action_global_promptWithdraw"
app:destination="@id/promptWithdraw" />
+ <action
+ android:id="@+id/action_global_manual_withdrawal"
+ app:destination="@id/nav_exchange_manual_withdrawal" />
+
<action
android:id="@+id/action_global_promptPayment"
app:destination="@id/promptPayment" />
diff --git a/wallet/src/main/res/values/strings.xml
b/wallet/src/main/res/values/strings.xml
index 731b03a..436116e 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -189,6 +189,7 @@ GNU Taler is immune against many types of fraud, such as
phishing of credit card
<string name="withdraw_initiated">Withdrawal initiated</string>
<string name="withdraw_title">Withdrawal</string>
+ <string name="withdraw_subtitle">Select target bank account</string>
<string name="withdraw_total">Withdraw</string>
<string name="withdraw_fees">Fee</string>
<string name="withdraw_restrict_age">Restrict Usage to Age</string>
@@ -216,6 +217,11 @@ GNU Taler is immune against many types of fraud, such as
phishing of credit card
<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>
+ <string name="withdraw_account">Account #%1$d</string>
+ <string name="withdraw_account_currency">Account #%1$d (%2$s)</string>
+ <string name="withdraw_transfer">Transfer</string>
+ <string name="withdraw_conversion">Conversion</string>
+ <string name="withdraw_conversion_support">This exchange supports currency
conversion</string>
<string name="exchange_settings_title">Exchanges</string>
<string name="exchange_settings_summary">Manage list of exchanges known to
this wallet</string>
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-taler-android] branch master updated (bdd47e0 -> 0f32834),
gnunet <=
- [taler-taler-android] 04/12: [wallet] Bump qtart to v0.9.4-dev.1, gnunet, 2024/01/08
- [taler-taler-android] 06/12: [wallet] Refactor p2p payments to show tx details when ready, gnunet, 2024/01/08
- [taler-taler-android] 05/12: [wallet] Layout improvement to manual withdrawal details, gnunet, 2024/01/08
- [taler-taler-android] 08/12: [wallet] Delete unused numCoins field, gnunet, 2024/01/08
- [taler-taler-android] 01/12: [wallet] Initial implementation of DD36., gnunet, 2024/01/08
- [taler-taler-android] 07/12: [wallet] Fix empty/null account list in manual withdrawal, gnunet, 2024/01/08
- [taler-taler-android] 03/12: [wallet] Manual withdrawal: hide fee when zero and relax paddings for smaller screens, gnunet, 2024/01/08
- [taler-taler-android] 02/12: [wallet] Fixed serialization issues, gnunet, 2024/01/08
- [taler-taler-android] 12/12: [wallet] support withdraw-exchange URI, gnunet, 2024/01/08
- [taler-taler-android] 11/12: [wallet] Add UX from now-deleted push/pull result screens into p2p tx details, gnunet, 2024/01/08