taler-android

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

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 }