taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

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 }