taler-android

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

ReviewExchangeTosScreen.kt (8406B)


      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.exchanges
     18 
     19 import androidx.compose.foundation.clickable
     20 import androidx.compose.foundation.layout.fillMaxWidth
     21 import androidx.compose.foundation.layout.padding
     22 import androidx.compose.foundation.lazy.LazyColumn
     23 import androidx.compose.material3.Button
     24 import androidx.compose.material3.DropdownMenuItem
     25 import androidx.compose.material3.ExperimentalMaterial3Api
     26 import androidx.compose.material3.ExposedDropdownMenuBox
     27 import androidx.compose.material3.LocalTextStyle
     28 import androidx.compose.material3.MaterialTheme
     29 import androidx.compose.material3.OutlinedTextField
     30 import androidx.compose.material3.Text
     31 import androidx.compose.runtime.Composable
     32 import androidx.compose.runtime.LaunchedEffect
     33 import androidx.compose.runtime.getValue
     34 import androidx.compose.runtime.mutableStateOf
     35 import androidx.compose.runtime.remember
     36 import androidx.compose.runtime.rememberCoroutineScope
     37 import androidx.compose.runtime.setValue
     38 import androidx.compose.ui.Modifier
     39 import androidx.compose.ui.res.stringResource
     40 import androidx.compose.ui.tooling.preview.Preview
     41 import androidx.compose.ui.unit.dp
     42 import com.mikepenz.markdown.m3.Markdown
     43 import com.mikepenz.markdown.m3.markdownTypography
     44 import com.mikepenz.markdown.model.markdownPadding
     45 import kotlinx.coroutines.launch
     46 import net.taler.wallet.R
     47 import net.taler.wallet.backend.TalerErrorInfo
     48 import net.taler.wallet.compose.BottomButtonBox
     49 import net.taler.wallet.compose.EmptyComposable
     50 import net.taler.wallet.compose.ErrorComposable
     51 import net.taler.wallet.compose.GlobalScaffold
     52 import net.taler.wallet.compose.LoadingScreen
     53 import net.taler.wallet.compose.TalerSurface
     54 import net.taler.wallet.main.MainViewModel
     55 import net.taler.wallet.systemBarsPaddingBottom
     56 import java.util.Locale
     57 
     58 @Composable
     59 fun ReviewExchangeTosScreen(
     60     model: MainViewModel,
     61     exchangeBaseUrl: String,
     62     readOnly: Boolean = false,
     63     onNavigateBack: () -> Unit,
     64 ) {
     65     val exchangeManager = model.exchangeManager
     66     val scope = rememberCoroutineScope()
     67     var tos: TosResponse? by remember { mutableStateOf(null) }
     68     var selectedLang by remember { mutableStateOf(Locale.getDefault().language) }
     69 
     70     LaunchedEffect(selectedLang) {
     71         tos = null
     72         tos = exchangeManager.getExchangeTos(exchangeBaseUrl, selectedLang)
     73     }
     74 
     75     TalerSurface {
     76         tos?.let { currentTos ->
     77             ReviewExchangeTosComposable(
     78                 model = model,
     79                 tos = currentTos,
     80                 readOnly = readOnly,
     81                 onSelectLang = { selectedLang = it },
     82                 onAcceptTos = {
     83                     scope.launch {
     84                         if (exchangeManager.acceptCurrentTos(
     85                                 exchangeBaseUrl = exchangeBaseUrl,
     86                                 currentEtag = currentTos.currentEtag,
     87                             )
     88                         ) {
     89                             onNavigateBack()
     90                         }
     91                     }
     92                 },
     93                 onNavigateBack = onNavigateBack,
     94             )
     95         } ?: LoadingScreen()
     96     }
     97 }
     98 
     99 @OptIn(ExperimentalMaterial3Api::class)
    100 @Composable
    101 fun ReviewExchangeTosComposable(
    102     tos: TosResponse,
    103     readOnly: Boolean,
    104     onSelectLang: (String) -> Unit,
    105     onAcceptTos: () -> Unit,
    106     onNavigateBack: () -> Unit,
    107     model: MainViewModel? = null,
    108 ) {
    109     if (tos.status == ExchangeTosStatus.MissingTos) {
    110         EmptyComposable(message = stringResource(R.string.exchange_tos_missing))
    111         return
    112     }
    113 
    114     var expanded by remember { mutableStateOf(false) }
    115 
    116     GlobalScaffold(
    117         model = model,
    118         title = { Text(stringResource(R.string.nav_exchange_tos)) },
    119         onNavigateBack = onNavigateBack,
    120         bottomBar = {
    121             if (!readOnly) BottomButtonBox {
    122                 Button(
    123                     modifier = Modifier
    124                         .systemBarsPaddingBottom(),
    125                     onClick = onAcceptTos,
    126                 ) {
    127                     Text(stringResource(R.string.exchange_tos_accept))
    128                 }
    129             }
    130         },
    131     ) { innerPadding ->
    132         LazyColumn(Modifier.padding(innerPadding)) {
    133             if (tos.tosAvailableLanguages.size > 1) item {
    134                 ExposedDropdownMenuBox(
    135                     expanded = expanded,
    136                     onExpandedChange = { expanded = it },
    137                 ) {
    138                     OutlinedTextField(
    139                         modifier = Modifier
    140                             .padding(horizontal = 16.dp)
    141                             .fillMaxWidth()
    142                             .clickable { expanded = true },
    143                         label = { Text(stringResource(R.string.language)) },
    144                         value = tos.contentLanguage?.let {
    145                             Locale(it).displayLanguage
    146                         } ?: "",
    147                         onValueChange = {},
    148                         readOnly = true,
    149                         enabled = false,
    150                         singleLine = true,
    151                         textStyle = LocalTextStyle.current.copy( // show text as if not disabled
    152                             color = MaterialTheme.colorScheme.onSurface,
    153                         ),
    154                     )
    155 
    156                     ExposedDropdownMenu (
    157                         expanded = expanded,
    158                         onDismissRequest = { expanded = false }
    159                     ) {
    160                         tos.tosAvailableLanguages.forEach {
    161                             DropdownMenuItem(
    162                                 { Text("${Locale(it).displayLanguage}") },
    163                                 onClick = {
    164                                     onSelectLang(it)
    165                                     expanded = false
    166                                 }
    167                             )
    168                         }
    169                     }
    170                 }
    171             }
    172 
    173             item {
    174                 Markdown(
    175                     content = tos.content.trimIndent(),
    176                     modifier = Modifier.padding(16.dp),
    177                     typography = markdownTypography(
    178                         h1 = MaterialTheme.typography.headlineLarge,
    179                         h2 = MaterialTheme.typography.headlineMedium,
    180                         h3 = MaterialTheme.typography.headlineSmall,
    181                         h4 = MaterialTheme.typography.titleLarge,
    182                         h5 = MaterialTheme.typography.titleMedium,
    183                         h6 = MaterialTheme.typography.titleSmall,
    184                         text = MaterialTheme.typography.bodyMedium,
    185                         paragraph = MaterialTheme.typography.bodyMedium,
    186                     ),
    187                     padding = markdownPadding(
    188                         block = 5.dp,
    189                     ),
    190                     error = { modifier ->
    191                         ErrorComposable(
    192                             TalerErrorInfo.makeCustomError(
    193                                 stringResource(R.string.exchange_tos_error, "")),
    194                             modifier = modifier,
    195                             devMode = false,
    196                         )
    197                     },
    198                 )
    199             }
    200         }
    201     }
    202 }
    203 
    204 @Preview
    205 @Composable
    206 fun ReviewExchangeTosComposablePreview() {
    207     TalerSurface {
    208         val tos = TosResponse(
    209             status = ExchangeTosStatus.Proposed,
    210             content = "# Terms of service\nThis is a terms of service, obviously.\n## H2\n### H3\n#### H4\n##### H5\n###### H6",
    211             currentEtag = "1.2.0",
    212             contentLanguage = "en",
    213             tosAvailableLanguages = listOf("en", "en_US"),
    214         )
    215 
    216         ReviewExchangeTosComposable( tos, false, {}, {}, {}, null)
    217     }
    218 }