learning_ai_clock/ios/ChronoMind/Views/Reschedule/RescheduleSheet.swift

231 lines
8.0 KiB
Swift

// Reschedule Sheet
// NL-style reschedule options: "I slept in", "Push everything", "Skip next"
import SwiftUI
struct RescheduleSheet: View {
@EnvironmentObject var store: TimerStore
@Environment(\.dismiss) private var dismiss
@State private var customMinutes: Double = 30
var body: some View {
NavigationStack {
ZStack {
CMColors.bg.ignoresSafeArea()
ScrollView {
VStack(spacing: CMSpacing.xl) {
// Smart suggestions (if any)
if !store.rescheduleSuggestions.isEmpty {
smartSuggestionsSection
}
// Quick actions
quickActionsSection
// Custom shift
customShiftSection
// Skip next
skipSection
}
.padding(.horizontal, CMSpacing.lg)
.padding(.bottom, CMSpacing.xxl)
}
}
.navigationTitle("Reschedule")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(CMColors.surface, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
.foregroundStyle(CMColors.textSecondary)
}
}
}
}
// MARK: - Smart Suggestions
private var smartSuggestionsSection: some View {
VStack(alignment: .leading, spacing: CMSpacing.md) {
HStack(spacing: CMSpacing.sm) {
Image(systemName: "sparkles")
.foregroundStyle(CMColors.accent)
Text("SUGGESTED")
.font(CMFonts.body(size: 11, weight: .bold))
.foregroundStyle(CMColors.textMuted)
.tracking(1.5)
}
ForEach(store.rescheduleSuggestions) { suggestion in
RescheduleOptionButton(
title: suggestion.title,
subtitle: suggestion.subtitle,
icon: suggestion.icon,
color: CMColors.accent
) {
applyAndDismiss(suggestion.action)
}
}
}
}
// MARK: - Quick Actions
private var quickActionsSection: some View {
VStack(alignment: .leading, spacing: CMSpacing.md) {
Text("QUICK ACTIONS")
.font(CMFonts.body(size: 11, weight: .bold))
.foregroundStyle(CMColors.textMuted)
.tracking(1.5)
ForEach(RescheduleSuggestion.quickActions) { action in
RescheduleOptionButton(
title: action.title,
subtitle: action.subtitle,
icon: action.icon,
color: CMColors.text
) {
applyAndDismiss(action.action)
}
}
}
}
// MARK: - Custom Shift
private var customShiftSection: some View {
VStack(alignment: .leading, spacing: CMSpacing.md) {
Text("CUSTOM")
.font(CMFonts.body(size: 11, weight: .bold))
.foregroundStyle(CMColors.textMuted)
.tracking(1.5)
VStack(spacing: CMSpacing.md) {
HStack {
Text("Shift by")
.font(CMFonts.body(size: 14))
.foregroundStyle(CMColors.textSecondary)
Spacer()
Text("\(Int(customMinutes)) min")
.font(CMFonts.mono(size: 18, weight: .bold))
.foregroundStyle(CMColors.accent)
}
Slider(value: $customMinutes, in: 5...120, step: 5)
.tint(CMColors.accent)
HStack(spacing: CMSpacing.md) {
Button {
applyAndDismiss(.pushAll(interval: TimeInterval(customMinutes * 60)))
} label: {
HStack {
Image(systemName: "arrow.right")
Text("Push Later")
}
.font(CMFonts.body(size: 14, weight: .semibold))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, CMSpacing.md)
.background(CMColors.accent)
.clipShape(RoundedRectangle(cornerRadius: CMRadius.sm))
}
Button {
applyAndDismiss(.pushAll(interval: TimeInterval(-customMinutes * 60)))
} label: {
HStack {
Image(systemName: "arrow.left")
Text("Pull Earlier")
}
.font(CMFonts.body(size: 14, weight: .semibold))
.foregroundStyle(CMColors.text)
.frame(maxWidth: .infinity)
.padding(.vertical, CMSpacing.md)
.background(CMColors.surface)
.clipShape(RoundedRectangle(cornerRadius: CMRadius.sm))
.overlay(
RoundedRectangle(cornerRadius: CMRadius.sm)
.stroke(CMColors.border, lineWidth: 1)
)
}
}
}
.padding(CMSpacing.lg)
.background(CMColors.surface)
.clipShape(RoundedRectangle(cornerRadius: CMRadius.md))
}
}
// MARK: - Skip
private var skipSection: some View {
VStack(alignment: .leading, spacing: CMSpacing.md) {
if let next = store.nextFiringTimer {
RescheduleOptionButton(
title: "Skip \"\(next.label)\"",
subtitle: "Dismiss your next timer and adjust remaining",
icon: "forward.fill",
color: CMColors.important
) {
applyAndDismiss(.skip(timerId: next.id))
}
}
}
}
// MARK: - Helpers
private func applyAndDismiss(_ action: RescheduleAction) {
HapticEngine.tap()
store.applyReschedule(action)
dismiss()
}
}
// MARK: - Option Button
struct RescheduleOptionButton: View {
let title: String
let subtitle: String
let icon: String
let color: Color
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: CMSpacing.md) {
Image(systemName: icon)
.font(.title3)
.foregroundStyle(color)
.frame(width: 32, height: 32)
VStack(alignment: .leading, spacing: CMSpacing.xxs) {
Text(title)
.font(CMFonts.body(size: 15, weight: .semibold))
.foregroundStyle(CMColors.text)
Text(subtitle)
.font(CMFonts.body(size: 12))
.foregroundStyle(CMColors.textMuted)
}
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(CMColors.textMuted)
}
.padding(CMSpacing.md)
.background(CMColors.surface)
.clipShape(RoundedRectangle(cornerRadius: CMRadius.sm))
.overlay(
RoundedRectangle(cornerRadius: CMRadius.sm)
.stroke(CMColors.border, lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}