From 9b9693fe4fab574401a95f6444759581ed601860 Mon Sep 17 00:00:00 2001 From: pepaskopec-blip Date: Fri, 18 Sep 2026 13:22:43 +0200 Subject: [PATCH] Keep Android dialogue text readable and move word picks out of the sentence. Co-authored-by: Cursor --- android/app/build.gradle.kts | 4 +- .../org/maturita/maturita/MainActivity.kt | 6 +- .../org/maturita/maturita/ui/Components.kt | 66 +++++-- .../org/maturita/maturita/ui/Exercises.kt | 166 ++++++++++-------- .../java/org/maturita/maturita/ui/Screens.kt | 7 +- 5 files changed, 160 insertions(+), 89 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 36dc28a..9eddf4c 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -13,8 +13,8 @@ android { applicationId = "org.maturita.maturita" minSdk = 26 targetSdk = 35 - versionCode = 3 - versionName = "android-3" + versionCode = 4 + versionName = "android-4" buildConfigField("String", "COMMIT", "\"$commit\"") buildConfigField("String", "UPDATE_REPO", "\"pepaskopec-blip/maturita.c\"") buildConfigField("String", "UPDATE_BRANCH", "\"builds\"") diff --git a/android/app/src/main/java/org/maturita/maturita/MainActivity.kt b/android/app/src/main/java/org/maturita/maturita/MainActivity.kt index e8c4b7f..50bf416 100644 --- a/android/app/src/main/java/org/maturita/maturita/MainActivity.kt +++ b/android/app/src/main/java/org/maturita/maturita/MainActivity.kt @@ -53,6 +53,8 @@ import org.maturita.maturita.ui.SearchOverlay import org.maturita.maturita.ui.SettingsIcon import org.maturita.maturita.ui.SettingsSheet import org.maturita.maturita.ui.SlidesScreen +import org.maturita.maturita.ui.appTextStyle +import org.maturita.maturita.ui.appTypography import org.maturita.maturita.ui.StatsIcon import org.maturita.maturita.ui.StatsScreen import org.maturita.maturita.ui.SubjectsScreen @@ -153,7 +155,7 @@ fun MaturitaApp(vm: AppViewModel) { error = p.error, ) } - MaterialTheme(colorScheme = scheme) { + MaterialTheme(colorScheme = scheme, typography = appTypography()) { MaturitaAppBody(vm) } } @@ -183,7 +185,7 @@ private fun MaturitaAppBody(vm: AppViewModel) { .padding(horizontal = 12.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, ) { - Text("maturita.c", color = p.text, fontWeight = FontWeight.Bold, fontSize = 15.sp, modifier = Modifier.weight(1f)) + Text("maturita.c", style = appTextStyle(15.sp, FontWeight.Medium, color = p.text), modifier = Modifier.weight(1f)) IconHit({ vm.searchOpen = true }) { SearchIcon(p.text) } IconHit({ vm.go(Route.Stats) }) { StatsIcon(p.text) } IconHit({ vm.settingsOpen = true }) { SettingsIcon(p.text) } diff --git a/android/app/src/main/java/org/maturita/maturita/ui/Components.kt b/android/app/src/main/java/org/maturita/maturita/ui/Components.kt index 0973d01..141409c 100644 --- a/android/app/src/main/java/org/maturita/maturita/ui/Components.kt +++ b/android/app/src/main/java/org/maturita/maturita/ui/Components.kt @@ -19,16 +19,54 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.material3.Typography import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontSynthesis import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.maturita.maturita.AppViewModel import org.maturita.maturita.data.Palette +fun appTextStyle( + fontSize: TextUnit, + fontWeight: FontWeight = FontWeight.Normal, + textAlign: TextAlign = TextAlign.Unspecified, + color: Color = Color.Unspecified, +): TextStyle { + val weight = if (fontWeight >= FontWeight.Bold) FontWeight.Medium else fontWeight + return TextStyle( + color = color, + fontSize = fontSize, + fontWeight = weight, + fontFamily = FontFamily.SansSerif, + fontSynthesis = FontSynthesis.None, + lineHeight = fontSize * 1.4f, + letterSpacing = 0.sp, + textAlign = textAlign, + ) +} + +fun appTypography(): Typography { + val base = appTextStyle(16.sp) + return Typography( + bodyLarge = base, + bodyMedium = appTextStyle(14.sp), + bodySmall = appTextStyle(12.sp), + titleLarge = appTextStyle(22.sp, FontWeight.Medium), + titleMedium = appTextStyle(18.sp, FontWeight.Medium), + titleSmall = appTextStyle(16.sp, FontWeight.Medium), + labelLarge = appTextStyle(14.sp, FontWeight.Medium), + labelMedium = appTextStyle(12.sp, FontWeight.Medium), + headlineMedium = appTextStyle(24.sp, FontWeight.Medium), + ) +} + private val Pill = RoundedCornerShape(999.dp) private val Card = RoundedCornerShape(20.dp) @@ -42,7 +80,7 @@ fun PrimaryButton(label: String, palette: Palette, modifier: Modifier = Modifier .padding(horizontal = 22.dp, vertical = 12.dp), contentAlignment = Alignment.Center, ) { - Text(label, color = palette.onAccent, fontWeight = FontWeight.SemiBold, fontSize = 15.sp) + Text(label, style = appTextStyle(15.sp, FontWeight.Medium, color = palette.onAccent)) } } @@ -55,7 +93,7 @@ fun PillButton(label: String, palette: Palette, modifier: Modifier = Modifier, o .clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 8.dp), ) { - Text(label, color = palette.text, fontWeight = FontWeight.SemiBold, fontSize = 13.sp) + Text(label, style = appTextStyle(13.sp, FontWeight.Medium, color = palette.text)) } } @@ -73,15 +111,23 @@ fun IconHit(onClick: () -> Unit, content: @Composable () -> Unit) { @Composable fun PageTop(vm: AppViewModel, back: () -> Unit, title: String, subtitle: String? = null) { val p = vm.palette - Box(Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 14.dp)) { - Box(Modifier.align(Alignment.CenterStart)) { - IconHit(back) { BackIcon(p.text) } - } - Column(Modifier.align(Alignment.Center).padding(horizontal = 48.dp), horizontalAlignment = Alignment.CenterHorizontally) { - Text(title, color = p.text, fontSize = 24.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center) - if (!subtitle.isNullOrBlank()) { - Text(subtitle, color = p.subtext, fontSize = 14.sp, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 4.dp)) + Column(Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 14.dp)) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Box(Modifier.align(Alignment.CenterStart)) { + IconHit(back) { BackIcon(p.text) } } + Text( + title, + modifier = Modifier.padding(horizontal = 48.dp), + style = appTextStyle(22.sp, FontWeight.Medium, TextAlign.Center, p.text), + ) + } + if (!subtitle.isNullOrBlank()) { + Text( + subtitle, + modifier = Modifier.fillMaxWidth().padding(top = 6.dp), + style = appTextStyle(14.sp, textAlign = TextAlign.Center, color = p.subtext), + ) } } } diff --git a/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt b/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt index 5e0a265..fb62ca4 100644 --- a/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt +++ b/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt @@ -37,6 +37,8 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import org.maturita.maturita.AppViewModel import org.maturita.maturita.data.J import org.maturita.maturita.data.Palette @@ -519,8 +521,9 @@ private fun DialogEx(vm: AppViewModel, title: String, sub: String?, spec: J, onD Text(d.str("name"), color = vm.palette.subtext, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(vertical = 6.dp)) d.arr("rows").forEach { row -> val idx = i++ - ComboLine(vm, pool, picks, expected, idx, row.str("answer"), - prefix = "${row.str("speaker")} ${row.str("before")}", + ComboLine( + vm, pool, picks, expected, idx, row.str("answer"), + prefix = listOf(row.str("speaker"), row.str("before")).filter { it.isNotBlank() }.joinToString(" "), suffix = row.str("after"), meaning = meanings.getOrNull(idx), revealed = revealed, @@ -644,6 +647,7 @@ private fun ComboRows( } } +@OptIn(ExperimentalLayoutApi::class) @Composable private fun ComboLine( vm: AppViewModel, @@ -663,60 +667,89 @@ private fun ComboLine( } val p = vm.palette var open by remember { mutableStateOf(false) } - val box = @Composable { - Column { - Box( - Modifier - .clip(RoundedCornerShape(12.dp)) - .background(p.mantle) - .border(1.dp, if (revealed && picks.getOrNull(idx).orEmpty().isNotEmpty()) { - if (answerAccepts(normalizeAnswer(picks.getOrNull(idx).orEmpty()), answer)) p.success else p.error - } else p.text.copy(0.1f), RoundedCornerShape(12.dp)) - .clickable { open = !open } - .padding(horizontal = 12.dp, vertical = 8.dp), - ) { - val pick = picks.getOrNull(idx).orEmpty() - Text(pick.ifEmpty { "—" }, color = if (pick.isEmpty()) p.overlay else p.text) - } - if (open) { - Column( - Modifier - .padding(top = 4.dp) - .heightIn(max = 220.dp) - .verticalScroll(rememberScrollState()) - .clip(RoundedCornerShape(12.dp)) - .background(p.surface0) - .border(1.dp, p.text.copy(0.08f), RoundedCornerShape(12.dp)), - ) { - pool.forEach { w -> - Text( - w, - color = p.text, - modifier = Modifier - .fillMaxWidth() - .clickable { - if (idx in picks.indices) picks[idx] = w - open = false - } - .padding(horizontal = 12.dp, vertical = 8.dp), - ) - } - } - } + val pick = picks.getOrNull(idx).orEmpty() + val mark = if (revealed && pick.isNotEmpty()) { + answerAccepts(normalizeAnswer(pick), answer) + } else null + val chip = @Composable { + Box( + Modifier + .clip(RoundedCornerShape(12.dp)) + .background(p.mantle) + .border( + 1.dp, + when (mark) { + true -> p.success + false -> p.error + null -> p.text.copy(0.12f) + }, + RoundedCornerShape(12.dp), + ) + .clickable { open = true } + .padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Text( + pick.ifEmpty { "—" }, + style = appTextStyle(15.sp, color = if (pick.isEmpty()) p.overlay else p.text), + ) } } if (inline) { - box() - return + chip() + } else { + FlowRow( + Modifier.fillMaxWidth().padding(bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.Center, + ) { + if (prefix.isNotBlank()) { + Text(prefix, style = appTextStyle(16.sp, color = p.text)) + } + chip() + if (suffix.isNotBlank()) { + Text(suffix, style = appTextStyle(16.sp, color = p.text)) + } + } } - Row(Modifier.fillMaxWidth().padding(bottom = 8.dp), verticalAlignment = Alignment.CenterVertically) { - if (prefix.isNotBlank()) Text(prefix, color = p.text, modifier = Modifier.padding(end = 6.dp)) - box() - if (suffix.isNotBlank()) Text(suffix, color = p.text, modifier = Modifier.padding(start = 6.dp)) + if (open) { + WordPicker(p, pool) { word -> + if (word != null && idx in picks.indices) picks[idx] = word + open = false + } } if (revealed && !meaning.isNullOrBlank()) Meaning(vm.tr(meaning), p, true) } +@Composable +private fun WordPicker(p: Palette, pool: List, onPick: (String?) -> Unit) { + Dialog( + onDismissRequest = { onPick(null) }, + properties = DialogProperties(usePlatformDefaultWidth = true), + ) { + Column( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(20.dp)) + .background(p.base) + .padding(10.dp) + .heightIn(max = 420.dp) + .verticalScroll(rememberScrollState()), + ) { + pool.forEach { w -> + Text( + w, + style = appTextStyle(16.sp, color = p.text), + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .clickable { onPick(w) } + .padding(horizontal = 14.dp, vertical = 12.dp), + ) + } + } + } +} + @OptIn(ExperimentalLayoutApi::class) @Composable private fun Ex2Ex(vm: AppViewModel, title: String, sub: String?, spec: J, onDone: () -> Unit) { @@ -968,29 +1001,20 @@ private fun VlsmEx(vm: AppViewModel, @Suppress("UNUSED_PARAMETER") lesson: J) { @Composable private fun PrefixMenu(value: String, p: Palette, onPick: (String) -> Unit) { var open by remember { mutableStateOf(false) } - Column(Modifier.padding(vertical = 4.dp)) { - Box( - Modifier.clip(RoundedCornerShape(12.dp)).background(p.mantle).clickable { open = !open }.padding(10.dp), - ) { Text(value.ifEmpty { "/…" }, color = p.text) } - if (open) { - Column( - Modifier - .heightIn(max = 220.dp) - .verticalScroll(rememberScrollState()) - .clip(RoundedCornerShape(12.dp)) - .background(p.surface0), - ) { - (8..30).forEach { n -> - Text( - "/$n", - color = p.text, - modifier = Modifier - .fillMaxWidth() - .clickable { onPick("/$n"); open = false } - .padding(horizontal = 12.dp, vertical = 8.dp), - ) - } - } + Box( + Modifier + .padding(vertical = 4.dp) + .clip(RoundedCornerShape(12.dp)) + .background(p.mantle) + .clickable { open = true } + .padding(10.dp), + ) { + Text(value.ifEmpty { "/…" }, style = appTextStyle(15.sp, color = p.text)) + } + if (open) { + WordPicker(p, (8..30).map { "/$it" }) { word -> + if (word != null) onPick(word) + open = false } } } diff --git a/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt b/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt index 2b2076d..f6779be 100644 --- a/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt +++ b/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt @@ -55,16 +55,15 @@ fun WelcomeScreen(vm: AppViewModel) { .verticalScroll(rememberScrollState()) .padding(horizontal = 24.dp, vertical = 16.dp), ) { - Text("maturita.c", color = p.text, fontWeight = FontWeight.Bold, fontSize = 15.sp) + Text("maturita.c", style = appTextStyle(15.sp, FontWeight.Medium, color = p.text)) Text( "sestavení ${org.maturita.maturita.BuildConfig.VERSION_NAME} · ${org.maturita.maturita.BuildConfig.COMMIT.take(7)}", - color = p.overlay, - fontSize = 12.sp, + style = appTextStyle(12.sp, color = p.overlay), ) Spacer(Modifier.height(12.dp)) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Column(Modifier.weight(1f)) { - Text(vm.tr("welcome_title"), color = p.text, fontSize = 36.sp, fontWeight = FontWeight.Bold, lineHeight = 40.sp) + Text(vm.tr("welcome_title"), style = appTextStyle(32.sp, FontWeight.Medium, color = p.text)) Spacer(Modifier.height(12.dp)) Text( vm.tr("welcome_body_android"),