Add year-1 civics lessons on life in a community and keep later years locked.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-02 11:44:21 +02:00
commit 515abdc6ce
28 changed files with 2665 additions and 12 deletions

View file

@ -8,7 +8,7 @@ Vzdělávací aplikace k přípravě na maturitu (repo `Graduately`). / An educa
## Česky
Rozhraní je v češtině a angličtině. Hotové jsou čtyři předměty; ostatní jsou
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.
| Úvod | Předměty | Německá cesta |
@ -68,13 +68,15 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
- **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é
- **Český jazyk a literatura** — mluvnice (20 cvičení) a maturitní četba
(82 knih: zápisky, kvíz, sestavení děje); literární teorie je zatím zamčená
- **Občanská nauka** — 1. ročník, 17 lekcí (člověk v lidském společenství);
ročníky 2–4 jsou zamčené
- 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`
### Použití
1. **Pokračuj** otevře mapu předmětů.
2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka) vede na učební cestu.
2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy) vede na učební cestu.
3. Uzly cvičení / lekcí otevřou obsah; zamčené nic nedělají.
4. **Hledat** (lupa), **Statistiky** (graf) a **Nastavení** (ozubené kolo) jsou vpravo nahoře.
@ -200,7 +202,7 @@ Licence: [GPL-3.0](LICENSE).
## English
The UI is Czech and English. Four subjects are playable; the rest are locked
The UI is Czech and English. Five subjects are playable; the rest are locked
placeholders. After install the app updates itself.
| Welcome | Subjects | German path |
@ -261,13 +263,15 @@ Intel Mac, another architecture, or hacking on the code →
- **Computer hardware** — year 1, 29 lessons (architecture, data, case and board, cards, drives, input devices); years 2–4 are locked
- **Czech language** — grammar (20 exercises) and required reading
(82 books: notes, quiz, plot ordering); literature theory is still locked
- **Civics** — year 1, 17 lessons (the individual and society);
years 2–4 are locked
- statistics, search (magnifier or `Cmd/Ctrl+K`), 10 palettes, dark/light mode, Czech/English
- quit with `Cmd/Super+Q` or `Alt+F4`
### Usage
1. **Continue** opens the subject map.
2. An open subject (flag, Wi‑Fi, chip, Czech flag) opens its path.
2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales) opens its path.
3. Exercise / lesson nodes open content; locked nodes do nothing.
4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right.

View file

@ -126,6 +126,11 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
refresh()
}
fun markOn(id: Int) {
progress.markOn(id)
refresh()
}
fun markMluv(n: Int) {
progress.markMluv(n)
refresh()

View file

@ -37,6 +37,8 @@ import org.maturita.maturita.ui.LitQuizScreen
import org.maturita.maturita.ui.MluvExercise
import org.maturita.maturita.ui.NetQuizScreen
import org.maturita.maturita.ui.NetYearsScreen
import org.maturita.maturita.ui.OnQuizScreen
import org.maturita.maturita.ui.OnYearsScreen
import org.maturita.maturita.ui.PlotScreen
import org.maturita.maturita.ui.RoadmapScreen
import org.maturita.maturita.ui.SearchScreen
@ -226,6 +228,21 @@ private fun MaturitaAppBody(vm: AppViewModel) {
}
}
is Route.HwEx -> HwQuizScreen(vm, r.id)
Route.OnYears -> OnYearsScreen(vm)
Route.OnMap -> LessonListScreen(
vm, vm.tr("on_year1"), vm.tr("on_sub"),
vm.content.on.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.onDone(id), Route.OnLesson(id))
},
)
is Route.OnLesson -> {
val l = vm.content.onLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.OnEx(r.id))
}
}
is Route.OnEx -> OnQuizScreen(vm, r.id)
Route.CzechMap -> CzechMapScreen(vm)
Route.Mluvnice -> LessonListScreen(
vm, vm.tr("Mluvnice"), vm.tr("mluv_sub"),

View file

@ -17,6 +17,10 @@ sealed class Route(val page: String) {
data object HwMap : Route("hwmap")
data class HwLesson(val id: Int) : Route("hwunit$id")
data class HwEx(val id: Int) : Route("hwex$id")
data object OnYears : Route("onyears")
data object OnMap : Route("onmap")
data class OnLesson(val id: Int) : Route("onunit$id")
data class OnEx(val id: Int) : Route("onex$id")
data object CzechMap : Route("czechmap")
data object Mluvnice : Route("mluvnice")
data class MluvEx(val n: Int) : Route("mluve$n")
@ -31,7 +35,8 @@ 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 MluvEx || this is BookQuiz || this is BookPlot || this is NetLesson || this is HwLesson
this is OnEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
this is NetLesson || this is HwLesson || this is OnLesson
/** Full stack from the practice root to this screen, so back always goes up one level. */
fun stack(): List<Route> = when (this) {
@ -50,6 +55,10 @@ sealed class Route(val page: String) {
HwMap -> listOf(Subjects, HwYears, HwMap)
is HwLesson -> listOf(Subjects, HwYears, HwMap, this)
is HwEx -> listOf(Subjects, HwYears, HwMap, HwLesson(id), this)
OnYears -> listOf(Subjects, OnYears)
OnMap -> listOf(Subjects, OnYears, OnMap)
is OnLesson -> listOf(Subjects, OnYears, OnMap, this)
is OnEx -> listOf(Subjects, OnYears, OnMap, OnLesson(id), this)
CzechMap -> listOf(Subjects, CzechMap)
Mluvnice -> listOf(Subjects, CzechMap, Mluvnice)
is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this)
@ -70,6 +79,8 @@ fun routeFromPage(page: String): Route? = when (page) {
"netmap" -> Route.NetMap
"hwyears" -> Route.HwYears
"hwmap" -> Route.HwMap
"onyears" -> Route.OnYears
"onmap" -> Route.OnMap
"czechmap" -> Route.CzechMap
"mluvnice" -> Route.Mluvnice
"readinglist" -> Route.ReadingList
@ -101,6 +112,12 @@ fun routeFromPage(page: String): Route? = when (page) {
Regex("""hwex(\d+)""").matchEntire(page)?.let {
return Route.HwEx(it.groupValues[1].toInt())
}
Regex("""onunit(\d+)""").matchEntire(page)?.let {
return Route.OnLesson(it.groupValues[1].toInt())
}
Regex("""onex(\d+)""").matchEntire(page)?.let {
return Route.OnEx(it.groupValues[1].toInt())
}
Regex("""mluve(\d+)""").matchEntire(page)?.let {
return Route.MluvEx(it.groupValues[1].toInt())
}

View file

@ -29,6 +29,7 @@ class Content(root: JSONObject) {
}
}
val hw = raw.arr("hw")
val on = raw.arr("on")
val mluvnice = raw.arr("mluvnice")
val books = raw.arr("books")
val changelog = raw.arr("changelog")
@ -36,6 +37,7 @@ class Content(root: JSONObject) {
fun germanUnit(id: Int) = german.firstOrNull { it.int("id") == id }
fun netLesson(id: Int) = netLessons.firstOrNull { it.int("id") == id }
fun hwLesson(id: Int) = hw.firstOrNull { it.int("id") == id }
fun onLesson(id: Int) = on.firstOrNull { it.int("id") == id }
fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n }
fun book(id: String) = books.firstOrNull { it.str("id") == id }

View file

@ -32,6 +32,9 @@ class ProgressStore(context: Context) {
fun hwDone(id: Int) = prefs.getBoolean("hw.$id", false)
fun markHw(id: Int) { prefs.edit().putBoolean("hw.$id", true).apply() }
fun onDone(id: Int) = prefs.getBoolean("on.$id", false)
fun markOn(id: Int) { prefs.edit().putBoolean("on.$id", true).apply() }
fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false)
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() }
@ -70,6 +73,14 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
sum.doneUnits += 1
}
}
content.on.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.onDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.size
sum.openUnits += 1
val md = content.mluvnice.count { p.mluvDone(it.int("id")) }

View file

@ -848,6 +848,18 @@ fun NetQuizScreen(vm: AppViewModel, id: Int) {
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) { vm.markNet(id) }
}
@Composable
fun OnQuizScreen(vm: AppViewModel, id: Int) {
val lesson = vm.content.onLesson(id) ?: return
val spec = J(org.json.JSONObject().apply {
put("type", "choice")
put("title", vm.tr(lesson.str("exTitleKey")))
put("sub", lesson.str("quizHeadKey"))
put("questions", lesson.o.optJSONArray("quiz"))
})
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) { vm.markOn(id) }
}
@Composable
fun HwQuizScreen(vm: AppViewModel, id: Int) {
val lesson = vm.content.hwLesson(id) ?: return

View file

@ -29,6 +29,7 @@ import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FormatListNumbered
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Quiz
import androidx.compose.material.icons.filled.Search
@ -217,6 +218,7 @@ private fun SubjectIcon(icon: String, open: Boolean) {
"de" -> GermanFlag(28.dp)
"wifi" -> WifiIcon(tint, 26.dp)
"chip" -> ChipIcon(tint, 26.dp)
"people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp))
"cz" -> CzechFlag(28.dp)
else -> Icon(Icons.Filled.Lock, null, tint = tint)
}
@ -298,6 +300,10 @@ 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")) }
}
"onyears", "onmap" -> {
sum.totalEx = vm.content.on.size
sum.doneEx = vm.content.on.count { vm.progress.onDone(it.int("id")) }
}
"czechmap" -> {
sum.totalEx = vm.content.mluvnice.size
sum.doneEx = vm.content.mluvnice.count { vm.progress.mluvDone(it.int("id")) }
@ -485,6 +491,14 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "hwunit${l.int("id")}", extra = "hardware")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "hwex${l.int("id")}")
}
add(vm.tr("on_year1"), vm.tr("search_lesson"), "onmap", extra = "rocnik")
add(vm.tr("on_year2"), vm.tr("search_lesson"), "onyears", locked = true, extra = "rocnik")
add(vm.tr("on_year3"), vm.tr("search_lesson"), "onyears", locked = true, extra = "rocnik")
add(vm.tr("on_year4"), vm.tr("search_lesson"), "onyears", locked = true, extra = "rocnik")
vm.content.on.forEach { l ->
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")}")
}
add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice")
vm.content.mluvnice.forEach { m -> add(m.str("name"), vm.tr("Mluvnice"), "mluve${m.int("id")}") }
add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist")
@ -583,6 +597,20 @@ fun HwYearsScreen(vm: AppViewModel) {
}
}
@Composable
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)) {
item {
NavRow(title = vm.tr("on_year1"), subtitle = vm.tr("on_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.OnMap) }
}
items(listOf("on_year2", "on_year3", "on_year4")) { key ->
NavRow(title = vm.tr(key), subtitle = vm.tr("on_year_locked_sub"), locked = true, onClick = null)
}
}
}
}
@Composable
fun CzechMapScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Český jazyk a literatura"), vm.tr("czech_sub")) { inner ->

View file

@ -6,6 +6,12 @@
# cs: bullet
# en: bullet
--- 2026-10-02
cs: Občanská nauka: 1. ročník, 17 lekcí (člověk v lidském společenství)
cs: Občanská nauka: další ročníky jsou zatím zamčené
en: Civics: year 1, 17 lessons (the individual and society)
en: Civics: later years are still locked
--- 2026-09-25
cs: Technické vybavení: pět lekcí o digitalizaci zvuku (vzorkování, kvantování, PCM)
cs: Technické vybavení: skříň, zdroj, deska, CPU, karty, disky a vstupní zařízení

View file

@ -128,6 +128,11 @@ final class AppModel: ObservableObject {
refresh()
}
func markOn(_ id: Int) {
progress.markOn(id)
refresh()
}
func markMluv(_ n: Int) {
progress.markMluv(n)
refresh()

View file

@ -31,6 +31,7 @@ final class Content {
let netTasks: [J]
let netAnswers: [[J]]
let hw: [J]
let on: [J]
let mluvnice: [J]
let books: [J]
let changelog: [J]
@ -62,6 +63,7 @@ final class Content {
netAnswers = []
}
hw = raw.arr("hw")
on = raw.arr("on")
mluvnice = raw.arr("mluvnice")
books = raw.arr("books")
changelog = raw.arr("changelog")
@ -70,6 +72,7 @@ final class Content {
func germanUnit(_ id: Int) -> J? { german.first { $0.int("id") == id } }
func netLesson(_ id: Int) -> J? { netLessons.first { $0.int("id") == id } }
func hwLesson(_ id: Int) -> J? { hw.first { $0.int("id") == id } }
func onLesson(_ id: Int) -> J? { on.first { $0.int("id") == id } }
func mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } }
func book(_ id: String) -> J? { books.first { $0.str("id") == id } }

View file

@ -34,6 +34,9 @@ final class ProgressStore {
func hwDone(_ id: Int) -> Bool { defaults.bool(forKey: "hw.\(id)") }
func markHw(_ id: Int) { defaults.set(true, forKey: "hw.\(id)") }
func onDone(_ id: Int) -> Bool { defaults.bool(forKey: "on.\(id)") }
func markOn(_ id: Int) { defaults.set(true, forKey: "on.\(id)") }
func mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") }
func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") }
@ -77,6 +80,14 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
sum.doneUnits += 1
}
}
for l in content.on {
sum.totalEx += 1
sum.openUnits += 1
if p.onDone(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.count
sum.openUnits += 1
let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count

View file

@ -23,6 +23,10 @@ enum Route: Hashable {
case hwMap
case hwLesson(Int)
case hwEx(Int)
case onYears
case onMap
case onLesson(Int)
case onEx(Int)
case czechMap
case mluvnice
case mluvEx(Int)
@ -48,6 +52,10 @@ enum Route: Hashable {
case .hwMap: return "hwmap"
case .hwLesson(let id): return "hwunit\(id)"
case .hwEx(let id): return "hwex\(id)"
case .onYears: return "onyears"
case .onMap: return "onmap"
case .onLesson(let id): return "onunit\(id)"
case .onEx(let id): return "onex\(id)"
case .czechMap: return "czechmap"
case .mluvnice: return "mluvnice"
case .mluvEx(let n): return "mluve\(n)"
@ -96,6 +104,14 @@ enum Route: Hashable {
return [.hwYears, .hwMap, .hwLesson(id)]
case .hwEx(let id):
return [.hwYears, .hwMap, .hwLesson(id), .hwEx(id)]
case .onYears:
return [.onYears]
case .onMap:
return [.onYears, .onMap]
case .onLesson(let id):
return [.onYears, .onMap, .onLesson(id)]
case .onEx(let id):
return [.onYears, .onMap, .onLesson(id), .onEx(id)]
case .czechMap:
return [.czechMap]
case .mluvnice:
@ -124,6 +140,8 @@ func routeFromPage(_ page: String) -> Route? {
case "netmap": return .netMap
case "hwyears": return .hwYears
case "hwmap": return .hwMap
case "onyears": return .onYears
case "onmap": return .onMap
case "czechmap": return .czechMap
case "mluvnice": return .mluvnice
case "readinglist": return .readingList
@ -167,6 +185,12 @@ func routeFromPage(_ page: String) -> Route? {
if let m = page.wholeMatch(of: /hwex(\d+)/), let n = Int(m.1) {
return .hwEx(n)
}
if let m = page.wholeMatch(of: /onunit(\d+)/), let n = Int(m.1) {
return .onLesson(n)
}
if let m = page.wholeMatch(of: /onex(\d+)/), let n = Int(m.1) {
return .onEx(n)
}
if let m = page.wholeMatch(of: /mluve(\d+)/), let n = Int(m.1) {
return .mluvEx(n)
}

View file

@ -465,6 +465,7 @@ func subjectSymbol(_ icon: String) -> String {
case "wifi": return "wifi"
case "chip": return "cpu"
case "cz": return "text.book.closed.fill"
case "people": return "person.2.fill"
default: return "lock.fill"
}
}

View file

@ -625,6 +625,25 @@ struct NetQuizScreen: View {
}
}
struct OnQuizScreen: View {
@ObservedObject var vm: AppModel
let id: Int
var body: some View {
if let lesson = vm.content.onLesson(id) {
let spec = J([
"type": "choice",
"title": vm.tr(lesson.str("exTitleKey")),
"sub": lesson.str("quizHeadKey"),
"questions": lesson.rawArray("quiz"),
])
DispatchExercise(vm: vm, spec: spec, titleFallback: vm.tr(lesson.str("exTitleKey"))) {
vm.markOn(id)
}
}
}
}
struct HwQuizScreen: View {
@ObservedObject var vm: AppModel
let id: Int

View file

@ -423,6 +423,9 @@ 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 "onyears", "onmap":
sum.totalEx = vm.content.on.count
sum.doneEx = vm.content.on.filter { vm.progress.onDone($0.int("id")) }.count
case "czechmap":
sum.totalEx = vm.content.mluvnice.count
sum.doneEx = vm.content.mluvnice.filter { vm.progress.mluvDone($0.int("id")) }.count
@ -561,6 +564,14 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "hwunit\(l.int("id"))", extra: "hardware")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "hwex\(l.int("id"))")
}
add(vm.tr("on_year1"), vm.tr("search_lesson"), "onmap", extra: "rocnik")
add(vm.tr("on_year2"), vm.tr("search_lesson"), "onyears", locked: true, extra: "rocnik")
add(vm.tr("on_year3"), vm.tr("search_lesson"), "onyears", locked: true, extra: "rocnik")
add(vm.tr("on_year4"), vm.tr("search_lesson"), "onyears", locked: true, extra: "rocnik")
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"))")
}
add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice")
add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist")
for b in vm.content.books {
@ -777,6 +788,29 @@ struct HwYearsScreen: View {
}
}
struct OnYearsScreen: View {
@ObservedObject var vm: AppModel
var body: some View {
List {
NavigationLink(value: Route.onMap) {
ListRowLabel(title: vm.tr("on_year1"), subtitle: vm.tr("on_sub"))
}
ForEach(["on_year2", "on_year3", "on_year4"], id: \.self) { key in
Label {
ListRowLabel(title: vm.tr(key), subtitle: vm.tr("on_year_locked_sub"))
} icon: {
Image(systemName: "lock.fill")
}
.foregroundStyle(.secondary)
}
}
.navigationTitle(vm.tr("Občanská nauka"))
.navigationSubtitle(vm.tr("on_years_sub"))
.appListStyle()
}
}
struct LessonRow: Identifiable {
let id: String
let title: String
@ -1345,6 +1379,29 @@ struct RouteDestination: View {
}
case .hwEx(let id):
HwQuizScreen(vm: vm, id: id)
case .onYears:
OnYearsScreen(vm: vm)
case .onMap:
LessonListScreen(
vm: vm,
title: vm.tr("on_year1"),
subtitle: vm.tr("on_sub"),
rows: vm.content.on.map { l in
let id = l.int("id")
return LessonRow(
id: "o\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.onDone(id),
destination: .onLesson(id)
)
}
)
case .onLesson(let id):
if let l = vm.content.onLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .onEx(id))
}
case .onEx(let id):
OnQuizScreen(vm: vm, id: id)
case .czechMap:
CzechMapScreen(vm: vm)
case .mluvnice:

View file

@ -1084,6 +1084,18 @@ def main() -> int:
"quiz": qs,
})
on_lessons = []
for i in range(1, 18):
on_lessons.append({
"id": i,
"titleKey": f"on_unit{i}",
"subKey": f"on_unit{i}_sub",
"exTitleKey": f"on_ex{i}_title",
"quizHeadKey": f"on_quiz{i}_head",
"slides": slides_items(A(f"build_on_unit{i}_page.slides")),
"quiz": choice_items(A(f"build_on_unit{i}_exercise_page.qs")),
})
hw_lessons = []
for i in range(1, 30):
hw_lessons.append({
@ -1481,7 +1493,7 @@ def main() -> int:
{"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": False, "target": None, "icon": "lock"},
{"key": "Občanská nauka", "open": True, "target": "onyears", "icon": "people"},
{"key": "English", "open": False, "target": None, "icon": "lock"},
{"key": "Matematika", "open": False, "target": None, "icon": "lock"},
{"key": "Fyzika", "open": False, "target": None, "icon": "lock"},
@ -1502,6 +1514,7 @@ def main() -> int:
"answers": net_ans(A("net_ans")),
},
"hw": hw_lessons,
"on": on_lessons,
"mluvnice": mluvnice,
"books": books,
"changelog": parse_changelog(ROOT / "data" / "changelog.txt"),
@ -1536,6 +1549,7 @@ def main() -> int:
print(f" net lessons with slides: {sum(1 for L in net_lessons if L['slides'])}")
print(f" net quizzes: {sum(1 for L in net_lessons if L['quiz'])}")
print(f" hw lessons: {sum(1 for L in hw_lessons if L['slides'])}")
print(f" on lessons: {sum(1 for L in on_lessons if L['slides'])}")
print(f" mluvnice: {len(mluvnice)}")
print(f" books quiz items: {[len(b['quiz']) for b in books]}")
print(f" changelog entries: {len(payload.get('changelog', []))}")
@ -1569,6 +1583,11 @@ def main() -> int:
missing.append(f"hw{L['id']} slides")
if not L["quiz"]:
missing.append(f"hw{L['id']} quiz")
for L in on_lessons:
if not L["slides"]:
missing.append(f"on{L['id']} slides")
if not L["quiz"]:
missing.append(f"on{L['id']} quiz")
for m in mluvnice:
if m["type"] in ("typed", "reveal") and not m.get("items"):
missing.append(f"mluv{m['id']}")

View file

@ -19,6 +19,7 @@
#define NET_SUBJ 1 /* index of "Správa počítačových sítí" */
#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 CSS_FILE "style.css"
#define CHANGELOG_FILE "changelog.txt"
#define APP_ID "org.maturita.Maturita"
@ -48,6 +49,7 @@ extern char *app_progress_u3;
extern char *app_settings_file;
extern char *app_progress_net;
extern char *app_progress_hw;
extern char *app_progress_on;
extern char *app_progress_mluvnice;
extern char *app_progress_cetba;
#define PROGRESS_DIR app_progress_dir
@ -57,6 +59,7 @@ extern char *app_progress_cetba;
#define SETTINGS_FILE app_settings_file
#define PROGRESS_NET app_progress_net
#define PROGRESS_HW app_progress_hw
#define PROGRESS_ON app_progress_on
#define NODE_SIZE 88.0
#define PATH_SPAC 240.0
#define ROAD_MX 150.0 /* horizontal canvas margin */
@ -114,6 +117,8 @@ extern char *app_progress_cetba;
#define NET_LESSONS 27
#define HW_UNITS 29
#define HW_LESSONS 29
#define ON_UNITS 17
#define ON_LESSONS 17
#define HW_SLIDES 3
#define HW2_SLIDES 6
#define HW3_SLIDES 2
@ -754,6 +759,8 @@ extern NetLesson net_lessons[NET_LESSONS];
extern NetLesson *net_notes_target;
extern NetLesson hw_lessons[HW_LESSONS];
extern GtkWidget *hw_nodes[HW_UNITS];
extern NetLesson on_lessons[ON_LESSONS];
extern GtkWidget *on_nodes[ON_UNITS];
extern GtkWidget *net_note_host;
extern int net_note_last_w;
extern GtkWidget *net_scroll;
@ -841,6 +848,16 @@ void mark_hw_done(int lesson_id);
void refresh_hw_completion_ui(void);
void progress_for_hw(ProgressSum *out);
void hw_rail_theme_reset(void);
void on_load_progress(void);
void on_save_progress(void);
void mark_on_done(int lesson_id);
void refresh_on_completion_ui(void);
void progress_for_on(ProgressSum *out);
void on_rail_theme_reset(void);
void on_lessons_apply_lang(void);
GtkWidget *build_onyears_page(void);
GtkWidget *build_onmap_page(void);
void add_on_pages(GtkStack *stack);
void czech_rail_theme_reset(void);
void hw_lessons_apply_lang(void);
GtkWidget *build_hwyears_page(void);
@ -927,6 +944,8 @@ GtkWidget *build_hw_unit29_page(void);
GtkWidget *build_hw_unit29_exercise_page(void);
void draw_chip_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data);
void draw_civics_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_book_icon(GtkDrawingArea *area, cairo_t *cr,

View file

@ -700,6 +700,110 @@ const TrEntry tr_ui[] = {
"Quiz: game controls and touch"},
{"hw_quiz29_head", "Kvíz k herním ovladačům a dotyku",
"Quiz on game controls and touch"},
{"on_sub", "Člověk v lidském společenství. Vyberte lekci na cestě.",
"The individual and society. Pick a lesson on the path."},
{"on_years_sub", "Vyberte ročník a začněte procvičovat.",
"Pick a school year and start practicing."},
{"on_year1", "1. ročník", "Year 1"},
{"on_year2", "2. ročník", "Year 2"},
{"on_year3", "3. ročník", "Year 3"},
{"on_year4", "4. ročník", "Year 4"},
{"on_year_locked_sub", "Zatím bez obsahu", "No content yet"},
{"on_unit1", "Výchova k občanství", "Civics"},
{"on_unit1_sub", "2 snímky o smyslu předmětu a pak kvíz.",
"2 slides on what the subject is for, then a quiz."},
{"on_ex1_title", "Kvíz: výchova k občanství", "Quiz: civics"},
{"on_quiz1_head", "Kvíz k výchově k občanství", "Quiz on civics"},
{"on_unit2", "Učení a volný čas", "Learning and free time"},
{"on_unit2_sub", "2 snímky o učení a volném čase a pak kvíz.",
"2 slides on learning and free time, then a quiz."},
{"on_ex2_title", "Kvíz: učení a volný čas", "Quiz: learning and free time"},
{"on_quiz2_head", "Kvíz k učení a volnému času",
"Quiz on learning and free time"},
{"on_unit3", "Osobnost člověka", "Personality"},
{"on_unit3_sub", "2 snímky o osobnosti a socializaci a pak kvíz.",
"2 slides on personality and socialisation, then a quiz."},
{"on_ex3_title", "Kvíz: osobnost člověka", "Quiz: personality"},
{"on_quiz3_head", "Kvíz k osobnosti", "Quiz on personality"},
{"on_unit4", "Etapy lidského života", "Stages of life"},
{"on_unit4_sub", "2 snímky o etapách života a pak kvíz.",
"2 slides on the stages of life, then a quiz."},
{"on_ex4_title", "Kvíz: etapy života", "Quiz: stages of life"},
{"on_quiz4_head", "Kvíz k etapám života", "Quiz on the stages of life"},
{"on_unit5", "Psychohygiena", "Mental hygiene"},
{"on_unit5_sub", "2 snímky o náročných situacích a psychohygieně a pak kvíz.",
"2 slides on hard situations and mental hygiene, then a quiz."},
{"on_ex5_title", "Kvíz: psychohygiena", "Quiz: mental hygiene"},
{"on_quiz5_head", "Kvíz k psychohygieně", "Quiz on mental hygiene"},
{"on_unit6", "Slušné chování", "Good manners"},
{"on_unit6_sub", "2 snímky o etiketě a vztazích a pak kvíz.",
"2 slides on manners and relationships, then a quiz."},
{"on_ex6_title", "Kvíz: slušné chování", "Quiz: good manners"},
{"on_quiz6_head", "Kvíz ke slušnému chování", "Quiz on good manners"},
{"on_unit7", "Komunikace a konflikty", "Communication and conflict"},
{"on_unit7_sub", "2 snímky o komunikaci a konfliktech a pak kvíz.",
"2 slides on communication and conflict, then a quiz."},
{"on_ex7_title", "Kvíz: komunikace a konflikty",
"Quiz: communication and conflict"},
{"on_quiz7_head", "Kvíz ke komunikaci a konfliktům",
"Quiz on communication and conflict"},
{"on_unit8", "Zdraví a životní styl", "Health and lifestyle"},
{"on_unit8_sub", "2 snímky o zdraví a životním stylu a pak kvíz.",
"2 slides on health and lifestyle, then a quiz."},
{"on_ex8_title", "Kvíz: zdraví a životní styl", "Quiz: health and lifestyle"},
{"on_quiz8_head", "Kvíz ke zdraví a životnímu stylu",
"Quiz on health and lifestyle"},
{"on_unit9", "Závislosti", "Addictions"},
{"on_unit9_sub", "2 snímky o závislostech a pomoci a pak kvíz.",
"2 slides on addictions and where to get help, then a quiz."},
{"on_ex9_title", "Kvíz: závislosti", "Quiz: addictions"},
{"on_quiz9_head", "Kvíz k závislostem", "Quiz on addictions"},
{"on_unit10", "Rodina a sociální skupiny", "Family and social groups"},
{"on_unit10_sub", "2 snímky o skupinách a rodině a pak kvíz.",
"2 slides on groups and the family, then a quiz."},
{"on_ex10_title", "Kvíz: rodina a skupiny", "Quiz: family and groups"},
{"on_quiz10_head", "Kvíz k rodině a sociálním skupinám",
"Quiz on the family and social groups"},
{"on_unit11", "Komunita, dav a veřejnost", "Community, crowd and the public"},
{"on_unit11_sub", "2 snímky o komunitě, davu a veřejnosti a pak kvíz.",
"2 slides on community, crowds and the public, then a quiz."},
{"on_ex11_title", "Kvíz: komunita a dav", "Quiz: community and crowds"},
{"on_quiz11_head", "Kvíz ke komunitě, davu a veřejnosti",
"Quiz on community, crowds and the public"},
{"on_unit12", "Vrstevníci a migrace", "Peers and migration"},
{"on_unit12_sub", "2 snímky o vrstevnících, solidaritě a migraci a pak kvíz.",
"2 slides on peers, solidarity and migration, then a quiz."},
{"on_ex12_title", "Kvíz: vrstevníci a migrace", "Quiz: peers and migration"},
{"on_quiz12_head", "Kvíz k vrstevníkům a migraci",
"Quiz on peers and migration"},
{"on_unit13", "Společnost a nerovnost", "Society and inequality"},
{"on_unit13_sub", "2 snímky o společnosti, vrstvách a chudobě a pak kvíz.",
"2 slides on society, strata and poverty, then a quiz."},
{"on_ex13_title", "Kvíz: společnost a nerovnost",
"Quiz: society and inequality"},
{"on_quiz13_head", "Kvíz ke společnosti a nerovnosti",
"Quiz on society and inequality"},
{"on_unit14", "Postavení mužů a žen", "Men, women and equality"},
{"on_unit14_sub", "2 snímky o rovnosti a genderu a pak kvíz.",
"2 slides on equality and gender, then a quiz."},
{"on_ex14_title", "Kvíz: postavení mužů a žen", "Quiz: men, women and equality"},
{"on_quiz14_head", "Kvíz k postavení mužů a žen",
"Quiz on men, women and equality"},
{"on_unit15", "Kultura", "Culture"},
{"on_unit15_sub", "2 snímky o hmotné a duchovní kultuře a pak kvíz.",
"2 slides on material and non-material culture, then a quiz."},
{"on_ex15_title", "Kvíz: kultura", "Quiz: culture"},
{"on_quiz15_head", "Kvíz ke kultuře", "Quiz on culture"},
{"on_unit16", "Víra a náboženství", "Faith and religion"},
{"on_unit16_sub", "2 snímky o víře, církvích a sektách a pak kvíz.",
"2 slides on faith, churches and sects, then a quiz."},
{"on_ex16_title", "Kvíz: víra a náboženství", "Quiz: faith and religion"},
{"on_quiz16_head", "Kvíz k víře a náboženství", "Quiz on faith and religion"},
{"on_unit17", "Rozpočet domácnosti", "Household budget"},
{"on_unit17_sub", "2 snímky o rozpočtu a finanční tísni a pak kvíz.",
"2 slides on the household budget and money trouble, then a quiz."},
{"on_ex17_title", "Kvíz: rozpočet domácnosti", "Quiz: household budget"},
{"on_quiz17_head", "Kvíz k rozpočtu domácnosti", "Quiz on the household budget"},
{"czech_sub", "Vyberte oblast a začněte procvičovat.",
"Pick an area and start practicing."},
{"Literatura", "Literatura", "Literature"},
@ -1723,6 +1827,7 @@ void refresh_welcome_heading(void) {
void refresh_stats_ui(void);
void net_lessons_apply_lang(void);
void hw_lessons_apply_lang(void);
void on_lessons_apply_lang(void);
void net_rail_theme_reset(void);
void apply_language(void) {
@ -1737,5 +1842,6 @@ void apply_language(void) {
changelog_apply_lang();
net_lessons_apply_lang();
hw_lessons_apply_lang();
on_lessons_apply_lang();
search_apply_lang();
}

View file

@ -142,6 +142,52 @@ void draw_chip_icon(GtkDrawingArea *area, cairo_t *cr,
}
}
/* Balance scale for Občanská nauka. */
void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data) {
const double s = MIN(width, height);
const double cx = width / 2.0;
const double top = height / 2.0 - s * 0.34;
const double beam_y = top + s * 0.22;
const double half = s * 0.28;
const Rgb c = mix_rgb(color_from_hex(app_theme.on_accent),
color_from_hex(app_theme.accent2), 0.22);
(void)area;
(void)data;
cairo_set_source_rgb(cr, c.r, c.g, c.b);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width(cr, MAX(1.6, s * 0.07));
cairo_move_to(cr, cx, top);
cairo_line_to(cr, cx, top + s * 0.62);
cairo_stroke(cr);
cairo_move_to(cr, cx - half, beam_y);
cairo_line_to(cr, cx + half, beam_y);
cairo_stroke(cr);
cairo_set_line_width(cr, MAX(1.4, s * 0.05));
cairo_move_to(cr, cx - half, beam_y);
cairo_line_to(cr, cx - half, beam_y + s * 0.16);
cairo_move_to(cr, cx + half, beam_y);
cairo_line_to(cr, cx + half, beam_y + s * 0.16);
cairo_stroke(cr);
cairo_new_path(cr);
cairo_arc(cr, cx - half, beam_y + s * 0.24, s * 0.11, 0.15 * G_PI, 0.85 * G_PI);
cairo_stroke(cr);
cairo_new_path(cr);
cairo_arc(cr, cx + half, beam_y + s * 0.24, s * 0.11, 0.15 * G_PI, 0.85 * G_PI);
cairo_stroke(cr);
cairo_set_line_width(cr, MAX(1.8, s * 0.08));
cairo_move_to(cr, cx - s * 0.16, top + s * 0.66);
cairo_line_to(cr, cx + s * 0.16, top + s * 0.66);
cairo_stroke(cr);
}
/* Open book glyph, painted in the given colour. */
static void book_paint(cairo_t *cr, int width, int height, Rgb c) {
const double w = width;

View file

@ -385,7 +385,8 @@ static gboolean shot_later(gpointer data) {
static gboolean run_smoke_test(gpointer data) {
static const char *pages[] = {
"welcome", "subjects", "roadmap", "stats",
"netyears", "netmap", "hwyears", "hwmap", "czechmap",
"netyears", "netmap", "hwyears", "hwmap", "onyears", "onmap",
"czechmap",
"mluvnice", "readinglist",
"cetba1984", "cetba1984quiz", "cetba1984dej",
"cetbaFuks", "cetbaFuksQuiz", "cetbaFuksDej",
@ -396,6 +397,7 @@ static gboolean run_smoke_test(gpointer data) {
"hwunit10", "hwex10", "hwunit15", "hwex15",
"hwunit20", "hwex20", "hwunit25", "hwex25",
"hwunit29", "hwex29",
"onunit1", "onex1", "onunit17", "onex17",
"unit1", "unit2", "unit3",
"u1e1", "u1e13", "u2e1", "u2e19", "u3e1", "u3e15",
"u1vocab", "u2vocab", "u3vocab",
@ -506,6 +508,9 @@ void activate(GtkApplication *app, gpointer user_data) {
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
gtk_stack_add_named(main_stack, build_hwyears_page(), "hwyears");
gtk_stack_add_named(main_stack, build_hwmap_page(), "hwmap");
gtk_stack_add_named(main_stack, build_onyears_page(), "onyears");
gtk_stack_add_named(main_stack, build_onmap_page(), "onmap");
add_on_pages(main_stack);
gtk_stack_add_named(main_stack, build_czechmap_page(), "czechmap");
gtk_stack_add_named(main_stack, build_mluvnice_page(), "mluvnice");
for (int n = 1; n <= 20; n++) {

2114
src/obcanka.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -54,6 +54,7 @@ void load_progress(void) {
unit_load_progress(&units[i]);
net_load_progress();
hw_load_progress();
on_load_progress();
}
/* Redraw the exercise rails of every unit that already has a page. */
@ -341,3 +342,94 @@ void progress_for_hw(ProgressSum *out) {
}
}
}
void on_load_progress(void) {
GKeyFile *kf = g_key_file_new();
GError *err = NULL;
if (!g_key_file_load_from_file(kf, PROGRESS_ON, G_KEY_FILE_NONE, &err)) {
if (err)
g_error_free(err);
g_key_file_free(kf);
return;
}
for (int i = 0; i < ON_LESSONS; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
on_lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
}
g_key_file_free(kf);
}
void on_save_progress(void) {
GKeyFile *kf = g_key_file_new();
for (int i = 0; i < ON_LESSONS; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
g_key_file_set_boolean(kf, "done", key, on_lessons[i].done);
}
if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) {
g_key_file_free(kf);
return;
}
gchar *data = g_key_file_to_data(kf, NULL, NULL);
g_key_file_free(kf);
if (data) {
GError *err = NULL;
g_file_set_contents(PROGRESS_ON, data, -1, &err);
if (err)
g_error_free(err);
g_free(data);
}
}
void refresh_on_completion_ui(void) {
for (int i = 0; i < ON_LESSONS; i++) {
GtkWidget *node = on_nodes[i];
GtkWidget *icon = on_lessons[i].done_icon;
if (!node)
continue;
if (on_lessons[i].done) {
gtk_widget_remove_css_class(node, "current");
gtk_widget_add_css_class(node, "done");
if (icon)
gtk_widget_set_visible(icon, TRUE);
} else {
gtk_widget_remove_css_class(node, "done");
gtk_widget_add_css_class(node, "current");
if (icon)
gtk_widget_set_visible(icon, FALSE);
}
}
}
void mark_on_done(int lesson_id) {
if (lesson_id < 0 || lesson_id >= ON_LESSONS)
return;
if (on_lessons[lesson_id].done)
return;
on_lessons[lesson_id].done = TRUE;
on_save_progress();
refresh_on_completion_ui();
refresh_stats_ui();
}
void progress_for_on(ProgressSum *out) {
out->total_ex += ON_LESSONS;
out->open_units += ON_LESSONS;
for (int i = 0; i < ON_LESSONS; i++) {
if (on_lessons[i].done) {
out->done_ex++;
out->done_units++;
}
}
}

View file

@ -22,7 +22,8 @@ static GtkWidget *search_empty;
static GPtrArray *search_items;
static gboolean subject_open(int i) {
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ;
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|| i == ON_SUBJ;
}
static const char *subject_page(int i) {
@ -34,6 +35,8 @@ static const char *subject_page(int i) {
return "hwyears";
if (i == CZ_SUBJ)
return "czechmap";
if (i == ON_SUBJ)
return "onyears";
return "subjects";
}
@ -199,6 +202,18 @@ static void catalog_rebuild(void) {
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
catalog_add_key("on_year1", "search_lesson", "onmap", FALSE, 1, "rocnik");
catalog_add_key("on_year2", "search_lesson", "onyears", TRUE, 1, "rocnik");
catalog_add_key("on_year3", "search_lesson", "onyears", TRUE, 1, "rocnik");
catalog_add_key("on_year4", "search_lesson", "onyears", TRUE, 1, "rocnik");
for (i = 1; i <= ON_LESSONS; i++) {
g_snprintf(key, sizeof(key), "on_unit%d", i);
g_snprintf(page, sizeof(page), "onunit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "obcanka");
g_snprintf(page, sizeof(page), "onex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
catalog_add_key("Český jazyk a literatura", "search_subject",
"czechmap", FALSE, 0, "cestina czech");
catalog_add_key("Literatura", "search_lesson", "czechmap", TRUE, 1, NULL);

View file

@ -69,6 +69,8 @@ void refresh_stats_ui(void) {
progress_for_net(&all);
else if (s == HW_SUBJ)
progress_for_hw(&all);
else if (s == ON_SUBJ)
progress_for_on(&all);
else if (s == CZ_SUBJ)
progress_for_mluvnice(&all);
else
@ -100,7 +102,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 == CZ_SUBJ) || sub_unit_count[s] > 0;
|| (s == ON_SUBJ) || (s == CZ_SUBJ)
|| sub_unit_count[s] > 0;
if (!count || !bar)
continue;
@ -108,6 +111,8 @@ void refresh_stats_ui(void) {
progress_for_net(&sp);
else if (s == HW_SUBJ)
progress_for_hw(&sp);
else if (s == ON_SUBJ)
progress_for_on(&sp);
else if (s == CZ_SUBJ)
progress_for_mluvnice(&sp);
else

View file

@ -4,8 +4,8 @@
/* Subjects page — icon grid, one tile per subject */
/* ------------------------------------------------------------------ */
/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware) and CZ_SUBJ
* (Czech) are unlocked. */
/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ
* (Czech) and ON_SUBJ (civics) are unlocked. */
const char *sub_keys[NUM_SUBJECTS] = {
"Deutsch",
"Správa počítačových sítí",
@ -153,7 +153,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;
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|| i == ON_SUBJ;
}
static GtkWidget *subject_icon(int i) {
@ -173,6 +174,9 @@ static GtkWidget *subject_icon(int i) {
else if (i == CZ_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_czech_flag, NULL, NULL);
else if (i == ON_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_civics_icon, NULL, NULL);
else
return icon_area_new(draw_lock_icon, 0.3451, 0.3569, 0.4392, 22);
return area;
@ -187,6 +191,8 @@ static const char *subject_target(int i) {
return "hwyears";
if (i == CZ_SUBJ)
return "czechmap";
if (i == ON_SUBJ)
return "onyears";
return NULL;
}

View file

@ -426,6 +426,7 @@ void apply_theme(void) {
}
net_rail_theme_reset();
hw_rail_theme_reset();
on_rail_theme_reset();
czech_rail_theme_reset();
book_rail_theme_reset();
}

View file

@ -20,6 +20,7 @@ char *app_progress_u3;
char *app_settings_file;
char *app_progress_net;
char *app_progress_hw;
char *app_progress_on;
char *app_progress_mluvnice;
char *app_progress_cetba;
@ -158,6 +159,7 @@ static void set_progress_paths(const char *write_root) {
g_free(app_settings_file);
g_free(app_progress_net);
g_free(app_progress_hw);
g_free(app_progress_on);
g_free(app_progress_mluvnice);
g_free(app_progress_cetba);
@ -168,6 +170,7 @@ static void set_progress_paths(const char *write_root) {
app_settings_file = g_build_filename(app_progress_dir, "settings.conf", NULL);
app_progress_net = g_build_filename(app_progress_dir, "net.conf", NULL);
app_progress_hw = g_build_filename(app_progress_dir, "hw.conf", NULL);
app_progress_on = g_build_filename(app_progress_dir, "on.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);
}