// // RuleDetailSheet.swift // Severed // import SwiftData import SwiftUI /// Rule summary presented as a plain sheet: inline title with a live status /// caption, the rule's facts as labeled rows, and "Edit Rule" — which pushes /// the editor. A hard-locked rule shows a lock notice instead. struct RuleDetailSheet: View { let rule: BlockingRule @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) private var modelContext @State private var isEditing = false @State private var pendingDeletion = false var body: some View { NavigationStack { TimelineView(.periodic(from: .now, by: 30)) { timeline in detailList(now: timeline.date) } .navigationDestination(isPresented: $isEditing) { RuleEditorView( mode: .edit(isEnabled: rule.isEnabled), draft: RuleDraft(rule: rule), onCommit: { draft in draft.apply(to: rule) isEditing = false }, onToggleEnabled: { rule.isEnabled.toggle() rule.pausedUntil = nil isEditing = false }, onDelete: { pendingDeletion = true dismiss() } ) } } .onDisappear { if pendingDeletion { modelContext.delete(rule) } } } private func detailList(now: Date) -> some View { let status = rule.status(at: now) return List { Section { detailRows } Section { if RulePolicy.canEdit(rule, at: now) { Button { isEditing = true } label: { Label("Edit Rule", systemImage: "pencil") } .accessibilityIdentifier("editRuleButton") } else { Label( "Hard Mode is on — this rule is locked until the block ends.", systemImage: "lock.fill" ) .foregroundStyle(.secondary) .accessibilityElement(children: .combine) .accessibilityIdentifier("hardModeLockedNotice") } } } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Close", systemImage: "xmark") { dismiss() } .accessibilityIdentifier("closeDetailButton") } ToolbarItem(placement: .principal) { VStack(spacing: 1) { Text(rule.name) .font(.headline) .accessibilityIdentifier("detailRuleName") Text("\(rule.kind.displayName), \(status.label(relativeTo: now))") .font(.caption) .foregroundStyle(.secondary) .accessibilityIdentifier("detailStatusLabel") } } } } @ViewBuilder private var detailRows: some View { switch rule.kind { case .schedule: row("During this time", rule.schedule.timeRangeLabel) row("On these days", rule.days.summary) row(rule.selectionMode.displayName, appCountLabel) row("Adult websites", rule.blockAdultContent ? "Blocked" : "Allowed") row("Unblocks allowed", rule.hardMode ? "No" : "Yes") case .timeLimit: row("When I use", appCountLabel) row("For this long", "\(rule.dailyLimitMinutes)m daily") row("On these days", rule.days.summary) row("Then block until", "Tomorrow") row("Adult websites", rule.blockAdultContent ? "Blocked" : "Allowed") row("Unblocks allowed", rule.hardMode ? "No" : "Yes") case .openLimit: row("When I open", appCountLabel) row("More than", "\(rule.maxOpens) opens daily") row("On these days", rule.days.summary) row("Then block until", "Tomorrow") row("Adult websites", rule.blockAdultContent ? "Blocked" : "Allowed") row("Unblocks allowed", rule.hardMode ? "No" : "Yes") } } private var appCountLabel: String { rule.selectionCount == 1 ? "1 App" : "\(rule.selectionCount) Apps" } private func row(_ label: String, _ value: String) -> some View { LabeledContent(label, value: value) .accessibilityElement(children: .combine) .accessibilityIdentifier("detailRow-\(label)") } }