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 }