AutoBackupCoordinator (new):
- @MainActor singleton that owns all auto-backup decisions
- Phase model: idle / checking / autoBackingUp / disconnected
- idle: nothing pending or destination not set
- checking: reconciliation in progress before decision
- autoBackingUp: coordinator-triggered BackupEngine run active
- disconnected: NAS offline, pending count queued for retry
- Combine subscription on BackupStatusService.$isRefreshing fires
handleReconciliationComplete() on every reconcile completion — the
single entry point for all auto-backup decisions
- Combine subscription on LANMonitor.$nasReachable fires
handleNASReachable() which retries only when phase == .disconnected
- Guards: autoBackupEnabled, quiet hours, chargingOnlyMode + battery state
- canAutoBackup also blocks when engine.job.status.isActive to prevent
double-start from concurrent photo library change + foreground events
BackupView:
- .task + .onChange(of: scenePhase) now call coordinator.onActive() instead
of statusService.refresh() directly — coordinator decides whether to also
start a backup after reconciliation completes
- ringContentID includes coordinator phase suffix → ring animates on
checking/disconnected transitions
- ringCenterContent case 0: when engine is idle, shows:
- disconnectedRingView if phase == .disconnected and needBackup > 0
(wifi.slash icon, "Waiting for NAS", queued count)
- checkingRingView if phase == .checking (spinner, "Checking…")
- existing ringMainView otherwise
- ringColor: dim orange (0.35 opacity) when disconnected with pending items
- Pull-to-refresh reconciliation triggers auto-backup via Combine chain
ConnectionStore:
- autoBackupEnabled: Bool, default true, UserDefaults-persisted
SettingsView:
- AUTO BACKUP section at top with autoBackupEnabled toggle
BackgroundTaskManager:
- BGProcessingTask: quiet-hours guard, requiresExternalPower mirrors
chargingOnlyMode, expiration handler calls engine.cancel() for clean exit
- BGAppRefreshTask: lightweight refresh only (no upload in background refresh)
- Both tasks reschedule themselves before doing any work
AppDelegate:
- UIDevice.current.isBatteryMonitoringEnabled = true for battery checks
NASBackupApp:
- AutoBackupCoordinator.shared injected as @StateObject + environmentObject
Co-Authored-By: Kutesir <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
36 lines
1.2 KiB
Swift
36 lines
1.2 KiB
Swift
import UIKit
|
|
import BackgroundTasks
|
|
import UserNotifications
|
|
|
|
final class AppDelegate: NSObject, UIApplicationDelegate {
|
|
func application(
|
|
_ application: UIApplication,
|
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
) -> Bool {
|
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
BackgroundTaskManager.registerTasks()
|
|
requestNotificationPermission()
|
|
setupLANMonitor()
|
|
return true
|
|
}
|
|
|
|
private func requestNotificationPermission() {
|
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in }
|
|
}
|
|
|
|
private func setupLANMonitor() {
|
|
let monitor = LANMonitor.shared
|
|
monitor.onTrustedNetworkJoined = { ssid in
|
|
let store = ConnectionStore.shared
|
|
guard store.lanTriggerEnabled, !store.isInQuietHours else { return }
|
|
let delay = TimeInterval(store.startDelaySeconds)
|
|
BackgroundTaskManager.scheduleBackup(delay: delay)
|
|
}
|
|
monitor.start()
|
|
}
|
|
|
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
|
BackgroundTaskManager.scheduleAppRefresh()
|
|
}
|
|
}
|