Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers, StoreKit product IDs, data store filenames, URL schemes, logger subsystems, Swift identifiers, user-facing strings (7 languages), file names, directory names, Xcode project, schemes, assets, and documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
154
Shared/ReflectApp.swift
Normal file
154
Shared/ReflectApp.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// ReflectApp.swift
|
||||
// Shared
|
||||
//
|
||||
// Created by Trey Tartt on 1/5/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import BackgroundTasks
|
||||
import WidgetKit
|
||||
|
||||
@main
|
||||
struct ReflectApp: App {
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
let dataController = DataController.shared
|
||||
@StateObject var iapManager = IAPManager.shared
|
||||
@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
|
||||
@State private var showStorageFallbackAlert = SharedModelContainer.isUsingInMemoryFallback
|
||||
|
||||
init() {
|
||||
// Configure UI test mode before anything else
|
||||
if UITestMode.isUITesting {
|
||||
UITestMode.configureIfNeeded()
|
||||
}
|
||||
|
||||
AnalyticsManager.shared.configure()
|
||||
|
||||
BGTaskScheduler.shared.cancelAllTaskRequests()
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTask.updateDBMissingID, using: nil) { (task) in
|
||||
BGTask.runFillInMissingDatesTask(task: task as! BGProcessingTask)
|
||||
}
|
||||
UNUserNotificationCenter.current().setBadgeCount(0)
|
||||
|
||||
// Reset tips session on app launch
|
||||
ReflectTipsManager.shared.resetSession()
|
||||
|
||||
// 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) {
|
||||
ReflectSubscriptionStoreView(source: "widget_deeplink")
|
||||
.environmentObject(iapManager)
|
||||
}
|
||||
.onOpenURL { url in
|
||||
handleDeepLink(url)
|
||||
}
|
||||
.alert("Data Storage Unavailable",
|
||||
isPresented: $showStorageFallbackAlert) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text("Your mood data cannot be saved permanently. Please restart the app. If the problem persists, reinstall the app.")
|
||||
}
|
||||
.onAppear {
|
||||
if SharedModelContainer.isUsingInMemoryFallback {
|
||||
AnalyticsManager.shared.track(.storageFallbackActivated)
|
||||
}
|
||||
if let url = AppDelegate.pendingDeepLinkURL {
|
||||
AppDelegate.pendingDeepLinkURL = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
handleDeepLink(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// Flush pending analytics events
|
||||
AnalyticsManager.shared.flush()
|
||||
// Lock the app when going to background
|
||||
authManager.lock()
|
||||
}
|
||||
|
||||
if newPhase == .active {
|
||||
UNUserNotificationCenter.current().setBadgeCount(0)
|
||||
|
||||
// Authenticate if locked - this must happen immediately on main thread
|
||||
if authManager.isLockEnabled && !authManager.isUnlocked {
|
||||
Task {
|
||||
await authManager.authenticate()
|
||||
}
|
||||
}
|
||||
|
||||
// Defer all non-critical foreground work to avoid blocking UI
|
||||
Task(priority: .utility) {
|
||||
// Refresh from disk to pick up widget/watch changes
|
||||
DataController.shared.refreshFromDisk()
|
||||
|
||||
// Fill in any missing dates (moved from AppDelegate)
|
||||
DataController.shared.fillInMissingDates()
|
||||
|
||||
// Clean up any duplicate entries (after backfill so backfill dupes are caught)
|
||||
DataController.shared.removeDuplicates()
|
||||
|
||||
// Reschedule notifications for new title
|
||||
LocalNotification.rescheduleNotifiations()
|
||||
|
||||
// Update super properties on foreground
|
||||
AnalyticsManager.shared.updateSuperProperties()
|
||||
}
|
||||
|
||||
// Defer Live Activity scheduling (heavy DB operations)
|
||||
Task(priority: .utility) {
|
||||
await LiveActivityScheduler.shared.scheduleBasedOnCurrentTime()
|
||||
}
|
||||
|
||||
// Catch up on side effects from widget/watch votes
|
||||
Task(priority: .utility) {
|
||||
await MoodLogger.shared.processPendingSideEffects()
|
||||
}
|
||||
|
||||
// Check subscription status (network call) - throttled
|
||||
Task(priority: .background) {
|
||||
await iapManager.checkSubscriptionStatus()
|
||||
await iapManager.trackSubscriptionAnalytics(source: "app_foreground")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleDeepLink(_ url: URL) {
|
||||
if url.scheme == "reflect" && url.host == "subscribe" {
|
||||
showSubscriptionFromWidget = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user