gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]