taler-android

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

WalletNavHost.kt (14405B)


      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
     18 
     19 import androidx.activity.ComponentActivity
     20 import androidx.compose.animation.core.tween
     21 import androidx.compose.animation.fadeIn
     22 import androidx.compose.animation.fadeOut
     23 import androidx.compose.animation.slideInHorizontally
     24 import androidx.compose.animation.slideOutHorizontally
     25 import androidx.compose.foundation.layout.fillMaxSize
     26 import androidx.compose.runtime.Composable
     27 import androidx.compose.runtime.DisposableEffect
     28 import androidx.compose.ui.Modifier
     29 import androidx.compose.ui.platform.LocalContext
     30 import androidx.compose.ui.platform.LocalFocusManager
     31 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
     32 import androidx.navigation.NavController
     33 import androidx.navigation.NavHostController
     34 import androidx.navigation.compose.NavHost
     35 import androidx.navigation.compose.composable
     36 import androidx.navigation.toRoute
     37 import net.taler.wallet.accounts.AddBankAccountScreen
     38 import net.taler.wallet.accounts.BankAccountsScreen
     39 import net.taler.wallet.backend.TalerErrorInfo
     40 import net.taler.wallet.deposit.DepositScreen
     41 import net.taler.wallet.deposit.PayToUriScreen
     42 import net.taler.wallet.donau.DonauStatementScreen
     43 import net.taler.wallet.donau.SetDonauScreen
     44 import net.taler.wallet.exchanges.ExchangeListScreen
     45 import net.taler.wallet.exchanges.ExchangeShoppingScreen
     46 import net.taler.wallet.exchanges.ReviewExchangeTosScreen
     47 import net.taler.wallet.main.MainScreen
     48 import net.taler.wallet.main.MainViewModel
     49 import net.taler.wallet.payment.PayTemplateScreen
     50 import net.taler.wallet.peer.IncomingPullPaymentScreen
     51 import net.taler.wallet.peer.IncomingPushPaymentScreen
     52 import net.taler.wallet.peer.OutgoingPullScreen
     53 import net.taler.wallet.peer.OutgoingPushScreen
     54 import net.taler.wallet.settings.PerformanceStatsScreen
     55 import net.taler.wallet.tokens.TokenListScreen
     56 import net.taler.wallet.tokens.TokenViewMode
     57 import net.taler.wallet.transactions.TransactionDetailScreen
     58 import net.taler.wallet.transfer.WireTransferDetailsScreen
     59 import net.taler.wallet.withdraw.PromptWithdrawScreen
     60 
     61 typealias NavigateCallback = (
     62     dest: WalletDestination,
     63     popupToStart: Boolean,
     64 ) -> Unit
     65 
     66 @Composable
     67 fun WalletNavHost(
     68     navController: NavHostController,
     69     model: MainViewModel,
     70     modifier: Modifier = Modifier,
     71     launchUri: String?,
     72     onScanQr: () -> Unit,
     73     onFulfillPayment: (url: String) -> Unit,
     74     onShowError: (TalerErrorInfo) -> Unit,
     75 ) {
     76     val context = LocalContext.current
     77     val focusManager = LocalFocusManager.current
     78     val keyboardController = LocalSoftwareKeyboardController.current
     79     val startDestination = if (launchUri != null) {
     80         WalletDestination.HandleUri(launchUri)
     81     } else {
     82         WalletDestination.Main
     83     }
     84 
     85     val onNavigate: NavigateCallback = { dest, popupToStart ->
     86         navController.navigate(dest) {
     87             if (popupToStart) {
     88                 // If startDestination is HandleUri, it means we are in "intent mode"
     89                 // where we want to close the app on back.
     90                 // We pop everything up to the root (the NavHost's startDestination) inclusively.
     91                 if (startDestination is WalletDestination.HandleUri) {
     92                     popUpTo(navController.graph.id) {
     93                         inclusive = true
     94                     }
     95                 } else {
     96                     popUpTo(startDestination) {
     97                         inclusive = false
     98                     }
     99                 }
    100             }
    101         }
    102     }
    103 
    104     val onNavigateBack: () -> Unit = {
    105         (context as? ComponentActivity)?.onBackPressedDispatcher?.onBackPressed()
    106     }
    107 
    108     DisposableEffect(navController) {
    109         val listener = NavController.OnDestinationChangedListener { _, _, _ ->
    110             keyboardController?.hide()
    111             focusManager.clearFocus()
    112         }
    113         navController.addOnDestinationChangedListener(listener)
    114         onDispose {
    115             navController.removeOnDestinationChangedListener(listener)
    116         }
    117     }
    118 
    119     NavHost(
    120         navController = navController,
    121         startDestination = startDestination,
    122         modifier = modifier.fillMaxSize(),
    123         enterTransition = {
    124             fadeIn(tween(250))
    125         },
    126         exitTransition = {
    127             fadeOut(tween(200)) + slideOutHorizontally { -it / 2 }
    128         },
    129         popEnterTransition = {
    130             fadeIn(tween(250)) + slideInHorizontally { -it / 2 }
    131         },
    132         popExitTransition = {
    133             fadeOut(tween(200))
    134         },
    135     ) {
    136         composable<WalletDestination.Main> {
    137             MainScreen(
    138                 model = model,
    139                 onNavigate = onNavigate,
    140                 onScanQr = onScanQr,
    141                 onFulfillPayment = onFulfillPayment,
    142                 onShowError = onShowError,
    143             )
    144         }
    145         composable<WalletDestination.HandleUri> { backStackEntry ->
    146             val dest = backStackEntry.toRoute<WalletDestination.HandleUri>()
    147             HandleUriScreen(
    148                 model = model,
    149                 uriString = dest.uri,
    150                 onNavigate = onNavigate,
    151                 onNavigateBack = onNavigateBack,
    152                 onShowError = onShowError,
    153             )
    154         }
    155         composable<WalletDestination.PromptWithdraw> { backStackEntry ->
    156             val dest = backStackEntry.toRoute<WalletDestination.PromptWithdraw>()
    157             PromptWithdrawScreen(
    158                 model = model,
    159                 dest = dest,
    160                 onNavigate = onNavigate,
    161                 onNavigateBack = onNavigateBack,
    162             )
    163         }
    164         composable<WalletDestination.ExchangeList> {
    165             ExchangeListScreen(
    166                 model = model,
    167                 onNavigate = onNavigate,
    168                 onNavigateBack = onNavigateBack,
    169             )
    170         }
    171         composable<WalletDestination.BankAccounts> { backStackEntry ->
    172             val dest = backStackEntry.toRoute<WalletDestination.BankAccounts>()
    173             BankAccountsScreen(
    174                 model = model,
    175                 currency = dest.currency,
    176                 onNavigate = onNavigate,
    177                 onNavigateBack = onNavigateBack,
    178             )
    179         }
    180         composable<WalletDestination.AddBankAccount> { backStackEntry ->
    181             val dest = backStackEntry.toRoute<WalletDestination.AddBankAccount>()
    182             AddBankAccountScreen(
    183                 model = model,
    184                 bankAccountId = dest.bankAccountId,
    185                 onNavigateBack = onNavigateBack,
    186                 onShowError = onShowError,
    187             )
    188         }
    189         composable<WalletDestination.ReviewExchangeTOS> { backStackEntry ->
    190             val dest = backStackEntry.toRoute<WalletDestination.ReviewExchangeTOS>()
    191             ReviewExchangeTosScreen(
    192                 model = model,
    193                 exchangeBaseUrl = dest.exchangeBaseUrl,
    194                 readOnly = dest.readOnly,
    195                 onNavigateBack = onNavigateBack
    196             )
    197         }
    198         composable<WalletDestination.PaytoUri> { backStackEntry ->
    199             val dest = backStackEntry.toRoute<WalletDestination.PaytoUri>()
    200             PayToUriScreen(
    201                 model = model,
    202                 uri = dest.uri,
    203                 onNavigate = onNavigate,
    204                 onNavigateBack = onNavigateBack,
    205             )
    206         }
    207         composable<WalletDestination.Deposit> { backStackEntry ->
    208             val dest = backStackEntry.toRoute<WalletDestination.Deposit>()
    209             DepositScreen(
    210                 model = model,
    211                 dest = dest,
    212                 onNavigate = onNavigate,
    213                 onNavigateBack = onNavigateBack,
    214             )
    215         }
    216         composable<WalletDestination.OutgoingPush> {
    217             OutgoingPushScreen(
    218                 model = model,
    219                 onNavigate = onNavigate,
    220                 onNavigateBack = onNavigateBack,
    221                 onShowError = { onShowError(it) }
    222             )
    223         }
    224         composable<WalletDestination.OutgoingPull> {
    225             OutgoingPullScreen(
    226                 model = model,
    227                 onNavigate = onNavigate,
    228                 onNavigateBack = onNavigateBack,
    229                 onShowError = { onShowError(it) }
    230             )
    231         }
    232         composable<WalletDestination.PromptPullPayment> {
    233             IncomingPullPaymentScreen(
    234                 model = model,
    235                 onNavigate = onNavigate,
    236                 onNavigateBack = onNavigateBack,
    237                 onShowError = { onShowError(it) }
    238             )
    239         }
    240         composable<WalletDestination.PromptPushPayment> {
    241             IncomingPushPaymentScreen(
    242                 model = model,
    243                 onNavigate = onNavigate,
    244                 onNavigateBack = onNavigateBack,
    245                 onShowError = { onShowError(it) }
    246             )
    247         }
    248         composable<WalletDestination.DiscountList> {
    249             TokenListScreen(
    250                 model = model,
    251                 onNavigate = onNavigate,
    252                 onNavigateBack = onNavigateBack,
    253                 viewMode = TokenViewMode.Discounts,
    254             )
    255         }
    256         composable<WalletDestination.PassList> {
    257             TokenListScreen(
    258                 model = model,
    259                 onNavigate = onNavigate,
    260                 onNavigateBack = onNavigateBack,
    261                 viewMode = TokenViewMode.Passes,
    262             )
    263         }
    264         composable<WalletDestination.SetDonau> { backStackEntry ->
    265             val dest = backStackEntry.toRoute<WalletDestination.SetDonau>()
    266             SetDonauScreen(
    267                 model = model,
    268                 donauBaseUrl = dest.donauBaseUrl,
    269                 onShowMessage = { /* TODO */ },
    270                 onShowError = { onShowError(it) },
    271                 onNavigateBack = onNavigateBack,
    272             )
    273         }
    274         composable<WalletDestination.DonauStatement> { backStackEntry ->
    275             val dest = backStackEntry.toRoute<WalletDestination.DonauStatement>()
    276             DonauStatementScreen(
    277                 model = model,
    278                 host = dest.host,
    279                 onNavigateBack = onNavigateBack,
    280             )
    281         }
    282         composable<WalletDestination.TransactionPayment> {
    283             TransactionDetailScreen(
    284                 model = model,
    285                 destination = WalletDestination.TransactionPayment,
    286                 onNavigate = onNavigate,
    287                 onNavigateBack = onNavigateBack,
    288             )
    289         }
    290         composable<WalletDestination.TransactionWithdrawal> {
    291         TransactionDetailScreen(
    292                 model = model,
    293                 destination = WalletDestination.TransactionWithdrawal,
    294                 onNavigate = onNavigate,
    295                 onNavigateBack = onNavigateBack,
    296             )
    297         }
    298         composable<WalletDestination.TransactionRefund> {
    299             TransactionDetailScreen(
    300                 model = model,
    301                 destination = WalletDestination.TransactionRefund,
    302                 onNavigate = onNavigate,
    303                 onNavigateBack = onNavigateBack,
    304             )
    305         }
    306         composable<WalletDestination.TransactionRefresh> {
    307             TransactionDetailScreen(
    308                 model = model,
    309                 destination = WalletDestination.TransactionRefresh,
    310                 onNavigate = onNavigate,
    311                 onNavigateBack = onNavigateBack,
    312             )
    313         }
    314         composable<WalletDestination.TransactionDeposit> {
    315             TransactionDetailScreen(
    316                 model = model,
    317                 destination = WalletDestination.TransactionDeposit,
    318                 onNavigate = onNavigate,
    319                 onNavigateBack = onNavigateBack,
    320             )
    321         }
    322         composable<WalletDestination.TransactionPeer> {
    323             TransactionDetailScreen(
    324                 model = model,
    325                 destination = WalletDestination.TransactionPeer,
    326                 onNavigate = onNavigate,
    327                 onNavigateBack = onNavigateBack,
    328             )
    329         }
    330         composable<WalletDestination.TransactionLoss> {
    331             TransactionDetailScreen(
    332                 model = model,
    333                 destination = WalletDestination.TransactionLoss,
    334                 onNavigate = onNavigate,
    335                 onNavigateBack = onNavigateBack,
    336             )
    337         }
    338         composable<WalletDestination.TransactionDummy> {
    339             TransactionDetailScreen(
    340                 model = model,
    341                 destination = WalletDestination.TransactionDummy,
    342                 onNavigate = onNavigate,
    343                 onNavigateBack = onNavigateBack,
    344             )
    345         }
    346         composable<WalletDestination.PromptPayTemplate> { backStackEntry ->
    347             val dest = backStackEntry.toRoute<WalletDestination.PromptPayTemplate>()
    348             PayTemplateScreen(
    349                 model = model,
    350                 uri = dest.uri,
    351                 onNavigate = onNavigate,
    352                 onNavigateBack = onNavigateBack,
    353                 onShowError = { onShowError(it) }
    354             )
    355         }
    356         composable<WalletDestination.ExchangeShopping> {
    357             ExchangeShoppingScreen(
    358                 model = model,
    359                 onNavigateBack = onNavigateBack,
    360             )
    361         }
    362         composable<WalletDestination.WireTransferDetails> { backStackEntry ->
    363             val dest = backStackEntry.toRoute<WalletDestination.WireTransferDetails>()
    364             WireTransferDetailsScreen(
    365                 model = model,
    366                 showQrCodes = dest.showQrCodes,
    367                 onNavigateBack = onNavigateBack,
    368             )
    369         }
    370         composable<WalletDestination.PerformanceStats> {
    371             PerformanceStatsScreen(
    372                 model = model,
    373                 onNavigateBack = onNavigateBack,
    374             )
    375         }
    376     }
    377 }