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.
62 lines
1.9 KiB
Swift
62 lines
1.9 KiB
Swift
//
|
|
// SeveredApp.swift
|
|
// Severed
|
|
//
|
|
// Created by Brendan Chen on 2025.08.09.
|
|
//
|
|
|
|
import SwiftData
|
|
import SwiftUI
|
|
|
|
@main
|
|
struct SeveredApp: App {
|
|
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 {
|
|
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 {
|
|
RootView()
|
|
.environment(authorization)
|
|
.environment(enforcer)
|
|
.preferredColorScheme(.dark)
|
|
.tint(Theme.accent)
|
|
}
|
|
.modelContainer(container)
|
|
}
|
|
}
|