From 0f47e034bda03bb55e065c3759a4a84c21cd39ff Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 23 Feb 2026 22:13:18 +0100 Subject: [PATCH] added first version file --- README.md | 5 +- watch-untis Watch App/ContentView.swift | 210 +++++++++++++++++++-- watch-untis Watch App/mock.json | 49 +++++ watch-untis Watch App/watch_untisApp.swift | 17 -- watch-untis.xcodeproj/project.pbxproj | 4 + 5 files changed, 249 insertions(+), 36 deletions(-) create mode 100644 watch-untis Watch App/mock.json delete mode 100644 watch-untis Watch App/watch_untisApp.swift diff --git a/README.md b/README.md index 785eeec..aba4194 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -README File +# Watch Untis +An Untis (light) app for the Apple Watch. + +Created by Theis diff --git a/watch-untis Watch App/ContentView.swift b/watch-untis Watch App/ContentView.swift index 62be0d7..9a574b2 100644 --- a/watch-untis Watch App/ContentView.swift +++ b/watch-untis Watch App/ContentView.swift @@ -1,24 +1,198 @@ -// -// ContentView.swift -// watch-untis Watch App -// -// Created by Theis on 23.02.26. -// - import SwiftUI +import Combine -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() +// MARK: - Models + +struct LessonBlock: Codable, Identifiable { + var id: String { "\(day)-\(period)" } + let day: String + let period: String + let timeStart: String + let timeEnd: String + let room: String + let subject: String + let group: String + let teacher: String + let color: String // "pink", "yellow", "green" + + enum CodingKeys: String, CodingKey { + case day, period + case timeStart = "time_start" + case timeEnd = "time_end" + case room, subject, group, teacher, color } } -#Preview { - ContentView() +struct TimetableResponse: Codable { + let day: String + let lessons: [LessonBlock] } + +// MARK: - ViewModel + +class TimetableViewModel: ObservableObject { + @Published var dayName: String = "" + @Published var lessons: [LessonBlock] = [] + @Published var isLoading = true + @Published var errorMessage: String? + + func fetchTimetable() { + isLoading = true + + guard let url = Bundle.main.url(forResource: "mock", withExtension: "json"), + let data = try? Data(contentsOf: url) else { + errorMessage = "mock.json nicht gefunden" + isLoading = false + return + } + + do { + let decoded = try JSONDecoder().decode(TimetableResponse.self, from: data) + self.dayName = decoded.day + self.lessons = decoded.lessons + } catch { + errorMessage = "JSON Fehler: \(error.localizedDescription)" + } + + isLoading = false + } + + + func loadMockData() { + dayName = "Montag" + lessons = [ + LessonBlock(day: "Montag", period: "1.", timeStart: "08:00", timeEnd: "08:55", + room: "255", subject: "D", group: "G2", teacher: "VanC", color: "pink"), + LessonBlock(day: "Montag", period: "2. & 3.", timeStart: "08:55", timeEnd: "10:25", + room: "255", subject: "M", group: "G2", teacher: "ScLa", color: "yellow"), + LessonBlock(day: "Montag", period: "4.", timeStart: "10:55", timeEnd: "11:40", + room: "254", subject: "E", group: "G4", teacher: "RadF", color: "green"), + LessonBlock(day: "Montag", period: "5.", timeStart: "11:40", timeEnd: "12:25", + room: "255", subject: "D", group: "G2", teacher: "VanC", color: "pink"), + ] + isLoading = false + } +} + +// MARK: - Lesson Block View + +struct LessonBlockView: View { + let lesson: LessonBlock + + var blockColor: Color { + switch lesson.color { + case "pink": return Color(red: 1.0, green: 0.85, blue: 0.88) + case "yellow": return Color(red: 1.0, green: 1.0, blue: 0.82) + case "green": return Color(red: 0.78, green: 0.95, blue: 0.84) + default: return Color.gray.opacity(0.3) + } + } + + var body: some View { + VStack(spacing: 0) { + // Top row: Room | Subject Group | Teacher + HStack { + Text(lesson.room) + .font(.system(size: 14, weight: .bold, design: .monospaced)) + Spacer() + Text("\(lesson.subject) \(lesson.group)") + .font(.system(size: 14, weight: .bold, design: .monospaced)) + Spacer() + Text(lesson.teacher) + .font(.system(size: 14, weight: .bold, design: .monospaced)) + } + .padding(.horizontal, 8) + .padding(.top, 6) + + // Bottom row: Period | Time + HStack { + Text(lesson.period) + .font(.system(size: 13, weight: .semibold, design: .monospaced)) + Spacer() + Text("\(lesson.timeStart)-\(lesson.timeEnd)") + .font(.system(size: 13, weight: .semibold, design: .monospaced)) + } + .padding(.horizontal, 8) + .padding(.bottom, 6) + } + .background(blockColor) + .cornerRadius(4) + .overlay( + RoundedRectangle(cornerRadius: 4) + .stroke(Color.black.opacity(0.3), lineWidth: 1) + ) + } +} + +// MARK: - Main Content View + +struct ContentView: View { + @StateObject private var viewModel = TimetableViewModel() + + var body: some View { + Group { + if viewModel.isLoading { + ProgressView("Laden...") + } else if let error = viewModel.errorMessage { + VStack { + Text(error) + .font(.caption) + .foregroundColor(.red) + Button("Erneut versuchen") { + viewModel.fetchTimetable() + } + .font(.caption2) + } + } else { + ScrollView { + VStack(spacing: 4) { + Text(viewModel.dayName) + .font(.system(size: 16, weight: .bold)) + .foregroundColor(.white) + .padding(.bottom, 4) + + ForEach(viewModel.lessons) { lesson in + LessonBlockView(lesson: lesson) + } + } + .padding(.horizontal, 2) + } + } + } + .onAppear { + viewModel.fetchTimetable() + } + } +} + +// MARK: - App Entry Point + +@main +struct StundenplanApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} + +/* + Expected JSON format from your backend: + + { + "day": "Montag", + "lessons": [ + { + "period": "1.", + "time_start": "08:00", + "time_end": "08:55", + "room": "255", + "subject": "D", + "group": "G2", + "teacher": "VanC", + "color": "pink", + "day": "Montag" + } + ] + } +*/ diff --git a/watch-untis Watch App/mock.json b/watch-untis Watch App/mock.json new file mode 100644 index 0000000..f437534 --- /dev/null +++ b/watch-untis Watch App/mock.json @@ -0,0 +1,49 @@ +{ + "day": "Montag", + "lessons": [ + { + "period": "1.", + "time_start": "08:00", + "time_end": "08:55", + "room": "255", + "subject": "D", + "group": "G2", + "teacher": "VanC", + "color": "pink", + "day": "Montag" + }, + { + "period": "2. & 3.", + "time_start": "08:55", + "time_end": "10:25", + "room": "255", + "subject": "M", + "group": "G2", + "teacher": "ScLa", + "color": "yellow", + "day": "Montag" + }, + { + "period": "4.", + "time_start": "10:55", + "time_end": "11:40", + "room": "254", + "subject": "E", + "group": "G4", + "teacher": "RadF", + "color": "green", + "day": "Montag" + }, + { + "period": "5.", + "time_start": "11:40", + "time_end": "12:25", + "room": "255", + "subject": "D", + "group": "G2", + "teacher": "VanC", + "color": "pink", + "day": "Montag" + } + ] +} diff --git a/watch-untis Watch App/watch_untisApp.swift b/watch-untis Watch App/watch_untisApp.swift deleted file mode 100644 index 20a539f..0000000 --- a/watch-untis Watch App/watch_untisApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// watch_untisApp.swift -// watch-untis Watch App -// -// Created by Theis on 23.02.26. -// - -import SwiftUI - -@main -struct watch_untis_Watch_AppApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/watch-untis.xcodeproj/project.pbxproj b/watch-untis.xcodeproj/project.pbxproj index 69064c1..f3add39 100644 --- a/watch-untis.xcodeproj/project.pbxproj +++ b/watch-untis.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ C694B1122F4CF390002D638B /* watch-untis Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = C694B1112F4CF390002D638B /* watch-untis Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; C694B1422F4CF533002D638B /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = C694B1412F4CF52C002D638B /* LICENSE */; }; C694B1442F4CF5D2002D638B /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = C694B1432F4CF5B5002D638B /* .gitignore */; }; + C694B1492F4CF8C1002D638B /* mock.json in Sources */ = {isa = PBXBuildFile; fileRef = C694B1482F4CF8C0002D638B /* mock.json */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -57,6 +58,7 @@ C694B12A2F4CF391002D638B /* watch-untis Watch AppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "watch-untis Watch AppUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C694B1412F4CF52C002D638B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; C694B1432F4CF5B5002D638B /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + C694B1482F4CF8C0002D638B /* mock.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = mock.json; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -111,6 +113,7 @@ C694B1232F4CF391002D638B /* watch-untis Watch AppTests */, C694B12D2F4CF391002D638B /* watch-untis Watch AppUITests */, C694B10C2F4CF390002D638B /* Products */, + C694B1482F4CF8C0002D638B /* mock.json */, ); sourceTree = ""; }; @@ -301,6 +304,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C694B1492F4CF8C1002D638B /* mock.json in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };