- Documented mobile-first responsive design as REQUIRED standard - Added web development tech preferences (Next.js, Tailwind, etc.) - Created memory/project-standards.md for coding guidelines
90 lines
2.6 KiB
Swift
90 lines
2.6 KiB
Swift
import SwiftUI
|
|
import AlarmKit
|
|
import Observation
|
|
|
|
@Observable
|
|
@MainActor
|
|
final class TimerStore {
|
|
var timeRemaining: TimeInterval = 25 * 60 // 25 min Pomodoro
|
|
var isRunning = false
|
|
var session: Session?
|
|
|
|
private var timer: Task<Void, Never>?
|
|
private let service = TimerService()
|
|
}
|
|
|
|
struct TimerView: View {
|
|
@State private var store = TimerStore()
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack(spacing: 40) {
|
|
Text(timeString(from: store.timeRemaining))
|
|
.font(.system(size: 80, weight: .bold, design: .rounded))
|
|
.foregroundStyle(.primary)
|
|
|
|
VStack(spacing: 20) {
|
|
Button(store.isRunning ? "Pause" : "Start") {
|
|
toggleTimer()
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.controlSize(.large)
|
|
|
|
Button("Reset") {
|
|
resetTimer()
|
|
}
|
|
.buttonStyle(.bordered)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.navigationTitle("Pomodoro Timer")
|
|
.sheet(isPresented: .constant(store.isRunning)) {
|
|
// Optional: Session summary sheet with AI
|
|
Text("Timer active!")
|
|
}
|
|
}
|
|
.glassEffect() // Liquid Glass for the nav stack
|
|
}
|
|
|
|
private func toggleTimer() {
|
|
if store.isRunning {
|
|
store.timer?.cancel()
|
|
store.timer = nil
|
|
store.isRunning = false
|
|
// Save session to SwiftData
|
|
store.session = Session(duration: store.timeRemaining, completed: true)
|
|
} else {
|
|
store.isRunning = true
|
|
store.timer = Task {
|
|
while store.timeRemaining > 0 && !Task.isCancelled {
|
|
try? await Task.sleep(for: .seconds(1))
|
|
store.timeRemaining -= 1
|
|
}
|
|
if !Task.isCancelled {
|
|
// Alarm via AlarmKit
|
|
await store.service.scheduleAlarm(for: store.timeRemaining)
|
|
store.isRunning = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func resetTimer() {
|
|
store.timer?.cancel()
|
|
store.timeRemaining = 25 * 60
|
|
store.isRunning = false
|
|
}
|
|
|
|
private func timeString(from timeInterval: TimeInterval) -> String {
|
|
let minutes = Int(timeInterval) / 60
|
|
let seconds = Int(timeInterval) % 60
|
|
return String(format: "%02d:%02d", minutes, seconds)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
TimerView()
|
|
}
|