Files
OpenAppLock/Severed/Models/RuleDraft.swift
Brendan Chen 1868d788f9 fix: editor usability and visual polish from UI scan
- Rule name is now an inline text field at the top of the editor; the
  pencil/rename button and alert are gone (it was unclear what the edit
  button did). Names are sanitized on commit: trimmed, falling back to
  the kind's default when emptied
- Day-of-week toggles fill the full row width with equal cells and
  >= 44pt tap targets
- 'Add Rule' commit button sits on a bar-material bottom inset so form
  content no longer collides with it while scrolling (found via
  computer-use scan; verified clean in a follow-up scan)
- Tests: 95 passing — new specs for the inline rename flow, day-toggle
  geometry, and name sanitization; three tests now scroll to reach
  bottom-of-form controls, matching real user behavior
- Spec §6 updated for the editor changes

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 15:44:25 -04:00

99 lines
3.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
// Severed
//
import Foundation
/// 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:)`.
struct RuleDraft: Hashable {
var name: String
var kind: RuleKind
var days: Set<Weekday>
var startMinutes: Int
var endMinutes: Int
var dailyLimitMinutes: Int
var maxOpens: Int
var hardMode: Bool
var blockAdultContent: Bool
var selectionMode: SelectionMode
var selectionData: Data?
var selectionCount: Int
/// A fresh draft for a new rule of the given kind, using the reference
/// app's defaults (95 weekdays schedule, 45m/day, 5 opens/day).
init(kind: RuleKind) {
self.name = kind.defaultRuleName
self.kind = kind
self.days = Weekday.weekdays
self.startMinutes = 9 * 60
self.endMinutes = 17 * 60
self.dailyLimitMinutes = 45
self.maxOpens = 5
self.hardMode = false
self.blockAdultContent = false
self.selectionMode = .block
self.selectionData = nil
self.selectionCount = 0
}
init(rule: BlockingRule) {
self.name = rule.name
self.kind = rule.kind
self.days = rule.days
self.startMinutes = rule.startMinutes
self.endMinutes = rule.endMinutes
self.dailyLimitMinutes = rule.dailyLimitMinutes
self.maxOpens = rule.maxOpens
self.hardMode = rule.hardMode
self.blockAdultContent = rule.blockAdultContent
self.selectionMode = rule.selectionMode
self.selectionData = rule.selectionData
self.selectionCount = rule.selectionCount
}
init(preset: RulePreset) {
self.init(kind: .schedule)
self.name = preset.name
self.days = preset.days
self.startMinutes = preset.startMinutes
self.endMinutes = preset.endMinutes
}
func apply(to rule: BlockingRule) {
rule.name = name
rule.kind = kind
rule.days = days
rule.startMinutes = startMinutes
rule.endMinutes = endMinutes
rule.dailyLimitMinutes = dailyLimitMinutes
rule.maxOpens = maxOpens
rule.hardMode = hardMode
rule.blockAdultContent = blockAdultContent
rule.selectionMode = selectionMode
rule.selectionData = selectionData
rule.selectionCount = selectionCount
}
func makeRule() -> BlockingRule {
let rule = BlockingRule(name: name, kind: kind)
apply(to: rule)
return rule
}
/// Trims the name and falls back to the kind's default when it is empty,
/// so a cleared name field can never produce an unnamed rule.
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: startMinutes, endMinutes: endMinutes, days: days)
}
}