Prepare for open-sourcing: scrub the prior-art attribution from the spec, agent guide, and code comments so the project documents its own behavior. - Reword RULES_FEATURE_SPEC.md to describe OpenAppLock's rules feature directly (drop the screen-recording source line and "as observed"/ "not demoed"/"as shown" framing) - Replace "the reference app/UI/style/defaults" comments and test names with direct descriptions of the behavior - No behavior change; comments, docs, and @Test display names only Co-Authored-By: Claude Opus 4.8 (1M context) <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
|
||
/// (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
|
||
}
|
||
}
|