feat: clone Opal's rules feature with hard block enforcement
Rebuild the app around recurring screen-time blocking rules modeled on Opal's My Apps tab: - Onboarding with FamilyControls Screen Time authorization - Apps home with Blocked Apps tiles and live rule cards - New Rule sheet: Schedule/Time Limit/Open Limit types + preset gallery - Rule editors: time windows (incl. overnight), day picker, app selection via FamilyActivityPicker (Block/Allow Only), rename, Hold to Commit - Hard Mode: active hard rules cannot be edited, disabled, deleted, or unblocked until their window ends; soft rules pause until next window - Shield enforcement through per-rule ManagedSettingsStore + RuleEnforcer - 73 unit tests (Swift Testing) + 16 UI tests (XCUITest) with launch-arg harness for in-memory storage, mocked authorization, seeded scenarios - docs/RULES_FEATURE_SPEC.md: spec derived from the reference recording Known gap: background window transitions and time/open-limit thresholds need a DeviceActivityMonitor extension; shields currently sync while the app is running.
This commit is contained in:
98
Severed/Models/RulePreset.swift
Normal file
98
Severed/Models/RulePreset.swift
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// RulePreset.swift
|
||||
// Severed
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// A suggested schedule rule shown in the New Rule sheet's preset gallery,
|
||||
/// mirroring the reference app's sections and timings.
|
||||
struct RulePreset: Identifiable, Hashable, Sendable {
|
||||
let id: String
|
||||
let name: String
|
||||
let startMinutes: Int
|
||||
let endMinutes: Int
|
||||
let days: Set<Weekday>
|
||||
let symbolName: String
|
||||
/// Gradient stand-in for the reference app's photo backgrounds.
|
||||
let gradientTop: Color
|
||||
let gradientBottom: Color
|
||||
|
||||
var schedule: RuleSchedule {
|
||||
RuleSchedule(startMinutes: startMinutes, endMinutes: endMinutes, days: days)
|
||||
}
|
||||
}
|
||||
|
||||
/// A titled group of presets ("Get More Done", "Sleep, Relax and Reset", …).
|
||||
struct RulePresetSection: Identifiable, Hashable, Sendable {
|
||||
let id: String
|
||||
let title: String
|
||||
let subtitle: String
|
||||
let presets: [RulePreset]
|
||||
|
||||
static let all: [RulePresetSection] = [
|
||||
RulePresetSection(
|
||||
id: "productivity",
|
||||
title: "Get More Done",
|
||||
subtitle: "Maximize your productivity while staying sane.",
|
||||
presets: [
|
||||
RulePreset(
|
||||
id: "work-time", name: "Work Time",
|
||||
startMinutes: 9 * 60, endMinutes: 17 * 60, days: Weekday.weekdays,
|
||||
symbolName: "briefcase.fill",
|
||||
gradientTop: Color(red: 0.16, green: 0.27, blue: 0.22),
|
||||
gradientBottom: Color(red: 0.05, green: 0.10, blue: 0.08)
|
||||
),
|
||||
RulePreset(
|
||||
id: "laser-focus", name: "Laser Focus",
|
||||
startMinutes: 14 * 60, endMinutes: 18 * 60, days: Weekday.weekdays,
|
||||
symbolName: "scope",
|
||||
gradientTop: Color(red: 0.13, green: 0.20, blue: 0.33),
|
||||
gradientBottom: Color(red: 0.04, green: 0.06, blue: 0.12)
|
||||
),
|
||||
]
|
||||
),
|
||||
RulePresetSection(
|
||||
id: "sleep",
|
||||
title: "Sleep, Relax and Reset",
|
||||
subtitle: "Sleep better, rise refreshed.",
|
||||
presets: [
|
||||
RulePreset(
|
||||
id: "wind-down", name: "Wind Down",
|
||||
startMinutes: 20 * 60, endMinutes: 22 * 60, days: Weekday.everyDay,
|
||||
symbolName: "moon.haze.fill",
|
||||
gradientTop: Color(red: 0.25, green: 0.18, blue: 0.33),
|
||||
gradientBottom: Color(red: 0.08, green: 0.05, blue: 0.12)
|
||||
),
|
||||
RulePreset(
|
||||
id: "deep-sleep", name: "Deep Sleep",
|
||||
startMinutes: 22 * 60, endMinutes: 6 * 60, days: Weekday.everyDay,
|
||||
symbolName: "moon.zzz.fill",
|
||||
gradientTop: Color(red: 0.10, green: 0.13, blue: 0.30),
|
||||
gradientBottom: Color(red: 0.02, green: 0.03, blue: 0.10)
|
||||
),
|
||||
]
|
||||
),
|
||||
RulePresetSection(
|
||||
id: "habits",
|
||||
title: "Build Healthy Habits",
|
||||
subtitle: "Spend time on important things.",
|
||||
presets: [
|
||||
RulePreset(
|
||||
id: "reading-time", name: "Reading Time",
|
||||
startMinutes: 19 * 60 + 45, endMinutes: 20 * 60 + 45, days: Weekday.everyDay,
|
||||
symbolName: "book.fill",
|
||||
gradientTop: Color(red: 0.33, green: 0.23, blue: 0.13),
|
||||
gradientBottom: Color(red: 0.12, green: 0.07, blue: 0.03)
|
||||
),
|
||||
RulePreset(
|
||||
id: "gym-time", name: "Gym Time",
|
||||
startMinutes: 17 * 60 + 30, endMinutes: 18 * 60 + 30, days: Weekday.everyDay,
|
||||
symbolName: "figure.run",
|
||||
gradientTop: Color(red: 0.13, green: 0.30, blue: 0.30),
|
||||
gradientBottom: Color(red: 0.03, green: 0.10, blue: 0.10)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user