Show a what's-new card on the home screen after each update.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-09-18 19:51:52 +02:00
commit 6556c6691b
21 changed files with 490 additions and 0 deletions

View file

@ -32,6 +32,8 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
private set
var update by mutableStateOf(UpdateState())
private set
var showChangelog by mutableStateOf(shouldShowChangelog())
private set
private val stack = mutableStateListOf<Route>(Route.Subjects)
val route: Route get() = stack.last()
@ -146,4 +148,21 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
fun installUpdate() {
updater.install { update = it }
}
fun dismissChangelog() {
progress.seenCommit = BuildConfig.COMMIT
showChangelog = false
}
private fun shouldShowChangelog(): Boolean {
if (content.changelog.isEmpty()) return false
return !sameBuild(BuildConfig.COMMIT, progress.seenCommit)
}
}
internal fun sameBuild(a: String?, b: String?): Boolean {
if (a.isNullOrEmpty() || b.isNullOrEmpty()) return false
if (a.equals(b, ignoreCase = true)) return true
val n = minOf(a.length, b.length)
return n >= 7 && a.regionMatches(0, b, 0, n, ignoreCase = true)
}

View file

@ -31,6 +31,7 @@ class Content(root: JSONObject) {
val hw = raw.arr("hw")
val mluvnice = raw.arr("mluvnice")
val books = raw.arr("books")
val changelog = raw.arr("changelog")
fun germanUnit(id: Int) = german.firstOrNull { it.int("id") == id }
fun netLesson(id: Int) = netLessons.firstOrNull { it.int("id") == id }

View file

@ -17,6 +17,10 @@ class ProgressStore(context: Context) {
get() = if (prefs.getString("lang", "cs") == "en") UiLang.En else UiLang.Cs
set(v) { prefs.edit().putString("lang", if (v == UiLang.En) "en" else "cs").apply() }
var seenCommit: String
get() = prefs.getString("seen_commit", "") ?: ""
set(v) { prefs.edit().putString("seen_commit", v).apply() }
fun germanDone(unit: Int, ex: Int) = prefs.getBoolean("g.$unit.$ex", false)
fun markGerman(unit: Int, ex: Int) { prefs.edit().putBoolean("g.$unit.$ex", true).apply() }
fun vocabDone(unit: Int) = prefs.getBoolean("g.$unit.vocab", false)

View file

@ -91,6 +91,9 @@ fun HomeScreen(vm: AppViewModel) {
val sum = summarize(vm.content, vm.progress)
val pct = if (sum.totalEx == 0) 0f else sum.doneEx / sum.totalEx.toFloat()
RootScaffold(vm, "Maturita") {
if (vm.showChangelog) {
item { ChangelogCard(vm) }
}
item {
Card(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
@ -141,6 +144,51 @@ fun HomeScreen(vm: AppViewModel) {
}
}
@Composable
private fun ChangelogCard(vm: AppViewModel) {
val langKey = if (vm.lang == UiLang.En) "en" else "cs"
val blocks = vm.content.changelog.take(3)
Card(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer),
) {
Column(Modifier.padding(20.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
vm.tr("changelog_title"),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onTertiaryContainer,
modifier = Modifier.weight(1f),
)
val date = blocks.firstOrNull()?.str("date").orEmpty()
if (date.isNotEmpty()) {
Text(
date,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f),
)
}
}
Spacer(Modifier.height(10.dp))
blocks.forEachIndexed { index, block ->
if (index > 0) Spacer(Modifier.height(8.dp))
block.strs(langKey).ifEmpty { block.strs("cs") }.forEach { line ->
Text(
"• $line",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onTertiaryContainer,
modifier = Modifier.padding(bottom = 4.dp),
)
}
}
Spacer(Modifier.height(8.dp))
FilledTonalButton(onClick = { vm.dismissChangelog() }) {
Text(vm.tr("changelog_dismiss"))
}
}
}
}
@Composable
private fun UpdateBanner(vm: AppViewModel) {
Card(