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. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
64
Severed/Models/RuleKind.swift
Normal file
64
Severed/Models/RuleKind.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// RuleKind.swift
|
||||
// Severed
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The three kinds of blocking rules, mirroring the reference app's "New Rule" sheet.
|
||||
enum RuleKind: String, Codable, CaseIterable, Sendable {
|
||||
/// Block selected apps during a recurring time window.
|
||||
case schedule
|
||||
/// Block selected apps after a daily usage budget is spent.
|
||||
case timeLimit
|
||||
/// Block selected apps after a number of opens per day.
|
||||
case openLimit
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .schedule: "Schedule"
|
||||
case .timeLimit: "Time Limit"
|
||||
case .openLimit: "Open Limit"
|
||||
}
|
||||
}
|
||||
|
||||
var exampleText: String {
|
||||
switch self {
|
||||
case .schedule: "e.g. 9-5, Daily"
|
||||
case .timeLimit: "e.g. 45m/day"
|
||||
case .openLimit: "e.g. 5 opens/day"
|
||||
}
|
||||
}
|
||||
|
||||
var symbolName: String {
|
||||
switch self {
|
||||
case .schedule: "calendar"
|
||||
case .timeLimit: "hourglass"
|
||||
case .openLimit: "lock.fill"
|
||||
}
|
||||
}
|
||||
|
||||
/// Default name given to a brand-new rule of this kind (Opal: "In the Zone", "Time Keeper").
|
||||
var defaultRuleName: String {
|
||||
switch self {
|
||||
case .schedule: "In the Zone"
|
||||
case .timeLimit: "Time Keeper"
|
||||
case .openLimit: "Gate Keeper"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How the rule's app selection is interpreted.
|
||||
enum SelectionMode: String, Codable, CaseIterable, Sendable {
|
||||
/// Block the selected apps; everything else stays available.
|
||||
case block
|
||||
/// Block everything except the selected apps.
|
||||
case allowOnly
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .block: "Block"
|
||||
case .allowOnly: "Allow Only"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user