jsdw_ios/QuickLocation/Manager/AppRestrict/AppRestrictManager.swift

153 lines
4.7 KiB
Swift

//
// 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 {
let stored = AppRestrictSharedStore.selection
let normalized = applicationOnlySelection(stored)
if !stored.categoryTokens.isEmpty || !stored.webDomainTokens.isEmpty {
AppRestrictSharedStore.selection = normalized
}
return normalized
}
set {
let normalized = applicationOnlySelection(newValue)
AppRestrictSharedStore.selection = normalized
// Drop enabled tokens that are no longer in selection
let apps = normalized.applicationTokens
AppRestrictSharedStore.enabledTokens = AppRestrictSharedStore.enabledTokens.intersection(apps)
refreshMonitoringAndShield()
}
}
var applicationTokens: [ApplicationToken] {
Array(selection.applicationTokens)
}
var enabledTokens: Set<ApplicationToken> {
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)
selection = current
}
private func applicationOnlySelection(_ source: FamilyActivitySelection) -> FamilyActivitySelection {
var result = FamilyActivitySelection(includeEntireCategory: false)
result.applicationTokens = source.applicationTokens
return result
}
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)")
}
}
}