import SwiftUI struct ContentView: View { @EnvironmentObject private var store: TimerStore var body: some View { VStack(spacing: 16) { header ScrollView { LazyVStack(spacing: 12) { ForEach($store.timers) { $timer in TimerRow(timer: $timer) .environmentObject(store) } } .padding(.horizontal) .padding(.bottom) } } .padding(.top) } private var header: some View { HStack { VStack(alignment: .leading, spacing: 4) { Text("MultiTimer") .font(.largeTitle) .bold() Text("Bis zu 15 parallele Timer mit Alarm und Wiederholung") .foregroundStyle(.secondary) } Spacer() Button { store.addTimer() } label: { Label("Timer hinzufügen", systemImage: "plus") } .disabled(store.timers.count >= 15) } .padding(.horizontal) } } struct TimerRow: View { @EnvironmentObject private var store: TimerStore @Binding var timer: TimerItem @State private var durationText: String = "" @State private var durationInputIsInvalid = false var body: some View { VStack(spacing: 12) { HStack(spacing: 12) { TextField("Name", text: $timer.name) .textFieldStyle(.roundedBorder) .font(.headline) Spacer() Text(timer.formattedRemaining) .font(.system(size: 34, weight: .semibold, design: .monospaced)) .frame(width: 150, alignment: .trailing) } ProgressView(value: timer.progress) .progressViewStyle(.linear) HStack(spacing: 12) { HStack(spacing: 6) { Text("Dauer") TextField("hh:mm", text: $durationText) .textFieldStyle(.roundedBorder) .font(.system(.body, design: .monospaced)) .frame(width: 70) .disabled(timer.isRunning) .onSubmit { commitDuration() } .onChange(of: durationText) { _ in durationInputIsInvalid = false } Text("hh:mm") .foregroundStyle(.secondary) } if durationInputIsInvalid { Text("Ungültig") .foregroundStyle(.red) .font(.caption) } Stepper( value: Binding( get: { timer.durationSeconds / 60 }, set: { newMinutes in let seconds = max(1, newMinutes) * 60 store.durationChanged(for: timer.id, newDurationSeconds: seconds) durationText = TimerItem.formatHHMM(seconds: seconds) } ), in: 1...(24 * 60) ) { Text("") } .labelsHidden() .disabled(timer.isRunning) Toggle("Auto-Wiederholung", isOn: $timer.autoRepeat) .toggleStyle(.checkbox) Spacer() if timer.isRunning { Button { commitDuration() store.pause(timer) } label: { Label("Pause", systemImage: "pause.fill") } } else { Button { commitDuration() store.start(timer) } label: { Label("Start", systemImage: "play.fill") } } Button { store.reset(timer) durationText = TimerItem.formatHHMM(seconds: timer.durationSeconds) } label: { Label("Reset", systemImage: "arrow.counterclockwise") } Button(role: .destructive) { store.removeTimer(timer) } label: { Label("Löschen", systemImage: "trash") } Text("Fertig: \(timer.completedCount)x") .foregroundStyle(.secondary) .frame(width: 80, alignment: .trailing) } } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 14)) .onAppear { durationText = TimerItem.formatHHMM(seconds: timer.durationSeconds) } .onChange(of: timer.durationSeconds) { newValue in if !timer.isRunning { durationText = TimerItem.formatHHMM(seconds: newValue) } } } private func commitDuration() { guard !timer.isRunning else { return } if let seconds = TimerItem.parseHHMM(durationText) { durationInputIsInvalid = false store.durationChanged(for: timer.id, newDurationSeconds: seconds) durationText = TimerItem.formatHHMM(seconds: seconds) } else { durationInputIsInvalid = true durationText = TimerItem.formatHHMM(seconds: timer.durationSeconds) } } }