mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Add four years of English from the gymnasium syllabus.
Each year mixes listening, reading, writing, gap-fill, quizzes, and a year test so the subject is usable through the maturita. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c561c0acb5
commit
bb93ec76ec
32 changed files with 6607 additions and 14 deletions
12
README.md
12
README.md
|
|
@ -8,8 +8,8 @@ Vzdělávací aplikace k přípravě na maturitu (repo `Graduately`). / An educa
|
|||
|
||||
## Česky
|
||||
|
||||
Rozhraní je v češtině a angličtině. Hotových je pět předmětů; ostatní jsou
|
||||
zatím zamčené placeholdery. Aplikace se po instalaci aktualizuje sama.
|
||||
Rozhraní je v češtině a angličtině. Otevřené předměty jsou v seznamu níže;
|
||||
ostatní jsou zatím zamčené. Aplikace se po instalaci aktualizuje sama.
|
||||
|
||||
| Úvod | Předměty | Německá cesta |
|
||||
| ---- | -------- | ------------- |
|
||||
|
|
@ -78,6 +78,8 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
|
|||
- **Občanská nauka** — 1. ročník, 17 lekcí (člověk v lidském společenství);
|
||||
2. ročník, 16 lekcí (člověk jako občan); 3. ročník, 16 lekcí (člověk a právo);
|
||||
4. ročník, 16 lekcí (hospodářství, svět a filozofie)
|
||||
- **Angličtina** — 4 ročníky, 32 lekcí podle osnov gymnázia (A2 až maturita):
|
||||
poslech, čtení, psaní, doplňování, kvízy a ročníkové testy
|
||||
- statistiky, hledání (lupa nebo `Cmd/Ctrl+K`), 10 témat, tmavý/světlý režim, čeština/angličtina
|
||||
- ukončení: `Cmd/Super+Q` nebo `Alt+F4`
|
||||
|
||||
|
|
@ -210,8 +212,8 @@ Licence: [GPL-3.0](LICENSE).
|
|||
|
||||
## English
|
||||
|
||||
The UI is Czech and English. Five subjects are playable; the rest are locked
|
||||
placeholders. After install the app updates itself.
|
||||
The UI is Czech and English. Open subjects are listed below; the rest are
|
||||
still locked. After install the app updates itself.
|
||||
|
||||
| Welcome | Subjects | German path |
|
||||
| ------- | -------- | ----------- |
|
||||
|
|
@ -282,6 +284,8 @@ Intel Mac, another architecture, or hacking on the code →
|
|||
year 2, 16 lessons (the citizen in a democratic state);
|
||||
year 3, 16 lessons (the individual and the law);
|
||||
year 4, 16 lessons (the economy, the world and philosophy)
|
||||
- **English** — 4 years, 32 lessons from the gymnasium syllabus (A2 through
|
||||
the maturita): listening, reading, writing, gap-fill, quizzes and year tests
|
||||
- statistics, search (magnifier or `Cmd/Ctrl+K`), 10 palettes, dark/light mode, Czech/English
|
||||
- quit with `Cmd/Super+Q` or `Alt+F4`
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,11 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
|
|||
refresh()
|
||||
}
|
||||
|
||||
fun markEn(year: Int, id: Int) {
|
||||
progress.markEn(year, id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markLit(id: Int) {
|
||||
progress.markLit(id)
|
||||
refresh()
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import org.maturita.maturita.ui.On3QuizScreen
|
|||
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.EngExScreen
|
||||
import org.maturita.maturita.ui.PlotScreen
|
||||
import org.maturita.maturita.ui.RoadmapScreen
|
||||
import org.maturita.maturita.ui.SearchScreen
|
||||
|
|
@ -238,6 +240,21 @@ private fun MaturitaAppBody(vm: AppViewModel) {
|
|||
}
|
||||
}
|
||||
is Route.HwEx -> HwQuizScreen(vm, r.id)
|
||||
Route.EnYears -> EnYearsScreen(vm)
|
||||
is Route.EnMap -> LessonListScreen(
|
||||
vm, vm.tr("en_year${r.year}"), vm.tr("en_y${r.year}_sub"),
|
||||
vm.content.enYear(r.year).map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.enDone(r.year, id), Route.EnLesson(r.year, id))
|
||||
},
|
||||
)
|
||||
is Route.EnLesson -> {
|
||||
val l = vm.content.enLesson(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.EnEx(r.year, r.id))
|
||||
}
|
||||
}
|
||||
is Route.EnEx -> EngExScreen(vm, r.year, r.id)
|
||||
Route.OnYears -> OnYearsScreen(vm)
|
||||
Route.OnMap -> LessonListScreen(
|
||||
vm, vm.tr("on_year1"), vm.tr("on_sub"),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ sealed class Route(val page: String) {
|
|||
data object On4Map : Route("on4map")
|
||||
data class On4Lesson(val id: Int) : Route("on4unit$id")
|
||||
data class On4Ex(val id: Int) : Route("on4ex$id")
|
||||
data object EnYears : Route("enyears")
|
||||
data class EnMap(val year: Int) : Route("en${year}map")
|
||||
data class EnLesson(val year: Int, val id: Int) : Route("en${year}unit$id")
|
||||
data class EnEx(val year: Int, val id: Int) : Route("en${year}ex$id")
|
||||
data object LitYears : Route("lityears")
|
||||
data object LitMap : Route("litmap")
|
||||
data class LitLesson(val id: Int) : Route("litunit$id")
|
||||
|
|
@ -93,7 +97,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 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 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 ||
|
||||
|
|
@ -134,6 +138,10 @@ sealed class Route(val page: String) {
|
|||
On4Map -> listOf(Subjects, OnYears, On4Map)
|
||||
is On4Lesson -> listOf(Subjects, OnYears, On4Map, this)
|
||||
is On4Ex -> listOf(Subjects, OnYears, On4Map, On4Lesson(id), this)
|
||||
EnYears -> listOf(Subjects, EnYears)
|
||||
is EnMap -> listOf(Subjects, EnYears, this)
|
||||
is EnLesson -> listOf(Subjects, EnYears, EnMap(year), this)
|
||||
is EnEx -> listOf(Subjects, EnYears, EnMap(year), EnLesson(year, id), this)
|
||||
LitYears -> listOf(Subjects, CzechMap, LitYears)
|
||||
LitMap -> listOf(Subjects, CzechMap, LitYears, LitMap)
|
||||
is LitLesson -> listOf(Subjects, CzechMap, LitYears, LitMap, this)
|
||||
|
|
@ -208,6 +216,7 @@ fun routeFromPage(page: String): Route? = when (page) {
|
|||
"on2map" -> Route.On2Map
|
||||
"on3map" -> Route.On3Map
|
||||
"on4map" -> Route.On4Map
|
||||
"enyears" -> Route.EnYears
|
||||
"lityears" -> Route.LitYears
|
||||
"litmap" -> Route.LitMap
|
||||
"lit2map" -> Route.Lit2Map
|
||||
|
|
@ -348,6 +357,15 @@ fun routeFromPage(page: String): Route? = when (page) {
|
|||
Regex("""litex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.LitEx(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""en(\d)map""").matchEntire(page)?.let {
|
||||
return Route.EnMap(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""en(\d)unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.EnLesson(it.groupValues[1].toInt(), it.groupValues[2].toInt())
|
||||
}
|
||||
Regex("""en(\d)ex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.EnEx(it.groupValues[1].toInt(), it.groupValues[2].toInt())
|
||||
}
|
||||
Regex("""on4unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.On4Lesson(it.groupValues[1].toInt())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ class Content(root: JSONObject) {
|
|||
val on2 = raw.arr("on2")
|
||||
val on3 = raw.arr("on3")
|
||||
val on4 = raw.arr("on4")
|
||||
val en = raw.arr("en")
|
||||
val en2 = raw.arr("en2")
|
||||
val en3 = raw.arr("en3")
|
||||
val en4 = raw.arr("en4")
|
||||
val lit = raw.arr("lit")
|
||||
val lit2 = raw.arr("lit2")
|
||||
val lit3 = raw.arr("lit3")
|
||||
|
|
@ -59,6 +63,13 @@ class Content(root: JSONObject) {
|
|||
fun on2Lesson(id: Int) = on2.firstOrNull { it.int("id") == id }
|
||||
fun on3Lesson(id: Int) = on3.firstOrNull { it.int("id") == id }
|
||||
fun on4Lesson(id: Int) = on4.firstOrNull { it.int("id") == id }
|
||||
fun enYear(year: Int) = when (year) {
|
||||
2 -> en2
|
||||
3 -> en3
|
||||
4 -> en4
|
||||
else -> en
|
||||
}
|
||||
fun enLesson(year: Int, id: Int) = enYear(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 }
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ class ProgressStore(context: Context) {
|
|||
fun on4Done(id: Int) = prefs.getBoolean("on4.$id", false)
|
||||
fun markOn4(id: Int) { prefs.edit().putBoolean("on4.$id", true).apply() }
|
||||
|
||||
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 litDone(id: Int) = prefs.getBoolean("lit.$id", false)
|
||||
fun markLit(id: Int) { prefs.edit().putBoolean("lit.$id", true).apply() }
|
||||
fun lit2Done(id: Int) = prefs.getBoolean("lit2.$id", false)
|
||||
|
|
@ -146,6 +149,16 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
|
|||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for (year in 1..4) {
|
||||
content.enYear(year).forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.enDone(year, l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
content.lit.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
|
|
|
|||
|
|
@ -38,8 +38,15 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.speech.tts.UtteranceProgressListener
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import java.util.Locale
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
|
|
@ -1370,4 +1377,156 @@ fun PlotScreen(vm: AppViewModel, id: String) {
|
|||
put("meanings", book.o.optJSONArray("plotMeaning"))
|
||||
})
|
||||
DispatchExercise(vm, spec, vm.tr(book.str("plotTitle"))) { vm.markBookPlot(id) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
|
||||
val lesson = vm.content.enLesson(year, id) ?: return
|
||||
val readQ = lesson.arr("readQuiz")
|
||||
val listenQ = lesson.arr("listenQuiz")
|
||||
val quiz = lesson.arr("quiz")
|
||||
val gapRows = lesson.arr("gaps")
|
||||
val writing = lesson.obj("writing")
|
||||
val listening = lesson.strOrNull("listening")
|
||||
val readPicks = remember(year, id) { mutableStateListOf(*Array(readQ.size) { -1 }) }
|
||||
val listenPicks = remember(year, id) { mutableStateListOf(*Array(listenQ.size) { -1 }) }
|
||||
val quizPicks = remember(year, id) { mutableStateListOf(*Array(quiz.size) { -1 }) }
|
||||
val gapText = remember(year, id) { mutableStateListOf(*Array(gapRows.size) { "" }) }
|
||||
var essay by remember(year, id) { mutableStateOf("") }
|
||||
var played by remember(year, id) { mutableStateOf(false) }
|
||||
var showScript by remember(year, id) { mutableStateOf(false) }
|
||||
var revealed by remember(year, id) { mutableStateOf(false) }
|
||||
var showModel by remember(year, id) { mutableStateOf(false) }
|
||||
var fb by remember(year, id) { mutableStateOf("" to "") }
|
||||
val context = LocalContext.current
|
||||
var tts by remember { mutableStateOf<TextToSpeech?>(null) }
|
||||
DisposableEffect(context) {
|
||||
var engine: TextToSpeech? = null
|
||||
engine = TextToSpeech(context) { status ->
|
||||
if (status == TextToSpeech.SUCCESS) engine?.language = Locale.UK
|
||||
}
|
||||
tts = engine
|
||||
onDispose {
|
||||
engine?.stop()
|
||||
engine?.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
fun picksOk(questions: List<J>, picks: List<Int>) =
|
||||
questions.indices.all { picks[it] == questions[it].int("correct") }
|
||||
|
||||
ExerciseScaffold(vm, vm.tr(lesson.str("titleKey")), null, vm.tr("check"), {
|
||||
if (listening != null && !played) {
|
||||
fb = vm.tr("en_listen_first") to "err"
|
||||
} else {
|
||||
val readOk = picksOk(readQ, readPicks)
|
||||
val listenOk = picksOk(listenQ, listenPicks)
|
||||
val quizOk = picksOk(quiz, quizPicks)
|
||||
val gapOk = gapRows.indices.count {
|
||||
answerAccepts(normalizeAnswer(gapText[it]), gapRows[it].str("answers"))
|
||||
}
|
||||
val writeOk = if (writing == null) {
|
||||
true
|
||||
} else {
|
||||
val norm = " ${normalizeAnswer(essay)} "
|
||||
val keysOk = writing.str("keys").split("|").all { key ->
|
||||
val w = normalizeAnswer(key)
|
||||
w.isEmpty() || norm.contains(" $w ")
|
||||
}
|
||||
keysOk && normalizeAnswer(essay).split(" ").count { it.isNotEmpty() } >= writing.int("minWords", 20)
|
||||
}
|
||||
revealed = true
|
||||
val all = readOk && listenOk && quizOk && gapOk == gapRows.size && writeOk
|
||||
fb = when {
|
||||
all -> {
|
||||
showModel = true
|
||||
vm.markEn(year, id)
|
||||
vm.tr("feedback_ok") to "ok"
|
||||
}
|
||||
writing != null && !writeOk && readOk && listenOk && quizOk && gapOk == gapRows.size ->
|
||||
vm.tr("en_write_short") to "err"
|
||||
else -> vm.tr("feedback_retry_short") to "err"
|
||||
}
|
||||
}
|
||||
}, fb.first, fb.second) {
|
||||
lesson.strOrNull("reading")?.let { reading ->
|
||||
Text(vm.tr("en_sec_read"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp))
|
||||
Text(reading)
|
||||
EngChoices(readQ, readPicks, revealed) { index, option -> readPicks[index] = option }
|
||||
}
|
||||
if (listening != null) {
|
||||
Text(vm.tr("en_sec_listen"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp))
|
||||
Text(vm.tr("en_listen_note"), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
OutlinedButton(onClick = {
|
||||
played = true
|
||||
showScript = false
|
||||
val engine = tts
|
||||
if (engine == null) {
|
||||
showScript = true
|
||||
} else {
|
||||
engine.language = Locale.UK
|
||||
engine.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
|
||||
override fun onStart(utteranceId: String?) {}
|
||||
override fun onDone(utteranceId: String?) {
|
||||
Handler(Looper.getMainLooper()).post { showScript = true }
|
||||
}
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onError(utteranceId: String?) {
|
||||
Handler(Looper.getMainLooper()).post { showScript = true }
|
||||
}
|
||||
})
|
||||
engine.speak(listening, TextToSpeech.QUEUE_FLUSH, null, "en-lesson")
|
||||
val wait = (listening.split(" ").size * 420L).coerceAtLeast(2500L)
|
||||
Handler(Looper.getMainLooper()).postDelayed({ showScript = true }, wait)
|
||||
}
|
||||
}) { Text(vm.tr("en_play")) }
|
||||
if (showScript) Text(listening)
|
||||
EngChoices(listenQ, listenPicks, revealed) { index, option -> listenPicks[index] = option }
|
||||
}
|
||||
if (gapRows.isNotEmpty()) {
|
||||
Text(vm.tr("en_sec_gap"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp))
|
||||
gapRows.forEachIndexed { i, row ->
|
||||
Text("${i + 1}. ${row.str("prompt")}", style = MaterialTheme.typography.titleSmall)
|
||||
val mark = if (!revealed) null else answerAccepts(normalizeAnswer(gapText[i]), row.str("answers"))
|
||||
WordField(gapText[i], { gapText[i] = it }, "", mark)
|
||||
if (revealed) row.strOrNull("meaning")?.let { Meaning(it, true) }
|
||||
}
|
||||
}
|
||||
if (writing != null) {
|
||||
Text(vm.tr("en_sec_write"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp))
|
||||
Text(writing.str("prompt"))
|
||||
OutlinedTextField(
|
||||
value = essay,
|
||||
onValueChange = { essay = it },
|
||||
modifier = Modifier.fillMaxWidth().height(160.dp),
|
||||
minLines = 5,
|
||||
)
|
||||
if (showModel) {
|
||||
Text(vm.tr("en_model"), style = MaterialTheme.typography.titleSmall)
|
||||
Text(writing.str("model"))
|
||||
}
|
||||
}
|
||||
if (quiz.isNotEmpty()) {
|
||||
Text(vm.tr("en_sec_quiz"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp))
|
||||
EngChoices(quiz, quizPicks, revealed) { index, option -> quizPicks[index] = option }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EngChoices(
|
||||
questions: List<J>,
|
||||
picks: List<Int>,
|
||||
revealed: Boolean,
|
||||
onPick: (Int, Int) -> Unit,
|
||||
) {
|
||||
questions.forEachIndexed { i, q ->
|
||||
Text("${i + 1}. ${q.str("prompt")}", style = MaterialTheme.typography.titleSmall, modifier = Modifier.padding(top = 8.dp))
|
||||
q.strs("options").forEachIndexed { o, opt ->
|
||||
val selected = picks.getOrElse(i) { -1 } == o
|
||||
val mark = if (revealed && selected) picks[i] == q.int("correct") else null
|
||||
OptionRow(opt, selected, mark) { onPick(i, o) }
|
||||
}
|
||||
if (revealed) q.strOrNull("expl")?.let { Meaning(it, true) }
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +195,26 @@ fun CzechFlag(iconSize: Dp = 48.dp) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UkFlag(iconSize: Dp = 48.dp) {
|
||||
DrawnIcon(iconSize) {
|
||||
val w = this.size.width
|
||||
val h = this.size.height
|
||||
val clip = Path().apply { addOval(androidx.compose.ui.geometry.Rect(0f, 0f, w, h)) }
|
||||
clipPath(clip) {
|
||||
drawRect(Color(0xFF012466), size = Size(w, h))
|
||||
drawLine(Color.White, Offset(0f, 0f), Offset(w, h), strokeWidth = w * 0.16f)
|
||||
drawLine(Color.White, Offset(w, 0f), Offset(0f, h), strokeWidth = w * 0.16f)
|
||||
drawLine(Color(0xFFCC0D26), Offset(0f, 0f), Offset(w, h), strokeWidth = w * 0.055f)
|
||||
drawLine(Color(0xFFCC0D26), Offset(w, 0f), Offset(0f, h), strokeWidth = w * 0.055f)
|
||||
drawRect(Color.White, topLeft = Offset(0f, h * 0.38f), size = Size(w, h * 0.24f))
|
||||
drawRect(Color.White, topLeft = Offset(w * 0.38f, 0f), size = Size(w * 0.24f, h))
|
||||
drawRect(Color(0xFFCC0D26), topLeft = Offset(0f, h * 0.43f), size = Size(w, h * 0.14f))
|
||||
drawRect(Color(0xFFCC0D26), topLeft = Offset(w * 0.43f, 0f), size = Size(w * 0.14f, h))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BookIcon(color: Color, iconSize: Dp = 22.dp) {
|
||||
DrawnIcon(iconSize) {
|
||||
|
|
|
|||
|
|
@ -223,6 +223,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)
|
||||
"uk" -> UkFlag(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))
|
||||
|
|
@ -306,6 +307,12 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
|
|||
sum.totalEx = vm.content.hw.size
|
||||
sum.doneEx = vm.content.hw.count { vm.progress.hwDone(it.int("id")) }
|
||||
}
|
||||
"enyears", "en1map", "en2map", "en3map", "en4map" -> {
|
||||
sum.totalEx = (1..4).sumOf { vm.content.enYear(it).size }
|
||||
sum.doneEx = (1..4).sumOf { year ->
|
||||
vm.content.enYear(year).count { vm.progress.enDone(year, it.int("id")) }
|
||||
}
|
||||
}
|
||||
"onyears", "onmap", "on2map", "on3map", "on4map" -> {
|
||||
sum.totalEx = vm.content.on.size + vm.content.on2.size + vm.content.on3.size + vm.content.on4.size
|
||||
sum.doneEx = vm.content.on.count { vm.progress.onDone(it.int("id")) } +
|
||||
|
|
@ -545,6 +552,13 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
|
|||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "on4unit${l.int("id")}", extra = "obcanka")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "on4ex${l.int("id")}")
|
||||
}
|
||||
for (year in 1..4) {
|
||||
add(vm.tr("en_year$year"), vm.tr("search_lesson"), "en${year}map", extra = "anglictina english rocnik")
|
||||
vm.content.enYear(year).forEach { l ->
|
||||
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("Literatura"), vm.tr("search_lesson"), "lityears", extra = "literatura")
|
||||
add(vm.tr("lit_year1"), vm.tr("search_lesson"), "litmap", extra = "rocnik")
|
||||
add(vm.tr("lit_year2"), vm.tr("search_lesson"), "lit2map", extra = "rocnik")
|
||||
|
|
@ -722,6 +736,21 @@ fun HwYearsScreen(vm: AppViewModel) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun EnYearsScreen(vm: AppViewModel) {
|
||||
DetailScaffold(vm, vm.tr("English"), vm.tr("en_years_sub")) { inner ->
|
||||
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
||||
items(4) { index ->
|
||||
val year = index + 1
|
||||
NavRow(
|
||||
title = vm.tr("en_year$year"),
|
||||
subtitle = vm.tr("en_y${year}_sub"),
|
||||
leading = { Text("$year.", style = MaterialTheme.typography.titleMedium) },
|
||||
) { vm.go(Route.EnMap(year)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun OnYearsScreen(vm: AppViewModel) {
|
||||
DetailScaffold(vm, vm.tr("Občanská nauka"), vm.tr("on_years_sub")) { inner ->
|
||||
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
# cs: bullet
|
||||
# en: bullet
|
||||
|
||||
--- 2026-10-07
|
||||
cs: Angličtina: čtyři ročníky, 32 lekcí podle osnov gymnázia
|
||||
cs: Poslech, čtení, psaní, doplňování, kvízy a testy od A2 k maturitě
|
||||
en: English: four years, 32 lessons from the gymnasium syllabus
|
||||
en: Listening, reading, writing, gap-fill, quizzes and tests from A2 to the exam
|
||||
|
||||
--- 2026-10-06
|
||||
cs: Matematika: opakování základní školy a čtyři ročníky, 40 lekcí
|
||||
cs: Příklady se píšou a kontrolují, u geometrie se rýsuje na mřížku
|
||||
|
|
|
|||
|
|
@ -166,6 +166,11 @@ final class AppModel: ObservableObject {
|
|||
refresh()
|
||||
}
|
||||
|
||||
func markEn(_ year: Int, _ id: Int) {
|
||||
progress.markEn(year, id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markLit(_ id: Int) {
|
||||
progress.markLit(id)
|
||||
refresh()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ final class Content {
|
|||
let on2: [J]
|
||||
let on3: [J]
|
||||
let on4: [J]
|
||||
let en: [J]
|
||||
let en2: [J]
|
||||
let en3: [J]
|
||||
let en4: [J]
|
||||
let lit: [J]
|
||||
let lit2: [J]
|
||||
let lit3: [J]
|
||||
|
|
@ -85,6 +89,10 @@ final class Content {
|
|||
on2 = raw.arr("on2")
|
||||
on3 = raw.arr("on3")
|
||||
on4 = raw.arr("on4")
|
||||
en = raw.arr("en")
|
||||
en2 = raw.arr("en2")
|
||||
en3 = raw.arr("en3")
|
||||
en4 = raw.arr("en4")
|
||||
lit = raw.arr("lit")
|
||||
lit2 = raw.arr("lit2")
|
||||
lit3 = raw.arr("lit3")
|
||||
|
|
@ -112,6 +120,15 @@ final class Content {
|
|||
func on2Lesson(_ id: Int) -> J? { on2.first { $0.int("id") == id } }
|
||||
func on3Lesson(_ id: Int) -> J? { on3.first { $0.int("id") == id } }
|
||||
func on4Lesson(_ id: Int) -> J? { on4.first { $0.int("id") == id } }
|
||||
func enYear(_ year: Int) -> [J] {
|
||||
switch year {
|
||||
case 2: return en2
|
||||
case 3: return en3
|
||||
case 4: return en4
|
||||
default: return en
|
||||
}
|
||||
}
|
||||
func enLesson(_ year: Int, _ id: Int) -> J? { enYear(year).first { $0.int("id") == id } }
|
||||
func litLesson(_ id: Int) -> J? { lit.first { $0.int("id") == id } }
|
||||
func lit2Lesson(_ id: Int) -> J? { lit2.first { $0.int("id") == id } }
|
||||
func lit3Lesson(_ id: Int) -> J? { lit3.first { $0.int("id") == id } }
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ final class ProgressStore {
|
|||
func on4Done(_ id: Int) -> Bool { defaults.bool(forKey: "on4.\(id)") }
|
||||
func markOn4(_ id: Int) { defaults.set(true, forKey: "on4.\(id)") }
|
||||
|
||||
func enDone(_ year: Int, _ id: Int) -> Bool { defaults.bool(forKey: "en.\(year).\(id)") }
|
||||
func markEn(_ year: Int, _ id: Int) { defaults.set(true, forKey: "en.\(year).\(id)") }
|
||||
|
||||
func litDone(_ id: Int) -> Bool { defaults.bool(forKey: "lit.\(id)") }
|
||||
func markLit(_ id: Int) { defaults.set(true, forKey: "lit.\(id)") }
|
||||
func lit2Done(_ id: Int) -> Bool { defaults.bool(forKey: "lit2.\(id)") }
|
||||
|
|
@ -153,6 +156,16 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
|
|||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for year in 1...4 {
|
||||
for l in content.enYear(year) {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.enDone(year, l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
for l in content.lit {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ enum Route: Hashable {
|
|||
case on4Map
|
||||
case on4Lesson(Int)
|
||||
case on4Ex(Int)
|
||||
case enYears
|
||||
case enMap(Int)
|
||||
case enLesson(Int, Int)
|
||||
case enEx(Int, Int)
|
||||
case litYears
|
||||
case litMap
|
||||
case litLesson(Int)
|
||||
|
|
@ -123,6 +127,10 @@ enum Route: Hashable {
|
|||
case .on4Map: return "on4map"
|
||||
case .on4Lesson(let id): return "on4unit\(id)"
|
||||
case .on4Ex(let id): return "on4ex\(id)"
|
||||
case .enYears: return "enyears"
|
||||
case .enMap(let year): return "en\(year)map"
|
||||
case .enLesson(let year, let id): return "en\(year)unit\(id)"
|
||||
case .enEx(let year, let id): return "en\(year)ex\(id)"
|
||||
case .litYears: return "lityears"
|
||||
case .litMap: return "litmap"
|
||||
case .litLesson(let id): return "litunit\(id)"
|
||||
|
|
@ -246,6 +254,14 @@ enum Route: Hashable {
|
|||
return [.onYears, .on4Map, .on4Lesson(id)]
|
||||
case .on4Ex(let id):
|
||||
return [.onYears, .on4Map, .on4Lesson(id), .on4Ex(id)]
|
||||
case .enYears:
|
||||
return [.enYears]
|
||||
case .enMap(let year):
|
||||
return [.enYears, .enMap(year)]
|
||||
case .enLesson(let year, let id):
|
||||
return [.enYears, .enMap(year), .enLesson(year, id)]
|
||||
case .enEx(let year, let id):
|
||||
return [.enYears, .enMap(year), .enLesson(year, id), .enEx(year, id)]
|
||||
case .litYears:
|
||||
return [.czechMap, .litYears]
|
||||
case .litMap:
|
||||
|
|
@ -377,6 +393,7 @@ func routeFromPage(_ page: String) -> Route? {
|
|||
case "on2map": return .on2Map
|
||||
case "on3map": return .on3Map
|
||||
case "on4map": return .on4Map
|
||||
case "enyears": return .enYears
|
||||
case "lityears": return .litYears
|
||||
case "litmap": return .litMap
|
||||
case "lit2map": return .lit2Map
|
||||
|
|
@ -529,6 +546,15 @@ func routeFromPage(_ page: String) -> Route? {
|
|||
if let m = page.wholeMatch(of: /litex(\d+)/), let n = Int(m.1) {
|
||||
return .litEx(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /en(\d)map/), let y = Int(m.1) {
|
||||
return .enMap(y)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /en(\d)unit(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||
return .enLesson(y, n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /en(\d)ex(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||
return .enEx(y, n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /on4unit(\d+)/), let n = Int(m.1) {
|
||||
return .on4Lesson(n)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -625,6 +625,7 @@ struct SubjectIcon: View {
|
|||
switch icon {
|
||||
case "de": GermanFlag(size: size)
|
||||
case "cz": CzechFlag(size: size)
|
||||
case "uk": UkFlag(size: size)
|
||||
default:
|
||||
Image(systemName: subjectSymbol(icon))
|
||||
.font(.system(size: size * 0.72, weight: .semibold))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import AVFoundation
|
||||
import SwiftUI
|
||||
|
||||
struct DialogEx: View {
|
||||
|
|
@ -1345,3 +1346,188 @@ struct PlotScreen: View {
|
|||
}
|
||||
}
|
||||
|
||||
private final class EnSpeaker: ObservableObject {
|
||||
private let synth = AVSpeechSynthesizer()
|
||||
|
||||
func speak(_ text: String) {
|
||||
if synth.isSpeaking { synth.stopSpeaking(at: .immediate) }
|
||||
let utterance = AVSpeechUtterance(string: text)
|
||||
utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
|
||||
?? AVSpeechSynthesisVoice(language: "en-US")
|
||||
utterance.rate = AVSpeechUtteranceDefaultSpeechRate * 0.9
|
||||
synth.speak(utterance)
|
||||
}
|
||||
}
|
||||
|
||||
struct EngExScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
let year: Int
|
||||
let id: Int
|
||||
@StateObject private var speaker: EnSpeaker
|
||||
@State private var readPicks: [Int]
|
||||
@State private var listenPicks: [Int]
|
||||
@State private var quizPicks: [Int]
|
||||
@State private var gaps: [String]
|
||||
@State private var essay = ""
|
||||
@State private var played = false
|
||||
@State private var showScript = false
|
||||
@State private var revealed = false
|
||||
@State private var showModel = false
|
||||
@State private var fb = ("", "")
|
||||
|
||||
init(vm: AppModel, year: Int, id: Int) {
|
||||
self.vm = vm
|
||||
self.year = year
|
||||
self.id = id
|
||||
let lesson = vm.content.enLesson(year, id)
|
||||
_readPicks = State(initialValue: Array(repeating: -1, count: lesson?.arr("readQuiz").count ?? 0))
|
||||
_listenPicks = State(initialValue: Array(repeating: -1, count: lesson?.arr("listenQuiz").count ?? 0))
|
||||
_quizPicks = State(initialValue: Array(repeating: -1, count: lesson?.arr("quiz").count ?? 0))
|
||||
_gaps = State(initialValue: Array(repeating: "", count: lesson?.arr("gaps").count ?? 0))
|
||||
_speaker = StateObject(wrappedValue: EnSpeaker())
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let lesson = vm.content.enLesson(year, id) {
|
||||
let p = vm.palette
|
||||
ExerciseScaffold(vm: vm, title: vm.tr(lesson.str("titleKey")), sub: nil, action: vm.tr("check"), onAction: {
|
||||
check(lesson)
|
||||
}, feedback: fb.0, kind: fb.1) {
|
||||
if let reading = lesson.strOrNull("reading") {
|
||||
section("en_sec_read")
|
||||
Text(reading).textSelection(.enabled)
|
||||
choiceBlock(lesson.arr("readQuiz"), picks: $readPicks)
|
||||
}
|
||||
if let listening = lesson.strOrNull("listening") {
|
||||
section("en_sec_listen")
|
||||
Text(vm.tr("en_listen_note"))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
PillButton(label: vm.tr("en_play")) {
|
||||
played = true
|
||||
showScript = false
|
||||
speaker.speak(listening)
|
||||
let wait = max(2.5, Double(listening.split(separator: " ").count) * 0.42)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
|
||||
showScript = true
|
||||
}
|
||||
}
|
||||
if showScript {
|
||||
Text(listening).textSelection(.enabled)
|
||||
}
|
||||
choiceBlock(lesson.arr("listenQuiz"), picks: $listenPicks)
|
||||
}
|
||||
let gapRows = lesson.arr("gaps")
|
||||
if !gapRows.isEmpty {
|
||||
section("en_sec_gap")
|
||||
ForEach(gapRows.indices, id: \.self) { i in
|
||||
Text("\(i + 1). \(gapRows[i].str("prompt"))")
|
||||
.font(.body.weight(.semibold))
|
||||
WordField(
|
||||
value: Binding(get: { gaps[i] }, set: { gaps[i] = $0 }),
|
||||
placeholder: "",
|
||||
mark: revealed ? answerAccepts(normalizeAnswer(gaps[i]), gapRows[i].str("answers")) : nil,
|
||||
palette: p
|
||||
)
|
||||
if revealed, let hint = gapRows[i].strOrNull("meaning") {
|
||||
Meaning(text: hint, palette: p, visible: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let writing = lesson.obj("writing") {
|
||||
section("en_sec_write")
|
||||
Text(writing.str("prompt"))
|
||||
TextEditor(text: $essay)
|
||||
.frame(minHeight: 140)
|
||||
.padding(8)
|
||||
.background(Color.primary.opacity(0.04), in: RoundedRectangle(cornerRadius: 12))
|
||||
if showModel {
|
||||
Text(vm.tr("en_model"))
|
||||
.font(.subheadline.weight(.semibold))
|
||||
Text(writing.str("model")).textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
let quiz = lesson.arr("quiz")
|
||||
if !quiz.isEmpty {
|
||||
section("en_sec_quiz")
|
||||
choiceBlock(quiz, picks: $quizPicks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func section(_ key: String) -> some View {
|
||||
Text(vm.tr(key))
|
||||
.font(.title3.weight(.bold))
|
||||
.padding(.top, 8)
|
||||
}
|
||||
|
||||
private func choiceBlock(_ questions: [J], picks: Binding<[Int]>) -> some View {
|
||||
let p = vm.palette
|
||||
return ForEach(questions.indices, id: \.self) { i in
|
||||
let q = questions[i]
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("\(i + 1). \(q.str("prompt"))")
|
||||
.font(.body.weight(.semibold))
|
||||
ForEach(Array(q.strs("options").enumerated()), id: \.offset) { o, opt in
|
||||
let selected = picks.wrappedValue[i] == o
|
||||
let mark: Bool? = revealed && selected ? picks.wrappedValue[i] == q.int("correct") : nil
|
||||
OptionChip(text: opt, selected: selected, mark: mark) {
|
||||
picks.wrappedValue[i] = o
|
||||
}
|
||||
}
|
||||
if revealed, let expl = q.strOrNull("expl") {
|
||||
Meaning(text: expl, palette: p, visible: true)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private func check(_ lesson: J) {
|
||||
if lesson.strOrNull("listening") != nil && !played {
|
||||
fb = (vm.tr("en_listen_first"), "err")
|
||||
return
|
||||
}
|
||||
let readOk = score(lesson.arr("readQuiz"), readPicks)
|
||||
let listenOk = score(lesson.arr("listenQuiz"), listenPicks)
|
||||
let quizOk = score(lesson.arr("quiz"), quizPicks)
|
||||
let gapRows = lesson.arr("gaps")
|
||||
let gapOk = gapRows.indices.filter {
|
||||
answerAccepts(normalizeAnswer(gaps[$0]), gapRows[$0].str("answers"))
|
||||
}.count
|
||||
var writeOk = true
|
||||
if let writing = lesson.obj("writing") {
|
||||
let keys = writing.str("keys").split(separator: "|").map(String.init)
|
||||
let enough = wordCount(essay) >= writing.int("minWords", 20)
|
||||
writeOk = enough && keys.allSatisfy { hasWord(essay, $0) }
|
||||
}
|
||||
revealed = true
|
||||
let all = readOk && listenOk && quizOk && gapOk == gapRows.count && writeOk
|
||||
if all {
|
||||
fb = (vm.tr("feedback_ok"), "ok")
|
||||
showModel = true
|
||||
vm.markEn(year, id)
|
||||
} else if lesson.obj("writing") != nil && !writeOk && readOk && listenOk && quizOk && gapOk == gapRows.count {
|
||||
fb = (vm.tr("en_write_short"), "err")
|
||||
} else {
|
||||
fb = (vm.tr("feedback_retry_short"), "err")
|
||||
}
|
||||
}
|
||||
|
||||
private func score(_ questions: [J], _ picks: [Int]) -> Bool {
|
||||
questions.indices.allSatisfy { picks[$0] == questions[$0].int("correct") }
|
||||
}
|
||||
|
||||
private func wordCount(_ text: String) -> Int {
|
||||
let n = normalizeAnswer(text)
|
||||
return n.isEmpty ? 0 : n.split(separator: " ").count
|
||||
}
|
||||
|
||||
private func hasWord(_ text: String, _ word: String) -> Bool {
|
||||
let n = " \(normalizeAnswer(text)) "
|
||||
let w = normalizeAnswer(word)
|
||||
return !w.isEmpty && n.contains(" \(w) ")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,3 +33,27 @@ struct CzechFlag: View {
|
|||
.frame(width: size, height: size)
|
||||
}
|
||||
}
|
||||
|
||||
struct UkFlag: View {
|
||||
var size: CGFloat = 48
|
||||
var body: some View {
|
||||
Canvas { ctx, sz in
|
||||
let w = sz.width
|
||||
let h = sz.height
|
||||
ctx.clip(to: Path(ellipseIn: CGRect(origin: .zero, size: sz)))
|
||||
ctx.fill(Path(CGRect(origin: .zero, size: sz)), with: .color(Color(red: 0.01, green: 0.14, blue: 0.40)))
|
||||
var saltire = Path()
|
||||
saltire.move(to: .zero)
|
||||
saltire.addLine(to: CGPoint(x: w, y: h))
|
||||
saltire.move(to: CGPoint(x: w, y: 0))
|
||||
saltire.addLine(to: CGPoint(x: 0, y: h))
|
||||
ctx.stroke(saltire, with: .color(.white), lineWidth: w * 0.16)
|
||||
ctx.stroke(saltire, with: .color(Color(red: 0.80, green: 0.05, blue: 0.15)), lineWidth: w * 0.055)
|
||||
ctx.fill(Path(CGRect(x: 0, y: h * 0.38, width: w, height: h * 0.24)), with: .color(.white))
|
||||
ctx.fill(Path(CGRect(x: w * 0.38, y: 0, width: w * 0.24, height: h)), with: .color(.white))
|
||||
ctx.fill(Path(CGRect(x: 0, y: h * 0.43, width: w, height: h * 0.14)), with: .color(Color(red: 0.80, green: 0.05, blue: 0.15)))
|
||||
ctx.fill(Path(CGRect(x: w * 0.43, y: 0, width: w * 0.14, height: h)), with: .color(Color(red: 0.80, green: 0.05, blue: 0.15)))
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,6 +689,11 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
|
|||
case "hwyears", "hwmap":
|
||||
sum.totalEx = vm.content.hw.count
|
||||
sum.doneEx = vm.content.hw.filter { vm.progress.hwDone($0.int("id")) }.count
|
||||
case "enyears", "en1map", "en2map", "en3map", "en4map":
|
||||
sum.totalEx = (1...4).reduce(0) { $0 + vm.content.enYear($1).count }
|
||||
sum.doneEx = (1...4).reduce(0) { acc, year in
|
||||
acc + vm.content.enYear(year).filter { vm.progress.enDone(year, $0.int("id")) }.count
|
||||
}
|
||||
case "onyears", "onmap", "on2map", "on3map", "on4map":
|
||||
sum.totalEx = vm.content.on.count + vm.content.on2.count + vm.content.on3.count + vm.content.on4.count
|
||||
sum.doneEx = vm.content.on.filter { vm.progress.onDone($0.int("id")) }.count
|
||||
|
|
@ -905,6 +910,12 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
|
|||
add(vm.tr("on_year2"), vm.tr("search_lesson"), "on2map", extra: "rocnik")
|
||||
add(vm.tr("on_year3"), vm.tr("search_lesson"), "on3map", extra: "rocnik")
|
||||
add(vm.tr("on_year4"), vm.tr("search_lesson"), "on4map", extra: "rocnik")
|
||||
for year in 1...4 {
|
||||
add(vm.tr("en_year\(year)"), vm.tr("search_lesson"), "en\(year)map", extra: "anglictina english rocnik")
|
||||
for l in vm.content.enYear(year) {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "en\(year)unit\(l.int("id"))", extra: "anglictina english")
|
||||
}
|
||||
}
|
||||
for l in vm.content.on {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "onunit\(l.int("id"))", extra: "obcanka")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "onex\(l.int("id"))")
|
||||
|
|
@ -1247,6 +1258,40 @@ struct HwYearsScreen: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct EnYearsScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
let years: [(String, String, Route)] = [
|
||||
("en_year1", "en_y1_sub", .enMap(1)),
|
||||
("en_year2", "en_y2_sub", .enMap(2)),
|
||||
("en_year3", "en_y3_sub", .enMap(3)),
|
||||
("en_year4", "en_y4_sub", .enMap(4)),
|
||||
]
|
||||
MacFillGrid(count: years.count, minWidth: 260, minHeight: 180) { i in
|
||||
NavigationLink(value: years[i].2) {
|
||||
MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.navigationTitle(vm.tr("English"))
|
||||
.navigationSubtitle(vm.tr("en_years_sub"))
|
||||
#else
|
||||
List {
|
||||
ForEach(1...4, id: \.self) { year in
|
||||
NavigationLink(value: Route.enMap(year)) {
|
||||
ListRowLabel(title: vm.tr("en_year\(year)"), subtitle: vm.tr("en_y\(year)_sub"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(vm.tr("English"))
|
||||
.navigationSubtitle(vm.tr("en_years_sub"))
|
||||
.appListStyle()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct OnYearsScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
|
||||
|
|
@ -2183,6 +2228,29 @@ struct RouteDestination: View {
|
|||
HwQuizScreen(vm: vm, id: id)
|
||||
case .onYears:
|
||||
OnYearsScreen(vm: vm)
|
||||
case .enYears:
|
||||
EnYearsScreen(vm: vm)
|
||||
case .enMap(let year):
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("en_year\(year)"),
|
||||
subtitle: vm.tr("en_y\(year)_sub"),
|
||||
rows: vm.content.enYear(year).map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(
|
||||
id: "e\(year)\(id)",
|
||||
title: vm.tr(l.str("titleKey")),
|
||||
done: vm.progress.enDone(year, id),
|
||||
destination: .enLesson(year, id)
|
||||
)
|
||||
}
|
||||
)
|
||||
case .enLesson(let year, let id):
|
||||
if let l = vm.content.enLesson(year, id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .enEx(year, id))
|
||||
}
|
||||
case .enEx(let year, let id):
|
||||
EngExScreen(vm: vm, year: year, id: id)
|
||||
case .onMap:
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
|
|
|
|||
|
|
@ -776,7 +776,10 @@ def main() -> int:
|
|||
arrays: dict[str, tuple[str, list]] = {}
|
||||
i18n = {}
|
||||
for path in sorted(SRC.glob("*.c")):
|
||||
text = strip_comments(path.read_text(encoding="utf-8"))
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
if path.name == "english.c":
|
||||
raw = expand_local_includes(raw, path.parent)
|
||||
text = strip_comments(raw)
|
||||
arrays.update(find_arrays(text))
|
||||
if path.name == "i18n.c":
|
||||
i18n = parse_i18n(strip_comments(expand_local_includes(text, path.parent)))
|
||||
|
|
@ -1606,13 +1609,66 @@ def main() -> int:
|
|||
ex_lines.append("")
|
||||
(SRC / "cetba_book_exercises.inc").write_text("\n".join(ex_lines) + "\n", encoding="utf-8")
|
||||
|
||||
def en_text(name):
|
||||
if name not in arrays:
|
||||
return None
|
||||
raw = A(name)
|
||||
if isinstance(raw, list):
|
||||
parts = [x for x in raw if isinstance(x, str)]
|
||||
return parts[0] if len(parts) == 1 else ("\n".join(parts) if parts else None)
|
||||
return raw if isinstance(raw, str) else None
|
||||
|
||||
def en_choice(name):
|
||||
if name not in arrays:
|
||||
return []
|
||||
quiz = choice_items(A(name))
|
||||
hint_name = name + "_hints"
|
||||
hints = str_list(A(hint_name)) if hint_name in arrays else []
|
||||
for qi, q in enumerate(quiz):
|
||||
if qi < len(hints) and hints[qi]:
|
||||
q["expl"] = hints[qi]
|
||||
return quiz
|
||||
|
||||
def en_year(year):
|
||||
lessons = []
|
||||
for i in range(1, 9):
|
||||
prefix = f"en_y{year}_l{i}"
|
||||
write = str_list(A(f"{prefix}_write")) if f"{prefix}_write" in arrays else []
|
||||
writing = None
|
||||
if len(write) >= 3:
|
||||
writing = {
|
||||
"prompt": write[0],
|
||||
"model": write[1],
|
||||
"keys": write[2],
|
||||
"minWords": int(write[3]) if len(write) > 3 and str(write[3]).isdigit() else 20,
|
||||
}
|
||||
lessons.append({
|
||||
"id": i,
|
||||
"titleKey": f"en_y{year}_u{i}",
|
||||
"subKey": f"en_y{year}_u{i}_sub",
|
||||
"slides": slides_items(A(f"{prefix}_slides")),
|
||||
"reading": en_text(f"{prefix}_read"),
|
||||
"readQuiz": en_choice(f"{prefix}_readq"),
|
||||
"listening": en_text(f"{prefix}_listen"),
|
||||
"listenQuiz": en_choice(f"{prefix}_listenq"),
|
||||
"gaps": typed_items(A(f"{prefix}_gaps")) if f"{prefix}_gaps" in arrays else [],
|
||||
"writing": writing,
|
||||
"quiz": en_choice(f"{prefix}_quiz"),
|
||||
})
|
||||
return lessons
|
||||
|
||||
en_lessons = en_year(1)
|
||||
en2_lessons = en_year(2)
|
||||
en3_lessons = en_year(3)
|
||||
en4_lessons = en_year(4)
|
||||
|
||||
subjects = [
|
||||
{"key": "Deutsch", "open": True, "target": "roadmap", "icon": "de"},
|
||||
{"key": "Správa počítačových sítí", "open": True, "target": "netyears", "icon": "wifi"},
|
||||
{"key": "Technické vybavení", "open": True, "target": "hwyears", "icon": "chip"},
|
||||
{"key": "Český jazyk a literatura", "open": True, "target": "czechmap", "icon": "cz"},
|
||||
{"key": "Občanská nauka", "open": True, "target": "onyears", "icon": "people"},
|
||||
{"key": "English", "open": False, "target": None, "icon": "lock"},
|
||||
{"key": "English", "open": True, "target": "enyears", "icon": "uk"},
|
||||
{"key": "Matematika", "open": True, "target": "matyears", "icon": "function"},
|
||||
{"key": "Fyzika", "open": True, "target": "fyzyears", "icon": "bolt"},
|
||||
{"key": "Základy Přírodopisných věd", "open": True, "target": "scimap", "icon": "flask"},
|
||||
|
|
@ -1651,6 +1707,10 @@ def main() -> int:
|
|||
"on2": on2_lessons,
|
||||
"on3": on3_lessons,
|
||||
"on4": on4_lessons,
|
||||
"en": en_lessons,
|
||||
"en2": en2_lessons,
|
||||
"en3": en3_lessons,
|
||||
"en4": en4_lessons,
|
||||
"mluvnice": mluvnice,
|
||||
"books": books,
|
||||
"changelog": parse_changelog(ROOT / "data" / "changelog.txt"),
|
||||
|
|
|
|||
2537
scripts/gen-english.py
Normal file
2537
scripts/gen-english.py
Normal file
File diff suppressed because it is too large
Load diff
1331
src/english.c
Normal file
1331
src/english.c
Normal file
File diff suppressed because it is too large
Load diff
86
src/english_i18n.inc
Normal file
86
src/english_i18n.inc
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{"en_years_sub", "Čtyři ročníky podle osnov gymnázia, od A2 k maturitě.", "Four years of the gymnasium syllabus, from A2 to the exam."},
|
||||
{"en_year1", "1. ročník", "Year 1"},
|
||||
{"en_year2", "2. ročník", "Year 2"},
|
||||
{"en_year3", "3. ročník", "Year 3"},
|
||||
{"en_year4", "4. ročník", "Year 4"},
|
||||
{"en_sec_read", "Čtení", "Reading"},
|
||||
{"en_sec_listen", "Poslech", "Listening"},
|
||||
{"en_sec_write", "Psaní", "Writing"},
|
||||
{"en_sec_gap", "Doplňte", "Complete"},
|
||||
{"en_sec_quiz", "Kvíz", "Quiz"},
|
||||
{"en_sec_test", "Jazyková část", "Language"},
|
||||
{"en_play", "Přehrát", "Play"},
|
||||
{"en_listen_note", "Nejdřív si přečtěte otázky. Nahrávku přehraje hlas zařízení. Bez něj se věty ukážou postupně.", "Read the questions first. The device voice plays the recording. Without one, the sentences appear one by one."},
|
||||
{"en_listen_first", "Nejdřív poslech přehrajte.", "Play the recording first."},
|
||||
{"en_listen_fallback", "Hlas není dostupný, věty se ukazují postupně.", "No voice is available, so the sentences appear one by one."},
|
||||
{"en_write_short", "Text je krátký nebo v něm chybí požadovaná slova.", "The text is short or it misses the required words."},
|
||||
{"en_model", "Vzor", "Model"},
|
||||
{"en_write_ph", "Pište anglicky…", "Write in English…"},
|
||||
{"en_y1_sub", "Osobní život, škola, domov a minulost. Úroveň A2.", "Personal life, school, home and the past. Level A2."},
|
||||
{"en_y1_u1", "Já a moje rodina", "Me and my family"},
|
||||
{"en_y1_u1_sub", "Sloveso be, have got a přivlastňovací zájmena. Doplňování a kvíz.", "The verb be, have got and possessives. Gaps and a quiz."},
|
||||
{"en_y1_u2", "Školní den", "A school day"},
|
||||
{"en_y1_u2_sub", "Přítomný čas prostý. Čtení a doplňování.", "The present simple. Reading and gaps."},
|
||||
{"en_y1_u3", "Víkend ve městě", "A weekend in town"},
|
||||
{"en_y1_u3_sub", "Přítomný čas průběhový pro domluvené plány. Poslech.", "The present continuous for arrangements. Listening."},
|
||||
{"en_y1_u4", "Můj pokoj", "My room"},
|
||||
{"en_y1_u4_sub", "There is / there are a předložky místa. Krátký popis a kvíz.", "There is / there are and prepositions. A short description and a quiz."},
|
||||
{"en_y1_u5", "Na trhu", "At the market"},
|
||||
{"en_y1_u5_sub", "Počitatelnost, some a any, how much a how many. Čtení a doplňování.", "Countability, some and any, how much and how many. Reading and gaps."},
|
||||
{"en_y1_u6", "Minulé léto", "Last summer"},
|
||||
{"en_y1_u6_sub", "Minulý čas prostý, pravidelná i nepravidelná slovesa. Poslech a kvíz.", "The past simple, regular and irregular verbs. Listening and a quiz."},
|
||||
{"en_y1_u7", "Zpráva kamarádovi", "A message to a friend"},
|
||||
{"en_y1_u7_sub", "Will a going to. Krátká neformální zpráva a doplňování.", "Will and going to. A short informal message and gaps."},
|
||||
{"en_y1_u8", "Test 1. ročníku", "Year 1 test"},
|
||||
{"en_y1_u8_sub", "Čtení, poslech, gramatika a krátké psaní. Úroveň A2.", "Reading, listening, grammar and a short piece of writing. Level A2."},
|
||||
{"en_y2_sub", "Cestování, zdraví, práce, média a příroda. Úroveň B1.", "Travel, health, work, the media and nature. Level B1."},
|
||||
{"en_y2_u1", "Na cestách", "On the road"},
|
||||
{"en_y2_u1_sub", "Stupňování a minulý čas průběhový. Doplňování a kvíz.", "Comparison and the past continuous. Gaps and a quiz."},
|
||||
{"en_y2_u2", "U lékaře", "At the clinic"},
|
||||
{"en_y2_u2_sub", "Should, must a have to. Čtení letáku a kvíz.", "Should, must and have to. A leaflet and a quiz."},
|
||||
{"en_y2_u3", "První brigáda", "A first job"},
|
||||
{"en_y2_u3_sub", "Předpřítomný čas, for a since. Poslech pohovoru a doplňování.", "The present perfect, for and since. A job interview and gaps."},
|
||||
{"en_y2_u4", "Recenze filmu", "A film review"},
|
||||
{"en_y2_u4_sub", "Vztažné věty who, which a that. Krátká recenze a kvíz.", "Relative clauses with who, which and that. A short review and a quiz."},
|
||||
{"en_y2_u5", "Počasí a výlet", "Weather and a trip"},
|
||||
{"en_y2_u5_sub", "Too a enough, will a going to v předpovědi. Čtení a doplňování.", "Too and enough, will and going to in a forecast. Reading and gaps."},
|
||||
{"en_y2_u6", "Festival", "A festival"},
|
||||
{"en_y2_u6_sub", "Předpřítomný čas proti minulému. Poslech a kvíz.", "Present perfect versus past simple. Listening and a quiz."},
|
||||
{"en_y2_u7", "Kdyby", "If I could"},
|
||||
{"en_y2_u7_sub", "První a druhá podmínková věta. Psaní a doplňování.", "First and second conditionals. Writing and gaps."},
|
||||
{"en_y2_u8", "Test 2. ročníku", "Year 2 test"},
|
||||
{"en_y2_u8_sub", "Čtení, poslech, gramatika B1 a krátké psaní.", "Reading, listening, B1 grammar and a short piece of writing."},
|
||||
{"en_y3_sub", "Společnost, prostředí, média a složitější gramatika. Úroveň B1 až B2.", "Society, the environment, the media and harder grammar. Level B1 to B2."},
|
||||
{"en_y3_u1", "Jak se to vyrábí", "How things are made"},
|
||||
{"en_y3_u1_sub", "Trpný rod v přítomnosti a minulosti. Doplňování a kvíz.", "The present and past passive. Gaps and a quiz."},
|
||||
{"en_y3_u2", "Škola, o které se mluví", "A school people talk about"},
|
||||
{"en_y3_u2_sub", "Nepřímá řeč oznamovací. Čtení a kvíz.", "Reported statements. Reading and a quiz."},
|
||||
{"en_y3_u3", "Poslední buk", "The last beech"},
|
||||
{"en_y3_u3_sub", "Podmínky a slovník prostředí. Poslech podcastu a doplňování.", "Conditionals and environment vocabulary. A podcast and gaps."},
|
||||
{"en_y3_u4", "Pro a proti", "For and against"},
|
||||
{"en_y3_u4_sub", "Stavba úvahy a spojovací výrazy. Esej a kvíz.", "Essay structure and linking words. An essay and a quiz."},
|
||||
{"en_y3_u5", "Kdo to musel být", "It must have been"},
|
||||
{"en_y3_u5_sub", "Modální slovesa dedukce. Čtení záhady a kvíz.", "Modals of deduction. A short mystery and a quiz."},
|
||||
{"en_y3_u6", "Zprávy dne", "The news desk"},
|
||||
{"en_y3_u6_sub", "Nepřímé otázky. Poslech rozhovoru a kvíz.", "Reported questions. An interview and a quiz."},
|
||||
{"en_y3_u7", "Frázová slovesa v příběhu", "Phrasal verbs in a story"},
|
||||
{"en_y3_u7_sub", "Běžná frázová slovesa. Doplňování a kvíz.", "Common phrasal verbs. Gaps and a quiz."},
|
||||
{"en_y3_u8", "Test 3. ročníku", "Year 3 test"},
|
||||
{"en_y3_u8_sub", "Čtení, poslech, gramatika B1+ a krátká úvaha.", "Reading, listening, B1+ grammar and a short argument."},
|
||||
{"en_y4_sub", "Maturitní dovednosti: mluvení, formální psaní, čtení, poslech a gramatika. Úroveň B2.", "Exam skills: speaking, formal writing, reading, listening and grammar. Level B2."},
|
||||
{"en_y4_u1", "Maturitní kartička", "The speaking card"},
|
||||
{"en_y4_u1_sub", "Strategie ústní zkoušky a kolokace osobních témat. Kvíz a doplňování.", "Speaking-exam strategy and personal-topic collocations. A quiz and gaps."},
|
||||
{"en_y4_u2", "Formální e-mail", "A formal email"},
|
||||
{"en_y4_u2_sub", "Žádost a stížnost. Psaní a kvíz rejstříku.", "A request and a complaint. Writing and a register quiz."},
|
||||
{"en_y4_u3", "Noční vlak", "The night train"},
|
||||
{"en_y4_u3_sub", "Delší čtení ve stylu didaktického testu. Čtyři otázky k článku.", "A longer reading in the style of the written paper. Four questions."},
|
||||
{"en_y4_u4", "Tábor ve Walesu", "A camp in Wales"},
|
||||
{"en_y4_u4_sub", "Delší poslech: pohovor. Pět otázek.", "A longer listening: an interview. Five questions."},
|
||||
{"en_y4_u5", "Názorová esej", "An opinion essay"},
|
||||
{"en_y4_u5_sub", "Struktura názoru. Esej a kvíz o odstavcích.", "Opinion-essay structure. An essay and a quiz about paragraphs."},
|
||||
{"en_y4_u6", "Slovní zásoba okruhů", "Topic vocabulary"},
|
||||
{"en_y4_u6_sub", "Kolokace k maturitním tématům. Doplňování a kvíz.", "Collocations for the exam topics. Gaps and a quiz."},
|
||||
{"en_y4_u7", "Gramatická ambulance", "Grammar clinic"},
|
||||
{"en_y4_u7_sub", "Smíšené jevy k didaktickému testu. Kvíz a doplňování.", "Mixed points for the language paper. A quiz and gaps."},
|
||||
{"en_y4_u8", "Cvičná maturita", "A practice paper"},
|
||||
{"en_y4_u8_sub", "Čtení, poslech, jazyková kompetence a krátká písemná práce.", "Reading, listening, language in use and a short piece of writing."},
|
||||
1907
src/english_lessons.inc
Normal file
1907
src/english_lessons.inc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@
|
|||
#define HW_SUBJ 2 /* index of "Technické vybavení" */
|
||||
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
|
||||
#define ON_SUBJ 4 /* index of "Občanská nauka" */
|
||||
#define EN_SUBJ 5 /* index of "English" */
|
||||
#define MATH_SUBJ 6 /* index of "Matematika" */
|
||||
#define PHY_SUBJ 7 /* index of "Fyzika" */
|
||||
#define SCI_SUBJ 8 /* index of "Základy Přírodopisných věd" */
|
||||
|
|
@ -56,6 +57,7 @@ extern char *app_progress_on;
|
|||
extern char *app_progress_on2;
|
||||
extern char *app_progress_on3;
|
||||
extern char *app_progress_on4;
|
||||
extern char *app_progress_en;
|
||||
extern char *app_progress_mluvnice;
|
||||
extern char *app_progress_cetba;
|
||||
extern char *app_progress_lit;
|
||||
|
|
@ -84,6 +86,7 @@ extern char *app_progress_mat4;
|
|||
#define PROGRESS_ON2 app_progress_on2
|
||||
#define PROGRESS_ON3 app_progress_on3
|
||||
#define PROGRESS_ON4 app_progress_on4
|
||||
#define PROGRESS_EN app_progress_en
|
||||
#define NODE_SIZE 88.0
|
||||
#define PATH_SPAC 240.0
|
||||
#define ROAD_MX 150.0 /* horizontal canvas margin */
|
||||
|
|
@ -903,6 +906,11 @@ void on_lessons_apply_lang(void);
|
|||
GtkWidget *build_onyears_page(void);
|
||||
GtkWidget *build_onmap_page(void);
|
||||
void add_on_pages(GtkStack *stack);
|
||||
void en_load_progress(void);
|
||||
void progress_for_en(ProgressSum *out);
|
||||
void en_rail_theme_reset(void);
|
||||
GtkWidget *build_enyears_page(void);
|
||||
void add_en_pages(GtkStack *stack);
|
||||
void on2_load_progress(void);
|
||||
void on2_save_progress(void);
|
||||
void mark_on2_done(int lesson_id);
|
||||
|
|
@ -1117,6 +1125,8 @@ void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr,
|
|||
int width, int height, gpointer data);
|
||||
void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data);
|
||||
void draw_uk_flag(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data);
|
||||
void draw_book_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data);
|
||||
void draw_book_badge_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ const TrEntry tr_ui[] = {
|
|||
{"Český jazyk a literatura", "Český jazyk a literatura",
|
||||
"Czech Language and Literature"},
|
||||
{"Občanská nauka", "Občanská nauka", "Civics"},
|
||||
{"English", "English", "English"},
|
||||
{"English", "Angličtina", "English"},
|
||||
{"Deutsch", "Deutsch", "Deutsch"},
|
||||
{"Matematika", "Matematika", "Mathematics"},
|
||||
{"Fyzika", "Fyzika", "Physics"},
|
||||
|
|
@ -1619,6 +1619,7 @@ const TrEntry tr_ui[] = {
|
|||
{"lit_fuks_plot_title", "Sestavte děj", "Order the plot"},
|
||||
{"lit_fuks_plot_sub", "Přetáhněte části příběhu do správného pořadí.",
|
||||
"Drag the story parts into the correct order."},
|
||||
#include "english_i18n.inc"
|
||||
#include "math_i18n.inc"
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
|
@ -2601,6 +2602,7 @@ void on_lessons_apply_lang(void);
|
|||
void on2_lessons_apply_lang(void);
|
||||
void on3_lessons_apply_lang(void);
|
||||
void on4_lessons_apply_lang(void);
|
||||
void en_lessons_apply_lang(void);
|
||||
void lit_lessons_apply_lang(void);
|
||||
void sci_lessons_apply_lang(void);
|
||||
void fyz_lessons_apply_lang(void);
|
||||
|
|
@ -2623,6 +2625,7 @@ void apply_language(void) {
|
|||
on2_lessons_apply_lang();
|
||||
on3_lessons_apply_lang();
|
||||
on4_lessons_apply_lang();
|
||||
en_lessons_apply_lang();
|
||||
lit_lessons_apply_lang();
|
||||
sci_lessons_apply_lang();
|
||||
fyz_lessons_apply_lang();
|
||||
|
|
|
|||
|
|
@ -416,6 +416,8 @@ static gboolean run_smoke_test(gpointer data) {
|
|||
"on2unit1", "on2ex1", "on2unit16", "on2ex16",
|
||||
"on3unit1", "on3ex1", "on3unit16", "on3ex16",
|
||||
"on4unit1", "on4ex1", "on4unit16", "on4ex16",
|
||||
"enyears", "en1map", "en4map",
|
||||
"en1unit1", "en1ex1", "en4unit8", "en4ex8",
|
||||
"unit1", "unit2", "unit3",
|
||||
"u1e1", "u1e13", "u2e1", "u2e19", "u3e1", "u3e15",
|
||||
"u1vocab", "u2vocab", "u3vocab",
|
||||
|
|
@ -534,6 +536,8 @@ void activate(GtkApplication *app, gpointer user_data) {
|
|||
gtk_stack_add_named(main_stack, build_on3map_page(), "on3map");
|
||||
add_on3_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_on4map_page(), "on4map");
|
||||
gtk_stack_add_named(main_stack, build_enyears_page(), "enyears");
|
||||
add_en_pages(main_stack);
|
||||
add_on4_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_matyears_page(), "matyears");
|
||||
gtk_stack_add_named(main_stack, build_mat0map_page(), "mat0map");
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ void load_progress(void) {
|
|||
on2_load_progress();
|
||||
on3_load_progress();
|
||||
on4_load_progress();
|
||||
en_load_progress();
|
||||
lit_load_progress();
|
||||
sci_load_progress();
|
||||
fyz_load_progress();
|
||||
|
|
|
|||
20
src/search.c
20
src/search.c
|
|
@ -23,7 +23,8 @@ static GPtrArray *search_items;
|
|||
|
||||
static gboolean subject_open(int i) {
|
||||
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|
||||
|| i == ON_SUBJ || i == MATH_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
|
||||
|| i == ON_SUBJ || i == EN_SUBJ || i == MATH_SUBJ || i == PHY_SUBJ
|
||||
|| i == SCI_SUBJ;
|
||||
}
|
||||
|
||||
static const char *subject_page(int i) {
|
||||
|
|
@ -37,6 +38,8 @@ static const char *subject_page(int i) {
|
|||
return "czechmap";
|
||||
if (i == ON_SUBJ)
|
||||
return "onyears";
|
||||
if (i == EN_SUBJ)
|
||||
return "enyears";
|
||||
if (i == MATH_SUBJ)
|
||||
return "matyears";
|
||||
if (i == PHY_SUBJ)
|
||||
|
|
@ -241,6 +244,21 @@ static void catalog_rebuild(void) {
|
|||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
|
||||
}
|
||||
|
||||
catalog_add_key("en_year1", "search_lesson", "en1map", FALSE, 1, "anglictina english");
|
||||
catalog_add_key("en_year2", "search_lesson", "en2map", FALSE, 1, "anglictina english");
|
||||
catalog_add_key("en_year3", "search_lesson", "en3map", FALSE, 1, "anglictina english");
|
||||
catalog_add_key("en_year4", "search_lesson", "en4map", FALSE, 1, "anglictina english");
|
||||
for (int y = 1; y <= 4; y++) {
|
||||
for (i = 1; i <= 8; i++) {
|
||||
g_snprintf(key, sizeof(key), "en_y%d_u%d", y, i);
|
||||
g_snprintf(page, sizeof(page), "en%dunit%d", y, i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "anglictina english");
|
||||
g_snprintf(page, sizeof(page), "en%dex%d", y, i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3,
|
||||
"poslech cteni psani kviz");
|
||||
}
|
||||
}
|
||||
|
||||
catalog_add_key("Český jazyk a literatura", "search_subject",
|
||||
"czechmap", FALSE, 0, "cestina czech");
|
||||
catalog_add_key("Literatura", "search_lesson", "lityears", FALSE, 1,
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ void refresh_stats_ui(void) {
|
|||
progress_for_hw(&all);
|
||||
else if (s == ON_SUBJ)
|
||||
progress_for_on(&all);
|
||||
else if (s == EN_SUBJ)
|
||||
progress_for_en(&all);
|
||||
else if (s == CZ_SUBJ) {
|
||||
progress_for_mluvnice(&all);
|
||||
progress_for_lit(&all);
|
||||
|
|
@ -110,7 +112,8 @@ void refresh_stats_ui(void) {
|
|||
GtkWidget *bar = stats_ui.subj[s].bar;
|
||||
ProgressSum sp = {0};
|
||||
gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ)
|
||||
|| (s == ON_SUBJ) || (s == CZ_SUBJ)
|
||||
|| (s == ON_SUBJ) || (s == EN_SUBJ)
|
||||
|| (s == CZ_SUBJ)
|
||||
|| (s == SCI_SUBJ) || (s == PHY_SUBJ)
|
||||
|| (s == MATH_SUBJ)
|
||||
|| sub_unit_count[s] > 0;
|
||||
|
|
@ -123,6 +126,8 @@ void refresh_stats_ui(void) {
|
|||
progress_for_hw(&sp);
|
||||
else if (s == ON_SUBJ)
|
||||
progress_for_on(&sp);
|
||||
else if (s == EN_SUBJ)
|
||||
progress_for_en(&sp);
|
||||
else if (s == CZ_SUBJ) {
|
||||
progress_for_mluvnice(&sp);
|
||||
progress_for_lit(&sp);
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@
|
|||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ
|
||||
* (Czech), ON_SUBJ (civics), MATH_SUBJ (mathematics), PHY_SUBJ
|
||||
* (physics) and SCI_SUBJ
|
||||
* (natural sciences) are unlocked. */
|
||||
* (Czech), ON_SUBJ (civics), EN_SUBJ (English), MATH_SUBJ (mathematics),
|
||||
* PHY_SUBJ (physics) and SCI_SUBJ (natural sciences) are unlocked. */
|
||||
const char *sub_keys[NUM_SUBJECTS] = {
|
||||
"Deutsch",
|
||||
"Správa počítačových sítí",
|
||||
|
|
@ -156,7 +155,8 @@ void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
|
|||
|
||||
static gboolean subject_is_open(int i) {
|
||||
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|
||||
|| i == ON_SUBJ || i == MATH_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
|
||||
|| i == ON_SUBJ || i == EN_SUBJ || i == MATH_SUBJ || i == PHY_SUBJ
|
||||
|| i == SCI_SUBJ;
|
||||
}
|
||||
|
||||
static GtkWidget *subject_icon(int i) {
|
||||
|
|
@ -179,6 +179,9 @@ static GtkWidget *subject_icon(int i) {
|
|||
else if (i == ON_SUBJ)
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
|
||||
draw_civics_icon, NULL, NULL);
|
||||
else if (i == EN_SUBJ)
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
|
||||
draw_uk_flag, NULL, NULL);
|
||||
else if (i == MATH_SUBJ)
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
|
||||
draw_math_icon, NULL, NULL);
|
||||
|
|
@ -204,6 +207,8 @@ static const char *subject_target(int i) {
|
|||
return "czechmap";
|
||||
if (i == ON_SUBJ)
|
||||
return "onyears";
|
||||
if (i == EN_SUBJ)
|
||||
return "enyears";
|
||||
if (i == MATH_SUBJ)
|
||||
return "matyears";
|
||||
if (i == PHY_SUBJ)
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ void apply_theme(void) {
|
|||
on2_rail_theme_reset();
|
||||
on3_rail_theme_reset();
|
||||
on4_rail_theme_reset();
|
||||
en_rail_theme_reset();
|
||||
czech_rail_theme_reset();
|
||||
lit_rail_theme_reset();
|
||||
sci_rail_theme_reset();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ char *app_progress_on;
|
|||
char *app_progress_on2;
|
||||
char *app_progress_on3;
|
||||
char *app_progress_on4;
|
||||
char *app_progress_en;
|
||||
char *app_progress_mluvnice;
|
||||
char *app_progress_cetba;
|
||||
char *app_progress_lit;
|
||||
|
|
@ -181,6 +182,7 @@ static void set_progress_paths(const char *write_root) {
|
|||
g_free(app_progress_on2);
|
||||
g_free(app_progress_on3);
|
||||
g_free(app_progress_on4);
|
||||
g_free(app_progress_en);
|
||||
g_free(app_progress_mluvnice);
|
||||
g_free(app_progress_cetba);
|
||||
g_free(app_progress_lit);
|
||||
|
|
@ -210,6 +212,7 @@ static void set_progress_paths(const char *write_root) {
|
|||
app_progress_on2 = g_build_filename(app_progress_dir, "on2.conf", NULL);
|
||||
app_progress_on3 = g_build_filename(app_progress_dir, "on3.conf", NULL);
|
||||
app_progress_on4 = g_build_filename(app_progress_dir, "on4.conf", NULL);
|
||||
app_progress_en = g_build_filename(app_progress_dir, "english.conf", NULL);
|
||||
app_progress_mluvnice = g_build_filename(app_progress_dir, "mluvnice.conf", NULL);
|
||||
app_progress_cetba = g_build_filename(app_progress_dir, "cetba.conf", NULL);
|
||||
app_progress_lit = g_build_filename(app_progress_dir, "lit.conf", NULL);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue