// // AppRestrictManager.swift // QuickLocation // import DeviceActivity import FamilyControls import Foundation import ManagedSettings import UIKit @available(iOS 16.0, *) final class AppRestrictManager { static let shared = AppRestrictManager() private let center = AuthorizationCenter.shared private let activityCenter = DeviceActivityCenter() private let activityName = DeviceActivityName(AppRestrictShared.activityName) private init() {} var authorizationStatus: AuthorizationStatus { center.authorizationStatus } var isAuthorized: Bool { center.authorizationStatus == .approved } var selection: FamilyActivitySelection { get { AppRestrictSharedStore.selection } set { AppRestrictSharedStore.selection = newValue // Drop enabled tokens that are no longer in selection let apps = newValue.applicationTokens AppRestrictSharedStore.enabledTokens = AppRestrictSharedStore.enabledTokens.intersection(apps) refreshMonitoringAndShield() } } var applicationTokens: [ApplicationToken] { Array(selection.applicationTokens) } var enabledTokens: Set { get { AppRestrictSharedStore.enabledTokens } set { AppRestrictSharedStore.enabledTokens = newValue refreshMonitoringAndShield() } } func requestAuthorization() async throws { try await center.requestAuthorization(for: .individual) } func mergeSelection(_ incoming: FamilyActivitySelection) { var current = selection current.applicationTokens.formUnion(incoming.applicationTokens) current.categoryTokens.formUnion(incoming.categoryTokens) current.webDomainTokens.formUnion(incoming.webDomainTokens) selection = current } func isEnabled(_ token: ApplicationToken) -> Bool { enabledTokens.contains(token) } func setEnabled(_ token: ApplicationToken, enabled: Bool) { var set = enabledTokens if enabled { set.insert(token) } else { set.remove(token) } enabledTokens = set } func catalogId(for token: ApplicationToken) -> String? { AppRestrictSharedStore.catalogId(for: token) } func link(catalogId: String, token: ApplicationToken, displayName: String? = nil, iconURL: String? = nil) { AppRestrictSharedStore.setLink( catalogId: catalogId, token: token, displayName: displayName, iconURL: iconURL ) } func catalogItem(for token: ApplicationToken) -> AppCatalogItem? { guard let record = AppRestrictSharedStore.linkRecord(for: token) else { return nil } return AppCatalogStore.resolve(link: record) } func unlink(_ token: ApplicationToken) { AppRestrictSharedStore.removeLink(for: token) } func removeApplication(_ token: ApplicationToken) { unlink(token) var set = enabledTokens set.remove(token) enabledTokens = set var current = selection current.applicationTokens.remove(token) selection = current } var shieldConfig: AppRestrictShieldConfig { get { AppRestrictSharedStore.shieldConfig } set { AppRestrictSharedStore.shieldConfig = newValue } } @discardableResult func saveCustomShieldImage(_ image: UIImage) -> Bool { AppRestrictSharedStore.saveCustomImage(image) } func refreshMonitoringAndShield() { let tokens = enabledTokens AppRestrictSharedStore.applyShield(for: tokens) activityCenter.stopMonitoring([activityName]) guard !tokens.isEmpty else { return } // Near-daily schedule so Monitor can re-apply after reboot / schedule boundaries. let schedule = DeviceActivitySchedule( intervalStart: DateComponents(hour: 0, minute: 0), intervalEnd: DateComponents(hour: 23, minute: 59), repeats: true ) do { try activityCenter.startMonitoring(activityName, during: schedule) } catch { // Shield already applied above; monitoring is best-effort. print("[AppRestrict] startMonitoring failed: \(error)") } } }