mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Keep Android dialogue text readable and move word picks out of the sentence.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
cc36881bb9
commit
9b9693fe4f
5 changed files with 160 additions and 89 deletions
|
|
@ -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\"")
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue