// // RuleDetailSheet.swift // Severed // import SwiftData import SwiftUI /// The card-style rule summary shown when tapping a rule: icon pair, live /// status, the key facts, and "Edit Rule" — which morphs the sheet into the /// editor, as in the reference app. A hard-locked rule shows a lock notice /// instead of the edit button. 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 { TimelineView(.periodic(from: .now, by: 30)) { timeline in if isEditing { RuleEditorView( mode: .edit(isEnabled: rule.isEnabled), draft: RuleDraft(rule: rule), onBack: { isEditing = false }, onCommit: { draft in draft.apply(to: rule) isEditing = false }, onToggleEnabled: { rule.isEnabled.toggle() rule.pausedUntil = nil isEditing = false }, onDelete: { pendingDeletion = true dismiss() } ) } else { detail(now: timeline.date) } } .background(Theme.background) .onDisappear { if pendingDeletion { modelContext.delete(rule) } } } private func detail(now: Date) -> some View { let status = rule.status(at: now) return VStack(spacing: 0) { HStack { CircleIconButton(systemImage: "xmark") { dismiss() } .accessibilityIdentifier("closeDetailButton") Spacer() } .padding(.horizontal, 20) .padding(.top, 18) ScrollView { VStack(spacing: 24) { RuleIconPair(kind: rule.kind, isActive: status.isActive) VStack(spacing: 6) { Text("\(rule.kind.displayName), \(status.label(relativeTo: now))") .font(.system(size: 13, weight: .medium)) .foregroundStyle(Theme.textSecondary) .accessibilityIdentifier("detailStatusLabel") Text(rule.name) .font(.system(size: 26, weight: .bold)) .accessibilityIdentifier("detailRuleName") } detailRows } .padding(.horizontal, 20) .padding(.top, 8) } footer(now: now) .padding(.horizontal, 20) .padding(.bottom, 16) } .foregroundStyle(.white) } private var detailRows: some View { VStack(spacing: 0) { switch rule.kind { case .schedule: row("During this time", rule.schedule.timeRangeLabel) divider row("On these days", rule.days.summary) divider row(rule.selectionMode.displayName, appCountLabel) divider row("Adult websites", rule.blockAdultContent ? "Blocked" : "Allowed") divider row("Unblocks allowed", rule.hardMode ? "No" : "Yes") case .timeLimit: row("When I use", appCountLabel) divider row("For this long", "\(rule.dailyLimitMinutes)m daily") divider row("On these days", rule.days.summary) divider row("Then block until", "Tomorrow") divider row("Adult websites", rule.blockAdultContent ? "Blocked" : "Allowed") divider row("Unblocks allowed", rule.hardMode ? "No" : "Yes") case .openLimit: row("When I open", appCountLabel) divider row("More than", "\(rule.maxOpens) opens daily") divider row("On these days", rule.days.summary) divider row("Then block until", "Tomorrow") divider row("Adult websites", rule.blockAdultContent ? "Blocked" : "Allowed") divider row("Unblocks allowed", rule.hardMode ? "No" : "Yes") } } .background(Theme.surface, in: RoundedRectangle(cornerRadius: 18)) } private var appCountLabel: String { rule.selectionCount == 1 ? "1 App" : "\(rule.selectionCount) Apps" } private func row(_ label: String, _ value: String) -> some View { HStack { Text(label) .font(.system(size: 15, weight: .medium)) Spacer() Text(value) .font(.system(size: 15)) .foregroundStyle(Theme.textSecondary) } .padding(.horizontal, 16) .padding(.vertical, 14) .accessibilityElement(children: .combine) .accessibilityIdentifier("detailRow-\(label)") } private var divider: some View { Divider().background(.white.opacity(0.06)).padding(.leading, 16) } @ViewBuilder private func footer(now: Date) -> some View { if RulePolicy.canEdit(rule, at: now) { Button { isEditing = true } label: { Label("Edit Rule", systemImage: "pencil") .font(.system(size: 17, weight: .semibold)) .foregroundStyle(.black) .frame(maxWidth: .infinity) .frame(height: 54) .background(.white, in: RoundedRectangle(cornerRadius: 27)) } .buttonStyle(.plain) .accessibilityIdentifier("editRuleButton") } else { HStack(spacing: 10) { Image(systemName: "lock.fill") Text("Hard Mode is on — this rule is locked until the block ends.") .font(.system(size: 14, weight: .medium)) } .foregroundStyle(Theme.textSecondary) .frame(maxWidth: .infinity) .padding(.vertical, 16) .background(Theme.surface, in: RoundedRectangle(cornerRadius: 18)) .accessibilityElement(children: .combine) .accessibilityIdentifier("hardModeLockedNotice") } } }