Replace the wide BlockingRule/RuleDraft "god struct" — where every option existed for every kind — with a RuleConfiguration sum type that carries only the options each kind actually has: .schedule(ScheduleConfig: window + selectionMode + blockAdultContent) .timeLimit(TimeLimitConfig: dailyLimitMinutes) .openLimit(OpenLimitConfig: maxOpens) Name, days, Hard Mode, app list, and pause stay common to all kinds. This makes illegal states unrepresentable: Block / Allow Only and Block Adult Content are now structurally Schedule-only. User-visible behavior change: the Time Limit and Open Limit editors no longer offer a Block Adult Content toggle, and their detail sheets drop the "Adult websites" row — those never made sense for a usage budget. Limit rules are always Block and never engage the web-content filter. BlockingRule keeps flat columns as raw persistence behind a computed `configuration` bridge (lowest SwiftData risk; the cross-process RuleSnapshot wire format and the Screen Time extensions are untouched). Logic, the editors, and the detail sheet all switch on the sum type. Spec (§1, §3.5/§3.6, §5.2) updated first; tests reworked to the new API with new structural-guarantee unit tests and a UI test asserting the Time Limit editor/detail omit adult content. Full suite green (180 tests). Co-Authored-By: Claude <noreply@anthropic.com>
91 lines
2.8 KiB
Swift
91 lines
2.8 KiB
Swift
//
|
||
// RuleConfiguration.swift
|
||
// OpenAppLock
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// The kind-specific options of a rule, modelled as a sum type so a rule can
|
||
/// only carry the options that belong to its kind. This makes illegal states
|
||
/// unrepresentable: an Open Limit rule cannot hold a time window, a Time Limit
|
||
/// rule cannot hold a Block/Allow-Only mode, and neither limit kind can hold
|
||
/// Block Adult Content — those are Schedule-only options.
|
||
///
|
||
/// Kind-common attributes (name, days, hardMode, isEnabled, appList,
|
||
/// pausedUntil) live on the owning `BlockingRule` / `RuleDraft`, not here.
|
||
enum RuleConfiguration: Hashable, Sendable {
|
||
case schedule(ScheduleConfig)
|
||
case timeLimit(TimeLimitConfig)
|
||
case openLimit(OpenLimitConfig)
|
||
|
||
var kind: RuleKind {
|
||
switch self {
|
||
case .schedule: .schedule
|
||
case .timeLimit: .timeLimit
|
||
case .openLimit: .openLimit
|
||
}
|
||
}
|
||
|
||
/// The default configuration for a brand-new rule of the given kind, using
|
||
/// the reference app's defaults (9–5 schedule, 45m/day, 5 opens/day).
|
||
static func `default`(for kind: RuleKind) -> RuleConfiguration {
|
||
switch kind {
|
||
case .schedule: .schedule(ScheduleConfig())
|
||
case .timeLimit: .timeLimit(TimeLimitConfig())
|
||
case .openLimit: .openLimit(OpenLimitConfig())
|
||
}
|
||
}
|
||
|
||
var scheduleConfig: ScheduleConfig? {
|
||
if case .schedule(let config) = self { config } else { nil }
|
||
}
|
||
|
||
var timeLimitConfig: TimeLimitConfig? {
|
||
if case .timeLimit(let config) = self { config } else { nil }
|
||
}
|
||
|
||
var openLimitConfig: OpenLimitConfig? {
|
||
if case .openLimit(let config) = self { config } else { nil }
|
||
}
|
||
}
|
||
|
||
/// Schedule-rule options: a recurring time window, how the app list is
|
||
/// interpreted, and whether the adult-website filter engages while the window
|
||
/// is active. A window whose end is at or before its start crosses midnight.
|
||
struct ScheduleConfig: Hashable, Sendable {
|
||
var startMinutes: Int
|
||
var endMinutes: Int
|
||
var selectionMode: SelectionMode
|
||
var blockAdultContent: Bool
|
||
|
||
init(
|
||
startMinutes: Int = 9 * 60,
|
||
endMinutes: Int = 17 * 60,
|
||
selectionMode: SelectionMode = .block,
|
||
blockAdultContent: Bool = false
|
||
) {
|
||
self.startMinutes = startMinutes
|
||
self.endMinutes = endMinutes
|
||
self.selectionMode = selectionMode
|
||
self.blockAdultContent = blockAdultContent
|
||
}
|
||
}
|
||
|
||
/// Time Limit option: a daily cumulative-usage budget in minutes.
|
||
struct TimeLimitConfig: Hashable, Sendable {
|
||
var dailyLimitMinutes: Int
|
||
|
||
init(dailyLimitMinutes: Int = 45) {
|
||
self.dailyLimitMinutes = dailyLimitMinutes
|
||
}
|
||
}
|
||
|
||
/// Open Limit option: a daily budget of app opens.
|
||
struct OpenLimitConfig: Hashable, Sendable {
|
||
var maxOpens: Int
|
||
|
||
init(maxOpens: Int = 5) {
|
||
self.maxOpens = maxOpens
|
||
}
|
||
}
|