- watchOS: add WatchSessionManager (WCSession bridge), WatchNotificationHandler (snooze/dismiss actions), recommendations() for AppIntentTimelineProvider - watchOS: WatchTimerStore tick loop with cascade haptics, App Group sync - macOS: CreateTimerSheet (countdown/alarm/pomodoro), launch-at-login toggle - macOS: MenuBarState showCreateSheet, MacSettingsView data tab - Xcode project updated with new file references
185 lines
6.7 KiB
Swift
185 lines
6.7 KiB
Swift
// ── Watch Timer Detail View ───────────────────────────────────
|
|
// Full detail view for a timer on Apple Watch
|
|
|
|
import SwiftUI
|
|
import WatchKit
|
|
|
|
struct WatchTimerDetailView: View {
|
|
let timer: TimerSnapshot
|
|
@EnvironmentObject var store: WatchTimerStore
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 12) {
|
|
// Urgency badge
|
|
Text(timer.urgency.rawValue.uppercased())
|
|
.font(.system(size: 10, weight: .bold))
|
|
.foregroundStyle(urgencyColor(timer.urgency))
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 3)
|
|
.background(urgencyColor(timer.urgency).opacity(0.2))
|
|
.clipShape(Capsule())
|
|
|
|
// Timer label
|
|
Text(timer.label)
|
|
.font(.system(size: 16, weight: .semibold))
|
|
.multilineTextAlignment(.center)
|
|
|
|
// Large countdown
|
|
Text(timer.targetTime, style: .timer)
|
|
.font(.system(size: 32, weight: .bold, design: .monospaced))
|
|
.foregroundStyle(urgencyColor(timer.urgency))
|
|
.contentTransition(.numericText())
|
|
|
|
// Target time
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "bell.fill")
|
|
.font(.system(size: 10))
|
|
Text(formatTime(timer.targetTime))
|
|
.font(.system(size: 13, weight: .medium))
|
|
}
|
|
.foregroundStyle(.secondary)
|
|
|
|
// Pomodoro info
|
|
if let round = timer.pomodoroCurrentRound,
|
|
let total = timer.pomodoroTotalRounds {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "target")
|
|
.font(.system(size: 10))
|
|
Text(timer.pomodoroIsBreak == true ? "Break" : "Round \(round)/\(total)")
|
|
.font(.system(size: 12, weight: .medium))
|
|
}
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
// Cascade progress
|
|
if timer.totalWarnings > 0 {
|
|
VStack(spacing: 4) {
|
|
HStack {
|
|
Text("Warnings")
|
|
.font(.system(size: 11))
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
Text("\(timer.firedWarnings)/\(timer.totalWarnings)")
|
|
.font(.system(size: 11, weight: .medium, design: .monospaced))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
ProgressView(value: Double(timer.firedWarnings), total: Double(timer.totalWarnings))
|
|
.tint(urgencyColor(timer.urgency))
|
|
}
|
|
.padding(.top, 4)
|
|
}
|
|
|
|
// Snooze info
|
|
if timer.snoozeCount > 0 {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "zzz")
|
|
.font(.system(size: 10))
|
|
Text("Snoozed \(timer.snoozeCount)x")
|
|
.font(.system(size: 11))
|
|
}
|
|
.foregroundStyle(.orange)
|
|
}
|
|
|
|
// Action buttons
|
|
Divider().padding(.vertical, 4)
|
|
|
|
actionButtons
|
|
}
|
|
.padding()
|
|
}
|
|
.navigationTitle(timer.type.rawValue.capitalized)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
// MARK: - Action Buttons
|
|
|
|
@ViewBuilder
|
|
private var actionButtons: some View {
|
|
switch timer.state {
|
|
case .firing:
|
|
HStack(spacing: 8) {
|
|
Button {
|
|
WKInterfaceDevice.current().play(.click)
|
|
store.sendCommand(.snooze(id: timer.id, minutes: 5))
|
|
} label: {
|
|
Label("Snooze 5m", systemImage: "moon.zzz")
|
|
.font(.system(size: 13, weight: .medium))
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(.orange)
|
|
|
|
Button {
|
|
WKInterfaceDevice.current().play(.success)
|
|
store.sendCommand(.dismiss(id: timer.id))
|
|
} label: {
|
|
Label("Dismiss", systemImage: "xmark.circle")
|
|
.font(.system(size: 13, weight: .medium))
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(.red)
|
|
}
|
|
|
|
case .active, .warning:
|
|
Button {
|
|
WKInterfaceDevice.current().play(.click)
|
|
store.sendCommand(.pause(id: timer.id))
|
|
} label: {
|
|
Label("Pause", systemImage: "pause.fill")
|
|
.font(.system(size: 14, weight: .medium))
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(.blue)
|
|
|
|
case .paused:
|
|
Button {
|
|
WKInterfaceDevice.current().play(.start)
|
|
store.sendCommand(.resume(id: timer.id))
|
|
} label: {
|
|
Label("Resume", systemImage: "play.fill")
|
|
.font(.system(size: 14, weight: .medium))
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(.green)
|
|
|
|
case .snoozed:
|
|
HStack(spacing: 8) {
|
|
Button {
|
|
WKInterfaceDevice.current().play(.click)
|
|
store.sendCommand(.snooze(id: timer.id, minutes: 5))
|
|
} label: {
|
|
Label("Snooze", systemImage: "moon.zzz")
|
|
.font(.system(size: 13, weight: .medium))
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(.orange)
|
|
|
|
Button {
|
|
WKInterfaceDevice.current().play(.success)
|
|
store.sendCommand(.dismiss(id: timer.id))
|
|
} label: {
|
|
Label("Dismiss", systemImage: "xmark.circle")
|
|
.font(.system(size: 13, weight: .medium))
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(.red)
|
|
}
|
|
|
|
default:
|
|
EmptyView()
|
|
}
|
|
}
|
|
|
|
private func urgencyColor(_ urgency: UrgencyLevel) -> Color {
|
|
switch urgency {
|
|
case .critical: return .red
|
|
case .important: return .orange
|
|
case .standard: return .yellow
|
|
case .gentle: return .green
|
|
case .passive: return .blue
|
|
}
|
|
}
|
|
}
|