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 }