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:
@@ -5,28 +5,57 @@
|
||||
// Created by Brendan Chen on 2025.08.09.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SeveredApp: App {
|
||||
var sharedModelContainer: ModelContainer = {
|
||||
let schema = Schema([
|
||||
Item.self,
|
||||
])
|
||||
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
|
||||
private let container: ModelContainer
|
||||
@State private var authorization: ScreenTimeAuthorization
|
||||
@State private var enforcer: RuleEnforcer
|
||||
|
||||
init() {
|
||||
let config = LaunchConfiguration.current
|
||||
|
||||
if let onboardingCompleted = config.onboardingCompleted {
|
||||
UserDefaults.standard.set(onboardingCompleted, forKey: "hasCompletedOnboarding")
|
||||
}
|
||||
|
||||
let schema = Schema([BlockingRule.self])
|
||||
let modelConfiguration = ModelConfiguration(
|
||||
schema: schema, isStoredInMemoryOnly: config.isUITesting
|
||||
)
|
||||
do {
|
||||
return try ModelContainer(for: schema, configurations: [modelConfiguration])
|
||||
container = try ModelContainer(for: schema, configurations: [modelConfiguration])
|
||||
} catch {
|
||||
fatalError("Could not create ModelContainer: \(error)")
|
||||
}
|
||||
}()
|
||||
|
||||
if let scenario = config.seedScenario {
|
||||
SampleRules.seed(scenario, into: container.mainContext)
|
||||
}
|
||||
|
||||
let authProvider: AuthorizationProviding =
|
||||
config.isUITesting
|
||||
? MockAuthorizationProvider(
|
||||
status: config.onboardingCompleted == false ? .notDetermined : .approved
|
||||
)
|
||||
: FamilyControlsAuthorizationProvider()
|
||||
_authorization = State(initialValue: ScreenTimeAuthorization(provider: authProvider))
|
||||
|
||||
let shields: ShieldApplying =
|
||||
config.isUITesting ? MockShieldController() : ManagedSettingsShieldController()
|
||||
_enforcer = State(initialValue: RuleEnforcer(shields: shields))
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
RootView()
|
||||
.environment(authorization)
|
||||
.environment(enforcer)
|
||||
.preferredColorScheme(.dark)
|
||||
.tint(Theme.accent)
|
||||
}
|
||||
.modelContainer(sharedModelContainer)
|
||||
.modelContainer(container)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user