mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Add four years of German listening, reading and writing beside the textbook.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3b4e7b283a
commit
b822c57875
31 changed files with 3589 additions and 102 deletions
|
|
@ -61,8 +61,8 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
|
|||
|
||||
### Co v aplikaci je
|
||||
|
||||
- **Deutsch** — 3 otevřené jednotky (Neue Freunde, Aus aller Welt, Bei uns zu
|
||||
Hause): cvičení, nápovědy, vokabeltraining
|
||||
- **Deutsch** — učebnice (Neue Freunde, Aus aller Welt, Bei uns zu Hause)
|
||||
a 4 ročníky, 24 lekcí od A1 k B1: poslech, čtení, psaní, doplňování a kvízy
|
||||
- **Správa počítačových sítí** — 1. ročník, 27 lekcí (VLSM + teorie a kvízy);
|
||||
ročníky 2–4 jsou zamčené
|
||||
- **Technické vybavení** — 1. ročník, 29 lekcí (architektura, data, skříň a deska, karty, disky, vstupní zařízení); ročníky 2–4 jsou zamčené
|
||||
|
|
@ -266,8 +266,8 @@ Intel Mac, another architecture, or hacking on the code →
|
|||
|
||||
### What’s inside
|
||||
|
||||
- **Deutsch** — 3 open units (Neue Freunde, Aus aller Welt, Bei uns zu Hause):
|
||||
exercises, hints, vocabulary training
|
||||
- **Deutsch** — the textbook (Neue Freunde, Aus aller Welt, Bei uns zu Hause)
|
||||
plus 4 years, 24 lessons from A1 to B1: listening, reading, writing, gap-fill and quizzes
|
||||
- **Computer networks** — year 1, 27 lessons (VLSM + theory/quizzes);
|
||||
years 2–4 are locked
|
||||
- **Computer hardware** — year 1, 29 lessons (architecture, data, case and board, cards, drives, input devices); years 2–4 are locked
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
|
|||
refresh()
|
||||
}
|
||||
|
||||
fun markDe(year: Int, id: Int) {
|
||||
progress.markDe(year, id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markLit(id: Int) {
|
||||
progress.markLit(id)
|
||||
refresh()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.maturita.maturita
|
||||
|
||||
import android.os.Bundle
|
||||
import java.util.Locale
|
||||
import android.util.TypedValue
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
|
|
@ -50,6 +51,7 @@ import org.maturita.maturita.ui.On4QuizScreen
|
|||
import org.maturita.maturita.ui.OnQuizScreen
|
||||
import org.maturita.maturita.ui.OnYearsScreen
|
||||
import org.maturita.maturita.ui.EnYearsScreen
|
||||
import org.maturita.maturita.ui.DeHomeScreen
|
||||
import org.maturita.maturita.ui.EngExScreen
|
||||
import org.maturita.maturita.ui.PlotScreen
|
||||
import org.maturita.maturita.ui.RoadmapScreen
|
||||
|
|
@ -206,6 +208,23 @@ private fun MaturitaAppBody(vm: AppViewModel) {
|
|||
Route.Welcome, Route.Subjects -> HomeScreen(vm)
|
||||
Route.Stats -> StatsScreen(vm)
|
||||
Route.Settings -> SettingsScreen(vm)
|
||||
Route.DeHome -> DeHomeScreen(vm)
|
||||
is Route.DeMap -> LessonListScreen(
|
||||
vm, vm.tr("de_year${r.year}"), vm.tr("de_y${r.year}_sub"),
|
||||
vm.content.deYear(r.year).map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.deDone(r.year, id), Route.DeLesson(r.year, id))
|
||||
},
|
||||
)
|
||||
is Route.DeLesson -> {
|
||||
val l = vm.content.deLesson(r.year, r.id)
|
||||
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||
vm.replace(Route.DeEx(r.year, r.id))
|
||||
}
|
||||
}
|
||||
is Route.DeEx -> EngExScreen(
|
||||
vm, r.year, r.id, Locale.GERMANY, vm.content.deLesson(r.year, r.id),
|
||||
) { vm.markDe(r.year, r.id) }
|
||||
Route.Roadmap -> RoadmapScreen(vm)
|
||||
is Route.UnitMap -> UnitMapScreen(vm, r.unitId)
|
||||
is Route.GermanEx -> GermanExercise(vm, r.unitId, r.ex)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ sealed class Route(val page: String) {
|
|||
data object Stats : Route("stats")
|
||||
data object Settings : Route("settings")
|
||||
data object Roadmap : Route("roadmap")
|
||||
data object DeHome : Route("dehome")
|
||||
data class DeMap(val year: Int) : Route("de${year}map")
|
||||
data class DeLesson(val year: Int, val id: Int) : Route("de${year}unit$id")
|
||||
data class DeEx(val year: Int, val id: Int) : Route("de${year}ex$id")
|
||||
data class UnitMap(val unitId: Int) : Route("unit${unitId + 1}")
|
||||
data class GermanEx(val unitId: Int, val ex: Int) : Route("u${unitId + 1}e$ex")
|
||||
data class Vocab(val unitId: Int) : Route("u${unitId + 1}vocab")
|
||||
|
|
@ -97,7 +101,7 @@ sealed class Route(val page: String) {
|
|||
/** Screens with their own bottom action bar; the navigation bar stays hidden there. */
|
||||
val isExercise: Boolean
|
||||
get() = this is GermanEx || this is Vocab || this is NetEx || this is HwEx ||
|
||||
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is EnLesson || this is EnEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
|
||||
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is EnLesson || this is EnEx || this is DeLesson || this is DeEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
|
||||
this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson ||
|
||||
this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex ||
|
||||
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
|
||||
|
|
@ -113,10 +117,14 @@ sealed class Route(val page: String) {
|
|||
Welcome, Subjects -> listOf(Subjects)
|
||||
Stats -> listOf(Stats)
|
||||
Settings -> listOf(Settings)
|
||||
Roadmap -> listOf(Subjects, Roadmap)
|
||||
is UnitMap -> listOf(Subjects, Roadmap, this)
|
||||
is GermanEx -> listOf(Subjects, Roadmap, UnitMap(unitId), this)
|
||||
is Vocab -> listOf(Subjects, Roadmap, UnitMap(unitId), this)
|
||||
DeHome -> listOf(Subjects, DeHome)
|
||||
is DeMap -> listOf(Subjects, DeHome, this)
|
||||
is DeLesson -> listOf(Subjects, DeHome, DeMap(year), this)
|
||||
is DeEx -> listOf(Subjects, DeHome, DeMap(year), DeLesson(year, id), this)
|
||||
Roadmap -> listOf(Subjects, DeHome, Roadmap)
|
||||
is UnitMap -> listOf(Subjects, DeHome, Roadmap, this)
|
||||
is GermanEx -> listOf(Subjects, DeHome, Roadmap, UnitMap(unitId), this)
|
||||
is Vocab -> listOf(Subjects, DeHome, Roadmap, UnitMap(unitId), this)
|
||||
NetYears -> listOf(Subjects, NetYears)
|
||||
NetMap -> listOf(Subjects, NetYears, NetMap)
|
||||
is NetLesson -> listOf(Subjects, NetYears, NetMap, this)
|
||||
|
|
@ -207,6 +215,7 @@ fun routeFromPage(page: String): Route? = when (page) {
|
|||
"stats" -> Route.Stats
|
||||
"settings" -> Route.Settings
|
||||
"roadmap" -> Route.Roadmap
|
||||
"dehome" -> Route.DeHome
|
||||
"netyears" -> Route.NetYears
|
||||
"netmap" -> Route.NetMap
|
||||
"hwyears" -> Route.HwYears
|
||||
|
|
@ -357,6 +366,15 @@ fun routeFromPage(page: String): Route? = when (page) {
|
|||
Regex("""litex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.LitEx(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""de(\d)map""").matchEntire(page)?.let {
|
||||
return Route.DeMap(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""de(\d)unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.DeLesson(it.groupValues[1].toInt(), it.groupValues[2].toInt())
|
||||
}
|
||||
Regex("""de(\d)ex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.DeEx(it.groupValues[1].toInt(), it.groupValues[2].toInt())
|
||||
}
|
||||
Regex("""en(\d)map""").matchEntire(page)?.let {
|
||||
return Route.EnMap(it.groupValues[1].toInt())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ class Content(root: JSONObject) {
|
|||
val en2 = raw.arr("en2")
|
||||
val en3 = raw.arr("en3")
|
||||
val en4 = raw.arr("en4")
|
||||
val de = raw.arr("de")
|
||||
val de2 = raw.arr("de2")
|
||||
val de3 = raw.arr("de3")
|
||||
val de4 = raw.arr("de4")
|
||||
val lit = raw.arr("lit")
|
||||
val lit2 = raw.arr("lit2")
|
||||
val lit3 = raw.arr("lit3")
|
||||
|
|
@ -70,6 +74,13 @@ class Content(root: JSONObject) {
|
|||
else -> en
|
||||
}
|
||||
fun enLesson(year: Int, id: Int) = enYear(year).firstOrNull { it.int("id") == id }
|
||||
fun deYear(year: Int) = when (year) {
|
||||
2 -> de2
|
||||
3 -> de3
|
||||
4 -> de4
|
||||
else -> de
|
||||
}
|
||||
fun deLesson(year: Int, id: Int) = deYear(year).firstOrNull { it.int("id") == id }
|
||||
fun litLesson(id: Int) = lit.firstOrNull { it.int("id") == id }
|
||||
fun lit2Lesson(id: Int) = lit2.firstOrNull { it.int("id") == id }
|
||||
fun lit3Lesson(id: Int) = lit3.firstOrNull { it.int("id") == id }
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ class ProgressStore(context: Context) {
|
|||
|
||||
fun enDone(year: Int, id: Int) = prefs.getBoolean("en.$year.$id", false)
|
||||
fun markEn(year: Int, id: Int) { prefs.edit().putBoolean("en.$year.$id", true).apply() }
|
||||
fun deDone(year: Int, id: Int) = prefs.getBoolean("de.$year.$id", false)
|
||||
fun markDe(year: Int, id: Int) { prefs.edit().putBoolean("de.$year.$id", true).apply() }
|
||||
|
||||
fun litDone(id: Int) = prefs.getBoolean("lit.$id", false)
|
||||
fun markLit(id: Int) { prefs.edit().putBoolean("lit.$id", true).apply() }
|
||||
|
|
@ -158,6 +160,14 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
|
|||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
content.deYear(year).forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.deDone(year, l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
content.lit.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
|
|
|
|||
|
|
@ -1382,8 +1382,15 @@ fun PlotScreen(vm: AppViewModel, id: String) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
|
||||
val lesson = vm.content.enLesson(year, id) ?: return
|
||||
fun EngExScreen(
|
||||
vm: AppViewModel,
|
||||
year: Int,
|
||||
id: Int,
|
||||
locale: Locale = Locale.UK,
|
||||
spec: J? = vm.content.enLesson(year, id),
|
||||
onDone: () -> Unit = { vm.markEn(year, id) },
|
||||
) {
|
||||
val lesson = spec ?: return
|
||||
val readQ = lesson.arr("readQuiz")
|
||||
val listenQ = lesson.arr("listenQuiz")
|
||||
val quiz = lesson.arr("quiz")
|
||||
|
|
@ -1410,7 +1417,7 @@ fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
|
|||
DisposableEffect(context) {
|
||||
var engine: TextToSpeech? = null
|
||||
engine = TextToSpeech(context) { status ->
|
||||
if (status == TextToSpeech.SUCCESS) engine?.language = Locale.UK
|
||||
if (status == TextToSpeech.SUCCESS) engine?.language = locale
|
||||
}
|
||||
tts = engine
|
||||
onDispose {
|
||||
|
|
@ -1447,7 +1454,7 @@ fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
|
|||
fb = when {
|
||||
all -> {
|
||||
showModel = true
|
||||
vm.markEn(year, id)
|
||||
onDone()
|
||||
vm.tr("feedback_ok") to "ok"
|
||||
}
|
||||
writing != null && !writeOk && readOk && listenOk && quizOk && gapOk == gapRows.size ->
|
||||
|
|
@ -1472,7 +1479,7 @@ fun EngExScreen(vm: AppViewModel, year: Int, id: Int) {
|
|||
val start = index.coerceIn(0, listening.length)
|
||||
val id = "en-${System.nanoTime()}"
|
||||
utterance = id
|
||||
engine.language = Locale.UK
|
||||
engine.language = locale
|
||||
engine.setSpeechRate(speed)
|
||||
engine.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
|
||||
override fun onStart(utteranceId: String?) {}
|
||||
|
|
|
|||
|
|
@ -294,10 +294,16 @@ private fun Metric(label: String, value: String, modifier: Modifier) {
|
|||
private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
|
||||
val sum = ProgressSum()
|
||||
when (s.str("target")) {
|
||||
"roadmap" -> vm.content.german.filter { it.bool("unlocked") }.forEach { u ->
|
||||
val n = u.strs("names").size
|
||||
sum.totalEx += n
|
||||
sum.doneEx += (1..n).count { vm.progress.germanDone(u.int("id"), it) }
|
||||
"dehome" -> {
|
||||
vm.content.german.filter { it.bool("unlocked") }.forEach { u ->
|
||||
val n = u.strs("names").size
|
||||
sum.totalEx += n
|
||||
sum.doneEx += (1..n).count { vm.progress.germanDone(u.int("id"), it) }
|
||||
}
|
||||
sum.totalEx += (1..4).sumOf { vm.content.deYear(it).size }
|
||||
sum.doneEx += (1..4).sumOf { year ->
|
||||
vm.content.deYear(year).count { vm.progress.deDone(year, it.int("id")) }
|
||||
}
|
||||
}
|
||||
"netyears" -> {
|
||||
sum.totalEx = vm.content.netLessons.size
|
||||
|
|
@ -558,6 +564,11 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
|
|||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "en${year}unit${l.int("id")}", extra = "anglictina english")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "en${year}ex${l.int("id")}")
|
||||
}
|
||||
add(vm.tr("de_year$year"), vm.tr("search_lesson"), "de${year}map", extra = "nemcina deutsch rocnik")
|
||||
vm.content.deYear(year).forEach { l ->
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "de${year}unit${l.int("id")}", extra = "nemcina deutsch")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "de${year}ex${l.int("id")}")
|
||||
}
|
||||
}
|
||||
add(vm.tr("Literatura"), vm.tr("search_lesson"), "lityears", extra = "literatura")
|
||||
add(vm.tr("lit_year1"), vm.tr("search_lesson"), "litmap", extra = "rocnik")
|
||||
|
|
@ -735,6 +746,28 @@ fun HwYearsScreen(vm: AppViewModel) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Composable
|
||||
fun DeHomeScreen(vm: AppViewModel) {
|
||||
DetailScaffold(vm, vm.tr("Deutsch"), vm.tr("de_home_sub")) { inner ->
|
||||
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
||||
item {
|
||||
NavRow(title = vm.tr("de_book_title"), subtitle = vm.tr("de_book_sub"), leading = { Text("U", style = MaterialTheme.typography.titleMedium) }) {
|
||||
vm.go(Route.Roadmap)
|
||||
}
|
||||
}
|
||||
items(4) { index ->
|
||||
val year = index + 1
|
||||
NavRow(
|
||||
title = vm.tr("de_year$year"),
|
||||
subtitle = vm.tr("de_y${year}_sub"),
|
||||
leading = { Text("$year.", style = MaterialTheme.typography.titleMedium) },
|
||||
) { vm.go(Route.DeMap(year)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EnYearsScreen(vm: AppViewModel) {
|
||||
DetailScaffold(vm, vm.tr("English"), vm.tr("en_years_sub")) { inner ->
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
# cs: bullet
|
||||
# en: bullet
|
||||
|
||||
--- 2026-10-07
|
||||
cs: Němčina: čtyři ročníky, 24 lekcí s poslechem, čtením a psaním
|
||||
cs: Učebnice Neue Freunde zůstává, k ní přibyl kurz od A1 k B1
|
||||
en: German: four years, 24 lessons with listening, reading and writing
|
||||
en: The Neue Freunde textbook stays, and a course from A1 to B1 sits beside it
|
||||
|
||||
--- 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ě
|
||||
|
|
|
|||
|
|
@ -171,6 +171,11 @@ final class AppModel: ObservableObject {
|
|||
refresh()
|
||||
}
|
||||
|
||||
func markDe(_ year: Int, _ id: Int) {
|
||||
progress.markDe(year, id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markLit(_ id: Int) {
|
||||
progress.markLit(id)
|
||||
refresh()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ final class Content {
|
|||
let en2: [J]
|
||||
let en3: [J]
|
||||
let en4: [J]
|
||||
let de: [J]
|
||||
let de2: [J]
|
||||
let de3: [J]
|
||||
let de4: [J]
|
||||
let lit: [J]
|
||||
let lit2: [J]
|
||||
let lit3: [J]
|
||||
|
|
@ -93,6 +97,10 @@ final class Content {
|
|||
en2 = raw.arr("en2")
|
||||
en3 = raw.arr("en3")
|
||||
en4 = raw.arr("en4")
|
||||
de = raw.arr("de")
|
||||
de2 = raw.arr("de2")
|
||||
de3 = raw.arr("de3")
|
||||
de4 = raw.arr("de4")
|
||||
lit = raw.arr("lit")
|
||||
lit2 = raw.arr("lit2")
|
||||
lit3 = raw.arr("lit3")
|
||||
|
|
@ -129,6 +137,15 @@ final class Content {
|
|||
}
|
||||
}
|
||||
func enLesson(_ year: Int, _ id: Int) -> J? { enYear(year).first { $0.int("id") == id } }
|
||||
func deYear(_ year: Int) -> [J] {
|
||||
switch year {
|
||||
case 2: return de2
|
||||
case 3: return de3
|
||||
case 4: return de4
|
||||
default: return de
|
||||
}
|
||||
}
|
||||
func deLesson(_ year: Int, _ id: Int) -> J? { deYear(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 } }
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ final class ProgressStore {
|
|||
|
||||
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 deDone(_ year: Int, _ id: Int) -> Bool { defaults.bool(forKey: "de.\(year).\(id)") }
|
||||
func markDe(_ year: Int, _ id: Int) { defaults.set(true, forKey: "de.\(year).\(id)") }
|
||||
|
||||
func litDone(_ id: Int) -> Bool { defaults.bool(forKey: "lit.\(id)") }
|
||||
func markLit(_ id: Int) { defaults.set(true, forKey: "lit.\(id)") }
|
||||
|
|
@ -165,6 +167,14 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
|
|||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for l in content.deYear(year) {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.deDone(year, l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
for l in content.lit {
|
||||
sum.totalEx += 1
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ enum Route: Hashable {
|
|||
case subjects
|
||||
case stats
|
||||
case roadmap
|
||||
case deHome
|
||||
case deMap(Int)
|
||||
case deLesson(Int, Int)
|
||||
case deEx(Int, Int)
|
||||
case unitMap(Int)
|
||||
case germanEx(Int, Int)
|
||||
case vocab(Int)
|
||||
|
|
@ -103,6 +107,10 @@ enum Route: Hashable {
|
|||
case .subjects: return "subjects"
|
||||
case .stats: return "stats"
|
||||
case .roadmap: return "roadmap"
|
||||
case .deHome: return "dehome"
|
||||
case .deMap(let year): return "de\(year)map"
|
||||
case .deLesson(let year, let id): return "de\(year)unit\(id)"
|
||||
case .deEx(let year, let id): return "de\(year)ex\(id)"
|
||||
case .unitMap(let unitId): return "unit\(unitId + 1)"
|
||||
case .germanEx(let unitId, let ex): return "u\(unitId + 1)e\(ex)"
|
||||
case .vocab(let unitId): return "u\(unitId + 1)vocab"
|
||||
|
|
@ -204,14 +212,22 @@ enum Route: Hashable {
|
|||
switch self {
|
||||
case .welcome, .subjects, .stats:
|
||||
return []
|
||||
case .deHome:
|
||||
return [.deHome]
|
||||
case .deMap(let year):
|
||||
return [.deHome, .deMap(year)]
|
||||
case .deLesson(let year, let id):
|
||||
return [.deHome, .deMap(year), .deLesson(year, id)]
|
||||
case .deEx(let year, let id):
|
||||
return [.deHome, .deMap(year), .deLesson(year, id), .deEx(year, id)]
|
||||
case .roadmap:
|
||||
return [.roadmap]
|
||||
return [.deHome, .roadmap]
|
||||
case .unitMap(let id):
|
||||
return [.roadmap, .unitMap(id)]
|
||||
return [.deHome, .roadmap, .unitMap(id)]
|
||||
case .germanEx(let unit, let ex):
|
||||
return [.roadmap, .unitMap(unit), .germanEx(unit, ex)]
|
||||
return [.deHome, .roadmap, .unitMap(unit), .germanEx(unit, ex)]
|
||||
case .vocab(let unit):
|
||||
return [.roadmap, .unitMap(unit), .vocab(unit)]
|
||||
return [.deHome, .roadmap, .unitMap(unit), .vocab(unit)]
|
||||
case .netYears:
|
||||
return [.netYears]
|
||||
case .netMap:
|
||||
|
|
@ -384,6 +400,7 @@ func routeFromPage(_ page: String) -> Route? {
|
|||
case "subjects": return .subjects
|
||||
case "stats": return .stats
|
||||
case "roadmap": return .roadmap
|
||||
case "dehome": return .deHome
|
||||
case "netyears": return .netYears
|
||||
case "netmap": return .netMap
|
||||
case "hwyears": return .hwYears
|
||||
|
|
@ -546,6 +563,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: /de(\d)map/), let y = Int(m.1) {
|
||||
return .deMap(y)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /de(\d)unit(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||
return .deLesson(y, n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /de(\d)ex(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||
return .deEx(y, n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /en(\d)map/), let y = Int(m.1) {
|
||||
return .enMap(y)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1359,6 +1359,7 @@ private final class EnSpeaker: NSObject, ObservableObject, AVSpeechSynthesizerDe
|
|||
@Published var rate: Float = 0.9
|
||||
@Published var volume: Float = 1
|
||||
@Published var phase: Phase = .idle
|
||||
var language = "en-GB"
|
||||
var onFinish: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
|
|
@ -1408,8 +1409,8 @@ private final class EnSpeaker: NSObject, ObservableObject, AVSpeechSynthesizerDe
|
|||
let rest = ns.substring(from: start)
|
||||
let utterance = AVSpeechUtterance(string: rest.isEmpty ? text : rest)
|
||||
if rest.isEmpty { base = 0 }
|
||||
utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
|
||||
?? AVSpeechSynthesisVoice(language: "en-US")
|
||||
utterance.voice = AVSpeechSynthesisVoice(language: language)
|
||||
?? AVSpeechSynthesisVoice(language: language.hasPrefix("de") ? "de-DE" : "en-US")
|
||||
utterance.rate = AVSpeechUtteranceDefaultSpeechRate * rate
|
||||
utterance.volume = volume
|
||||
synth.speak(utterance)
|
||||
|
|
@ -1443,6 +1444,9 @@ struct EngExScreen: View {
|
|||
@ObservedObject var vm: AppModel
|
||||
let year: Int
|
||||
let id: Int
|
||||
let voice: String
|
||||
let lessonOf: (AppModel, Int, Int) -> J?
|
||||
let mark: (AppModel, Int, Int) -> Void
|
||||
@StateObject private var speaker: EnSpeaker
|
||||
@State private var readPicks: [Int]
|
||||
@State private var listenPicks: [Int]
|
||||
|
|
@ -1455,20 +1459,27 @@ struct EngExScreen: View {
|
|||
@State private var showModel = false
|
||||
@State private var fb = ("", "")
|
||||
|
||||
init(vm: AppModel, year: Int, id: Int) {
|
||||
init(vm: AppModel, year: Int, id: Int, voice: String = "en-GB",
|
||||
lesson: @escaping (AppModel, Int, Int) -> J? = { $0.content.enLesson($1, $2) },
|
||||
mark: @escaping (AppModel, Int, Int) -> Void = { $0.markEn($1, $2) }) {
|
||||
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())
|
||||
self.voice = voice
|
||||
self.lessonOf = lesson
|
||||
self.mark = mark
|
||||
let row = lesson(vm, year, id)
|
||||
_readPicks = State(initialValue: Array(repeating: -1, count: row?.arr("readQuiz").count ?? 0))
|
||||
_listenPicks = State(initialValue: Array(repeating: -1, count: row?.arr("listenQuiz").count ?? 0))
|
||||
_quizPicks = State(initialValue: Array(repeating: -1, count: row?.arr("quiz").count ?? 0))
|
||||
_gaps = State(initialValue: Array(repeating: "", count: row?.arr("gaps").count ?? 0))
|
||||
let speaker = EnSpeaker()
|
||||
speaker.language = voice
|
||||
_speaker = StateObject(wrappedValue: speaker)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let lesson = vm.content.enLesson(year, id) {
|
||||
if let lesson = lessonOf(vm, year, id) {
|
||||
let p = vm.palette
|
||||
ExerciseScaffold(vm: vm, title: vm.tr(lesson.str("titleKey")), sub: nil, action: vm.tr("check"), onAction: {
|
||||
check(lesson)
|
||||
|
|
@ -1619,7 +1630,7 @@ struct EngExScreen: View {
|
|||
if all {
|
||||
fb = (vm.tr("feedback_ok"), "ok")
|
||||
showModel = true
|
||||
vm.markEn(year, id)
|
||||
mark(vm, year, id)
|
||||
} else if lesson.obj("writing") != nil && !writeOk && readOk && listenOk && quizOk && gapOk == gapRows.count {
|
||||
fb = (vm.tr("en_write_short"), "err")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -662,12 +662,16 @@ struct StatsScreen: View {
|
|||
private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
|
||||
var sum = ProgressSum()
|
||||
switch s.str("target") {
|
||||
case "roadmap":
|
||||
case "dehome":
|
||||
for u in vm.content.german where u.bool("unlocked") {
|
||||
let n = u.strs("names").count
|
||||
sum.totalEx += n
|
||||
sum.doneEx += (1...n).filter { vm.progress.germanDone(u.int("id"), $0) }.count
|
||||
}
|
||||
sum.totalEx += (1...4).reduce(0) { $0 + vm.content.deYear($1).count }
|
||||
sum.doneEx += (1...4).reduce(0) { acc, year in
|
||||
acc + vm.content.deYear(year).filter { vm.progress.deDone(year, $0.int("id")) }.count
|
||||
}
|
||||
case "netyears":
|
||||
sum.totalEx = vm.content.netLessons.count
|
||||
sum.doneEx = vm.content.netLessons.filter { vm.progress.netDone($0.int("id")) }.count
|
||||
|
|
@ -900,6 +904,10 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
|
|||
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")
|
||||
}
|
||||
add(vm.tr("de_year\(year)"), vm.tr("search_lesson"), "de\(year)map", extra: "nemcina deutsch rocnik")
|
||||
for l in vm.content.deYear(year) {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "de\(year)unit\(l.int("id"))", extra: "nemcina deutsch")
|
||||
}
|
||||
}
|
||||
for l in vm.content.on {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "onunit\(l.int("id"))", extra: "obcanka")
|
||||
|
|
@ -1243,6 +1251,43 @@ struct HwYearsScreen: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DeHomeScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
let rows: [(String, String, String, Route)] = [
|
||||
("U", "de_book_title", "de_book_sub", .roadmap),
|
||||
("1", "de_year1", "de_y1_sub", .deMap(1)),
|
||||
("2", "de_year2", "de_y2_sub", .deMap(2)),
|
||||
("3", "de_year3", "de_y3_sub", .deMap(3)),
|
||||
("4", "de_year4", "de_y4_sub", .deMap(4)),
|
||||
]
|
||||
MacFillGrid(count: rows.count, minWidth: 260, minHeight: 180) { i in
|
||||
NavigationLink(value: rows[i].3) {
|
||||
MacLinkCard(title: vm.tr(rows[i].1), subtitle: vm.tr(rows[i].2), badge: rows[i].0)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.navigationTitle(vm.tr("Deutsch"))
|
||||
.navigationSubtitle(vm.tr("de_home_sub"))
|
||||
#else
|
||||
List {
|
||||
NavigationLink(value: Route.roadmap) {
|
||||
ListRowLabel(title: vm.tr("de_book_title"), subtitle: vm.tr("de_book_sub"))
|
||||
}
|
||||
ForEach(1...4, id: \.self) { year in
|
||||
NavigationLink(value: Route.deMap(year)) {
|
||||
ListRowLabel(title: vm.tr("de_year\(year)"), subtitle: vm.tr("de_y\(year)_sub"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(vm.tr("Deutsch"))
|
||||
.navigationSubtitle(vm.tr("de_home_sub"))
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct EnYearsScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
|
||||
|
|
@ -2157,6 +2202,29 @@ struct RouteDestination: View {
|
|||
PracticeHome(vm: vm)
|
||||
case .stats:
|
||||
StatsScreen(vm: vm)
|
||||
case .deHome:
|
||||
DeHomeScreen(vm: vm)
|
||||
case .deMap(let year):
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("de_year\(year)"),
|
||||
subtitle: vm.tr("de_y\(year)_sub"),
|
||||
rows: vm.content.deYear(year).map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(
|
||||
id: "d\(year)\(id)",
|
||||
title: vm.tr(l.str("titleKey")),
|
||||
done: vm.progress.deDone(year, id),
|
||||
destination: .deLesson(year, id)
|
||||
)
|
||||
}
|
||||
)
|
||||
case .deLesson(let year, let id):
|
||||
if let l = vm.content.deLesson(year, id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .deEx(year, id))
|
||||
}
|
||||
case .deEx(let year, let id):
|
||||
EngExScreen(vm: vm, year: year, id: id, voice: "de-DE", lesson: { $0.content.deLesson($1, $2) }, mark: { $0.markDe($1, $2) })
|
||||
case .roadmap:
|
||||
RoadmapScreen(vm: vm)
|
||||
case .unitMap(let id):
|
||||
|
|
|
|||
|
|
@ -777,7 +777,7 @@ def main() -> int:
|
|||
i18n = {}
|
||||
for path in sorted(SRC.glob("*.c")):
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
if path.name == "english.c":
|
||||
if path.name in ("english.c", "german_course.c"):
|
||||
raw = expand_local_includes(raw, path.parent)
|
||||
text = strip_comments(raw)
|
||||
arrays.update(find_arrays(text))
|
||||
|
|
@ -1629,10 +1629,10 @@ def main() -> int:
|
|||
q["expl"] = hints[qi]
|
||||
return quiz
|
||||
|
||||
def en_year(year):
|
||||
def skill_year(tag, year, count):
|
||||
lessons = []
|
||||
for i in range(1, 9):
|
||||
prefix = f"en_y{year}_l{i}"
|
||||
for i in range(1, count + 1):
|
||||
prefix = f"{tag}_y{year}_l{i}"
|
||||
write = str_list(A(f"{prefix}_write")) if f"{prefix}_write" in arrays else []
|
||||
writing = None
|
||||
if len(write) >= 3:
|
||||
|
|
@ -1644,8 +1644,8 @@ def main() -> int:
|
|||
}
|
||||
lessons.append({
|
||||
"id": i,
|
||||
"titleKey": f"en_y{year}_u{i}",
|
||||
"subKey": f"en_y{year}_u{i}_sub",
|
||||
"titleKey": f"{tag}_y{year}_u{i}",
|
||||
"subKey": f"{tag}_y{year}_u{i}_sub",
|
||||
"slides": slides_items(A(f"{prefix}_slides")),
|
||||
"reading": en_text(f"{prefix}_read"),
|
||||
"readQuiz": en_choice(f"{prefix}_readq"),
|
||||
|
|
@ -1657,13 +1657,17 @@ def main() -> int:
|
|||
})
|
||||
return lessons
|
||||
|
||||
en_lessons = en_year(1)
|
||||
en2_lessons = en_year(2)
|
||||
en3_lessons = en_year(3)
|
||||
en4_lessons = en_year(4)
|
||||
en_lessons = skill_year("en", 1, 8)
|
||||
en2_lessons = skill_year("en", 2, 8)
|
||||
en3_lessons = skill_year("en", 3, 8)
|
||||
en4_lessons = skill_year("en", 4, 8)
|
||||
de_lessons = skill_year("de", 1, 6)
|
||||
de2_lessons = skill_year("de", 2, 6)
|
||||
de3_lessons = skill_year("de", 3, 6)
|
||||
de4_lessons = skill_year("de", 4, 6)
|
||||
|
||||
subjects = [
|
||||
{"key": "Deutsch", "open": True, "target": "roadmap", "icon": "de"},
|
||||
{"key": "Deutsch", "open": True, "target": "dehome", "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"},
|
||||
|
|
@ -1711,6 +1715,10 @@ def main() -> int:
|
|||
"en2": en2_lessons,
|
||||
"en3": en3_lessons,
|
||||
"en4": en4_lessons,
|
||||
"de": de_lessons,
|
||||
"de2": de2_lessons,
|
||||
"de3": de3_lessons,
|
||||
"de4": de4_lessons,
|
||||
"mluvnice": mluvnice,
|
||||
"books": books,
|
||||
"changelog": parse_changelog(ROOT / "data" / "changelog.txt"),
|
||||
|
|
|
|||
|
|
@ -2401,8 +2401,8 @@ def emit_questions(name, items, lines):
|
|||
lines.append("")
|
||||
|
||||
|
||||
def emit_lesson(year, index, lesson, lines, inits):
|
||||
p = f"en_y{year}_l{index}"
|
||||
def emit_lesson(year, index, lesson, lines, inits, prefix="en"):
|
||||
p = f"{prefix}_y{year}_l{index}"
|
||||
lines.append(f"static const NetSlide {p}_slides[] = {{")
|
||||
for kicker, title, tip, bullets in lesson["slides"]:
|
||||
lines.append(" {")
|
||||
|
|
|
|||
1387
scripts/gen-german.py
Normal file
1387
scripts/gen-german.py
Normal file
File diff suppressed because it is too large
Load diff
136
src/english.c
136
src/english.c
|
|
@ -17,28 +17,6 @@
|
|||
#define EN_YEARS 4
|
||||
#define EN_N 8
|
||||
|
||||
typedef struct {
|
||||
const NetSlide *slides;
|
||||
int n_slides;
|
||||
const char *reading;
|
||||
const ChoiceQ *readq;
|
||||
const char **read_hints;
|
||||
int n_read;
|
||||
const char *listening;
|
||||
const ChoiceQ *listenq;
|
||||
const char **listen_hints;
|
||||
int n_listen;
|
||||
const TypedQ *gaps;
|
||||
int n_gaps;
|
||||
const char *write_prompt;
|
||||
const char *write_model;
|
||||
const char *write_keys;
|
||||
int write_min;
|
||||
const ChoiceQ *quiz;
|
||||
const char **quiz_hints;
|
||||
int n_quiz;
|
||||
} EnLesson;
|
||||
|
||||
#include "english_lessons.inc"
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -51,6 +29,7 @@ typedef struct {
|
|||
int year;
|
||||
int index;
|
||||
const EnLesson *lesson;
|
||||
void (*on_done)(int year, int index);
|
||||
GtkWidget *feedback;
|
||||
GtkWidget *model;
|
||||
GArray *picks;
|
||||
|
|
@ -69,6 +48,7 @@ typedef struct {
|
|||
gboolean heard;
|
||||
double speed;
|
||||
double gain;
|
||||
const char *voice;
|
||||
GPid pid;
|
||||
guint timer;
|
||||
guint tick_ms;
|
||||
|
|
@ -646,7 +626,7 @@ GtkWidget *build_enmap_page(int year) {
|
|||
/* Slides */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void en_slide_apply(NetLesson *L) {
|
||||
void skill_slide_apply(NetLesson *L) {
|
||||
char name[16];
|
||||
|
||||
if (!L || !L->stack)
|
||||
|
|
@ -705,7 +685,7 @@ static void en_slide_prev(GtkButton *button, gpointer data) {
|
|||
(void)button;
|
||||
if (L && L->idx > 0) {
|
||||
L->idx--;
|
||||
en_slide_apply(L);
|
||||
skill_slide_apply(L);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,7 +697,7 @@ static void en_slide_next(GtkButton *button, gpointer data) {
|
|||
return;
|
||||
if (L->idx + 1 < L->n_slides) {
|
||||
L->idx++;
|
||||
en_slide_apply(L);
|
||||
skill_slide_apply(L);
|
||||
} else {
|
||||
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
|
||||
}
|
||||
|
|
@ -726,12 +706,12 @@ static void en_slide_next(GtkButton *button, gpointer data) {
|
|||
void en_lessons_apply_lang(void) {
|
||||
for (int y = 0; y < EN_YEARS; y++)
|
||||
for (int i = 0; i < EN_N; i++)
|
||||
en_slide_apply(&en_rt[y][i]);
|
||||
skill_slide_apply(&en_rt[y][i]);
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_unit_page(int year, int index) {
|
||||
NetLesson *L = &en_rt[year][index];
|
||||
const EnLesson *lesson = &en_lessons[year][index];
|
||||
GtkWidget *skill_unit_page(NetLesson *L, const EnLesson *lesson,
|
||||
const char *back, const char *title_key,
|
||||
const char *sub_key) {
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *nav;
|
||||
|
|
@ -746,8 +726,7 @@ static GtkWidget *build_en_unit_page(int year, int index) {
|
|||
gtk_widget_set_margin_top(page, 20);
|
||||
gtk_widget_set_margin_bottom(page, 20);
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar(en_map_page[year], en_title_key[year][index],
|
||||
en_sub_key[year][index]));
|
||||
top_bar(back, title_key, sub_key));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
|
|
@ -795,7 +774,7 @@ static GtkWidget *build_en_unit_page(int year, int index) {
|
|||
}
|
||||
en_rescale_notes(0);
|
||||
net_notes_target = NULL;
|
||||
en_slide_apply(L);
|
||||
skill_slide_apply(L);
|
||||
return page;
|
||||
}
|
||||
|
||||
|
|
@ -1054,14 +1033,58 @@ static void en_mac_levels(EnEx *ctx) {
|
|||
ctx->synth, sel_registerName("setVolume:"), vol);
|
||||
}
|
||||
|
||||
static gboolean en_mac_ensure(EnEx *ctx) {
|
||||
static const char *voices[] = {
|
||||
static gboolean en_mac_try_voice(id synth, const char *name) {
|
||||
return ((BOOL (*)(id, SEL, id))objc_msgSend)(
|
||||
synth, sel_registerName("setVoice:"), en_ns(name));
|
||||
}
|
||||
|
||||
static void en_mac_choose(id synth, const char *lang) {
|
||||
static const char *en_voices[] = {
|
||||
"com.apple.voice.compact.en-GB.Daniel",
|
||||
"com.apple.speech.synthesis.voice.daniel",
|
||||
"com.apple.voice.compact.en-US.Samantha",
|
||||
"com.apple.speech.synthesis.voice.samantha",
|
||||
NULL
|
||||
};
|
||||
static const char *de_voices[] = {
|
||||
"com.apple.voice.compact.de-DE.Anna",
|
||||
"com.apple.speech.synthesis.voice.anna",
|
||||
"com.apple.voice.compact.de-DE.Helena",
|
||||
"com.apple.speech.synthesis.voice.petra",
|
||||
NULL
|
||||
};
|
||||
gboolean german = lang && g_str_has_prefix(lang, "de");
|
||||
const char **list = german ? de_voices : en_voices;
|
||||
const char *needle = german ? "de-DE" : "en-GB";
|
||||
Class cls;
|
||||
id voices;
|
||||
unsigned long n;
|
||||
|
||||
for (int i = 0; list[i]; i++) {
|
||||
if (en_mac_try_voice(synth, list[i]))
|
||||
return;
|
||||
}
|
||||
cls = objc_getClass("NSSpeechSynthesizer");
|
||||
if (!cls)
|
||||
return;
|
||||
voices = ((id (*)(Class, SEL))objc_msgSend)(
|
||||
cls, sel_registerName("availableVoices"));
|
||||
if (!voices)
|
||||
return;
|
||||
n = ((unsigned long (*)(id, SEL))objc_msgSend)(
|
||||
voices, sel_registerName("count"));
|
||||
for (unsigned long i = 0; i < n; i++) {
|
||||
id name = ((id (*)(id, SEL, unsigned long))objc_msgSend)(
|
||||
voices, sel_registerName("objectAtIndex:"), i);
|
||||
const char *utf = ((const char *(*)(id, SEL))objc_msgSend)(
|
||||
name, sel_registerName("UTF8String"));
|
||||
|
||||
if (utf && strstr(utf, needle) && en_mac_try_voice(synth, utf))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean en_mac_ensure(EnEx *ctx) {
|
||||
Class cls;
|
||||
id obj;
|
||||
|
||||
|
|
@ -1075,11 +1098,7 @@ static gboolean en_mac_ensure(EnEx *ctx) {
|
|||
if (!obj)
|
||||
return FALSE;
|
||||
ctx->synth = obj;
|
||||
for (int i = 0; voices[i]; i++) {
|
||||
if (((BOOL (*)(id, SEL, id))objc_msgSend)(
|
||||
obj, sel_registerName("setVoice:"), en_ns(voices[i])))
|
||||
break;
|
||||
}
|
||||
en_mac_choose(obj, ctx->voice);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -1163,14 +1182,16 @@ static gboolean en_launch(EnEx *ctx) {
|
|||
g_snprintf(rate, sizeof rate, "%d", (int)(160.0 * speed));
|
||||
g_snprintf(amp, sizeof amp, "%d", (int)(ctx->gain * 200.0));
|
||||
{
|
||||
char *a[] = {"espeak-ng", "-v", "en", "-s", rate, "-a", amp, text, NULL};
|
||||
char *b[] = {"espeak", "-v", "en", "-s", rate, "-a", amp, text, NULL};
|
||||
const char *voice = ctx->voice && ctx->voice[0] ? ctx->voice : "en";
|
||||
char *a[] = {"espeak-ng", "-v", (char *)voice, "-s", rate, "-a", amp, text, NULL};
|
||||
char *b[] = {"espeak", "-v", (char *)voice, "-s", rate, "-a", amp, text, NULL};
|
||||
#ifdef __APPLE__
|
||||
char *c[] = {"say", "-r", rate, text, NULL};
|
||||
char **tries[] = {c};
|
||||
char *c_en[] = {"say", "-r", rate, text, NULL};
|
||||
char *c_de[] = {"say", "-v", "Anna", "-r", rate, text, NULL};
|
||||
char **tries[] = {g_str_has_prefix(voice, "de") ? c_de : c_en};
|
||||
int n = 1;
|
||||
#else
|
||||
char *c[] = {"spd-say", "-l", "en", "-r", rate, text, NULL};
|
||||
char *c[] = {"spd-say", "-l", (char *)voice, "-r", rate, text, NULL};
|
||||
char **tries[] = {a, b, c};
|
||||
int n = 3;
|
||||
#endif
|
||||
|
|
@ -1416,7 +1437,8 @@ static void en_check(GtkButton *button, gpointer data) {
|
|||
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
|
||||
if (ctx->model)
|
||||
gtk_widget_set_visible(ctx->model, TRUE);
|
||||
mark_en_done(ctx->year, ctx->index);
|
||||
if (ctx->on_done)
|
||||
ctx->on_done(ctx->year, ctx->index);
|
||||
} else if (ctx->essay && !write_ok &&
|
||||
mcq_ok == (int)ctx->picks->len &&
|
||||
gap_ok == ctx->lesson->n_gaps) {
|
||||
|
|
@ -1426,8 +1448,10 @@ static void en_check(GtkButton *button, gpointer data) {
|
|||
}
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_ex_page(int year, int index) {
|
||||
const EnLesson *lesson = &en_lessons[year][index];
|
||||
GtkWidget *skill_ex_page(const EnLesson *lesson, int year, int index,
|
||||
void (*on_done)(int, int),
|
||||
const char *back, const char *title_key,
|
||||
const char *voice) {
|
||||
EnEx *ctx = g_new0(EnEx, 1);
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
|
|
@ -1437,6 +1461,8 @@ static GtkWidget *build_en_ex_page(int year, int index) {
|
|||
ctx->year = year;
|
||||
ctx->index = index;
|
||||
ctx->lesson = lesson;
|
||||
ctx->on_done = on_done;
|
||||
ctx->voice = voice && voice[0] ? voice : "en";
|
||||
ctx->picks = g_array_new(FALSE, FALSE, sizeof(EnPick));
|
||||
ctx->entries = g_array_new(FALSE, FALSE, sizeof(GtkWidget *));
|
||||
ctx->gap_hints = g_array_new(FALSE, TRUE, sizeof(GtkWidget *));
|
||||
|
|
@ -1446,9 +1472,7 @@ static GtkWidget *build_en_ex_page(int year, int index) {
|
|||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar(en_unit_page[year][index],
|
||||
en_title_key[year][index], NULL));
|
||||
gtk_box_append(GTK_BOX(page), top_bar(back, title_key, NULL));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
|
|
@ -1586,6 +1610,18 @@ static GtkWidget *build_en_ex_page(int year, int index) {
|
|||
return page;
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_unit_page(int year, int index) {
|
||||
return skill_unit_page(&en_rt[year][index], &en_lessons[year][index],
|
||||
en_map_page[year], en_title_key[year][index],
|
||||
en_sub_key[year][index]);
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_ex_page(int year, int index) {
|
||||
return skill_ex_page(&en_lessons[year][index], year, index, mark_en_done,
|
||||
en_unit_page[year][index], en_title_key[year][index],
|
||||
"en");
|
||||
}
|
||||
|
||||
void add_en_pages(GtkStack *stack) {
|
||||
en_prepare();
|
||||
for (int y = 0; y < EN_YEARS; y++) {
|
||||
|
|
|
|||
234
src/german_course.c
Normal file
234
src/german_course.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#include "graduately.h"
|
||||
|
||||
/* Skill course next to the three textbook units. Four years, same
|
||||
* listening / reading / writing player as English. */
|
||||
|
||||
#define DE_YEARS 4
|
||||
#define DE_N 6
|
||||
|
||||
#include "german_lessons.inc"
|
||||
|
||||
static NetLesson de_rt[DE_YEARS][DE_N];
|
||||
static char de_map_page[DE_YEARS][16];
|
||||
static char de_unit_page[DE_YEARS][DE_N][16];
|
||||
static char de_ex_page[DE_YEARS][DE_N][16];
|
||||
static char de_title_key[DE_YEARS][DE_N][16];
|
||||
static char de_sub_key[DE_YEARS][DE_N][20];
|
||||
static gboolean de_ready;
|
||||
|
||||
static void de_prepare(void) {
|
||||
if (de_ready)
|
||||
return;
|
||||
de_ready = TRUE;
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
g_snprintf(de_map_page[y], sizeof de_map_page[y], "de%dmap", y + 1);
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
g_snprintf(de_unit_page[y][i], sizeof de_unit_page[y][i],
|
||||
"de%dunit%d", y + 1, i + 1);
|
||||
g_snprintf(de_ex_page[y][i], sizeof de_ex_page[y][i],
|
||||
"de%dex%d", y + 1, i + 1);
|
||||
g_snprintf(de_title_key[y][i], sizeof de_title_key[y][i],
|
||||
"de_y%d_u%d", y + 1, i + 1);
|
||||
g_snprintf(de_sub_key[y][i], sizeof de_sub_key[y][i],
|
||||
"de_y%d_u%d_sub", y + 1, i + 1);
|
||||
de_rt[y][i].unit_page = de_unit_page[y][i];
|
||||
de_rt[y][i].ex_page = de_ex_page[y][i];
|
||||
de_rt[y][i].n_slides = (guint)de_lessons[y][i].n_slides;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void de_save_progress(void) {
|
||||
GKeyFile *kf = g_key_file_new();
|
||||
gchar *data;
|
||||
GError *err = NULL;
|
||||
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
char key[8];
|
||||
|
||||
g_snprintf(key, sizeof key, "%d-%d", y + 1, i + 1);
|
||||
g_key_file_set_boolean(kf, "done", key, de_rt[y][i].done);
|
||||
}
|
||||
}
|
||||
data = g_key_file_to_data(kf, NULL, NULL);
|
||||
if (!g_file_set_contents(PROGRESS_DE, data, -1, &err)) {
|
||||
g_warning("german course: %s", err->message);
|
||||
g_error_free(err);
|
||||
}
|
||||
g_free(data);
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
|
||||
void de_load_progress(void) {
|
||||
GKeyFile *kf = g_key_file_new();
|
||||
GError *err = NULL;
|
||||
|
||||
de_prepare();
|
||||
if (!g_key_file_load_from_file(kf, PROGRESS_DE, G_KEY_FILE_NONE, &err)) {
|
||||
if (err)
|
||||
g_error_free(err);
|
||||
g_key_file_free(kf);
|
||||
return;
|
||||
}
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
char key[8];
|
||||
|
||||
g_snprintf(key, sizeof key, "%d-%d", y + 1, i + 1);
|
||||
de_rt[y][i].done = g_key_file_get_boolean(kf, "done", key, NULL);
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
|
||||
static void mark_de_done(int year, int index) {
|
||||
if (year < 0 || year >= DE_YEARS || index < 0 || index >= DE_N)
|
||||
return;
|
||||
if (de_rt[year][index].done)
|
||||
return;
|
||||
de_rt[year][index].done = TRUE;
|
||||
de_save_progress();
|
||||
refresh_stats_ui();
|
||||
}
|
||||
|
||||
void progress_for_de(ProgressSum *out) {
|
||||
out->total_ex += DE_YEARS * DE_N;
|
||||
out->open_units += DE_YEARS * DE_N;
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
if (de_rt[y][i].done) {
|
||||
out->done_ex++;
|
||||
out->done_units++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void de_lessons_apply_lang(void) {
|
||||
for (int y = 0; y < DE_YEARS; y++)
|
||||
for (int i = 0; i < DE_N; i++)
|
||||
skill_slide_apply(&de_rt[y][i]);
|
||||
}
|
||||
|
||||
static GtkWidget *de_nav_button(const char *title_key, const char *sub_key,
|
||||
const char *target, const char *badge) {
|
||||
GtkWidget *btn = gtk_button_new();
|
||||
GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
|
||||
GtkWidget *num = gtk_label_new(badge);
|
||||
GtkWidget *texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||
GtkWidget *title = gtk_label_new(NULL);
|
||||
GtkWidget *sub = gtk_label_new(NULL);
|
||||
|
||||
gtk_widget_add_css_class(btn, "unit-node");
|
||||
gtk_widget_add_css_class(btn, "current");
|
||||
gtk_widget_set_hexpand(btn, TRUE);
|
||||
gtk_widget_set_margin_start(row, 18);
|
||||
gtk_widget_set_margin_end(row, 18);
|
||||
gtk_widget_set_margin_top(row, 10);
|
||||
gtk_widget_set_margin_bottom(row, 10);
|
||||
gtk_button_set_child(GTK_BUTTON(btn), row);
|
||||
gtk_widget_add_css_class(num, "unit-number");
|
||||
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(row), num);
|
||||
gtk_widget_set_hexpand(texts, TRUE);
|
||||
gtk_box_append(GTK_BOX(row), texts);
|
||||
i18n_bind(title, title_key, 0);
|
||||
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(title, "unit-name");
|
||||
gtk_box_append(GTK_BOX(texts), title);
|
||||
i18n_bind(sub, sub_key, 0);
|
||||
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(sub), 0);
|
||||
gtk_widget_add_css_class(sub, "quiz-intro");
|
||||
gtk_box_append(GTK_BOX(texts), sub);
|
||||
g_object_set_data_full(G_OBJECT(btn), "target", g_strdup(target), g_free);
|
||||
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||
return btn;
|
||||
}
|
||||
|
||||
static GtkWidget *de_list_page(const char *back, const char *title,
|
||||
const char *sub, GtkWidget *list) {
|
||||
GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
GtkWidget *scroll = gtk_scrolled_window_new();
|
||||
|
||||
gtk_widget_set_hexpand(page, TRUE);
|
||||
gtk_widget_set_vexpand(page, TRUE);
|
||||
gtk_widget_set_margin_start(page, 32);
|
||||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
gtk_box_append(GTK_BOX(page), top_bar(back, title, sub));
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_margin_top(scroll, 18);
|
||||
gtk_box_append(GTK_BOX(page), scroll);
|
||||
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_size_request(list, 520, -1);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||
return page;
|
||||
}
|
||||
|
||||
GtkWidget *build_dehome_page(void) {
|
||||
GtkWidget *list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
static const char *years[] = {
|
||||
"de_year1", "de_year2", "de_year3", "de_year4",
|
||||
};
|
||||
static const char *subs[] = {
|
||||
"de_y1_sub", "de_y2_sub", "de_y3_sub", "de_y4_sub",
|
||||
};
|
||||
|
||||
de_prepare();
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
de_nav_button("de_book_title", "de_book_sub", "roadmap", "U"));
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
char badge[8];
|
||||
|
||||
g_snprintf(badge, sizeof badge, "%d", y + 1);
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
de_nav_button(years[y], subs[y], de_map_page[y], badge));
|
||||
}
|
||||
return de_list_page("subjects", "Deutsch", "de_home_sub", list);
|
||||
}
|
||||
|
||||
static GtkWidget *build_de_map(int year) {
|
||||
GtkWidget *list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
static const char *year_keys[] = {
|
||||
"de_year1", "de_year2", "de_year3", "de_year4",
|
||||
};
|
||||
static const char *sub_keys[] = {
|
||||
"de_y1_sub", "de_y2_sub", "de_y3_sub", "de_y4_sub",
|
||||
};
|
||||
|
||||
de_prepare();
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
char badge[8];
|
||||
|
||||
g_snprintf(badge, sizeof badge, "%d", i + 1);
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
de_nav_button(de_title_key[year][i],
|
||||
de_sub_key[year][i],
|
||||
de_unit_page[year][i], badge));
|
||||
}
|
||||
return de_list_page("dehome", year_keys[year], sub_keys[year], list);
|
||||
}
|
||||
|
||||
void add_de_pages(GtkStack *stack) {
|
||||
de_prepare();
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
gtk_stack_add_named(stack, build_de_map(y), de_map_page[y]);
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
gtk_stack_add_named(stack,
|
||||
skill_unit_page(&de_rt[y][i], &de_lessons[y][i],
|
||||
de_map_page[y], de_title_key[y][i],
|
||||
de_sub_key[y][i]),
|
||||
de_unit_page[y][i]);
|
||||
gtk_stack_add_named(stack,
|
||||
skill_ex_page(&de_lessons[y][i], y, i, mark_de_done,
|
||||
de_unit_page[y][i], de_title_key[y][i], "de"),
|
||||
de_ex_page[y][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/german_i18n.inc
Normal file
60
src/german_i18n.inc
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{"de_home_sub", "Učebnice a čtyři ročníky s poslechem, čtením a psaním.", "The textbook plus four years of listening, reading and writing."},
|
||||
{"de_book_title", "Učebnice", "Textbook"},
|
||||
{"de_book_sub", "Neue Freunde, Aus aller Welt a Bei uns zu Hause.", "Neue Freunde, Aus aller Welt and Bei uns zu Hause."},
|
||||
{"de_years_title", "Ročníky", "School years"},
|
||||
{"de_y1_sub", "Pozdravy, rodina, škola, jídlo a den. Úroveň A1.", "Greetings, family, school, food and the day. Level A1."},
|
||||
{"de_year1", "1. ročník", "Year 1"},
|
||||
{"de_y1_u1", "Pozdravy a představení", "Greetings and introductions"},
|
||||
{"de_y1_u1_sub", "Sein, heißen a woher. Doplňování a kvíz.", "Sein, heißen and woher. Gaps and a quiz."},
|
||||
{"de_y1_u2", "Rodina", "Family"},
|
||||
{"de_y1_u2_sub", "Haben a přivlastňovací zájmena. Čtení a doplňování.", "Haben and possessives. Reading and gaps."},
|
||||
{"de_y1_u3", "Ve škole", "At school"},
|
||||
{"de_y1_u3_sub", "Přítomný čas. Poslech a doplňování.", "The present tense. Listening and gaps."},
|
||||
{"de_y1_u4", "Jídlo", "Food"},
|
||||
{"de_y1_u4_sub", "Möchten a 4. pád. Krátký dialog a kvíz.", "Möchten and the accusative. A short dialogue and a quiz."},
|
||||
{"de_y1_u5", "Můj den", "My day"},
|
||||
{"de_y1_u5_sub", "Čas a odlučitelné předpony. Čtení a doplňování.", "Time and separable verbs. Reading and gaps."},
|
||||
{"de_y1_u6", "Test 1. ročníku", "Year 1 test"},
|
||||
{"de_y1_u6_sub", "Čtení, poslech, gramatika A1 a krátké psaní.", "Reading, listening, A1 grammar and a short text."},
|
||||
{"de_y2_sub", "Město, nákupy, volný čas, bydlení a perfektum. Úroveň A2.", "Town, shopping, free time, home and the perfect. Level A2."},
|
||||
{"de_year2", "2. ročník", "Year 2"},
|
||||
{"de_y2_u1", "Ve městě", "In town"},
|
||||
{"de_y2_u1_sub", "Wo a wohin, předložky. Doplňování a kvíz.", "Wo and wohin, prepositions. Gaps and a quiz."},
|
||||
{"de_y2_u2", "Nákupy", "Shopping"},
|
||||
{"de_y2_u2_sub", "Ceny a 4. pád v obchodě. Čtení a kvíz.", "Prices and the accusative in a shop. Reading and a quiz."},
|
||||
{"de_y2_u3", "Volný čas", "Free time"},
|
||||
{"de_y2_u3_sub", "Modální slovesa. Poslech a doplňování.", "Modal verbs. Listening and gaps."},
|
||||
{"de_y2_u4", "Bydlení", "Home"},
|
||||
{"de_y2_u4_sub", "Místnosti a předložky. Popis bytu.", "Rooms and prepositions. A description of a flat."},
|
||||
{"de_y2_u5", "Perfektum", "The perfect tense"},
|
||||
{"de_y2_u5_sub", "Haben nebo sein a příčestí. Doplňování a kvíz.", "Haben or sein plus the participle. Gaps and a quiz."},
|
||||
{"de_y2_u6", "Test 2. ročníku", "Year 2 test"},
|
||||
{"de_y2_u6_sub", "Čtení, poslech, A2 a krátké psaní.", "Reading, listening, A2 and a short text."},
|
||||
{"de_y3_sub", "Zdraví, cestování, věty s weil, práce a srovnání. Úroveň A2+.", "Health, travel, weil-clauses, work and comparison. Level A2+."},
|
||||
{"de_year3", "3. ročník", "Year 3"},
|
||||
{"de_y3_u1", "U lékaře", "At the doctor's"},
|
||||
{"de_y3_u1_sub", "Tělo, tut a rozkaz. Čtení a kvíz.", "The body, tut and the imperative. Reading and a quiz."},
|
||||
{"de_y3_u2", "Cesta", "A trip"},
|
||||
{"de_y3_u2_sub", "Perfektum v vyprávění. Poslech a doplňování.", "The perfect in a story. Listening and gaps."},
|
||||
{"de_y3_u3", "Weil, dass, wenn", "Weil, dass, wenn"},
|
||||
{"de_y3_u3_sub", "Vedlejší věta a sloveso na konci. Doplňování a kvíz.", "Subordinate clauses and the verb at the end. Gaps and a quiz."},
|
||||
{"de_y3_u4", "Práce a vykání", "Work and Sie"},
|
||||
{"de_y3_u4_sub", "Formální věta. E-mail a kvíz.", "A formal sentence. An email and a quiz."},
|
||||
{"de_y3_u5", "Srovnání a příroda", "Comparison and nature"},
|
||||
{"de_y3_u5_sub", "Komparativ a superlativ. Čtení a doplňování.", "Comparative and superlative. Reading and gaps."},
|
||||
{"de_y3_u6", "Test 3. ročníku", "Year 3 test"},
|
||||
{"de_y3_u6_sub", "Čtení, poslech, weil a krátký názor.", "Reading, listening, weil and a short opinion."},
|
||||
{"de_y4_sub", "Média, formální dopis, čtení, konjunktiv a maturitní témata. Úroveň B1.", "Media, a formal letter, reading, the subjunctive and exam topics. Level B1."},
|
||||
{"de_year4", "4. ročník", "Year 4"},
|
||||
{"de_y4_u1", "Média", "The media"},
|
||||
{"de_y4_u1_sub", "Poslech rozhovoru a kvíz k slovům.", "An interview and a vocabulary quiz."},
|
||||
{"de_y4_u2", "Formální e-mail", "A formal email"},
|
||||
{"de_y4_u2_sub", "Stížnost nebo žádost. Psaní a kvíz.", "A complaint or a request. Writing and a quiz."},
|
||||
{"de_y4_u3", "Čtení: noční vlak", "Reading: the night train"},
|
||||
{"de_y4_u3_sub", "Delší text a otázky jako u maturity.", "A longer text and exam-style questions."},
|
||||
{"de_y4_u4", "Kdyby", "If I were"},
|
||||
{"de_y4_u4_sub", "Konjunktiv II. Doplňování a kvíz.", "Konjunktiv II. Gaps and a quiz."},
|
||||
{"de_y4_u5", "Témata k maturitě", "Exam topics"},
|
||||
{"de_y4_u5_sub", "Kolokace k běžným okruhům. Doplňování a kvíz.", "Collocations for common topics. Gaps and a quiz."},
|
||||
{"de_y4_u6", "Cvičná zkouška", "A practice paper"},
|
||||
{"de_y4_u6_sub", "Čtení, poslech, gramatika a krátký text.", "Reading, listening, grammar and a short text."},
|
||||
1453
src/german_lessons.inc
Normal file
1453
src/german_lessons.inc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -58,6 +58,7 @@ extern char *app_progress_on2;
|
|||
extern char *app_progress_on3;
|
||||
extern char *app_progress_on4;
|
||||
extern char *app_progress_en;
|
||||
extern char *app_progress_de;
|
||||
extern char *app_progress_mluvnice;
|
||||
extern char *app_progress_cetba;
|
||||
extern char *app_progress_lit;
|
||||
|
|
@ -87,6 +88,7 @@ extern char *app_progress_mat4;
|
|||
#define PROGRESS_ON3 app_progress_on3
|
||||
#define PROGRESS_ON4 app_progress_on4
|
||||
#define PROGRESS_EN app_progress_en
|
||||
#define PROGRESS_DE app_progress_de
|
||||
#define NODE_SIZE 88.0
|
||||
#define PATH_SPAC 240.0
|
||||
#define ROAD_MX 150.0 /* horizontal canvas margin */
|
||||
|
|
@ -622,6 +624,27 @@ typedef struct {
|
|||
const char *tip;
|
||||
const char *line[8];
|
||||
} NetSlide;
|
||||
typedef struct {
|
||||
const NetSlide *slides;
|
||||
int n_slides;
|
||||
const char *reading;
|
||||
const ChoiceQ *readq;
|
||||
const char **read_hints;
|
||||
int n_read;
|
||||
const char *listening;
|
||||
const ChoiceQ *listenq;
|
||||
const char **listen_hints;
|
||||
int n_listen;
|
||||
const TypedQ *gaps;
|
||||
int n_gaps;
|
||||
const char *write_prompt;
|
||||
const char *write_model;
|
||||
const char *write_keys;
|
||||
int write_min;
|
||||
const ChoiceQ *quiz;
|
||||
const char **quiz_hints;
|
||||
int n_quiz;
|
||||
} EnLesson;
|
||||
typedef struct {
|
||||
const ChoiceQ *qs;
|
||||
int n;
|
||||
|
|
@ -911,6 +934,18 @@ void progress_for_en(ProgressSum *out);
|
|||
void en_rail_theme_reset(void);
|
||||
GtkWidget *build_enyears_page(void);
|
||||
void add_en_pages(GtkStack *stack);
|
||||
GtkWidget *skill_unit_page(NetLesson *L, const EnLesson *lesson,
|
||||
const char *back, const char *title_key,
|
||||
const char *sub_key);
|
||||
GtkWidget *skill_ex_page(const EnLesson *lesson, int year, int index,
|
||||
void (*on_done)(int, int),
|
||||
const char *back, const char *title_key,
|
||||
const char *voice);
|
||||
void skill_slide_apply(NetLesson *L);
|
||||
GtkWidget *build_dehome_page(void);
|
||||
void add_de_pages(GtkStack *stack);
|
||||
void de_load_progress(void);
|
||||
void progress_for_de(ProgressSum *out);
|
||||
void on2_load_progress(void);
|
||||
void on2_save_progress(void);
|
||||
void mark_on2_done(int lesson_id);
|
||||
|
|
|
|||
|
|
@ -1620,6 +1620,7 @@ const TrEntry tr_ui[] = {
|
|||
{"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 "german_i18n.inc"
|
||||
#include "math_i18n.inc"
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
|
@ -2603,6 +2604,7 @@ 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 de_lessons_apply_lang(void);
|
||||
void lit_lessons_apply_lang(void);
|
||||
void sci_lessons_apply_lang(void);
|
||||
void fyz_lessons_apply_lang(void);
|
||||
|
|
@ -2626,6 +2628,7 @@ void apply_language(void) {
|
|||
on3_lessons_apply_lang();
|
||||
on4_lessons_apply_lang();
|
||||
en_lessons_apply_lang();
|
||||
de_lessons_apply_lang();
|
||||
lit_lessons_apply_lang();
|
||||
sci_lessons_apply_lang();
|
||||
fyz_lessons_apply_lang();
|
||||
|
|
|
|||
|
|
@ -418,6 +418,8 @@ static gboolean run_smoke_test(gpointer data) {
|
|||
"on4unit1", "on4ex1", "on4unit16", "on4ex16",
|
||||
"enyears", "en1map", "en4map",
|
||||
"en1unit1", "en1ex1", "en4unit8", "en4ex8",
|
||||
"dehome", "de1map", "de4map",
|
||||
"de1unit1", "de1ex1", "de4unit6", "de4ex6",
|
||||
"unit1", "unit2", "unit3",
|
||||
"u1e1", "u1e13", "u2e1", "u2e19", "u3e1", "u3e15",
|
||||
"u1vocab", "u2vocab", "u3vocab",
|
||||
|
|
@ -523,6 +525,8 @@ void activate(GtkApplication *app, gpointer user_data) {
|
|||
gtk_stack_add_named(main_stack, welcome_page, "welcome");
|
||||
gtk_stack_add_named(main_stack, build_subjects_page(), "subjects");
|
||||
gtk_stack_add_named(main_stack, roadmap_page, "roadmap");
|
||||
gtk_stack_add_named(main_stack, build_dehome_page(), "dehome");
|
||||
add_de_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_stats_page(), "stats");
|
||||
gtk_stack_add_named(main_stack, build_netyears_page(), "netyears");
|
||||
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ void load_progress(void) {
|
|||
on3_load_progress();
|
||||
on4_load_progress();
|
||||
en_load_progress();
|
||||
de_load_progress();
|
||||
lit_load_progress();
|
||||
sci_load_progress();
|
||||
fyz_load_progress();
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ GtkWidget *build_roadmap_page(void) {
|
|||
gtk_widget_set_margin_bottom(page, 24);
|
||||
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar("subjects", "roadmap_title", "roadmap_sub"));
|
||||
top_bar("dehome", "roadmap_title", "roadmap_sub"));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
|
|
|
|||
13
src/search.c
13
src/search.c
|
|
@ -29,7 +29,7 @@ static gboolean subject_open(int i) {
|
|||
|
||||
static const char *subject_page(int i) {
|
||||
if (i == 0)
|
||||
return "roadmap";
|
||||
return "dehome";
|
||||
if (i == NET_SUBJ)
|
||||
return "netyears";
|
||||
if (i == HW_SUBJ)
|
||||
|
|
@ -248,6 +248,17 @@ static void catalog_rebuild(void) {
|
|||
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");
|
||||
catalog_add_key("de_book_title", "search_unit", "roadmap", FALSE, 1, "nemcina deutsch ucebnice");
|
||||
for (i = 1; i <= 4; i++) {
|
||||
g_snprintf(key, sizeof key, "de_year%d", i);
|
||||
g_snprintf(page, sizeof page, "de%dmap", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 1, "nemcina deutsch rocnik");
|
||||
for (n = 1; n <= 6; n++) {
|
||||
g_snprintf(key, sizeof key, "de_y%d_u%d", i, n);
|
||||
g_snprintf(page, sizeof page, "de%dunit%d", i, n);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "nemcina deutsch");
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
10
src/stats.c
10
src/stats.c
|
|
@ -83,8 +83,11 @@ void refresh_stats_ui(void) {
|
|||
progress_for_fyz(&all);
|
||||
else if (s == MATH_SUBJ)
|
||||
progress_for_math(&all);
|
||||
else
|
||||
else {
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
||||
if (s == 0)
|
||||
progress_for_de(&all);
|
||||
}
|
||||
}
|
||||
|
||||
pct = all.total_ex > 0 ? (all.done_ex * 100) / all.total_ex : 0;
|
||||
|
|
@ -138,8 +141,11 @@ void refresh_stats_ui(void) {
|
|||
progress_for_fyz(&sp);
|
||||
else if (s == MATH_SUBJ)
|
||||
progress_for_math(&sp);
|
||||
else
|
||||
else {
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
|
||||
if (s == 0)
|
||||
progress_for_de(&sp);
|
||||
}
|
||||
if (has_content) {
|
||||
tmp = g_strdup_printf(tr("stats_ex_fmt"), sp.done_ex, sp.total_ex);
|
||||
gtk_label_set_text(GTK_LABEL(count), tmp);
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ static GtkWidget *subject_icon(int i) {
|
|||
|
||||
static const char *subject_target(int i) {
|
||||
if (i == 0)
|
||||
return "roadmap";
|
||||
return "dehome";
|
||||
if (i == NET_SUBJ)
|
||||
return "netyears";
|
||||
if (i == HW_SUBJ)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ char *app_progress_on2;
|
|||
char *app_progress_on3;
|
||||
char *app_progress_on4;
|
||||
char *app_progress_en;
|
||||
char *app_progress_de;
|
||||
char *app_progress_mluvnice;
|
||||
char *app_progress_cetba;
|
||||
char *app_progress_lit;
|
||||
|
|
@ -183,6 +184,7 @@ static void set_progress_paths(const char *write_root) {
|
|||
g_free(app_progress_on3);
|
||||
g_free(app_progress_on4);
|
||||
g_free(app_progress_en);
|
||||
g_free(app_progress_de);
|
||||
g_free(app_progress_mluvnice);
|
||||
g_free(app_progress_cetba);
|
||||
g_free(app_progress_lit);
|
||||
|
|
@ -213,6 +215,7 @@ static void set_progress_paths(const char *write_root) {
|
|||
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_de = g_build_filename(app_progress_dir, "german-course.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