146 lines
5.7 KiB
Swift
146 lines
5.7 KiB
Swift
// ── Reschedule Undo Banner ────────────────────────────────────
|
|
// Toast-style banner that appears after reschedule with undo button
|
|
|
|
import SwiftUI
|
|
|
|
struct RescheduleUndoBanner: View {
|
|
@EnvironmentObject var store: TimerStore
|
|
@State private var isVisible = false
|
|
|
|
var body: some View {
|
|
if let result = store.lastReschedule {
|
|
HStack(spacing: CMSpacing.md) {
|
|
Image(systemName: "arrow.uturn.backward.circle.fill")
|
|
.font(.title3)
|
|
.foregroundStyle(CMColors.accent)
|
|
|
|
VStack(alignment: .leading, spacing: CMSpacing.xxs) {
|
|
Text(result.description)
|
|
.font(CMFonts.body(size: 13, weight: .medium))
|
|
.foregroundStyle(CMColors.text)
|
|
Text("\(result.affectedTimerIds.count) timer\(result.affectedTimerIds.count == 1 ? "" : "s") affected")
|
|
.font(CMFonts.body(size: 11))
|
|
.foregroundStyle(CMColors.textMuted)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Button {
|
|
HapticEngine.tap()
|
|
withAnimation(.easeInOut(duration: 0.3)) {
|
|
store.undoReschedule()
|
|
}
|
|
} label: {
|
|
Text("Undo")
|
|
.font(CMFonts.body(size: 14, weight: .bold))
|
|
.foregroundStyle(CMColors.accent)
|
|
.padding(.horizontal, CMSpacing.md)
|
|
.padding(.vertical, CMSpacing.sm)
|
|
.background(CMColors.accent.opacity(0.15))
|
|
.clipShape(Capsule())
|
|
}
|
|
|
|
Button {
|
|
withAnimation(.easeOut(duration: 0.2)) {
|
|
store.lastReschedule = nil
|
|
}
|
|
} label: {
|
|
Image(systemName: "xmark")
|
|
.font(.caption.weight(.bold))
|
|
.foregroundStyle(CMColors.textMuted)
|
|
}
|
|
}
|
|
.padding(CMSpacing.md)
|
|
.background(CMColors.surface)
|
|
.clipShape(RoundedRectangle(cornerRadius: CMRadius.md))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: CMRadius.md)
|
|
.stroke(CMColors.accent.opacity(0.3), lineWidth: 1)
|
|
)
|
|
.shadow(color: CMShadow.md, radius: 8, y: 4)
|
|
.padding(.horizontal, CMSpacing.lg)
|
|
.transition(.move(edge: .top).combined(with: .opacity))
|
|
.onAppear {
|
|
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
|
|
isVisible = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Smart Suggestion Banner
|
|
|
|
struct RescheduleSuggestionBanner: View {
|
|
@EnvironmentObject var store: TimerStore
|
|
@State private var showSheet = false
|
|
|
|
var body: some View {
|
|
if !store.rescheduleSuggestions.isEmpty {
|
|
VStack(spacing: CMSpacing.sm) {
|
|
HStack(spacing: CMSpacing.sm) {
|
|
Image(systemName: "sparkles")
|
|
.foregroundStyle(CMColors.accent)
|
|
|
|
Text("Running late? Reschedule your timers")
|
|
.font(CMFonts.body(size: 13, weight: .medium))
|
|
.foregroundStyle(CMColors.text)
|
|
|
|
Spacer()
|
|
|
|
Button {
|
|
store.clearRescheduleSuggestions()
|
|
} label: {
|
|
Image(systemName: "xmark")
|
|
.font(.caption.weight(.bold))
|
|
.foregroundStyle(CMColors.textMuted)
|
|
}
|
|
}
|
|
|
|
HStack(spacing: CMSpacing.sm) {
|
|
// Show first suggestion as quick action
|
|
if let first = store.rescheduleSuggestions.first {
|
|
Button {
|
|
HapticEngine.tap()
|
|
store.applyReschedule(first.action)
|
|
store.clearRescheduleSuggestions()
|
|
} label: {
|
|
Text(first.title)
|
|
.font(CMFonts.body(size: 13, weight: .semibold))
|
|
.foregroundStyle(.white)
|
|
.padding(.horizontal, CMSpacing.md)
|
|
.padding(.vertical, CMSpacing.sm)
|
|
.background(CMColors.accent)
|
|
.clipShape(Capsule())
|
|
}
|
|
}
|
|
|
|
Button {
|
|
showSheet = true
|
|
} label: {
|
|
Text("More options")
|
|
.font(CMFonts.body(size: 13, weight: .medium))
|
|
.foregroundStyle(CMColors.accent)
|
|
.padding(.horizontal, CMSpacing.md)
|
|
.padding(.vertical, CMSpacing.sm)
|
|
.background(CMColors.accent.opacity(0.1))
|
|
.clipShape(Capsule())
|
|
}
|
|
}
|
|
}
|
|
.padding(CMSpacing.md)
|
|
.background(CMColors.surface)
|
|
.clipShape(RoundedRectangle(cornerRadius: CMRadius.md))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: CMRadius.md)
|
|
.stroke(CMColors.accent.opacity(0.3), lineWidth: 1)
|
|
)
|
|
.padding(.horizontal, CMSpacing.lg)
|
|
.transition(.move(edge: .top).combined(with: .opacity))
|
|
.sheet(isPresented: $showSheet) {
|
|
RescheduleSheet()
|
|
}
|
|
}
|
|
}
|
|
}
|