- Add Block Adult Content toggle to all three rule editors, persisted as BlockingRule.blockAdultContent (inline default for clean migration of existing stores) and surfaced in the detail sheet as an 'Adult websites: Blocked/Allowed' row - Engage Screen Time's adult-website filter (webContent.blockedByFilter = .auto()) alongside the rule's shield and clear it when the shield clears - Replace the New Rule sheet's view-swap with a NavigationStack push (navigationDestination(item:)); the editor uses native chrome there (system back button, inline title, toolbar rename), enabling the push animation and edge-swipe back - Tests: 93 passing (+2 unit: draft round-trip and enforcer forwarding; +3 UI: toggle-to-detail flow, default-allowed row, swipe-back as a behavioral proof of native navigation) - Spec updated accordingly (editor sections, behavior, data model, navigation note)
97 lines
3.2 KiB
Swift
97 lines
3.2 KiB
Swift
//
|
|
// BlockingRule.swift
|
|
// Severed
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftData
|
|
|
|
/// A recurring screen-time blocking rule.
|
|
///
|
|
/// Times are stored as minutes from midnight so the schedule repeats cleanly and
|
|
/// is independent of time zones at creation. A window whose end is at or before
|
|
/// its start (e.g. 22:00 → 06:00) crosses midnight into the following day.
|
|
@Model
|
|
final class BlockingRule {
|
|
@Attribute(.unique) var id: UUID
|
|
var name: String
|
|
var kindRaw: String
|
|
var isEnabled: Bool
|
|
/// Hard block: while the rule is active it cannot be disabled, edited, or unblocked.
|
|
var hardMode: Bool
|
|
/// Engage Screen Time's adult-website filter while this rule is blocking.
|
|
/// Inline default so existing stores migrate cleanly.
|
|
var blockAdultContent: Bool = false
|
|
var selectionModeRaw: String
|
|
/// Encoded `FamilyActivitySelection` (opaque tokens). Nil until the user picks apps.
|
|
var selectionData: Data?
|
|
/// Denormalized count of selected apps/categories/domains for display.
|
|
var selectionCount: Int
|
|
var dayNumbers: [Int]
|
|
var startMinutes: Int
|
|
var endMinutes: Int
|
|
/// Daily usage budget for `.timeLimit` rules.
|
|
var dailyLimitMinutes: Int
|
|
/// Daily open budget for `.openLimit` rules.
|
|
var maxOpens: Int
|
|
/// When set, the rule's current window is suspended (user tapped Unblock).
|
|
/// Cleared automatically once the date passes; never set while Hard Mode is active.
|
|
var pausedUntil: Date?
|
|
var createdAt: Date
|
|
|
|
init(
|
|
id: UUID = UUID(),
|
|
name: String,
|
|
kind: RuleKind = .schedule,
|
|
isEnabled: Bool = true,
|
|
hardMode: Bool = false,
|
|
blockAdultContent: Bool = false,
|
|
selectionMode: SelectionMode = .block,
|
|
selectionData: Data? = nil,
|
|
selectionCount: Int = 0,
|
|
days: Set<Weekday> = Weekday.weekdays,
|
|
startMinutes: Int = 9 * 60,
|
|
endMinutes: Int = 17 * 60,
|
|
dailyLimitMinutes: Int = 45,
|
|
maxOpens: Int = 5,
|
|
pausedUntil: Date? = nil,
|
|
createdAt: Date = .now
|
|
) {
|
|
self.id = id
|
|
self.name = name
|
|
self.kindRaw = kind.rawValue
|
|
self.isEnabled = isEnabled
|
|
self.hardMode = hardMode
|
|
self.blockAdultContent = blockAdultContent
|
|
self.selectionModeRaw = selectionMode.rawValue
|
|
self.selectionData = selectionData
|
|
self.selectionCount = selectionCount
|
|
self.dayNumbers = days.map(\.rawValue).sorted()
|
|
self.startMinutes = startMinutes
|
|
self.endMinutes = endMinutes
|
|
self.dailyLimitMinutes = dailyLimitMinutes
|
|
self.maxOpens = maxOpens
|
|
self.pausedUntil = pausedUntil
|
|
self.createdAt = createdAt
|
|
}
|
|
|
|
var kind: RuleKind {
|
|
get { RuleKind(rawValue: kindRaw) ?? .schedule }
|
|
set { kindRaw = newValue.rawValue }
|
|
}
|
|
|
|
var selectionMode: SelectionMode {
|
|
get { SelectionMode(rawValue: selectionModeRaw) ?? .block }
|
|
set { selectionModeRaw = newValue.rawValue }
|
|
}
|
|
|
|
var days: Set<Weekday> {
|
|
get { Set(dayNumbers.compactMap(Weekday.init(rawValue:))) }
|
|
set { dayNumbers = newValue.map(\.rawValue).sorted() }
|
|
}
|
|
|
|
var schedule: RuleSchedule {
|
|
RuleSchedule(startMinutes: startMinutes, endMinutes: endMinutes, days: days)
|
|
}
|
|
}
|