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:
84
Severed/Models/RuleDraft.swift
Normal file
84
Severed/Models/RuleDraft.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// RuleDraft.swift
|
||||
// Severed
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Value-type working copy of a rule used by the editors, so cancelling an
|
||||
/// edit never touches the persisted model.
|
||||
struct RuleDraft: Equatable {
|
||||
var name: String
|
||||
var kind: RuleKind
|
||||
var days: Set<Weekday>
|
||||
var startMinutes: Int
|
||||
var endMinutes: Int
|
||||
var dailyLimitMinutes: Int
|
||||
var maxOpens: Int
|
||||
var hardMode: Bool
|
||||
var selectionMode: SelectionMode
|
||||
var selectionData: Data?
|
||||
var selectionCount: Int
|
||||
|
||||
/// A fresh draft for a new rule of the given kind, using the reference
|
||||
/// app's defaults (9–5 weekdays schedule, 45m/day, 5 opens/day).
|
||||
init(kind: RuleKind) {
|
||||
self.name = kind.defaultRuleName
|
||||
self.kind = kind
|
||||
self.days = Weekday.weekdays
|
||||
self.startMinutes = 9 * 60
|
||||
self.endMinutes = 17 * 60
|
||||
self.dailyLimitMinutes = 45
|
||||
self.maxOpens = 5
|
||||
self.hardMode = false
|
||||
self.selectionMode = .block
|
||||
self.selectionData = nil
|
||||
self.selectionCount = 0
|
||||
}
|
||||
|
||||
init(rule: BlockingRule) {
|
||||
self.name = rule.name
|
||||
self.kind = rule.kind
|
||||
self.days = rule.days
|
||||
self.startMinutes = rule.startMinutes
|
||||
self.endMinutes = rule.endMinutes
|
||||
self.dailyLimitMinutes = rule.dailyLimitMinutes
|
||||
self.maxOpens = rule.maxOpens
|
||||
self.hardMode = rule.hardMode
|
||||
self.selectionMode = rule.selectionMode
|
||||
self.selectionData = rule.selectionData
|
||||
self.selectionCount = rule.selectionCount
|
||||
}
|
||||
|
||||
init(preset: RulePreset) {
|
||||
self.init(kind: .schedule)
|
||||
self.name = preset.name
|
||||
self.days = preset.days
|
||||
self.startMinutes = preset.startMinutes
|
||||
self.endMinutes = preset.endMinutes
|
||||
}
|
||||
|
||||
func apply(to rule: BlockingRule) {
|
||||
rule.name = name
|
||||
rule.kind = kind
|
||||
rule.days = days
|
||||
rule.startMinutes = startMinutes
|
||||
rule.endMinutes = endMinutes
|
||||
rule.dailyLimitMinutes = dailyLimitMinutes
|
||||
rule.maxOpens = maxOpens
|
||||
rule.hardMode = hardMode
|
||||
rule.selectionMode = selectionMode
|
||||
rule.selectionData = selectionData
|
||||
rule.selectionCount = selectionCount
|
||||
}
|
||||
|
||||
func makeRule() -> BlockingRule {
|
||||
let rule = BlockingRule(name: name, kind: kind)
|
||||
apply(to: rule)
|
||||
return rule
|
||||
}
|
||||
|
||||
var schedule: RuleSchedule {
|
||||
RuleSchedule(startMinutes: startMinutes, endMinutes: endMinutes, days: days)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user