mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Android moves to Material 3 (navigation bar, app bars, list items, bottom sheets, pager) with a real back stack. iOS hides the tab bar in exercises so the bottom action is reachable, aligns content, and uses system colours so themes and dark mode work. Both pick a readable accent tint. Desktop keeps the VLSM table inside the window and gives statistics the same header rhythm as other pages. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
5.7 KiB
Swift
177 lines
5.7 KiB
Swift
import SwiftUI
|
|
|
|
@main
|
|
struct MaturitaApp: App {
|
|
@StateObject private var vm = AppModel()
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
NativeRoot(vm: vm)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct NativeRoot: View {
|
|
@ObservedObject var vm: AppModel
|
|
|
|
var body: some View {
|
|
Group {
|
|
if showsUpdateAccessory {
|
|
tabs.tabViewBottomAccessory { UpdateAccessory(vm: vm) }
|
|
} else {
|
|
tabs
|
|
}
|
|
}
|
|
.tint(vm.palette.tint)
|
|
.preferredColorScheme(vm.mode == .dark ? .dark : .light)
|
|
.onAppear { vm.checkUpdate(false) }
|
|
}
|
|
|
|
private var showsUpdateAccessory: Bool {
|
|
vm.update.canInstall || vm.update.status == "downloading" || vm.update.status == "staged"
|
|
}
|
|
|
|
private var tabs: some View {
|
|
TabView(selection: $vm.tab) {
|
|
Tab(vm.tr("subjects_title"), systemImage: "square.grid.2x2.fill", value: .practice) {
|
|
NavigationStack(path: $vm.practicePath) {
|
|
PracticeHome(vm: vm)
|
|
.navigationDestination(for: Route.self) { route in
|
|
RouteDestination(vm: vm, route: route)
|
|
}
|
|
}
|
|
}
|
|
Tab(vm.tr("stats"), systemImage: "chart.bar.fill", value: .progress) {
|
|
NavigationStack {
|
|
StatsScreen(vm: vm)
|
|
}
|
|
}
|
|
Tab(vm.tr("settings"), systemImage: "gearshape.fill", value: .settings) {
|
|
NavigationStack {
|
|
SettingsScreen(vm: vm)
|
|
}
|
|
}
|
|
Tab(vm.tr("search_placeholder"), systemImage: "magnifyingglass", value: .search, role: .search) {
|
|
NavigationStack {
|
|
SearchScreen(vm: vm)
|
|
}
|
|
}
|
|
}
|
|
.tabViewStyle(.sidebarAdaptable)
|
|
.tabBarMinimizeBehavior(.onScrollDown)
|
|
}
|
|
}
|
|
|
|
private struct UpdateAccessory: View {
|
|
@ObservedObject var vm: AppModel
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Text(bannerText)
|
|
.font(.subheadline)
|
|
Spacer()
|
|
if vm.update.canInstall {
|
|
Button(vm.tr("update_install")) { vm.installUpdate() }
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
}
|
|
.padding(.horizontal, 4)
|
|
}
|
|
|
|
private var bannerText: String {
|
|
if let arg = vm.update.messageArg {
|
|
return vm.fmt(vm.update.messageKey, arg)
|
|
}
|
|
return vm.tr(vm.update.messageKey)
|
|
}
|
|
}
|
|
|
|
struct RouteDestination: View {
|
|
@ObservedObject var vm: AppModel
|
|
let route: Route
|
|
|
|
var body: some View {
|
|
switch route {
|
|
case .welcome, .subjects:
|
|
PracticeHome(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:
|
|
LessonListScreen(
|
|
title: vm.tr("net_year1"),
|
|
subtitle: vm.tr("net_sub"),
|
|
rows: vm.content.netLessons.map { l in
|
|
let id = l.int("id")
|
|
return LessonRow(
|
|
id: "n\(id)",
|
|
title: vm.tr(l.str("titleKey")),
|
|
done: vm.progress.netDone(id),
|
|
destination: .netLesson(id)
|
|
)
|
|
}
|
|
)
|
|
case .netLesson(let id):
|
|
if let l = vm.content.netLesson(id) {
|
|
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .netEx(id))
|
|
}
|
|
case .netEx(let id):
|
|
NetQuizScreen(vm: vm, id: id)
|
|
case .hwMap:
|
|
LessonListScreen(
|
|
title: vm.tr("Technické vybavení"),
|
|
subtitle: vm.tr("hw_sub"),
|
|
rows: vm.content.hw.map { l in
|
|
let id = l.int("id")
|
|
return LessonRow(
|
|
id: "h\(id)",
|
|
title: vm.tr(l.str("titleKey")),
|
|
done: vm.progress.hwDone(id),
|
|
destination: .hwLesson(id)
|
|
)
|
|
}
|
|
)
|
|
case .hwLesson(let id):
|
|
if let l = vm.content.hwLesson(id) {
|
|
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .hwEx(id))
|
|
}
|
|
case .hwEx(let id):
|
|
HwQuizScreen(vm: vm, id: id)
|
|
case .czechMap:
|
|
CzechMapScreen(vm: vm)
|
|
case .mluvnice:
|
|
LessonListScreen(
|
|
title: vm.tr("Mluvnice"),
|
|
subtitle: vm.tr("mluv_sub"),
|
|
rows: vm.content.mluvnice.map { m in
|
|
let n = m.int("id")
|
|
return LessonRow(
|
|
id: "m\(n)",
|
|
title: m.str("name"),
|
|
done: vm.progress.mluvDone(n),
|
|
destination: .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)
|
|
}
|
|
}
|
|
}
|