Add four years of German listening, reading and writing beside the textbook.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-07 19:30:00 +02:00
commit b822c57875
31 changed files with 3589 additions and 102 deletions

View file

@ -151,6 +151,11 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
refresh()
}
fun markDe(year: Int, id: Int) {
progress.markDe(year, id)
refresh()
}
fun markLit(id: Int) {
progress.markLit(id)
refresh()

View file

@ -1,6 +1,7 @@
package org.maturita.maturita
import android.os.Bundle
import java.util.Locale
import android.util.TypedValue
import android.view.ViewGroup
import android.widget.Button
@ -50,6 +51,7 @@ import org.maturita.maturita.ui.On4QuizScreen
import org.maturita.maturita.ui.OnQuizScreen
import org.maturita.maturita.ui.OnYearsScreen
import org.maturita.maturita.ui.EnYearsScreen
import org.maturita.maturita.ui.DeHomeScreen
import org.maturita.maturita.ui.EngExScreen
import org.maturita.maturita.ui.PlotScreen
import org.maturita.maturita.ui.RoadmapScreen
@ -206,6 +208,23 @@ private fun MaturitaAppBody(vm: AppViewModel) {
Route.Welcome, Route.Subjects -> HomeScreen(vm)
Route.Stats -> StatsScreen(vm)
Route.Settings -> SettingsScreen(vm)
Route.DeHome -> DeHomeScreen(vm)
is Route.DeMap -> LessonListScreen(
vm, vm.tr("de_year${r.year}"), vm.tr("de_y${r.year}_sub"),
vm.content.deYear(r.year).map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.deDone(r.year, id), Route.DeLesson(r.year, id))
},
)
is Route.DeLesson -> {
val l = vm.content.deLesson(r.year, r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.DeEx(r.year, r.id))
}
}
is Route.DeEx -> EngExScreen(
vm, r.year, r.id, Locale.GERMANY, vm.content.deLesson(r.year, r.id),
) { vm.markDe(r.year, r.id) }
Route.Roadmap -> RoadmapScreen(vm)
is Route.UnitMap -> UnitMapScreen(vm, r.unitId)
is Route.GermanEx -> GermanExercise(vm, r.unitId, r.ex)

View file

@ -6,6 +6,10 @@ sealed class Route(val page: String) {
data object Stats : Route("stats")
data object Settings : Route("settings")
data object Roadmap : Route("roadmap")
data object DeHome : Route("dehome")
data class DeMap(val year: Int) : Route("de${year}map")
data class DeLesson(val year: Int, val id: Int) : Route("de${year}unit$id")
data class DeEx(val year: Int, val id: Int) : Route("de${year}ex$id")
data class UnitMap(val unitId: Int) : Route("unit${unitId + 1}")
data class GermanEx(val unitId: Int, val ex: Int) : Route("u${unitId + 1}e$ex")
data class Vocab(val unitId: Int) : Route("u${unitId + 1}vocab")
@ -97,7 +101,7 @@ sealed class Route(val page: String) {
/** Screens with their own bottom action bar; the navigation bar stays hidden there. */
val isExercise: Boolean
get() = this is GermanEx || this is Vocab || this is NetEx || this is HwEx ||
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is EnLesson || this is EnEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is EnLesson || this is EnEx || this is DeLesson || this is DeEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson ||
this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex ||
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
@ -113,10 +117,14 @@ sealed class Route(val page: String) {
Welcome, Subjects -> listOf(Subjects)
Stats -> listOf(Stats)
Settings -> listOf(Settings)
Roadmap -> listOf(Subjects, Roadmap)
is UnitMap -> listOf(Subjects, Roadmap, this)
is GermanEx -> listOf(Subjects, Roadmap, UnitMap(unitId), this)
is Vocab -> listOf(Subjects, Roadmap, UnitMap(unitId), this)
DeHome -> listOf(Subjects, DeHome)
is DeMap -> listOf(Subjects, DeHome, this)
is DeLesson -> listOf(Subjects, DeHome, DeMap(year), this)
is DeEx -> listOf(Subjects, DeHome, DeMap(year), DeLesson(year, id), this)
Roadmap -> listOf(Subjects, DeHome, Roadmap)
is UnitMap -> listOf(Subjects, DeHome, Roadmap, this)
is GermanEx -> listOf(Subjects, DeHome, Roadmap, UnitMap(unitId), this)
is Vocab -> listOf(Subjects, DeHome, Roadmap, UnitMap(unitId), this)
NetYears -> listOf(Subjects, NetYears)
NetMap -> listOf(Subjects, NetYears, NetMap)
is NetLesson -> listOf(Subjects, NetYears, NetMap, this)
@ -207,6 +215,7 @@ fun routeFromPage(page: String): Route? = when (page) {
"stats" -> Route.Stats
"settings" -> Route.Settings
"roadmap" -> Route.Roadmap
"dehome" -> Route.DeHome
"netyears" -> Route.NetYears
"netmap" -> Route.NetMap
"hwyears" -> Route.HwYears
@ -357,6 +366,15 @@ fun routeFromPage(page: String): Route? = when (page) {
Regex("""litex(\d+)""").matchEntire(page)?.let {
return Route.LitEx(it.groupValues[1].toInt())
}
Regex("""de(\d)map""").matchEntire(page)?.let {
return Route.DeMap(it.groupValues[1].toInt())
}
Regex("""de(\d)unit(\d+)""").matchEntire(page)?.let {
return Route.DeLesson(it.groupValues[1].toInt(), it.groupValues[2].toInt())
}
Regex("""de(\d)ex(\d+)""").matchEntire(page)?.let {
return Route.DeEx(it.groupValues[1].toInt(), it.groupValues[2].toInt())
}
Regex("""en(\d)map""").matchEntire(page)?.let {
return Route.EnMap(it.groupValues[1].toInt())
}

View file

@ -37,6 +37,10 @@ class Content(root: JSONObject) {
val en2 = raw.arr("en2")
val en3 = raw.arr("en3")
val en4 = raw.arr("en4")
val de = raw.arr("de")
val de2 = raw.arr("de2")
val de3 = raw.arr("de3")
val de4 = raw.arr("de4")
val lit = raw.arr("lit")
val lit2 = raw.arr("lit2")
val lit3 = raw.arr("lit3")
@ -70,6 +74,13 @@ class Content(root: JSONObject) {
else -> en
}
fun enLesson(year: Int, id: Int) = enYear(year).firstOrNull { it.int("id") == id }
fun deYear(year: Int) = when (year) {
2 -> de2
3 -> de3
4 -> de4
else -> de
}
fun deLesson(year: Int, id: Int) = deYear(year).firstOrNull { it.int("id") == id }
fun litLesson(id: Int) = lit.firstOrNull { it.int("id") == id }
fun lit2Lesson(id: Int) = lit2.firstOrNull { it.int("id") == id }
fun lit3Lesson(id: Int) = lit3.firstOrNull { it.int("id") == id }

View file

@ -46,6 +46,8 @@ class ProgressStore(context: Context) {
fun enDone(year: Int, id: Int) = prefs.getBoolean("en.$year.$id", false)
fun markEn(year: Int, id: Int) { prefs.edit().putBoolean("en.$year.$id", true).apply() }
fun deDone(year: Int, id: Int) = prefs.getBoolean("de.$year.$id", false)
fun markDe(year: Int, id: Int) { prefs.edit().putBoolean("de.$year.$id", true).apply() }
fun litDone(id: Int) = prefs.getBoolean("lit.$id", false)
fun markLit(id: Int) { prefs.edit().putBoolean("lit.$id", true).apply() }
@ -158,6 +160,14 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
sum.doneUnits += 1
}
}
content.deYear(year).forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.deDone(year, l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
}
content.lit.forEach { l ->
sum.totalEx += 1

View file

@ -1382,8 +1382,15 @@ fun PlotScreen(vm: AppViewModel, id: String) {
}
@Composable
fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = vm.content.enLesson(year, id) ?: return
fun EngExScreen(
vm: AppViewModel,
year: Int,
id: Int,
locale: Locale = Locale.UK,
spec: J? = vm.content.enLesson(year, id),
onDone: () -> Unit = { vm.markEn(year, id) },
) {
val lesson = spec ?: return
val readQ = lesson.arr("readQuiz")
val listenQ = lesson.arr("listenQuiz")
val quiz = lesson.arr("quiz")
@ -1410,7 +1417,7 @@ fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
DisposableEffect(context) {
var engine: TextToSpeech? = null
engine = TextToSpeech(context) { status ->
if (status == TextToSpeech.SUCCESS) engine?.language = Locale.UK
if (status == TextToSpeech.SUCCESS) engine?.language = locale
}
tts = engine
onDispose {
@ -1447,7 +1454,7 @@ fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
fb = when {
all -> {
showModel = true
vm.markEn(year, id)
onDone()
vm.tr("feedback_ok") to "ok"
}
writing != null && !writeOk && readOk && listenOk && quizOk && gapOk == gapRows.size ->
@ -1472,7 +1479,7 @@ fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
val start = index.coerceIn(0, listening.length)
val id = "en-${System.nanoTime()}"
utterance = id
engine.language = Locale.UK
engine.language = locale
engine.setSpeechRate(speed)
engine.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {}

View file

@ -294,10 +294,16 @@ private fun Metric(label: String, value: String, modifier: Modifier) {
private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
val sum = ProgressSum()
when (s.str("target")) {
"roadmap" -> vm.content.german.filter { it.bool("unlocked") }.forEach { u ->
val n = u.strs("names").size
sum.totalEx += n
sum.doneEx += (1..n).count { vm.progress.germanDone(u.int("id"), it) }
"dehome" -> {
vm.content.german.filter { it.bool("unlocked") }.forEach { u ->
val n = u.strs("names").size
sum.totalEx += n
sum.doneEx += (1..n).count { vm.progress.germanDone(u.int("id"), it) }
}
sum.totalEx += (1..4).sumOf { vm.content.deYear(it).size }
sum.doneEx += (1..4).sumOf { year ->
vm.content.deYear(year).count { vm.progress.deDone(year, it.int("id")) }
}
}
"netyears" -> {
sum.totalEx = vm.content.netLessons.size
@ -558,6 +564,11 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "en${year}unit${l.int("id")}", extra = "anglictina english")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "en${year}ex${l.int("id")}")
}
add(vm.tr("de_year$year"), vm.tr("search_lesson"), "de${year}map", extra = "nemcina deutsch rocnik")
vm.content.deYear(year).forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "de${year}unit${l.int("id")}", extra = "nemcina deutsch")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "de${year}ex${l.int("id")}")
}
}
add(vm.tr("Literatura"), vm.tr("search_lesson"), "lityears", extra = "literatura")
add(vm.tr("lit_year1"), vm.tr("search_lesson"), "litmap", extra = "rocnik")
@ -735,6 +746,28 @@ fun HwYearsScreen(vm: AppViewModel) {
}
}
@Composable
@Composable
fun DeHomeScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Deutsch"), vm.tr("de_home_sub")) { inner ->
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
item {
NavRow(title = vm.tr("de_book_title"), subtitle = vm.tr("de_book_sub"), leading = { Text("U", style = MaterialTheme.typography.titleMedium) }) {
vm.go(Route.Roadmap)
}
}
items(4) { index ->
val year = index + 1
NavRow(
title = vm.tr("de_year$year"),
subtitle = vm.tr("de_y${year}_sub"),
leading = { Text("$year.", style = MaterialTheme.typography.titleMedium) },
) { vm.go(Route.DeMap(year)) }
}
}
}
}
@Composable
fun EnYearsScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("English"), vm.tr("en_years_sub")) { inner ->