Add chemistry and biology lessons for natural sciences.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-06 09:09:07 +02:00
commit bd7ab09b1d
30 changed files with 2519 additions and 13 deletions

View file

@ -166,6 +166,16 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
refresh()
}
fun markChem(id: Int) {
progress.markChem(id)
refresh()
}
fun markBio(id: Int) {
progress.markBio(id)
refresh()
}
fun markMluv(n: Int) {
progress.markMluv(n)
refresh()

View file

@ -35,6 +35,8 @@ import org.maturita.maturita.ui.LessonListScreen
import org.maturita.maturita.ui.LitYearsScreen
import org.maturita.maturita.ui.LessonRow
import org.maturita.maturita.ui.LitQuizScreen
import org.maturita.maturita.ui.SciMapScreen
import org.maturita.maturita.ui.SciQuizScreen
import org.maturita.maturita.ui.MluvExercise
import org.maturita.maturita.ui.NetQuizScreen
import org.maturita.maturita.ui.NetYearsScreen
@ -346,6 +348,35 @@ private fun MaturitaAppBody(vm: AppViewModel) {
}
}
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id)
Route.SciMap -> SciMapScreen(vm)
Route.ChemMap -> LessonListScreen(
vm, vm.tr("Chemie"), vm.tr("chem_sub"),
vm.content.chem.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.chemDone(id), Route.ChemLesson(id))
},
)
is Route.ChemLesson -> {
val l = vm.content.chemLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.ChemEx(r.id))
}
}
is Route.ChemEx -> SciQuizScreen(vm, "chem", r.id)
Route.BioMap -> LessonListScreen(
vm, vm.tr("Biologie"), vm.tr("bio_sub"),
vm.content.bio.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.bioDone(id), Route.BioLesson(id))
},
)
is Route.BioLesson -> {
val l = vm.content.bioLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.BioEx(r.id))
}
}
is Route.BioEx -> SciQuizScreen(vm, "bio", r.id)
Route.CzechMap -> CzechMapScreen(vm)
Route.Mluvnice -> LessonListScreen(
vm, vm.tr("Mluvnice"), vm.tr("mluv_sub"),

View file

@ -43,6 +43,13 @@ sealed class Route(val page: String) {
data object Lit4Map : Route("lit4map")
data class Lit4Lesson(val id: Int) : Route("lit4unit$id")
data class Lit4Ex(val id: Int) : Route("lit4ex$id")
data object SciMap : Route("scimap")
data object ChemMap : Route("chemmap")
data class ChemLesson(val id: Int) : Route("chemunit$id")
data class ChemEx(val id: Int) : Route("chemex$id")
data object BioMap : Route("biomap")
data class BioLesson(val id: Int) : Route("biounit$id")
data class BioEx(val id: Int) : Route("bioex$id")
data object CzechMap : Route("czechmap")
data object Mluvnice : Route("mluvnice")
data class MluvEx(val n: Int) : Route("mluve$n")
@ -60,7 +67,8 @@ sealed class Route(val page: String) {
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || 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
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx
/** Full stack from the practice root to this screen, so back always goes up one level. */
fun stack(): List<Route> = when (this) {
@ -105,6 +113,13 @@ sealed class Route(val page: String) {
Lit4Map -> listOf(Subjects, CzechMap, LitYears, Lit4Map)
is Lit4Lesson -> listOf(Subjects, CzechMap, LitYears, Lit4Map, this)
is Lit4Ex -> listOf(Subjects, CzechMap, LitYears, Lit4Map, Lit4Lesson(id), this)
SciMap -> listOf(Subjects, SciMap)
ChemMap -> listOf(Subjects, SciMap, ChemMap)
is ChemLesson -> listOf(Subjects, SciMap, ChemMap, this)
is ChemEx -> listOf(Subjects, SciMap, ChemMap, ChemLesson(id), this)
BioMap -> listOf(Subjects, SciMap, BioMap)
is BioLesson -> listOf(Subjects, SciMap, BioMap, this)
is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this)
CzechMap -> listOf(Subjects, CzechMap)
Mluvnice -> listOf(Subjects, CzechMap, Mluvnice)
is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this)
@ -135,6 +150,9 @@ fun routeFromPage(page: String): Route? = when (page) {
"lit2map" -> Route.Lit2Map
"lit3map" -> Route.Lit3Map
"lit4map" -> Route.Lit4Map
"scimap" -> Route.SciMap
"chemmap" -> Route.ChemMap
"biomap" -> Route.BioMap
"czechmap" -> Route.CzechMap
"mluvnice" -> Route.Mluvnice
"readinglist" -> Route.ReadingList
@ -166,6 +184,18 @@ fun routeFromPage(page: String): Route? = when (page) {
Regex("""hwex(\d+)""").matchEntire(page)?.let {
return Route.HwEx(it.groupValues[1].toInt())
}
Regex("""chemunit(\d+)""").matchEntire(page)?.let {
return Route.ChemLesson(it.groupValues[1].toInt())
}
Regex("""chemex(\d+)""").matchEntire(page)?.let {
return Route.ChemEx(it.groupValues[1].toInt())
}
Regex("""biounit(\d+)""").matchEntire(page)?.let {
return Route.BioLesson(it.groupValues[1].toInt())
}
Regex("""bioex(\d+)""").matchEntire(page)?.let {
return Route.BioEx(it.groupValues[1].toInt())
}
Regex("""lit4unit(\d+)""").matchEntire(page)?.let {
return Route.Lit4Lesson(it.groupValues[1].toInt())
}

View file

@ -37,6 +37,8 @@ class Content(root: JSONObject) {
val lit2 = raw.arr("lit2")
val lit3 = raw.arr("lit3")
val lit4 = raw.arr("lit4")
val chem = raw.arr("chem")
val bio = raw.arr("bio")
val mluvnice = raw.arr("mluvnice")
val books = raw.arr("books")
val changelog = raw.arr("changelog")
@ -52,6 +54,8 @@ class Content(root: JSONObject) {
fun lit2Lesson(id: Int) = lit2.firstOrNull { it.int("id") == id }
fun lit3Lesson(id: Int) = lit3.firstOrNull { it.int("id") == id }
fun lit4Lesson(id: Int) = lit4.firstOrNull { it.int("id") == id }
fun chemLesson(id: Int) = chem.firstOrNull { it.int("id") == id }
fun bioLesson(id: Int) = bio.firstOrNull { it.int("id") == id }
fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n }
fun book(id: String) = books.firstOrNull { it.str("id") == id }

View file

@ -53,6 +53,11 @@ class ProgressStore(context: Context) {
fun lit4Done(id: Int) = prefs.getBoolean("lit4.$id", false)
fun markLit4(id: Int) { prefs.edit().putBoolean("lit4.$id", true).apply() }
fun chemDone(id: Int) = prefs.getBoolean("chem.$id", false)
fun markChem(id: Int) { prefs.edit().putBoolean("chem.$id", true).apply() }
fun bioDone(id: Int) = prefs.getBoolean("bio.$id", false)
fun markBio(id: Int) { prefs.edit().putBoolean("bio.$id", true).apply() }
fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false)
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() }
@ -155,6 +160,22 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
sum.doneUnits += 1
}
}
content.chem.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.chemDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.bio.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.bioDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.size
sum.openUnits += 1
val md = content.mluvnice.count { p.mluvDone(it.int("id")) }

View file

@ -849,6 +849,20 @@ fun NetQuizScreen(vm: AppViewModel, id: Int) {
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) { vm.markNet(id) }
}
@Composable
fun SciQuizScreen(vm: AppViewModel, area: String, id: Int) {
val lesson = (if (area == "bio") vm.content.bioLesson(id) else vm.content.chemLesson(id)) ?: return
val spec = J(org.json.JSONObject().apply {
put("type", "choice")
put("title", vm.tr(lesson.str("exTitleKey")))
put("sub", lesson.str("quizHeadKey"))
put("questions", lesson.o.optJSONArray("quiz"))
})
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) {
if (area == "bio") vm.markBio(id) else vm.markChem(id)
}
}
@Composable
fun LitQuizScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = when (year) {

View file

@ -32,6 +32,7 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Quiz
import androidx.compose.material.icons.filled.Science
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Lightbulb
import androidx.compose.material3.Card
@ -220,6 +221,7 @@ private fun SubjectIcon(icon: String, open: Boolean) {
"chip" -> ChipIcon(tint, 26.dp)
"people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp))
"cz" -> CzechFlag(28.dp)
"flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp))
else -> Icon(Icons.Filled.Lock, null, tint = tint)
}
}
@ -307,6 +309,11 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
vm.content.on3.count { vm.progress.on3Done(it.int("id")) } +
vm.content.on4.count { vm.progress.on4Done(it.int("id")) }
}
"scimap" -> {
sum.totalEx = vm.content.chem.size + vm.content.bio.size
sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } +
vm.content.bio.count { vm.progress.bioDone(it.int("id")) }
}
"czechmap" -> {
val litDone = vm.content.lit.count { vm.progress.litDone(it.int("id")) } +
vm.content.lit2.count { vm.progress.lit2Done(it.int("id")) } +
@ -540,6 +547,16 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit${l.int("id")}", extra = "literatura")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${l.int("id")}")
}
add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra = "chemie")
add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra = "biologie")
vm.content.chem.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "chemunit${l.int("id")}", extra = "chemie")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "chemex${l.int("id")}")
}
vm.content.bio.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "biounit${l.int("id")}", extra = "biologie")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "bioex${l.int("id")}")
}
add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice")
vm.content.mluvnice.forEach { m -> add(m.str("name"), vm.tr("Mluvnice"), "mluve${m.int("id")}") }
add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist")
@ -666,6 +683,24 @@ fun LitYearsScreen(vm: AppViewModel) {
}
}
@Composable
fun SciMapScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Základy Přírodopisných věd"), vm.tr("sci_sub")) { inner ->
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
item {
NavRow(
title = vm.tr("Chemie"),
subtitle = vm.tr("chem_sub"),
leading = { Icon(Icons.Filled.Science, null, tint = MaterialTheme.colorScheme.primary) },
) { vm.go(Route.ChemMap) }
}
item {
NavRow(title = vm.tr("Biologie"), subtitle = vm.tr("bio_sub")) { vm.go(Route.BioMap) }
}
}
}
}
@Composable
fun CzechMapScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Český jazyk a literatura"), vm.tr("czech_sub")) { inner ->