Add interactive mathematics for elementary review and four secondary-school years.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-06 14:49:25 +02:00
commit 0e24e8c649
38 changed files with 9507 additions and 11 deletions

View file

@ -191,6 +191,31 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
refresh()
}
fun markMat0(id: Int) {
progress.markMat0(id)
refresh()
}
fun markMat(id: Int) {
progress.markMat(id)
refresh()
}
fun markMat2(id: Int) {
progress.markMat2(id)
refresh()
}
fun markMat3(id: Int) {
progress.markMat3(id)
refresh()
}
fun markMat4(id: Int) {
progress.markMat4(id)
refresh()
}
fun markFyz4(id: Int) {
progress.markFyz4(id)
refresh()

View file

@ -33,6 +33,8 @@ import org.maturita.maturita.ui.HwQuizScreen
import org.maturita.maturita.ui.HwYearsScreen
import org.maturita.maturita.ui.LessonListScreen
import org.maturita.maturita.ui.FyzQuizScreen
import org.maturita.maturita.ui.MathPracticeScreen
import org.maturita.maturita.ui.MatYearsScreen
import org.maturita.maturita.ui.FyzYearsScreen
import org.maturita.maturita.ui.LitYearsScreen
import org.maturita.maturita.ui.LessonRow
@ -350,6 +352,77 @@ private fun MaturitaAppBody(vm: AppViewModel) {
}
}
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id)
Route.MatYears -> MatYearsScreen(vm)
Route.Mat0Map -> LessonListScreen(
vm, vm.tr("mat0_year"), vm.tr("mat0_sub"),
vm.content.mat0.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat0Done(id), Route.Mat0Lesson(id))
},
)
is Route.Mat0Lesson -> {
val l = vm.content.mat0Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat0Ex(r.id))
}
}
is Route.Mat0Ex -> MathPracticeScreen(vm, 0, r.id)
Route.MatMap -> LessonListScreen(
vm, vm.tr("mat_year1"), vm.tr("mat_sub"),
vm.content.mat.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.matDone(id), Route.MatLesson(id))
},
)
is Route.MatLesson -> {
val l = vm.content.matLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.MatEx(r.id))
}
}
is Route.MatEx -> MathPracticeScreen(vm, 1, r.id)
Route.Mat2Map -> LessonListScreen(
vm, vm.tr("mat_year2"), vm.tr("mat2_sub"),
vm.content.mat2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat2Done(id), Route.Mat2Lesson(id))
},
)
is Route.Mat2Lesson -> {
val l = vm.content.mat2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat2Ex(r.id))
}
}
is Route.Mat2Ex -> MathPracticeScreen(vm, 2, r.id)
Route.Mat3Map -> LessonListScreen(
vm, vm.tr("mat_year3"), vm.tr("mat3_sub"),
vm.content.mat3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat3Done(id), Route.Mat3Lesson(id))
},
)
is Route.Mat3Lesson -> {
val l = vm.content.mat3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat3Ex(r.id))
}
}
is Route.Mat3Ex -> MathPracticeScreen(vm, 3, r.id)
Route.Mat4Map -> LessonListScreen(
vm, vm.tr("mat_year4"), vm.tr("mat4_sub"),
vm.content.mat4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat4Done(id), Route.Mat4Lesson(id))
},
)
is Route.Mat4Lesson -> {
val l = vm.content.mat4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat4Ex(r.id))
}
}
is Route.Mat4Ex -> MathPracticeScreen(vm, 4, r.id)
Route.FyzYears -> FyzYearsScreen(vm)
Route.FyzMap -> LessonListScreen(
vm, vm.tr("fyz_year1"), vm.tr("fyz_sub"),

View file

@ -50,6 +50,22 @@ sealed class Route(val page: String) {
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 MatYears : Route("matyears")
data object Mat0Map : Route("mat0map")
data class Mat0Lesson(val id: Int) : Route("mat0unit$id")
data class Mat0Ex(val id: Int) : Route("mat0ex$id")
data object MatMap : Route("matmap")
data class MatLesson(val id: Int) : Route("matunit$id")
data class MatEx(val id: Int) : Route("matex$id")
data object Mat2Map : Route("mat2map")
data class Mat2Lesson(val id: Int) : Route("mat2unit$id")
data class Mat2Ex(val id: Int) : Route("mat2ex$id")
data object Mat3Map : Route("mat3map")
data class Mat3Lesson(val id: Int) : Route("mat3unit$id")
data class Mat3Ex(val id: Int) : Route("mat3ex$id")
data object Mat4Map : Route("mat4map")
data class Mat4Lesson(val id: Int) : Route("mat4unit$id")
data class Mat4Ex(val id: Int) : Route("mat4ex$id")
data object FyzYears : Route("fyzyears")
data object FyzMap : Route("fyzmap")
data class FyzLesson(val id: Int) : Route("fyzunit$id")
@ -83,7 +99,10 @@ sealed class Route(val page: String) {
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx ||
this is FyzLesson || this is FyzEx || this is Fyz2Lesson || this is Fyz2Ex ||
this is Fyz3Lesson || this is Fyz3Ex || this is Fyz4Lesson || this is Fyz4Ex
this is Fyz3Lesson || this is Fyz3Ex || this is Fyz4Lesson || this is Fyz4Ex ||
this is Mat0Lesson || this is Mat0Ex || this is MatLesson || this is MatEx ||
this is Mat2Lesson || this is Mat2Ex || this is Mat3Lesson || this is Mat3Ex ||
this is Mat4Lesson || this is Mat4Ex
/** Full stack from the practice root to this screen, so back always goes up one level. */
fun stack(): List<Route> = when (this) {
@ -135,6 +154,22 @@ sealed class Route(val page: String) {
BioMap -> listOf(Subjects, SciMap, BioMap)
is BioLesson -> listOf(Subjects, SciMap, BioMap, this)
is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this)
MatYears -> listOf(Subjects, MatYears)
Mat0Map -> listOf(Subjects, MatYears, Mat0Map)
is Mat0Lesson -> listOf(Subjects, MatYears, Mat0Map, this)
is Mat0Ex -> listOf(Subjects, MatYears, Mat0Map, Mat0Lesson(id), this)
MatMap -> listOf(Subjects, MatYears, MatMap)
is MatLesson -> listOf(Subjects, MatYears, MatMap, this)
is MatEx -> listOf(Subjects, MatYears, MatMap, MatLesson(id), this)
Mat2Map -> listOf(Subjects, MatYears, Mat2Map)
is Mat2Lesson -> listOf(Subjects, MatYears, Mat2Map, this)
is Mat2Ex -> listOf(Subjects, MatYears, Mat2Map, Mat2Lesson(id), this)
Mat3Map -> listOf(Subjects, MatYears, Mat3Map)
is Mat3Lesson -> listOf(Subjects, MatYears, Mat3Map, this)
is Mat3Ex -> listOf(Subjects, MatYears, Mat3Map, Mat3Lesson(id), this)
Mat4Map -> listOf(Subjects, MatYears, Mat4Map)
is Mat4Lesson -> listOf(Subjects, MatYears, Mat4Map, this)
is Mat4Ex -> listOf(Subjects, MatYears, Mat4Map, Mat4Lesson(id), this)
FyzYears -> listOf(Subjects, FyzYears)
FyzMap -> listOf(Subjects, FyzYears, FyzMap)
is FyzLesson -> listOf(Subjects, FyzYears, FyzMap, this)
@ -181,6 +216,12 @@ fun routeFromPage(page: String): Route? = when (page) {
"scimap" -> Route.SciMap
"chemmap" -> Route.ChemMap
"biomap" -> Route.BioMap
"matyears" -> Route.MatYears
"mat0map" -> Route.Mat0Map
"matmap" -> Route.MatMap
"mat2map" -> Route.Mat2Map
"mat3map" -> Route.Mat3Map
"mat4map" -> Route.Mat4Map
"fyzyears" -> Route.FyzYears
"fyzmap" -> Route.FyzMap
"fyz2map" -> Route.Fyz2Map
@ -229,6 +270,36 @@ fun routeFromPage(page: String): Route? = when (page) {
Regex("""bioex(\d+)""").matchEntire(page)?.let {
return Route.BioEx(it.groupValues[1].toInt())
}
Regex("""mat0unit(\d+)""").matchEntire(page)?.let {
return Route.Mat0Lesson(it.groupValues[1].toInt())
}
Regex("""mat0ex(\d+)""").matchEntire(page)?.let {
return Route.Mat0Ex(it.groupValues[1].toInt())
}
Regex("""mat4unit(\d+)""").matchEntire(page)?.let {
return Route.Mat4Lesson(it.groupValues[1].toInt())
}
Regex("""mat4ex(\d+)""").matchEntire(page)?.let {
return Route.Mat4Ex(it.groupValues[1].toInt())
}
Regex("""mat3unit(\d+)""").matchEntire(page)?.let {
return Route.Mat3Lesson(it.groupValues[1].toInt())
}
Regex("""mat3ex(\d+)""").matchEntire(page)?.let {
return Route.Mat3Ex(it.groupValues[1].toInt())
}
Regex("""mat2unit(\d+)""").matchEntire(page)?.let {
return Route.Mat2Lesson(it.groupValues[1].toInt())
}
Regex("""mat2ex(\d+)""").matchEntire(page)?.let {
return Route.Mat2Ex(it.groupValues[1].toInt())
}
Regex("""matunit(\d+)""").matchEntire(page)?.let {
return Route.MatLesson(it.groupValues[1].toInt())
}
Regex("""matex(\d+)""").matchEntire(page)?.let {
return Route.MatEx(it.groupValues[1].toInt())
}
Regex("""fyz4unit(\d+)""").matchEntire(page)?.let {
return Route.Fyz4Lesson(it.groupValues[1].toInt())
}

View file

@ -63,3 +63,656 @@ fun hasUmlaut(s: String?): Boolean {
return s.any { it == 'ä' || it == 'ö' || it == 'ü' || it == 'ß' ||
it == 'Ä' || it == 'Ö' || it == 'Ü' }
}
// Same rules as src/math_check.c. Do not route numbers through normalizeAnswer.
private data class MFrac(val n: Long = 0, val d: Long = 1)
private class MTerm(
var ok: Boolean = true,
var a: MFrac = MFrac(),
var b: MFrac = MFrac(),
var rad: Long = 0,
var p: MFrac = MFrac(),
)
private fun mGcd(a0: Long, b0: Long): Long {
var a = if (a0 < 0) -a0 else a0
var b = if (b0 < 0) -b0 else b0
while (b != 0L) {
val t = a % b
a = b
b = t
}
return if (a == 0L) 1 else a
}
private fun mMul(a: Long, b: Long): Long? = try {
Math.multiplyExact(a, b)
} catch (_: ArithmeticException) {
null
}
private fun mAddL(a: Long, b: Long): Long? = try {
Math.addExact(a, b)
} catch (_: ArithmeticException) {
null
}
private fun mSet(n0: Long, d0: Long): MFrac? {
if (d0 == 0L) return null
var n = n0
var d = d0
if (d < 0) {
n = -n
d = -d
}
val g = mGcd(n, d)
return MFrac(n / g, d / g)
}
private fun mAddF(x: MFrac, y: MFrac): MFrac? {
val xn = mMul(x.n, y.d) ?: return null
val yn = mMul(y.n, x.d) ?: return null
val d = mMul(x.d, y.d) ?: return null
val s = mAddL(xn, yn) ?: return null
return mSet(s, d)
}
private fun mMulF(x: MFrac, y: MFrac): MFrac? {
val n = mMul(x.n, y.n) ?: return null
val d = mMul(x.d, y.d) ?: return null
return mSet(n, d)
}
private fun mDivF(x: MFrac, y: MFrac): MFrac? {
if (y.n == 0L) return null
return mMulF(x, MFrac(y.d, y.n))
}
private fun mSameF(x: MFrac, y: MFrac) = x.n == y.n && x.d == y.d
private fun mPow(b: Long, e: Int): Long {
var r = 1L
var i = 0
while (i < e) {
r *= b
i++
}
return r
}
private fun mSqrtReduce(n0: Long): Pair<Long, Long>? {
if (n0 < 0 || n0 > 1_000_000) return null
var n = n0
var out = 1L
var f = 2L
while (f * f <= n) {
var c = 0
while (n % f == 0L) {
n /= f
c++
}
if (c >= 2) out *= mPow(f, c / 2)
if (c % 2 == 1) n *= f
f = if (f == 2L) 3 else f + 2
if (f > 10000) break
}
return out to n
}
private fun mNorm(t: MTerm) {
if (!t.ok) return
if (t.rad > 1) {
val s = mSqrtReduce(t.rad) ?: run { t.ok = false; return }
t.b = mMulF(t.b, MFrac(s.first, 1)) ?: run { t.ok = false; return }
t.rad = s.second
}
if (t.rad <= 1) {
t.a = mAddF(t.a, t.b) ?: run { t.ok = false; return }
t.b = MFrac()
t.rad = 0
}
if (t.b.n == 0L) t.rad = 0
}
private fun mFail() = MTerm(ok = false)
private fun mAdd(x: MTerm, y: MTerm): MTerm {
if (!x.ok || !y.ok) return mFail()
val o = MTerm()
o.a = mAddF(x.a, y.a) ?: return mFail()
o.p = mAddF(x.p, y.p) ?: return mFail()
if (x.rad == 0L && y.rad == 0L) {
o.b = MFrac()
o.rad = 0
} else if (x.rad == 0L) {
o.b = y.b
o.rad = y.rad
} else if (y.rad == 0L || x.rad == y.rad) {
o.b = mAddF(x.b, y.b) ?: return mFail()
o.rad = x.rad
} else return mFail()
mNorm(o)
return o
}
private fun mScale(x: MTerm, f: MFrac): MTerm {
if (!x.ok) return mFail()
val o = MTerm()
o.a = mMulF(x.a, f) ?: return mFail()
o.b = mMulF(x.b, f) ?: return mFail()
o.p = mMulF(x.p, f) ?: return mFail()
o.rad = x.rad
mNorm(o)
return o
}
private fun mMulT(x: MTerm, y: MTerm): MTerm {
if (!x.ok || !y.ok) return mFail()
if ((x.p.n != 0L && (y.p.n != 0L || y.rad != 0L)) ||
(y.p.n != 0L && (x.p.n != 0L || x.rad != 0L))
) return mFail()
if (x.rad != 0L && y.rad != 0L && x.rad != y.rad) return mFail()
val o = MTerm()
o.a = mMulF(x.a, y.a) ?: return mFail()
if (x.rad == 0L && y.rad == 0L) {
o.b = MFrac()
o.rad = 0
} else if (x.rad == 0L) {
o.b = mMulF(x.a, y.b) ?: return mFail()
o.rad = y.rad
} else if (y.rad == 0L) {
o.b = mMulF(y.a, x.b) ?: return mFail()
o.rad = x.rad
} else {
val ab = mMulF(x.a, y.b) ?: return mFail()
val ba = mMulF(x.b, y.a) ?: return mFail()
val cross = mAddF(ab, ba) ?: return mFail()
val sq = mMul(x.rad, y.rad) ?: return mFail()
val bb = mMulF(x.b, y.b) ?: return mFail()
val extra = mMulF(bb, MFrac(sq, 1)) ?: return mFail()
o.a = mAddF(o.a, extra) ?: return mFail()
o.b = cross
o.rad = x.rad
}
o.p = if (x.p.n != 0L) x.p else y.p
if ((y.a.n != 1L || y.a.d != 1L || y.b.n != 0L || y.rad != 0L || y.p.n != 0L) && x.p.n != 0L) {
o.p = mMulF(x.p, y.a) ?: return mFail()
}
if ((x.a.n != 1L || x.a.d != 1L || x.b.n != 0L || x.rad != 0L || x.p.n != 0L) &&
y.p.n != 0L && x.p.n == 0L
) {
o.p = mMulF(y.p, x.a) ?: return mFail()
}
mNorm(o)
return o
}
private fun mDivT(x: MTerm, y: MTerm): MTerm {
if (!x.ok || !y.ok || y.p.n != 0L) return mFail()
if (y.rad == 0L) {
val f = mDivF(MFrac(1, 1), y.a) ?: return mFail()
return mScale(x, f)
}
if (x.p.n != 0L) return mFail()
val yb = mMulF(y.b, MFrac(y.rad, 1)) ?: return mFail()
val bb = mMulF(y.b, yb) ?: return mFail()
val aa = mMulF(y.a, y.a) ?: return mFail()
val den = mAddF(aa, MFrac(-bb.n, bb.d)) ?: return mFail()
val num = MTerm()
num.a = y.a
num.b = MFrac(-y.b.n, y.b.d)
num.rad = y.rad
val inv = mDivF(MFrac(1, 1), den) ?: return mFail()
return mMulT(x, mScale(num, inv))
}
private sealed class MTok {
class Num(val n: Long, val d: Long) : MTok()
object Sqrt : MTok()
object Pi : MTok()
object Add : MTok()
object Sub : MTok()
object Mul : MTok()
object Div : MTok()
object Lp : MTok()
object Rp : MTok()
}
private fun mLex(s: String): List<MTok>? {
val toks = ArrayList<MTok>()
var i = 0
fun peek(k: Int) = if (i + k < s.length) s[i + k] else null
while (i < s.length) {
while (i < s.length && s[i].isWhitespace()) i++
if (i >= s.length) break
val ch = s[i]
if (ch.isDigit() || ((ch == '.' || ch == ',') && peek(1)?.isDigit() == true)) {
var whole = 0L
var saw = false
while (i < s.length && s[i].isDigit()) {
saw = true
whole = whole * 10 + (s[i] - '0')
i++
}
if (i < s.length && (s[i] == '.' || s[i] == ',') && i + 1 < s.length && s[i + 1].isDigit()) {
i++
var frac = 0L
var div = 1L
while (i < s.length && s[i].isDigit()) {
frac = frac * 10 + (s[i] - '0')
div *= 10
i++
}
val n = mAddL(mMul(whole, div) ?: return null, frac) ?: return null
toks.add(MTok.Num(n, div))
} else if (saw) {
toks.add(MTok.Num(whole, 1))
} else return null
continue
}
when (ch) {
'+' -> toks.add(MTok.Add)
'-', '\u2212', '\u2013' -> toks.add(MTok.Sub)
'*', '\u00D7', '\u00B7' -> toks.add(MTok.Mul)
'/', ':' -> toks.add(MTok.Div)
'(' -> toks.add(MTok.Lp)
')' -> toks.add(MTok.Rp)
'\u03C0' -> toks.add(MTok.Pi)
'\u221A' -> toks.add(MTok.Sqrt)
else -> {
if (ch in 'A'..'Z' || ch in 'a'..'z') {
val start = i
while (i < s.length && (s[i] in 'A'..'Z' || s[i] in 'a'..'z')) i++
when (s.substring(start, i).lowercase()) {
"pi" -> toks.add(MTok.Pi)
"sqrt" -> toks.add(MTok.Sqrt)
else -> return null
}
continue
}
return null
}
}
i++
}
return toks
}
private class MParse(val t: List<MTok>) {
var i = 0
fun peek() = if (i < t.size) t[i] else null
fun take(): MTok? = if (i < t.size) t[i++] else null
fun at(k: Int) = if (k in t.indices) t[k] else null
fun expr(): MTerm? {
var left = term() ?: return null
while (true) {
val p = peek()
if (p !is MTok.Add && p !is MTok.Sub) break
take()
val r = term() ?: return null
left = if (p is MTok.Sub) mAdd(left, mScale(r, MFrac(-1, 1))) else mAdd(left, r)
if (!left.ok) return null
}
return left
}
fun term(): MTerm? {
var left = unary() ?: return null
while (true) {
val p = peek()
if (p !is MTok.Mul && p !is MTok.Div) break
take()
val r = unary() ?: return null
left = if (p is MTok.Div) mDivT(left, r) else mMulT(left, r)
if (!left.ok) return null
}
return left
}
fun unary(): MTerm? {
if (peek() is MTok.Sub) {
take()
val v = unary() ?: return null
return mScale(v, MFrac(-1, 1))
}
if (peek() is MTok.Add) {
take()
return unary()
}
return factor()
}
fun factor(): MTerm? {
var left = primary() ?: return null
while (true) {
when (peek()) {
is MTok.Sqrt, is MTok.Pi, is MTok.Lp -> {
val r = primary() ?: return null
left = mMulT(left, r)
if (!left.ok) return null
}
is MTok.Num -> return null
else -> return left
}
}
}
fun primary(): MTerm? {
when (val p = take()) {
is MTok.Num -> {
val n = p.n
val d = p.d
if (peek() is MTok.Div && at(i + 1) is MTok.Num) {
take()
val n2 = take() as? MTok.Num ?: return null
if (n2.n <= 0 || n2.d != 1L || d != 1L) return null
val o = MTerm()
o.a = mSet(n, n2.n) ?: return null
return o
}
if (d == 1L && n >= 0 && peek() is MTok.Num) {
val saved = i
val n2 = take() as MTok.Num
val n3tok = at(i + 1)
if (peek() is MTok.Div && n3tok is MTok.Num && n2.d == 1L && n3tok.d == 1L && n2.n >= 0 && n3tok.n > 0) {
take()
take()
val frac = mSet(n2.n, n3tok.n) ?: return null
val whole = MTerm()
whole.a = MFrac(n, 1)
val part = MTerm()
part.a = frac
return mAdd(whole, part)
}
i = saved
}
val o = MTerm()
o.a = mSet(n, d) ?: return null
return o
}
is MTok.Pi -> {
val o = MTerm()
o.p = MFrac(1, 1)
return o
}
is MTok.Sqrt -> {
if (peek() is MTok.Lp) {
take()
val inner = expr() ?: return null
if (take() !is MTok.Rp) return null
return mSqrtTerm(inner)
}
val num = peek()
if (num is MTok.Num && num.d == 1L && num.n >= 0) {
take()
val inner = MTerm()
inner.a = MFrac(num.n, 1)
return mSqrtTerm(inner)
}
return null
}
is MTok.Lp -> {
val inner = expr() ?: return null
if (take() !is MTok.Rp) return null
return inner
}
else -> return null
}
}
}
private fun mSqrtTerm(inner: MTerm): MTerm? {
if (!inner.ok || inner.p.n != 0L || inner.rad != 0L || inner.b.n != 0L) return null
if (inner.a.n < 0 || inner.a.d != 1L) return null
val s = mSqrtReduce(inner.a.n) ?: return null
val o = MTerm()
o.b = MFrac(s.first, 1)
o.rad = s.second
mNorm(o)
return if (o.ok) o else null
}
private fun mStripUnits(raw: String): String {
var s = raw.trim()
val units = listOf("procenta", "procent", "cm", "mm", "dm", "km", "m")
var cut = true
while (cut) {
cut = false
s = s.trimEnd()
if (s.endsWith("°") || s.endsWith("%") || s.endsWith(".")) {
s = s.dropLast(1)
cut = true
continue
}
for (u in units) {
if (s.length <= u.length) continue
if (s.lowercase().endsWith(u)) {
val keep = s.dropLast(u.length)
if (keep.lastOrNull()?.isLetter() == true) continue
s = keep
cut = true
break
}
}
}
return s.trim()
}
private fun mPrep(raw: String): String {
var s = raw.trim()
val pairs = listOf('(' to ')', '[' to ']', '{' to '}')
while (s.length >= 2 && pairs.any { it.first == s.first() && it.second == s.last() }) {
s = s.substring(1, s.length - 1).trim()
}
return mStripUnits(s).trim()
}
private fun mParseTerm(s: String): MTerm? {
val text = mPrep(s)
if (text.isEmpty()) return null
val toks = mLex(text) ?: return null
if (toks.isEmpty()) return null
val p = MParse(toks)
val v = p.expr() ?: return null
if (p.i != toks.size || !v.ok) return null
return v
}
private fun mExact(x: MTerm, y: MTerm) =
x.ok && y.ok && mSameF(x.a, y.a) && mSameF(x.b, y.b) && x.rad == y.rad && mSameF(x.p, y.p)
private fun mDouble(t: MTerm): Double {
var v = t.a.n.toDouble() / t.a.d + t.p.n.toDouble() / t.p.d * Math.PI
if (t.b.n != 0L && t.rad > 0) v += t.b.n.toDouble() / t.b.d * Math.sqrt(t.rad.toDouble())
return v
}
private fun mClose(x: MTerm, y: MTerm): Boolean {
if (!x.ok || !y.ok) return false
if (mExact(x, y)) return true
if (x.p.n != 0L || y.p.n != 0L) return false
val diff = Math.abs(mDouble(x) - mDouble(y))
return if (x.b.n != 0L || y.b.n != 0L) diff <= 0.001 else diff <= 0.0005
}
private fun mFold(s: String): String {
val out = StringBuilder()
for (ch in s.lowercase()) {
when (ch) {
'á', 'à', 'ä', 'â' -> out.append('a')
'č', 'ć' -> out.append('c')
'ď' -> out.append('d')
'é', 'ě', 'ë', 'ê' -> out.append('e')
'í', 'ï' -> out.append('i')
'ň' -> out.append('n')
'ó', 'ö', 'ô' -> out.append('o')
'ř' -> out.append('r')
'š', 'ś' -> out.append('s')
'ť' -> out.append('t')
'ú', 'ů', 'ü' -> out.append('u')
'ý' -> out.append('y')
'ž', 'ź' -> out.append('z')
else -> if (ch.isLetter() || ch.isDigit()) out.append(ch)
}
}
return out.toString()
}
private fun mSplit(raw: String): List<String>? {
val s = raw.trim()
if (s.isEmpty()) return null
val semi = s.contains(';')
val parts = ArrayList<String>()
var rest = s
while (rest.isNotEmpty()) {
val idx = if (semi) rest.indexOf(';') else rest.indexOf(", ")
val step = if (semi) 1 else 2
val piece: String
val next: String
if (idx < 0) {
piece = rest.trim()
next = ""
} else {
piece = rest.substring(0, idx).trim()
next = rest.substring(idx + step)
}
if (piece.isEmpty()) return null
parts.add(piece)
if (idx < 0) break
rest = next
}
return parts.ifEmpty { null }
}
fun mathAnswerOk(spec: String?, user: String?): Boolean {
if (spec == null || user == null) return false
val bodySpec = spec.trim()
if (bodySpec.startsWith("word:")) {
val opts = bodySpec.removePrefix("word:").split('|').map { mFold(it) }
return opts.contains(mFold(user))
}
if (bodySpec.startsWith("set:") || bodySpec.startsWith("pair:")) {
val ordered = bodySpec.startsWith("pair:")
val body = bodySpec.removePrefix(if (ordered) "pair:" else "set:")
val want = mSplit(body) ?: return false
val got = mSplit(user) ?: return false
if (want.size != got.size || want.isEmpty()) return false
val wt = want.map { mParseTerm(it) ?: return false }
val gt = got.map { mParseTerm(it) ?: return false }
if (ordered) return wt.indices.all { mClose(wt[it], gt[it]) }
val used = BooleanArray(gt.size)
for (w in wt) {
var hit = gt.indices.firstOrNull { !used[it] && mExact(w, gt[it]) }
if (hit == null) hit = gt.indices.firstOrNull { !used[it] && mClose(w, gt[it]) }
if (hit == null) return false
used[hit] = true
}
return true
}
if (!bodySpec.startsWith("num:")) return false
val tw = mParseTerm(bodySpec.removePrefix("num:")) ?: return false
val tg = mParseTerm(user) ?: return false
return mClose(tw, tg)
}
fun mathIsDraw(spec: String?): Boolean {
if (spec == null) return false
return spec.startsWith("square:") || spec.startsWith("mid:") || spec.startsWith("right:") ||
spec.startsWith("para:") || spec.startsWith("points:")
}
fun mathDrawNeed(spec: String?): Int {
if (spec == null) return 0
if (spec.startsWith("square:")) return 2
if (spec.startsWith("mid:") || spec.startsWith("right:") || spec.startsWith("para:")) return 1
if (spec.startsWith("points:")) {
return spec.removePrefix("points:").split(';').count { !it.startsWith("fix:") }
}
return 0
}
private fun mParseXY(s: String): Pair<Int, Int>? {
val bits = s.trim().split(',')
if (bits.size != 2) return null
val x = bits[0].trim().toIntOrNull() ?: return null
val y = bits[1].trim().toIntOrNull() ?: return null
return x to y
}
fun mathDrawGiven(spec: String?): List<Pair<Int, Int>> {
if (spec == null) return emptyList()
if (spec.startsWith("points:")) {
return spec.removePrefix("points:").split(';').mapNotNull { part ->
if (!part.startsWith("fix:")) null else mParseXY(part.removePrefix("fix:"))
}
}
val body: String
val n: Int
when {
spec.startsWith("para:") -> { body = spec.removePrefix("para:"); n = 3 }
spec.startsWith("square:") -> { body = spec.removePrefix("square:"); n = 2 }
spec.startsWith("right:") -> { body = spec.removePrefix("right:"); n = 2 }
spec.startsWith("mid:") -> { body = spec.removePrefix("mid:"); n = 2 }
else -> return emptyList()
}
return body.split(';').take(n).mapNotNull { mParseXY(it) }
}
private fun mOnBoard(p: Pair<Int, Int>) = p.first in 0..10 && p.second in 0..8
fun mathDrawOk(spec: String?, pts: List<Pair<Int, Int>>): Boolean {
if (spec == null || mathDrawNeed(spec) != pts.size) return false
if (pts.any { !mOnBoard(it) }) return false
if (pts.map { "${it.first},${it.second}" }.toSet().size != pts.size) return false
val g = mathDrawGiven(spec)
if (spec.startsWith("mid:")) {
if (g.size != 2 || (g[0].first + g[1].first) % 2 != 0 || (g[0].second + g[1].second) % 2 != 0) return false
return pts[0] == (g[0].first + g[1].first) / 2 to (g[0].second + g[1].second) / 2
}
if (spec.startsWith("para:")) {
if (g.size != 3) return false
return pts[0] == g[1].first + g[2].first - g[0].first to g[1].second + g[2].second - g[0].second
}
if (spec.startsWith("right:")) {
if (g.size != 2) return false
val c = pts[0]
if (c == g[0] || c == g[1]) return false
val ux = g[0].first - c.first
val uy = g[0].second - c.second
val vx = g[1].first - c.first
val vy = g[1].second - c.second
return ux * vx + uy * vy == 0 && (ux != 0 || uy != 0) && (vx != 0 || vy != 0)
}
if (spec.startsWith("square:")) {
if (g.size != 2 || pts.size != 2) return false
val a = g[0]
val b = g[1]
val dx = b.first - a.first
val dy = b.second - a.second
if (dx == 0 && dy == 0) return false
val c1 = b.first - dy to b.second + dx
val d1 = a.first - dy to a.second + dx
val c2 = b.first + dy to b.second - dx
val d2 = a.first + dy to a.second - dx
fun same(p: Pair<Int, Int>, q: Pair<Int, Int>, r: Pair<Int, Int>, s: Pair<Int, Int>) =
(p == r && q == s) || (p == s && q == r)
if ((!mOnBoard(c1) || !mOnBoard(d1)) && (!mOnBoard(c2) || !mOnBoard(d2))) return false
return same(pts[0], pts[1], c1, d1) || same(pts[0], pts[1], c2, d2)
}
if (spec.startsWith("points:")) {
val want = spec.removePrefix("points:").split(';').mapNotNull { part ->
if (part.startsWith("fix:")) null else mParseXY(part)
}
if (want.size != pts.size) return false
val used = BooleanArray(pts.size)
for (w in want) {
val j = pts.indices.firstOrNull { !used[it] && pts[it] == w } ?: return false
used[j] = true
}
return true
}
return false
}

View file

@ -43,6 +43,11 @@ class Content(root: JSONObject) {
val fyz2 = raw.arr("fyz2")
val fyz3 = raw.arr("fyz3")
val fyz4 = raw.arr("fyz4")
val mat0 = raw.arr("mat0")
val mat = raw.arr("mat")
val mat2 = raw.arr("mat2")
val mat3 = raw.arr("mat3")
val mat4 = raw.arr("mat4")
val mluvnice = raw.arr("mluvnice")
val books = raw.arr("books")
val changelog = raw.arr("changelog")
@ -64,6 +69,11 @@ class Content(root: JSONObject) {
fun fyz2Lesson(id: Int) = fyz2.firstOrNull { it.int("id") == id }
fun fyz3Lesson(id: Int) = fyz3.firstOrNull { it.int("id") == id }
fun fyz4Lesson(id: Int) = fyz4.firstOrNull { it.int("id") == id }
fun mat0Lesson(id: Int) = mat0.firstOrNull { it.int("id") == id }
fun matLesson(id: Int) = mat.firstOrNull { it.int("id") == id }
fun mat2Lesson(id: Int) = mat2.firstOrNull { it.int("id") == id }
fun mat3Lesson(id: Int) = mat3.firstOrNull { it.int("id") == id }
fun mat4Lesson(id: Int) = mat4.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

@ -65,6 +65,16 @@ class ProgressStore(context: Context) {
fun markFyz3(id: Int) { prefs.edit().putBoolean("fyz3.$id", true).apply() }
fun fyz4Done(id: Int) = prefs.getBoolean("fyz4.$id", false)
fun markFyz4(id: Int) { prefs.edit().putBoolean("fyz4.$id", true).apply() }
fun mat0Done(id: Int) = prefs.getBoolean("mat0.$id", false)
fun markMat0(id: Int) { prefs.edit().putBoolean("mat0.$id", true).apply() }
fun matDone(id: Int) = prefs.getBoolean("mat.$id", false)
fun markMat(id: Int) { prefs.edit().putBoolean("mat.$id", true).apply() }
fun mat2Done(id: Int) = prefs.getBoolean("mat2.$id", false)
fun markMat2(id: Int) { prefs.edit().putBoolean("mat2.$id", true).apply() }
fun mat3Done(id: Int) = prefs.getBoolean("mat3.$id", false)
fun markMat3(id: Int) { prefs.edit().putBoolean("mat3.$id", true).apply() }
fun mat4Done(id: Int) = prefs.getBoolean("mat4.$id", false)
fun markMat4(id: Int) { prefs.edit().putBoolean("mat4.$id", true).apply() }
fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false)
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() }
@ -216,6 +226,46 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
sum.doneUnits += 1
}
}
content.mat0.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.mat0Done(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.mat.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.matDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.mat2.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.mat2Done(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.mat3.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.mat3Done(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.mat4.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.mat4Done(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

@ -1,6 +1,8 @@
package org.maturita.maturita.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -12,6 +14,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
@ -45,18 +48,28 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.maturita.maturita.AppViewModel
import org.maturita.maturita.data.J
import org.maturita.maturita.data.answerAccepts
import org.maturita.maturita.data.mathAnswerOk
import org.maturita.maturita.data.mathDrawGiven
import org.maturita.maturita.data.mathDrawNeed
import org.maturita.maturita.data.mathDrawOk
import org.maturita.maturita.data.mathIsDraw
import org.maturita.maturita.data.answerIncomplete
import org.maturita.maturita.data.hasUmlaut
import org.maturita.maturita.data.netTxtEq
@ -863,6 +876,198 @@ fun SciQuizScreen(vm: AppViewModel, area: String, id: Int) {
}
}
@Composable
fun MathPracticeScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = when (year) {
0 -> vm.content.mat0Lesson(id)
2 -> vm.content.mat2Lesson(id)
3 -> vm.content.mat3Lesson(id)
4 -> vm.content.mat4Lesson(id)
else -> vm.content.matLesson(id)
} ?: return
val problems = lesson.arr("problems")
var texts by remember(year, id) { mutableStateOf(List(problems.size) { "" }) }
var placed by remember(year, id) { mutableStateOf(List(problems.size) { emptyList<Pair<Int, Int>>() }) }
var marks by remember(year, id) { mutableStateOf(List<Boolean?>(problems.size) { null }) }
var revealed by remember(year, id) { mutableStateOf(false) }
var feedback by remember(year, id) { mutableStateOf("") }
var kind by remember(year, id) { mutableStateOf("") }
ExerciseScaffold(
vm,
vm.tr(lesson.str("exTitleKey")),
vm.tr(lesson.str("quizHeadKey")),
vm.tr("check"),
{
var all = problems.isNotEmpty()
val next = MutableList<Boolean?>(problems.size) { null }
problems.forEachIndexed { i, problem ->
val spec = problem.str("answer")
val good = if (mathIsDraw(spec)) mathDrawOk(spec, placed[i]) else mathAnswerOk(spec, texts[i])
next[i] = good
if (!good) all = false
}
marks = next
revealed = true
if (all) {
feedback = vm.tr("feedback_ok")
kind = "ok"
when (year) {
0 -> vm.markMat0(id)
2 -> vm.markMat2(id)
3 -> vm.markMat3(id)
4 -> vm.markMat4(id)
else -> vm.markMat(id)
}
} else {
feedback = vm.tr("feedback_retry_short")
kind = "bad"
}
},
feedback,
kind,
) {
Text(
vm.tr("math_quiz_intro"),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.height(8.dp))
problems.forEachIndexed { i, problem ->
val spec = problem.str("answer")
val border = when (marks.getOrNull(i)) {
true -> Color(0xFF2E7D32)
false -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.outlineVariant
}
Column(
Modifier
.fillMaxWidth()
.padding(bottom = 10.dp)
.border(1.5.dp, border, MaterialTheme.shapes.medium)
.padding(12.dp),
) {
Text(problem.str("prompt"), style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(8.dp))
if (mathIsDraw(spec)) {
Text(
vm.tr("math_draw_hint"),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.height(6.dp))
MathBoard(mathDrawGiven(spec), placed[i], mathDrawNeed(spec), marks.getOrNull(i)) { next ->
placed = placed.toMutableList().also { it[i] = next }
}
Row {
OutlinedButton(onClick = {
val next = placed[i].dropLast(1)
placed = placed.toMutableList().also { it[i] = next }
}) { Text(vm.tr("math_undo")) }
Spacer(Modifier.width(8.dp))
OutlinedButton(onClick = {
placed = placed.toMutableList().also { it[i] = emptyList() }
}) { Text(vm.tr("math_clear")) }
}
} else {
OutlinedTextField(
value = texts[i],
onValueChange = { value -> texts = texts.toMutableList().also { it[i] = value } },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text(vm.tr("math_answer_ph")) },
singleLine = true,
)
}
if (revealed) {
Spacer(Modifier.height(6.dp))
Text(
problem.str("hint"),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
}
}
}
@Composable
private fun MathBoard(
given: List<Pair<Int, Int>>,
user: List<Pair<Int, Int>>,
need: Int,
mark: Boolean?,
onChange: (List<Pair<Int, Int>>) -> Unit,
) {
val measurer = rememberTextMeasurer()
val accent = MaterialTheme.colorScheme.primary
val ink = MaterialTheme.colorScheme.onSurface
val grid = MaterialTheme.colorScheme.onSurfaceVariant
val paper = MaterialTheme.colorScheme.surfaceVariant
val ok = Color(0xFF2E7D32)
val bad = MaterialTheme.colorScheme.error
Canvas(
Modifier
.size(316.dp, 260.dp)
.pointerInput(given, user, need) {
detectTapGestures { pos ->
val cell = size.width.toFloat() / (10f + 36f / 28f)
val pad = cell * 18f / 28f
val cx = (pos.x - pad) / cell
val cy = (size.height.toFloat() - pad - pos.y) / cell
val ix = kotlin.math.round(cx)
val iy = kotlin.math.round(cy)
if (kotlin.math.abs(cx - ix) > 0.45f || kotlin.math.abs(cy - iy) > 0.45f) return@detectTapGestures
val x = ix.toInt()
val y = iy.toInt()
if (x !in 0..10 || y !in 0..8) return@detectTapGestures
val hit = x to y
if (given.any { it == hit }) return@detectTapGestures
val next = user.toMutableList()
val idx = next.indexOf(hit)
if (idx >= 0) next.removeAt(idx)
else if (next.size < need) next.add(hit)
else return@detectTapGestures
onChange(next)
}
},
) {
val cell = size.width / (10f + 36f / 28f)
val pad = cell * 18f / 28f
fun at(x: Int, y: Int) = Offset(pad + x * cell, size.height - pad - y * cell)
drawRect(paper)
for (i in 0..10) {
val x = pad + i * cell
drawLine(grid.copy(alpha = 0.55f), Offset(x, pad), Offset(x, size.height - pad), strokeWidth = 1f)
}
for (i in 0..8) {
val y = pad + i * cell
drawLine(grid.copy(alpha = 0.55f), Offset(pad, y), Offset(size.width - pad, y), strokeWidth = 1f)
}
drawLine(ink, Offset(pad, size.height - pad), Offset(size.width - pad, size.height - pad), strokeWidth = 2.4f)
drawLine(ink, Offset(pad, size.height - pad), Offset(pad, pad), strokeWidth = 2.4f)
if (given.size >= 2) {
drawLine(accent, at(given[0].first, given[0].second), at(given[1].first, given[1].second), strokeWidth = 3.5f)
if (given.size >= 3) {
drawLine(accent, at(given[0].first, given[0].second), at(given[2].first, given[2].second), strokeWidth = 3.5f)
}
}
val names = listOf("A", "B", "C", "D")
val label = TextStyle(color = ink, fontSize = 13.sp)
given.forEachIndexed { i, g ->
if (i >= names.size) return@forEachIndexed
val pt = at(g.first, g.second)
drawCircle(accent, radius = 7f, center = pt)
drawText(measurer, names[i], topLeft = Offset(pt.x + 8f, pt.y - 22f), style = label)
}
val dot = if (mark == false) bad else ok
user.forEachIndexed { i, u ->
val pt = at(u.first, u.second)
drawCircle(dot, radius = 7f, center = pt)
drawText(measurer, "${i + 1}", topLeft = Offset(pt.x + 8f, pt.y - 22f), style = label)
}
}
}
@Composable
fun FyzQuizScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = when (year) {

View file

@ -26,6 +26,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.MenuBook
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.Functions
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FormatListNumbered
@ -224,6 +225,7 @@ private fun SubjectIcon(icon: String, open: Boolean) {
"cz" -> CzechFlag(28.dp)
"flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp))
"bolt" -> Icon(Icons.Filled.Bolt, null, tint = tint, modifier = Modifier.size(26.dp))
"function" -> Icon(Icons.Filled.Functions, null, tint = tint, modifier = Modifier.size(26.dp))
else -> Icon(Icons.Filled.Lock, null, tint = tint)
}
}
@ -316,6 +318,14 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } +
vm.content.bio.count { vm.progress.bioDone(it.int("id")) }
}
"matyears", "mat0map", "matmap", "mat2map", "mat3map", "mat4map" -> {
sum.totalEx = vm.content.mat0.size + vm.content.mat.size + vm.content.mat2.size + vm.content.mat3.size + vm.content.mat4.size
sum.doneEx = vm.content.mat0.count { vm.progress.mat0Done(it.int("id")) } +
vm.content.mat.count { vm.progress.matDone(it.int("id")) } +
vm.content.mat2.count { vm.progress.mat2Done(it.int("id")) } +
vm.content.mat3.count { vm.progress.mat3Done(it.int("id")) } +
vm.content.mat4.count { vm.progress.mat4Done(it.int("id")) }
}
"fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map" -> {
sum.totalEx = vm.content.fyz.size + vm.content.fyz2.size + vm.content.fyz3.size + vm.content.fyz4.size
sum.doneEx = vm.content.fyz.count { vm.progress.fyzDone(it.int("id")) } +
@ -556,6 +566,32 @@ 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("Matematika"), vm.tr("search_lesson"), "matyears", extra = "matematika")
add(vm.tr("mat0_year"), vm.tr("search_lesson"), "mat0map", extra = "zakladka")
add(vm.tr("mat_year1"), vm.tr("search_lesson"), "matmap", extra = "rocnik")
add(vm.tr("mat_year2"), vm.tr("search_lesson"), "mat2map", extra = "rocnik")
add(vm.tr("mat_year3"), vm.tr("search_lesson"), "mat3map", extra = "rocnik")
add(vm.tr("mat_year4"), vm.tr("search_lesson"), "mat4map", extra = "rocnik")
vm.content.mat0.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat0unit${l.int("id")}", extra = "matematika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat0ex${l.int("id")}")
}
vm.content.mat.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "matunit${l.int("id")}", extra = "matematika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "matex${l.int("id")}")
}
vm.content.mat2.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat2unit${l.int("id")}", extra = "matematika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat2ex${l.int("id")}")
}
vm.content.mat3.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat3unit${l.int("id")}", extra = "matematika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat3ex${l.int("id")}")
}
vm.content.mat4.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat4unit${l.int("id")}", extra = "matematika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat4ex${l.int("id")}")
}
add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra = "fyzika")
add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra = "rocnik")
add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra = "rocnik")
@ -713,6 +749,21 @@ fun LitYearsScreen(vm: AppViewModel) {
}
}
@Composable
fun MatYearsScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Matematika"), vm.tr("mat_years_sub")) { inner ->
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
item {
NavRow(title = vm.tr("mat0_year"), subtitle = vm.tr("mat0_sub"), leading = { Text("ZŠ", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat0Map) }
NavRow(title = vm.tr("mat_year1"), subtitle = vm.tr("mat_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.MatMap) }
NavRow(title = vm.tr("mat_year2"), subtitle = vm.tr("mat2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat2Map) }
NavRow(title = vm.tr("mat_year3"), subtitle = vm.tr("mat3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat3Map) }
NavRow(title = vm.tr("mat_year4"), subtitle = vm.tr("mat4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat4Map) }
}
}
}
}
@Composable
fun FyzYearsScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Fyzika"), vm.tr("fyz_years_sub")) { inner ->