GlobalScaffold.kt (6080B)
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.compose 18 19 import androidx.compose.foundation.background 20 import androidx.compose.foundation.layout.Column 21 import androidx.compose.foundation.layout.PaddingValues 22 import androidx.compose.foundation.layout.Row 23 import androidx.compose.foundation.layout.RowScope 24 import androidx.compose.foundation.layout.WindowInsets 25 import androidx.compose.foundation.layout.WindowInsetsSides 26 import androidx.compose.foundation.layout.exclude 27 import androidx.compose.foundation.layout.fillMaxSize 28 import androidx.compose.foundation.layout.fillMaxWidth 29 import androidx.compose.foundation.layout.only 30 import androidx.compose.foundation.layout.padding 31 import androidx.compose.foundation.layout.systemBars 32 import androidx.compose.material.icons.Icons 33 import androidx.compose.material.icons.automirrored.filled.ArrowBack 34 import androidx.compose.material.icons.filled.BugReport 35 import androidx.compose.material3.ExperimentalMaterial3Api 36 import androidx.compose.material3.Icon 37 import androidx.compose.material3.IconButton 38 import androidx.compose.material3.MaterialTheme 39 import androidx.compose.material3.Scaffold 40 import androidx.compose.material3.ScaffoldDefaults 41 import androidx.compose.material3.Text 42 import androidx.compose.material3.TopAppBar 43 import androidx.compose.material3.TopAppBarDefaults 44 import androidx.compose.material3.rememberTopAppBarState 45 import androidx.compose.runtime.Composable 46 import androidx.compose.runtime.livedata.observeAsState 47 import androidx.compose.ui.Modifier 48 import androidx.compose.ui.input.nestedscroll.nestedScroll 49 import androidx.compose.ui.platform.LocalFocusManager 50 import androidx.compose.ui.res.stringResource 51 import androidx.compose.ui.text.style.TextAlign 52 import androidx.compose.ui.unit.dp 53 import net.taler.wallet.R 54 import net.taler.wallet.main.MainViewModel 55 56 @OptIn(ExperimentalMaterial3Api::class) 57 @Composable 58 fun GlobalScaffold( 59 model: MainViewModel?, 60 modifier: Modifier = Modifier, 61 title: (@Composable () -> Unit)? = null, 62 navigationIcon: (@Composable () -> Unit)? = null, 63 actions: @Composable RowScope.() -> Unit = {}, 64 floatingActionButton: @Composable () -> Unit = {}, 65 bottomBar: @Composable () -> Unit = {}, 66 tabs: @Composable () -> Unit = {}, 67 snackbarHost: @Composable () -> Unit = {}, 68 contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets.exclude( 69 WindowInsets.systemBars.only(WindowInsetsSides.Bottom), 70 ), 71 onNavigateBack: (() -> Unit)? = null, 72 content: @Composable ((PaddingValues) -> Unit), 73 ) { 74 val scrollBehavior = TopAppBarDefaults 75 .pinnedScrollBehavior(rememberTopAppBarState()) 76 val localFocusManager = LocalFocusManager.current 77 78 val devMode = model?.devMode?.observeAsState(false) 79 val online = model?.networkManager?.networkStatus?.observeAsState(true) 80 81 Scaffold( 82 modifier = modifier 83 .fillMaxSize() 84 .nestedScroll(scrollBehavior.nestedScrollConnection), 85 topBar = { 86 Column { 87 if (title != null) { 88 TopAppBar( 89 title = title, 90 navigationIcon = navigationIcon ?: { 91 if (onNavigateBack != null) { 92 IconButton(onClick = { 93 localFocusManager.clearFocus() 94 onNavigateBack() 95 }) { 96 Icon( 97 Icons.AutoMirrored.Default.ArrowBack, 98 contentDescription = stringResource(R.string.button_back), 99 ) 100 } 101 } 102 }, 103 actions = { 104 Row { 105 actions() 106 if (devMode != null && devMode.value) { 107 IconButton(onClick = { 108 model.showObservabilityLog() 109 }) { 110 Icon( 111 Icons.Default.BugReport, 112 contentDescription = stringResource(R.string.observability_title), 113 ) 114 } 115 } 116 } 117 }, 118 scrollBehavior = scrollBehavior, 119 ) 120 } 121 122 tabs() 123 124 if (online != null && !online.value) Text( 125 text = stringResource(R.string.offline_banner), 126 textAlign = TextAlign.Center, 127 modifier = Modifier 128 .background(MaterialTheme.colorScheme.errorContainer) 129 .padding(8.dp) 130 .fillMaxWidth(), 131 color = MaterialTheme.colorScheme.onErrorContainer, 132 ) 133 } 134 }, 135 floatingActionButton = floatingActionButton, 136 bottomBar = bottomBar, 137 contentWindowInsets = contentWindowInsets, 138 snackbarHost = snackbarHost, 139 ) { innerPadding -> 140 content(innerPadding) 141 } 142 }