- 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
167 lines
5.4 KiB
Swift
167 lines
5.4 KiB
Swift
// Sheet for creating a new timer from the macOS menu bar popover
|
|
|
|
import SwiftUI
|
|
|
|
struct CreateTimerSheet: View {
|
|
@EnvironmentObject var store: MacTimerStore
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@State private var label = ""
|
|
@State private var timerType: CMTimerType = .countdown
|
|
@State private var hours: Int = 0
|
|
@State private var minutes: Int = 25
|
|
@State private var alarmTime = Date().addingTimeInterval(3600)
|
|
@State private var pomodoroRounds: Int = 4
|
|
@State private var workMinutes: Int = 25
|
|
@State private var breakMinutes: Int = 5
|
|
@State private var urgency: UrgencyLevel = .standard
|
|
@State private var cascadePreset: CascadePreset = .standard
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
// Header
|
|
HStack {
|
|
Text("New Timer")
|
|
.font(CMFonts.display(size: 16))
|
|
.foregroundStyle(CMColors.text)
|
|
Spacer()
|
|
Button {
|
|
dismiss()
|
|
} label: {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.font(.title3)
|
|
.foregroundStyle(CMColors.textMuted)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
|
|
Divider().background(CMColors.border)
|
|
|
|
// Form
|
|
Form {
|
|
TextField("Timer Label", text: $label)
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
Picker("Type", selection: $timerType) {
|
|
Text("Countdown").tag(CMTimerType.countdown)
|
|
Text("Alarm").tag(CMTimerType.alarm)
|
|
Text("Pomodoro").tag(CMTimerType.pomodoro)
|
|
}
|
|
|
|
switch timerType {
|
|
case .countdown:
|
|
countdownFields
|
|
case .alarm:
|
|
alarmFields
|
|
case .pomodoro:
|
|
pomodoroFields
|
|
default:
|
|
EmptyView()
|
|
}
|
|
|
|
Picker("Urgency", selection: $urgency) {
|
|
ForEach(UrgencyLevel.allCases) { level in
|
|
HStack {
|
|
Circle()
|
|
.fill(CMColors.urgencyColor(level))
|
|
.frame(width: 8, height: 8)
|
|
Text(getUrgencyConfig(level).label)
|
|
}
|
|
.tag(level)
|
|
}
|
|
}
|
|
|
|
if timerType != .pomodoro {
|
|
Picker("Cascade", selection: $cascadePreset) {
|
|
ForEach(CascadePreset.allCases.filter { $0 != .custom }) { preset in
|
|
Text(preset.label).tag(preset)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.formStyle(.grouped)
|
|
.scrollContentBackground(.hidden)
|
|
.background(CMColors.bg)
|
|
|
|
Divider().background(CMColors.border)
|
|
|
|
// Actions
|
|
HStack {
|
|
Button("Cancel") {
|
|
dismiss()
|
|
}
|
|
.keyboardShortcut(.cancelAction)
|
|
|
|
Spacer()
|
|
|
|
Button("Create") {
|
|
createTimer()
|
|
dismiss()
|
|
}
|
|
.keyboardShortcut(.defaultAction)
|
|
.disabled(label.isEmpty && timerType != .pomodoro)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 10)
|
|
}
|
|
.frame(width: 360, height: 420)
|
|
.background(CMColors.bg)
|
|
}
|
|
|
|
// MARK: - Type-Specific Fields
|
|
|
|
private var countdownFields: some View {
|
|
HStack {
|
|
Stepper("Hours: \(hours)", value: $hours, in: 0...23)
|
|
Stepper("Minutes: \(minutes)", value: $minutes, in: 0...59)
|
|
}
|
|
}
|
|
|
|
private var alarmFields: some View {
|
|
DatePicker("Target Time", selection: $alarmTime, displayedComponents: [.date, .hourAndMinute])
|
|
}
|
|
|
|
private var pomodoroFields: some View {
|
|
Group {
|
|
Stepper("Rounds: \(pomodoroRounds)", value: $pomodoroRounds, in: 1...12)
|
|
Stepper("Work: \(workMinutes)m", value: $workMinutes, in: 5...60, step: 5)
|
|
Stepper("Break: \(breakMinutes)m", value: $breakMinutes, in: 1...30)
|
|
}
|
|
}
|
|
|
|
// MARK: - Create
|
|
|
|
private func createTimer() {
|
|
let timerLabel = label.isEmpty ? "Focus Session" : label
|
|
|
|
switch timerType {
|
|
case .countdown:
|
|
let totalSeconds = TimeInterval((hours * 3600) + (minutes * 60))
|
|
guard totalSeconds > 0 else { return }
|
|
store.addCountdown(label: timerLabel, durationSeconds: totalSeconds)
|
|
|
|
case .alarm:
|
|
store.addAlarm(label: timerLabel, targetTime: alarmTime, urgency: urgency)
|
|
|
|
case .pomodoro:
|
|
let config = PomodoroConfig(
|
|
workMinutes: workMinutes,
|
|
breakMinutes: breakMinutes,
|
|
longBreakMinutes: breakMinutes * 3,
|
|
rounds: pomodoroRounds
|
|
)
|
|
let timer = createPomodoro(CreatePomodoroParams(
|
|
label: timerLabel,
|
|
config: config,
|
|
urgency: urgency
|
|
))
|
|
store.timers.append(timer)
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|