Files
OpenAppLock/OpenAppLock/Services/ScreenTimeAuthorization.swift
Brendan Chen 1d5e79ba7c refactor: rename app from Severed to OpenAppLock
Full rename ahead of App Store submission: project, targets, bundle
identifiers (dev.bchen.Severed -> dev.bchen.OpenAppLock), entitlements
file, app entry point, user-facing onboarding strings, test helpers,
and docs. Verified: all 62 unit tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 18:05:06 -04:00

86 lines
2.3 KiB
Swift

//
// ScreenTimeAuthorization.swift
// OpenAppLock
//
import FamilyControls
import Foundation
import Observation
enum ScreenTimeAuthorizationStatus: Equatable, Sendable {
case notDetermined
case denied
case approved
}
/// Abstracts FamilyControls authorization so views and tests never touch
/// `AuthorizationCenter` directly.
protocol AuthorizationProviding {
var currentStatus: ScreenTimeAuthorizationStatus { get }
func requestAuthorization() async throws
}
/// Real Screen Time authorization via FamilyControls.
struct FamilyControlsAuthorizationProvider: AuthorizationProviding {
var currentStatus: ScreenTimeAuthorizationStatus {
switch AuthorizationCenter.shared.authorizationStatus {
case .approved: .approved
case .denied: .denied
case .notDetermined: .notDetermined
@unknown default: .notDetermined
}
}
func requestAuthorization() async throws {
try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
}
}
/// In-memory provider for unit and UI tests.
final class MockAuthorizationProvider: AuthorizationProviding {
var status: ScreenTimeAuthorizationStatus
var requestShouldFail: Bool
init(status: ScreenTimeAuthorizationStatus = .notDetermined, requestShouldFail: Bool = false) {
self.status = status
self.requestShouldFail = requestShouldFail
}
var currentStatus: ScreenTimeAuthorizationStatus { status }
func requestAuthorization() async throws {
if requestShouldFail {
status = .denied
throw FamilyControlsError.authorizationCanceled
}
status = .approved
}
}
/// Observable authorization state for the UI.
@Observable
final class ScreenTimeAuthorization {
private(set) var status: ScreenTimeAuthorizationStatus
private(set) var lastRequestFailed = false
private let provider: AuthorizationProviding
init(provider: AuthorizationProviding) {
self.provider = provider
self.status = provider.currentStatus
}
func refresh() {
status = provider.currentStatus
}
func request() async {
do {
try await provider.requestAuthorization()
lastRequestFailed = false
} catch {
lastRequestFailed = true
}
refresh()
}
}