refactor: rename app from Severed to OpenAppLock
Full rename ahead of App Store submission: project, targets, bundle identifiers (dev.bchen.Severed -> dev.bchen.OpenAppLock), entitlements file, app entry point, user-facing onboarding strings, test helpers, and docs. Verified: all 62 unit tests pass.
This commit is contained in:
134
OpenAppLock/Views/Rules/RuleDetailSheet.swift
Normal file
134
OpenAppLock/Views/Rules/RuleDetailSheet.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// RuleDetailSheet.swift
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user