Files
OpenAppLock/OpenAppLock/Models/RuleDraft.swift
Brendan Chen d005ed4fb1 docs: remove references to the source app and recording
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>
2026-06-13 22:23:14 -04:00

116 lines
4.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// RuleDraft.swift
// OpenAppLock
//
import Foundation
import SwiftData
/// Value-type working copy of a rule used by the editors, so cancelling an
/// edit never touches the persisted model. Hashable so it can drive
/// `navigationDestination(item:)`.
///
/// The kind-specific options live in `configuration`, so each editor branch
/// only ever sees the options that belong to its kind (the Schedule editor
/// gets Block/Allow-Only and Block Adult Content; the limit editors do not).
struct RuleDraft: Hashable {
var name: String
var days: Set<Weekday>
var hardMode: Bool
/// Reference to the persisted list the rule will use. App lists are
/// managed (created/edited) directly by the picker, so the draft only
/// carries the pointer.
var appList: AppList?
var configuration: RuleConfiguration
var kind: RuleKind { configuration.kind }
/// A fresh draft for a new rule of the given kind, using the default
/// values (95 weekdays schedule, 45m/day, 5 opens/day).
init(kind: RuleKind) {
self.name = kind.defaultRuleName
self.days = Weekday.weekdays
self.hardMode = false
self.appList = nil
self.configuration = .default(for: kind)
}
init(rule: BlockingRule) {
self.name = rule.name
self.days = rule.days
self.hardMode = rule.hardMode
self.appList = rule.appList
self.configuration = rule.configuration
}
init(preset: RulePreset) {
self.init(kind: .schedule)
self.name = preset.name
self.days = preset.days
self.configuration = .schedule(
ScheduleConfig(startMinutes: preset.startMinutes, endMinutes: preset.endMinutes))
}
/// Writes the draft back onto a rule. The rule (and the chosen list) must
/// already be inserted in a context: SwiftData relationships may only be
/// assigned between managed models (see `BlockingRule.appList`).
func apply(to rule: BlockingRule) {
rule.name = name
rule.days = days
rule.hardMode = hardMode
rule.configuration = configuration
if rule.appList !== appList {
rule.appList = appList
}
}
/// Creates and inserts a new rule from this draft. The rule is inserted
/// *before* the draft is applied so the app-list relationship is only
/// ever written on a managed model.
@discardableResult
func insertRule(into context: ModelContext) -> BlockingRule {
let rule = BlockingRule(name: name, configuration: configuration)
context.insert(rule)
apply(to: rule)
return rule
}
/// Trims the name, falling back to the kind's default when it is empty.
/// (Block / Allow Only no longer needs sanitizing: the sum type makes it
/// impossible for a limit draft to carry a selection mode at all.)
func sanitized() -> RuleDraft {
var copy = self
let trimmed = name.trimmingCharacters(in: .whitespaces)
copy.name = trimmed.isEmpty ? kind.defaultRuleName : trimmed
return copy
}
var schedule: RuleSchedule {
RuleSchedule(
startMinutes: scheduleConfig.startMinutes,
endMinutes: scheduleConfig.endMinutes,
days: days)
}
}
extension RuleDraft {
/// Typed projections of the active configuration, used to bind editor
/// controls. The getter returns kind defaults when the draft is a
/// different kind; each editor only reads the projection for its own kind,
/// and writing through it repackages the configuration to that kind.
var scheduleConfig: ScheduleConfig {
get { configuration.scheduleConfig ?? ScheduleConfig() }
set { configuration = .schedule(newValue) }
}
var timeLimitConfig: TimeLimitConfig {
get { configuration.timeLimitConfig ?? TimeLimitConfig() }
set { configuration = .timeLimit(newValue) }
}
var openLimitConfig: OpenLimitConfig {
get { configuration.openLimitConfig ?? OpenLimitConfig() }
set { configuration = .openLimit(newValue) }
}
}