Files
Reflect/Shared/FeelsApp.swift
Trey t f7da61d6ca Consolidate extension data providers and add side effects catch-up
- Create unified ExtensionDataProvider for Widget and Watch targets
- Remove duplicate WatchDataProvider and WatchConnectivityManager from Watch App
- Add side effects catch-up mechanism in MoodLogger for widget votes
- Process pending side effects on app launch and midnight background task
- Reduce ~450 lines of duplicated code across targets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 22:50:23 -06:00

100 lines
3.7 KiB
Swift

//
// FeelsApp.swift
// Shared
//
// Created by Trey Tartt on 1/5/22.
//
import SwiftUI
import SwiftData
import BackgroundTasks
import WidgetKit
import TipKit
@main
struct FeelsApp: App {
@Environment(\.scenePhase) private var scenePhase
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
let dataController = DataController.shared
@StateObject var iapManager = IAPManager()
@StateObject var authManager = BiometricAuthManager()
@StateObject var healthKitManager = HealthKitManager.shared
@AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) private var firstLaunchDate = Date()
@State private var showSubscriptionFromWidget = false
init() {
BGTaskScheduler.shared.cancelAllTaskRequests()
BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTask.updateDBMissingID, using: nil) { (task) in
BGTask.runFillInMissingDatesTask(task: task as! BGProcessingTask)
}
UNUserNotificationCenter.current().setBadgeCount(0)
// Configure TipKit
TipsManager.shared.configure()
// Initialize Live Activity scheduler
LiveActivityScheduler.shared.scheduleBasedOnCurrentTime()
// Initialize Watch Connectivity for cross-device widget updates
_ = WatchConnectivityManager.shared
}
var body: some Scene {
WindowGroup {
ZStack {
MainTabView(dayView: DayView(viewModel: DayViewViewModel(addMonthStartWeekdayPadding: false)),
monthView: MonthView(viewModel: DayViewViewModel(addMonthStartWeekdayPadding: true)),
yearView: YearView(viewModel: YearViewModel()),
insightsView: InsightsView())
.modelContainer(dataController.container)
.environmentObject(iapManager)
.environmentObject(authManager)
.environmentObject(healthKitManager)
.sheet(isPresented: $showSubscriptionFromWidget) {
FeelsSubscriptionStoreView()
.environmentObject(iapManager)
}
.onOpenURL { url in
if url.scheme == "feels" && url.host == "subscribe" {
showSubscriptionFromWidget = true
}
}
// Lock screen overlay
if authManager.isLockEnabled && !authManager.isUnlocked {
LockScreenView(authManager: authManager)
.transition(.opacity)
}
}
}.onChange(of: scenePhase) { _, newPhase in
if newPhase == .background {
BGTask.scheduleBackgroundProcessing()
WidgetCenter.shared.reloadAllTimelines()
// Lock the app when going to background
authManager.lock()
}
if newPhase == .active {
UNUserNotificationCenter.current().setBadgeCount(0)
// Check subscription status on each app launch
Task {
await iapManager.checkSubscriptionStatus()
}
// Authenticate if locked
if authManager.isLockEnabled && !authManager.isUnlocked {
Task {
await authManager.authenticate()
}
}
// Reschedule Live Activity when app becomes active
LiveActivityScheduler.shared.scheduleBasedOnCurrentTime()
// Catch up on side effects from widget/watch votes
MoodLogger.shared.processPendingSideEffects()
}
}
}
}