Reputation: 1
So I have been working on this system of selecting apps to shield them. then you can unshield those applications and they should automatically reshield. however after I stop tasks in Xcode they only reshield if I put my own app in the foreground. I have a notification which generates at the same time as the app should be reshielded and interestingly it still generates after I stop tasks in Xcode but it doesnt reshield. this is my relevant code:
@main
struct YourApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
if url.absoluteString == "yourapp://navigateToSwiftUIView" {
// Navigate to SwiftUIView if this URL scheme is opened
NotificationCenter.default.post(name: .navigateToSwiftUIView, object: nil)
}
}
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
// Register the background refresh task for reshielding
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.yourapp.refreshShield", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if let appData = response.notification.request.content.userInfo["appToken"] as? Data,
let appToken = try? JSONDecoder().decode(ApplicationToken.self, from: appData) {
NotificationCenter.default.post(name: .navigateToSwiftUIView, object: nil, userInfo: ["appToken": appToken])
}
completionHandler()
}
// MARK: - Background Task Handling
func handleAppRefresh(task: BGAppRefreshTask) {
// Reschedule the background refresh
scheduleAppRefresh()
// Perform reshielding check
TimerManager.shared.timers.keys.forEach { appToken in
TimerManager.shared.reapplyShieldIfNeeded(for: appToken)
}
task.setTaskCompleted(success: true)
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.yourapp.refreshShield")
request.earliestBeginDate = Date(timeIntervalSinceNow: 10) // Adjust timing as needed
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Failed to submit BGAppRefreshTask: \(error)")
}
}
}
class TimerManager: ObservableObject {
var timers = [ApplicationToken: Timer]()
private let store = ManagedSettingsStore()
private var unshieldedApps = Set<ApplicationToken>()
// Dictionary to store unshield logs with timestamps for calculating daily, weekly, monthly counts
private var unshieldLogs = [String: [Date]]()
private var hourlyUnshieldLogs = [String: [Int]]()
static let shared = TimerManager()
private init() {
loadUnshieldLogs()
}
func isAppUnshielded(_ appToken: ApplicationToken) -> Bool {
return unshieldedApps.contains(appToken)
}
func pauseShield(for appToken: ApplicationToken) {
unshieldedApps.insert(appToken)
// Immediate unshield
store.shield.applications?.remove(appToken)
logUnshieldEvent(for: appToken)
logHourlyUnshieldEvent(for: appToken)
// Schedule a 10-second Timer
let timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in
self?.reapplyShieldIfNeeded(for: appToken)
self?.timers[appToken] = nil
}
timers[appToken] = timer
// Schedule a background notification as backup
scheduleReapplyShieldNotification(for: appToken)
}
private func scheduleReapplyShieldNotification(for appToken: ApplicationToken) {
let content = UNMutableNotificationContent()
content.title = "Reapplying Shield"
content.body = "Shield will be reapplied for the selected app."
// Pass appToken data for decoding in NotificationServiceExtension
if let appData = try? JSONEncoder().encode(appToken) {
content.userInfo = ["appToken": appData]
}
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "ReapplyShield_\(appToken)", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
func reapplyShield(for appToken: ApplicationToken) {
store.shield.applications?.insert(appToken)
unshieldedApps.remove(appToken)
}
func reapplyShieldIfNeeded(for appToken: ApplicationToken) {
// Check if the app should still be reshielded
if unshieldedApps.contains(appToken) {
reapplyShield(for: appToken)
}
}
}
class NotificationServiceExtension: UNNotificationServiceExtension {
private let store = ManagedSettingsStore()
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
if let appData = request.content.userInfo["appToken"] as? Data,
let appToken = try? JSONDecoder().decode(ApplicationToken.self, from: appData) {
TimerManager.shared.reapplyShield(for: appToken)
}
contentHandler(request.content)
}
}
Upvotes: 0
Views: 32