From 6a078be17ec0b6574759ed51311b100360c37142 Mon Sep 17 00:00:00 2001 From: CelloSerenity <195480169+CelloSerenity@users.noreply.github.com> Date: Sun, 27 Sep 2026 13:54:35 -0600 Subject: [PATCH] fix: require a background keep alive --- StikDebug/App/AppBootstrapper.swift | 11 ++- .../Services/BackgroundLocationManager.swift | 71 ++++++++++++------- StikDebug/Views/SettingsView.swift | 34 ++++++++- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/StikDebug/App/AppBootstrapper.swift b/StikDebug/App/AppBootstrapper.swift index 67b88ba..3a07ebc 100644 --- a/StikDebug/App/AppBootstrapper.swift +++ b/StikDebug/App/AppBootstrapper.swift @@ -29,10 +29,15 @@ enum AppBootstrapper { } private static func startConfiguredKeepAliveServices() { - guard UserDefaults.standard.bool(forKey: "keepAliveAudio") else { - return + let defaults = UserDefaults.standard + if defaults.bool(forKey: "keepAliveAudio") { + BackgroundAudioManager.shared.start() + } else if defaults.bool(forKey: "keepAliveLocation") { + BackgroundLocationManager.shared.start() + } else { + defaults.set(true, forKey: "keepAliveAudio") + BackgroundAudioManager.shared.start() } - BackgroundAudioManager.shared.start() } private static func applyDocumentPickerCopyWorkaround() { diff --git a/StikDebug/Services/BackgroundLocationManager.swift b/StikDebug/Services/BackgroundLocationManager.swift index 993da4c..589c0b7 100644 --- a/StikDebug/Services/BackgroundLocationManager.swift +++ b/StikDebug/Services/BackgroundLocationManager.swift @@ -10,6 +10,7 @@ final class BackgroundLocationManager: NSObject, CLLocationManagerDelegate { private let locationManager = CLLocationManager() private var isRunning = false + private var persistentEnabled = false private var activityCount = 0 private override init() { @@ -22,7 +23,35 @@ final class BackgroundLocationManager: NSObject, CLLocationManagerDelegate { } func start() { - isRunning = true + persistentEnabled = true + refreshRunningState() + } + + func stop() { + persistentEnabled = false + refreshRunningState() + } + + func requestStart() { + activityCount += 1 + refreshRunningState() + } + + func requestStop() { + activityCount = max(activityCount - 1, 0) + refreshRunningState() + } + + private func refreshRunningState() { + let shouldRun = persistentEnabled || (activityCount > 0 && UserDefaults.standard.bool(forKey: "keepAliveLocation")) + guard shouldRun != isRunning else { return } + + isRunning = shouldRun + guard shouldRun else { + locationManager.stopUpdatingLocation() + return + } + switch locationManager.authorizationStatus { case .authorizedAlways: locationManager.startUpdatingLocation() @@ -30,27 +59,10 @@ final class BackgroundLocationManager: NSObject, CLLocationManagerDelegate { locationManager.requestAlwaysAuthorization() case .notDetermined: locationManager.requestAlwaysAuthorization() - default: - break - } - } - - func stop() { - isRunning = false - locationManager.stopUpdatingLocation() - } - - func requestStart() { - activityCount += 1 - if activityCount == 1, UserDefaults.standard.bool(forKey: "keepAliveLocation") { - start() - } - } - - func requestStop() { - activityCount = max(activityCount - 1, 0) - if activityCount == 0 { - stop() + case .denied, .restricted: + restoreAudioFallback() + @unknown default: + restoreAudioFallback() } } @@ -59,11 +71,22 @@ final class BackgroundLocationManager: NSObject, CLLocationManagerDelegate { switch manager.authorizationStatus { case .authorizedAlways, .authorizedWhenInUse: manager.startUpdatingLocation() - default: - break + case .denied, .restricted: + restoreAudioFallback() + case .notDetermined: + return + @unknown default: + restoreAudioFallback() } } + private func restoreAudioFallback() { + persistentEnabled = false + isRunning = activityCount > 0 && UserDefaults.standard.bool(forKey: "keepAliveLocation") + UserDefaults.standard.set(true, forKey: "keepAliveAudio") + BackgroundAudioManager.shared.start() + } + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { // Location fixes may fail (e.g. no GPS indoors) — that's fine. // The manager just needs to be running, not actually fix a location. diff --git a/StikDebug/Views/SettingsView.swift b/StikDebug/Views/SettingsView.swift index eac156b..bc32e50 100644 --- a/StikDebug/Views/SettingsView.swift +++ b/StikDebug/Views/SettingsView.swift @@ -95,8 +95,7 @@ struct SettingsView: View { } } .onChange(of: keepAliveAudio) { _, enabled in - if enabled { BackgroundAudioManager.shared.start() } - else { BackgroundAudioManager.shared.stop() } + handleAudioKeepAliveChange(enabled) } Toggle(isOn: $keepAliveLocation) { @@ -107,7 +106,7 @@ struct SettingsView: View { } } .onChange(of: keepAliveLocation) { _, enabled in - if !enabled { BackgroundLocationManager.shared.stop() } + handleLocationKeepAliveChange(enabled) } } header: { @@ -242,6 +241,35 @@ struct SettingsView: View { return "Version \(appVersion) • iOS \(UIDevice.current.systemVersion) • \(txmLabel)" } + private func handleAudioKeepAliveChange(_ enabled: Bool) { + if enabled { + BackgroundAudioManager.shared.start() + BackgroundLocationManager.shared.stop() + return + } + + if !keepAliveLocation { + keepAliveLocation = true + } + BackgroundAudioManager.shared.stop() + BackgroundLocationManager.shared.start() + } + + private func handleLocationKeepAliveChange(_ enabled: Bool) { + if enabled { + if !keepAliveAudio { + BackgroundLocationManager.shared.start() + } + return + } + + BackgroundLocationManager.shared.stop() + if !keepAliveAudio { + keepAliveAudio = true + BackgroundAudioManager.shared.start() + } + } + // MARK: - Business Logic private func openAppFolder() {