feat: enforce time and open limits in the background via Screen Time extensions
- Shared/ layer compiled into the app and three new extension targets: rule snapshots in the app group, the usage ledger, monitoring-plan naming, and LimitEnforcement (shared, unit-tested event reactions) - RuleScheduler mirrors rules to the app group and reconciles DeviceActivity monitoring: one daily 00:00-23:59 activity per limit rule, with a cumulative usage-threshold event per budget minute for time limits; activities restart only when their configuration changes (a restart resets threshold accounting) - OpenAppLockMonitor (DeviceActivityMonitor): midnight budget resets, records usage minutes, shields at the budget, re-shields when a granted open session ends - OpenAppLockShieldConfig: open-limit shields show 'Opened X of N times today' with an 'Open (Y left)' secondary button - OpenAppLockShieldAction: an Open press spends one open, lifts the rule's shield, and starts the ~15-minute one-shot session - extensions are classic NSExtension app extensions (the ExtensionKit product type expects an @main entry and made the app fail to install); shield-store tracking moved to app-group defaults so the app and extensions see one consistent set - maps iOS 26's new .approvedWithDataAccess authorization status to approved (it previously fell through @unknown default to notDetermined) - shares the OpenAppLock scheme (Xcode dropped the autocreated one when targets were added)
This commit is contained in:
@@ -371,7 +371,33 @@ enum SelectionMode: String, Codable { case block, allowOnly }
|
||||
6. Preset gallery content + polish (gradients, photos, haptics, live
|
||||
countdown timers).
|
||||
|
||||
### 5.5 Out of scope (seen in video, not part of "rules")
|
||||
### 5.5 Background enforcement architecture (implemented)
|
||||
|
||||
- **App group** `group.dev.bchen.OpenAppLock` shares three stores between the
|
||||
app and its extensions: `RuleSnapshotStore` (Codable rule mirror, written
|
||||
by `RuleScheduler` on every enforcement refresh), `UsageLedger` (per-rule,
|
||||
per-day minutes/opens), and the shield-store tracking list.
|
||||
- **`RuleScheduler` (app)** reconciles DeviceActivity monitoring with the
|
||||
enabled limit rules: one repeating 00:00–23:59 activity per rule
|
||||
(`rule-<uuid>`); time-limit rules carry one cumulative usage-threshold
|
||||
event per budget minute (`minutes-<k>`) over the rule's app list.
|
||||
Activities restart only when their configuration changes, because a
|
||||
restart resets threshold accounting.
|
||||
- **`OpenAppLockMonitor`** (DeviceActivityMonitor extension): interval start
|
||||
= midnight reset (open-limit rules re-shield so opens can be counted;
|
||||
time-limit shields clear for the fresh budget); each `minutes-<k>` event
|
||||
records usage and shields at the budget; a finished `open-session-<uuid>`
|
||||
one-shot re-shields after a granted open.
|
||||
- **`OpenAppLockShieldConfig`** (ShieldConfiguration extension): open-limit
|
||||
shields show "Opened X of N times today" with an "Open (Y left)" secondary
|
||||
button; other shields show the blocking rule's name.
|
||||
- **`OpenAppLockShieldAction`** (ShieldAction extension): the Open press
|
||||
spends one open in the ledger, lifts the rule's shield, and starts the
|
||||
~15-minute one-shot session (DeviceActivity's minimum interval).
|
||||
- All shared logic lives in `Shared/` (notably `LimitEnforcement`), unit
|
||||
tested from the app test target.
|
||||
|
||||
### 5.6 Out of scope (seen in video, not part of "rules")
|
||||
|
||||
- Onboarding flow, paywall ("You know Opal works. Make it permanent."),
|
||||
Home tab gem/score UI, Timer tab (one-off focus sessions, "Leave Early?"
|
||||
|
||||
Reference in New Issue
Block a user