PromptWithdrawScreen.kt (7330B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2026 Taler Systems S.A. 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 package net.taler.wallet.withdraw 18 19 import androidx.compose.runtime.Composable 20 import androidx.compose.runtime.LaunchedEffect 21 import androidx.compose.runtime.getValue 22 import androidx.compose.runtime.livedata.observeAsState 23 import androidx.compose.runtime.mutableStateOf 24 import androidx.compose.runtime.remember 25 import androidx.compose.runtime.setValue 26 import androidx.compose.ui.platform.LocalContext 27 import androidx.lifecycle.map 28 import net.taler.common.Amount 29 import net.taler.wallet.R 30 import net.taler.wallet.WalletDestination 31 import net.taler.wallet.compose.AmountScope 32 import net.taler.wallet.compose.GlobalScaffold 33 import net.taler.wallet.compose.LoadingScreen 34 import net.taler.wallet.compose.collectAsStateLifecycleAware 35 import net.taler.wallet.exchanges.SelectExchangeComposable 36 import net.taler.wallet.main.MainViewModel 37 import net.taler.wallet.main.ViewMode 38 import androidx.compose.material3.Text 39 import androidx.compose.ui.res.stringResource 40 import androidx.compose.ui.Modifier 41 import androidx.compose.foundation.layout.fillMaxSize 42 import androidx.compose.foundation.layout.padding 43 import net.taler.wallet.NavigateCallback 44 45 @Composable 46 fun PromptWithdrawScreen( 47 model: MainViewModel, 48 dest: WalletDestination.PromptWithdraw, 49 onNavigate: NavigateCallback, 50 onNavigateBack: () -> Unit, 51 ) { 52 val context = LocalContext.current 53 val withdrawManager = model.withdrawManager 54 val balanceManager = model.balanceManager 55 val transactionManager = model.transactionManager 56 val exchangeManager = model.exchangeManager 57 58 val status by withdrawManager.withdrawStatus.collectAsStateLifecycleAware() 59 val viewMode by model.viewMode.collectAsStateLifecycleAware() 60 val devMode by model.devMode.observeAsState(false) 61 62 var showExchangeSelect by remember { mutableStateOf(false) } 63 64 val selectedScope = remember(viewMode) { 65 (viewMode as? ViewMode.Transactions)?.selectedScope 66 } 67 68 val scopes by balanceManager.balances 69 .map { bl -> bl.map { it.scopeInfo } } 70 .map { bl -> 71 val scope = status.amountInfo?.scopeInfo 72 if (scope != null && !bl.contains(scope)) { 73 bl + scope 74 } else bl 75 }.observeAsState(emptyList()) 76 77 val initialAmount = status.selectedAmount 78 ?: selectedScope?.let { Amount.zero(it.currency) } 79 ?: scopes.firstOrNull()?.let { Amount.zero(it.currency) } 80 81 val initialScope = status.selectedScope 82 ?: selectedScope 83 ?: scopes.firstOrNull() 84 85 LaunchedEffect(status.status) { 86 if (status.status == WithdrawStatus.Status.None) { 87 if (dest.withdrawUri != null) { 88 withdrawManager.prepareBankIntegratedWithdrawal(dest.withdrawUri, context, loading = true) 89 } else if (dest.withdrawExchangeUri != null) { 90 withdrawManager.prepareManualWithdrawal(dest.withdrawExchangeUri) 91 } else if (dest.exchangeBaseUrl != null) { 92 withdrawManager.getWithdrawalDetailsForExchange(dest.exchangeBaseUrl, loading = true) 93 } else if (initialAmount != null) { 94 withdrawManager.getWithdrawalDetailsForAmount( 95 amount = initialAmount, 96 scopeInfo = initialScope, 97 ) 98 } 99 } 100 } 101 102 LaunchedEffect(status.status) { 103 when (status.status) { 104 WithdrawStatus.Status.Success, 105 WithdrawStatus.Status.ManualTransferRequired, 106 WithdrawStatus.Status.AlreadyConfirmed -> { 107 status.transactionId?.let { txId -> 108 if (transactionManager.selectTransaction(txId)) { 109 status.amountInfo?.scopeInfo?.let { s -> model.selectScope(s) } 110 onNavigate(WalletDestination.TransactionWithdrawal, true) 111 } else { 112 onNavigateBack() 113 } 114 } 115 } 116 else -> {} 117 } 118 } 119 120 // Detect ToS acceptance 121 val exchanges by exchangeManager.exchanges.observeAsState() 122 LaunchedEffect(exchanges) { 123 exchanges?.let { withdrawManager.refreshTosStatus(it) } 124 } 125 126 GlobalScaffold( 127 model = model, 128 modifier = Modifier.fillMaxSize(), 129 title = { 130 Text( 131 status.selectedSpec?.symbol?.let { symbol -> 132 stringResource(R.string.nav_prompt_withdraw_currency, symbol) 133 } ?: dest.amount?.let { Amount.fromJSONString(it) }?.currency?.let { currency -> 134 stringResource(R.string.nav_prompt_withdraw_currency, currency) 135 } ?: stringResource(R.string.nav_prompt_withdraw), 136 ) 137 }, 138 onNavigateBack = onNavigateBack, 139 ) { paddingValues -> 140 if (status.selectedAmount == null && status.selectedScope == null) { 141 LoadingScreen(Modifier.padding(paddingValues)) 142 } else { 143 WithdrawalShowInfo( 144 modifier = Modifier.padding(paddingValues), 145 status = status, 146 devMode = devMode, 147 initialAmountScope = initialAmount?.let { amount -> 148 initialScope?.let { scope -> 149 AmountScope(amount, scope) 150 } 151 }, 152 editableScope = dest.editableCurrency, 153 scopes = scopes, 154 onSelectExchange = { 155 showExchangeSelect = true 156 }, 157 onSelectAmount = { amount, scope -> 158 withdrawManager.getWithdrawalDetailsForAmount( 159 amount = amount, 160 scopeInfo = scope, 161 loading = scope != status.selectedScope, 162 ) 163 }, 164 onTosReview = { 165 status.exchangeBaseUrl?.let { 166 onNavigate(WalletDestination.ReviewExchangeTOS(it), false) 167 } 168 }, 169 onConfirm = { age -> 170 status.selectedScope?.let { model.selectScope(it) } 171 withdrawManager.acceptWithdrawal(age) 172 }, 173 ) 174 } 175 } 176 177 if (showExchangeSelect) { 178 val possibleExchanges = status.uriInfo?.possibleExchanges ?: emptyList() 179 SelectExchangeComposable( 180 exchanges = possibleExchanges, 181 onExchangeSelected = { 182 showExchangeSelect = false 183 withdrawManager.getWithdrawalDetailsForExchange(it.exchangeBaseUrl) 184 } 185 ) 186 } 187 }