Files

179 lines
5.6 KiB
Swift

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)
}
}
}