added first version file
This commit is contained in:
@@ -1 +1,4 @@
|
|||||||
README File
|
# Watch Untis
|
||||||
|
An Untis (light) app for the Apple Watch.
|
||||||
|
|
||||||
|
Created by Theis
|
||||||
|
|||||||
@@ -1,24 +1,198 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// watch-untis Watch App
|
|
||||||
//
|
|
||||||
// Created by Theis on 23.02.26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
struct ContentView: View {
|
// MARK: - Models
|
||||||
var body: some View {
|
|
||||||
VStack {
|
struct LessonBlock: Codable, Identifiable {
|
||||||
Image(systemName: "globe")
|
var id: String { "\(day)-\(period)" }
|
||||||
.imageScale(.large)
|
let day: String
|
||||||
.foregroundStyle(.tint)
|
let period: String
|
||||||
Text("Hello, world!")
|
let timeStart: String
|
||||||
}
|
let timeEnd: String
|
||||||
.padding()
|
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 {
|
struct TimetableResponse: Codable {
|
||||||
ContentView()
|
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
49
watch-untis Watch App/mock.json
Normal file
49
watch-untis Watch App/mock.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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, ); }; };
|
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 */; };
|
C694B1422F4CF533002D638B /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = C694B1412F4CF52C002D638B /* LICENSE */; };
|
||||||
C694B1442F4CF5D2002D638B /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = C694B1432F4CF5B5002D638B /* .gitignore */; };
|
C694B1442F4CF5D2002D638B /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = C694B1432F4CF5B5002D638B /* .gitignore */; };
|
||||||
|
C694B1492F4CF8C1002D638B /* mock.json in Sources */ = {isa = PBXBuildFile; fileRef = C694B1482F4CF8C0002D638B /* mock.json */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy 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; };
|
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 = "<group>"; };
|
C694B1412F4CF52C002D638B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||||
C694B1432F4CF5B5002D638B /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
C694B1432F4CF5B5002D638B /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
||||||
|
C694B1482F4CF8C0002D638B /* mock.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = mock.json; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
@@ -111,6 +113,7 @@
|
|||||||
C694B1232F4CF391002D638B /* watch-untis Watch AppTests */,
|
C694B1232F4CF391002D638B /* watch-untis Watch AppTests */,
|
||||||
C694B12D2F4CF391002D638B /* watch-untis Watch AppUITests */,
|
C694B12D2F4CF391002D638B /* watch-untis Watch AppUITests */,
|
||||||
C694B10C2F4CF390002D638B /* Products */,
|
C694B10C2F4CF390002D638B /* Products */,
|
||||||
|
C694B1482F4CF8C0002D638B /* mock.json */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -301,6 +304,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
C694B1492F4CF8C1002D638B /* mock.json in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user