Add a SwiftUI iOS app that plays the same maturita content.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-09-18 14:01:01 +02:00
commit 6c7336c7bb
33 changed files with 4498 additions and 10 deletions

View file

@ -0,0 +1,154 @@
import SwiftUI
@main
struct MaturitaApp: App {
@StateObject private var vm = AppModel()
var body: some Scene {
WindowGroup {
MaturitaRoot(vm: vm)
}
}
}
struct MaturitaRoot: View {
@ObservedObject var vm: AppModel
var body: some View {
let p = vm.palette
ZStack {
p.base.ignoresSafeArea()
VStack(spacing: 0) {
HStack {
Text("maturita.c")
.font(.system(size: 15, weight: .medium))
.foregroundStyle(p.text)
Spacer()
IconHit(action: { vm.searchOpen = true }) { SearchIcon(color: p.text) }
IconHit(action: { vm.go(.stats) }) { StatsIcon(color: p.text) }
IconHit(action: { vm.settingsOpen = true }) { SettingsIcon(color: p.text) }
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
if vm.update.canInstall || vm.update.status == "downloading" || vm.update.status == "staged" {
HStack {
Text(bannerText)
.font(.system(size: 13))
.foregroundStyle(p.text)
Spacer()
if vm.update.canInstall {
PillButton(label: vm.tr("update_install"), palette: p) { vm.installUpdate() }
}
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(p.surface1, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
.padding(.horizontal, 12)
}
routeView
.id(vm.tick)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
if vm.searchOpen {
SearchOverlay(vm: vm).ignoresSafeArea(edges: .bottom)
}
if vm.settingsOpen {
SettingsSheet(vm: vm).ignoresSafeArea(edges: .bottom)
}
}
.preferredColorScheme(vm.mode == .dark ? .dark : .light)
.onAppear { vm.checkUpdate(false) }
}
private var bannerText: String {
if let arg = vm.update.messageArg {
return vm.fmt(vm.update.messageKey, arg)
}
return vm.tr(vm.update.messageKey)
}
@ViewBuilder
private var routeView: some View {
switch vm.route {
case .welcome: WelcomeScreen(vm: vm)
case .subjects: SubjectsScreen(vm: vm)
case .stats: StatsScreen(vm: vm)
case .roadmap: RoadmapScreen(vm: vm)
case .unitMap(let id): UnitMapScreen(vm: vm, unitId: id)
case .germanEx(let unit, let ex): GermanExercise(vm: vm, unitId: unit, ex: ex)
case .vocab(let id): VocabExercise(vm: vm, unitId: id)
case .netYears: NetYearsScreen(vm: vm)
case .netMap:
LessonMapScreen(
vm: vm,
title: vm.tr("net_year1"),
sub: vm.tr("net_sub"),
nodes: vm.content.netLessons.map { l in
let id = l.int("id")
return MapNode(
id: "n\(id)",
label: vm.tr(l.str("titleKey")),
locked: false,
done: vm.progress.netDone(id),
current: !vm.progress.netDone(id),
onClick: { vm.go(.netLesson(id)) }
)
}
)
case .netLesson(let id):
if let l = vm.content.netLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), sub: vm.tr(l.str("subKey")), slides: l.arr("slides")) {
vm.go(.netEx(id))
}
}
case .netEx(let id): NetQuizScreen(vm: vm, id: id)
case .hwMap:
LessonMapScreen(
vm: vm,
title: vm.tr("Technické vybavení"),
sub: vm.tr("hw_sub"),
nodes: vm.content.hw.map { l in
let id = l.int("id")
return MapNode(
id: "h\(id)",
label: vm.tr(l.str("titleKey")),
locked: false,
done: vm.progress.hwDone(id),
current: !vm.progress.hwDone(id),
onClick: { vm.go(.hwLesson(id)) }
)
}
)
case .hwLesson(let id):
if let l = vm.content.hwLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), sub: vm.tr(l.str("subKey")), slides: l.arr("slides")) {
vm.go(.hwEx(id))
}
}
case .hwEx(let id): HwQuizScreen(vm: vm, id: id)
case .czechMap: CzechMapScreen(vm: vm)
case .mluvnice:
LessonMapScreen(
vm: vm,
title: vm.tr("Mluvnice"),
sub: "Cvičení z mluvnice – styl maturita / přijímačky z ČJL",
nodes: vm.content.mluvnice.map { m in
let n = m.int("id")
return MapNode(
id: "m\(n)",
label: m.str("name"),
locked: false,
done: vm.progress.mluvDone(n),
current: !vm.progress.mluvDone(n),
onClick: { vm.go(.mluvEx(n)) }
)
}
)
case .mluvEx(let n): MluvExercise(vm: vm, n: n)
case .readingList: BookListScreen(vm: vm)
case .book(let id): BookScreen(vm: vm, id: id)
case .bookQuiz(let id): LitQuizScreen(vm: vm, id: id)
case .bookPlot(let id): PlotScreen(vm: vm, id: id)
}
}
}