Harden iOS app: fix concurrency, animations, formatters, privacy, and logging
- Eliminate NumberFormatters shared singleton data race; use local formatters - Add reduceMotion checks to empty-state animations in 3 list views - Wrap 68+ print() statements in #if DEBUG across push notification code - Remove redundant .receive(on: DispatchQueue.main) in SubscriptionCache - Remove redundant initializeLookups() call from iOSApp.init() - Clean up StoreKitManager Task capture in listenForTransactions() - Add memory warning observer to AuthenticatedImage cache - Cache parseContent result in UpgradePromptView init - Add DiskSpace and FileTimestamp API declarations to Privacy Manifest - Add FIXME for analytics debug/production API key separation - Use static formatter in PropertyHeaderCard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import UserNotifications
|
||||
import BackgroundTasks
|
||||
import ComposeApp
|
||||
|
||||
@MainActor
|
||||
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
|
||||
func application(
|
||||
@@ -19,20 +20,20 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
BackgroundTaskManager.shared.registerBackgroundTasks()
|
||||
|
||||
// Request notification permission
|
||||
Task { @MainActor in
|
||||
Task {
|
||||
await PushNotificationManager.shared.requestNotificationPermission()
|
||||
}
|
||||
|
||||
// Clear badge when app launches
|
||||
Task { @MainActor in
|
||||
PushNotificationManager.shared.clearBadge()
|
||||
}
|
||||
PushNotificationManager.shared.clearBadge()
|
||||
|
||||
// Initialize StoreKit and check for existing subscriptions
|
||||
// This ensures we have the user's subscription status ready before they interact
|
||||
Task {
|
||||
_ = StoreKitManager.shared
|
||||
print("✅ StoreKit initialized at app launch")
|
||||
#if DEBUG
|
||||
print("StoreKit initialized at app launch")
|
||||
#endif
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -42,9 +43,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Clear badge when app becomes active
|
||||
Task { @MainActor in
|
||||
PushNotificationManager.shared.clearBadge()
|
||||
}
|
||||
PushNotificationManager.shared.clearBadge()
|
||||
|
||||
// Refresh StoreKit subscription status when app comes to foreground
|
||||
// This ensures we have the latest subscription state if it changed while app was in background
|
||||
@@ -59,31 +58,29 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
_ application: UIApplication,
|
||||
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
||||
) {
|
||||
Task { @MainActor in
|
||||
PushNotificationManager.shared.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
|
||||
}
|
||||
PushNotificationManager.shared.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
|
||||
}
|
||||
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
didFailToRegisterForRemoteNotificationsWithError error: Error
|
||||
) {
|
||||
Task { @MainActor in
|
||||
PushNotificationManager.shared.didFailToRegisterForRemoteNotifications(withError: error)
|
||||
}
|
||||
PushNotificationManager.shared.didFailToRegisterForRemoteNotifications(withError: error)
|
||||
}
|
||||
|
||||
// MARK: - UNUserNotificationCenterDelegate
|
||||
|
||||
// Called when notification is received while app is in foreground
|
||||
func userNotificationCenter(
|
||||
nonisolated func userNotificationCenter(
|
||||
_ center: UNUserNotificationCenter,
|
||||
willPresent notification: UNNotification,
|
||||
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
||||
) {
|
||||
#if DEBUG
|
||||
let userInfo = notification.request.content.userInfo
|
||||
let payloadKeys = Array(userInfo.keys).map { String(describing: $0) }.sorted()
|
||||
print("📬 Notification received in foreground. Keys: \(payloadKeys)")
|
||||
print("Notification received in foreground. Keys: \(payloadKeys)")
|
||||
#endif
|
||||
|
||||
// Passive mode in foreground: present banner/sound, but do not
|
||||
// mutate read state or trigger navigation automatically.
|
||||
@@ -91,7 +88,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
}
|
||||
|
||||
// Called when user taps on notification or selects an action
|
||||
func userNotificationCenter(
|
||||
nonisolated func userNotificationCenter(
|
||||
_ center: UNUserNotificationCenter,
|
||||
didReceive response: UNNotificationResponse,
|
||||
withCompletionHandler completionHandler: @escaping () -> Void
|
||||
@@ -99,9 +96,11 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
let actionIdentifier = response.actionIdentifier
|
||||
|
||||
print("👆 User interacted with notification - Action: \(actionIdentifier)")
|
||||
#if DEBUG
|
||||
print("User interacted with notification - Action: \(actionIdentifier)")
|
||||
let payloadKeys = Array(userInfo.keys).map { String(describing: $0) }.sorted()
|
||||
print(" Payload keys: \(payloadKeys)")
|
||||
#endif
|
||||
|
||||
Task { @MainActor in
|
||||
// Handle action buttons or default tap
|
||||
@@ -109,8 +108,9 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
// User tapped the notification body - navigate to task
|
||||
PushNotificationManager.shared.handleNotificationTap(userInfo: userInfo)
|
||||
} else if actionIdentifier == UNNotificationDismissActionIdentifier {
|
||||
// User dismissed the notification
|
||||
print("📤 Notification dismissed")
|
||||
#if DEBUG
|
||||
print("Notification dismissed")
|
||||
#endif
|
||||
} else {
|
||||
// User selected an action button
|
||||
PushNotificationManager.shared.handleNotificationAction(
|
||||
@@ -118,8 +118,8 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
userInfo: userInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler()
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ struct NotificationCategories {
|
||||
static func registerCategories() {
|
||||
let categories = createAllCategories()
|
||||
UNUserNotificationCenter.current().setNotificationCategories(categories)
|
||||
#if DEBUG
|
||||
print("Registered \(categories.count) notification categories")
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Creates all notification categories for the app
|
||||
|
||||
@@ -3,8 +3,9 @@ import UIKit
|
||||
import UserNotifications
|
||||
import ComposeApp
|
||||
|
||||
@MainActor
|
||||
class PushNotificationManager: NSObject, ObservableObject {
|
||||
@MainActor static let shared = PushNotificationManager()
|
||||
static let shared = PushNotificationManager()
|
||||
|
||||
@Published var deviceToken: String?
|
||||
@Published var notificationPermissionGranted = false
|
||||
@@ -43,18 +44,24 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
notificationPermissionGranted = granted
|
||||
|
||||
if granted {
|
||||
#if DEBUG
|
||||
print("✅ Notification permission granted")
|
||||
#endif
|
||||
// Register for remote notifications on main thread
|
||||
await MainActor.run {
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("❌ Notification permission denied")
|
||||
#endif
|
||||
}
|
||||
|
||||
return granted
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error requesting notification permission: \(error)")
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -65,16 +72,21 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
|
||||
self.deviceToken = tokenString
|
||||
let redactedToken = "\(tokenString.prefix(8))...\(tokenString.suffix(8))"
|
||||
#if DEBUG
|
||||
print("📱 APNs device token: \(redactedToken)")
|
||||
#endif
|
||||
|
||||
// Register with backend
|
||||
Task {
|
||||
await registerDeviceWithBackend(token: tokenString)
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.registerDeviceWithBackend(token: tokenString)
|
||||
}
|
||||
}
|
||||
|
||||
func didFailToRegisterForRemoteNotifications(withError error: Error) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to register for remote notifications: \(error)")
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Backend Registration
|
||||
@@ -82,30 +94,38 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
/// Call this after login to register any pending device token
|
||||
func registerDeviceAfterLogin() {
|
||||
guard let token = deviceToken else {
|
||||
#if DEBUG
|
||||
print("⚠️ No device token available for registration")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
await registerDeviceWithBackend(token: token, force: false)
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.registerDeviceWithBackend(token: token, force: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this when app returns from background to check and register if needed
|
||||
func checkAndRegisterDeviceIfNeeded() {
|
||||
guard let token = deviceToken else {
|
||||
#if DEBUG
|
||||
print("⚠️ No device token available for registration check")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
await registerDeviceWithBackend(token: token, force: false)
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.registerDeviceWithBackend(token: token, force: false)
|
||||
}
|
||||
}
|
||||
|
||||
private func registerDeviceWithBackend(token: String, force: Bool = false) async {
|
||||
guard TokenStorage.shared.getToken() != nil else {
|
||||
#if DEBUG
|
||||
print("⚠️ No auth token available, will register device after login")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,12 +136,16 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
// Skip only if both token and user identity match.
|
||||
if !force, token == lastRegisteredToken {
|
||||
if let currentUserId, currentUserId == lastRegisteredUserId {
|
||||
#if DEBUG
|
||||
print("📱 Device token already registered for current user, skipping")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
|
||||
if currentUserId == nil, lastRegisteredUserId == nil {
|
||||
#if DEBUG
|
||||
print("📱 Device token already registered, skipping")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -145,55 +169,73 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
let result = try await APILayer.shared.registerDevice(request: request)
|
||||
|
||||
if let success = result as? ApiResultSuccess<DeviceRegistrationResponse> {
|
||||
#if DEBUG
|
||||
if success.data != nil {
|
||||
print("✅ Device registered successfully")
|
||||
} else {
|
||||
print("✅ Device registration acknowledged")
|
||||
}
|
||||
#endif
|
||||
// Cache the token on successful registration
|
||||
await MainActor.run {
|
||||
self.lastRegisteredToken = token
|
||||
self.lastRegisteredUserId = currentUserId
|
||||
}
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to register device: \(error.message)")
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result type from device registration")
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error registering device: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Handle Notifications
|
||||
|
||||
func handleNotification(userInfo: [AnyHashable: Any]) {
|
||||
#if DEBUG
|
||||
print("📬 Received notification: \(redactedPayloadSummary(userInfo: userInfo))")
|
||||
#endif
|
||||
|
||||
// Extract notification data
|
||||
if let notificationId = stringValue(for: "notification_id", in: userInfo) {
|
||||
#if DEBUG
|
||||
print("Notification ID: \(notificationId)")
|
||||
#endif
|
||||
|
||||
// Mark as read when user taps notification
|
||||
Task {
|
||||
await markNotificationAsRead(notificationId: notificationId)
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.markNotificationAsRead(notificationId: notificationId)
|
||||
}
|
||||
}
|
||||
|
||||
if let type = userInfo["type"] as? String {
|
||||
#if DEBUG
|
||||
print("Notification type: \(type)")
|
||||
#endif
|
||||
handleNotificationType(type: type, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when user taps the notification body (not an action button)
|
||||
func handleNotificationTap(userInfo: [AnyHashable: Any]) {
|
||||
#if DEBUG
|
||||
print("📬 Handling notification tap")
|
||||
#endif
|
||||
|
||||
// Mark as read
|
||||
if let notificationId = stringValue(for: "notification_id", in: userInfo) {
|
||||
Task {
|
||||
await markNotificationAsRead(notificationId: notificationId)
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.markNotificationAsRead(notificationId: notificationId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +246,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
let limitationsEnabled = subscription?.limitationsEnabled ?? false // Default to false (allow) if not loaded
|
||||
let canNavigateToTask = isPremium || !limitationsEnabled
|
||||
|
||||
#if DEBUG
|
||||
print("📬 Push nav check: isPremium=\(isPremium), limitationsEnabled=\(limitationsEnabled), canNavigate=\(canNavigateToTask), subscription=\(subscription != nil ? "loaded" : "nil")")
|
||||
#endif
|
||||
|
||||
if canNavigateToTask {
|
||||
// Navigate to task detail
|
||||
@@ -224,12 +268,15 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
|
||||
/// Called when user selects an action button on the notification
|
||||
func handleNotificationAction(actionIdentifier: String, userInfo: [AnyHashable: Any]) {
|
||||
#if DEBUG
|
||||
print("🔘 Handling notification action: \(actionIdentifier)")
|
||||
#endif
|
||||
|
||||
// Mark as read
|
||||
if let notificationId = stringValue(for: "notification_id", in: userInfo) {
|
||||
Task {
|
||||
await markNotificationAsRead(notificationId: notificationId)
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.markNotificationAsRead(notificationId: notificationId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +295,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
|
||||
// Extract task ID
|
||||
guard let taskId = intValue(for: "task_id", in: userInfo) else {
|
||||
#if DEBUG
|
||||
print("❌ No task_id found in notification")
|
||||
#endif
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,7 +322,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
navigateToEditTask(taskId: taskId)
|
||||
|
||||
default:
|
||||
#if DEBUG
|
||||
print("⚠️ Unknown action: \(actionIdentifier)")
|
||||
#endif
|
||||
navigateToTask(taskId: taskId)
|
||||
}
|
||||
}
|
||||
@@ -282,38 +333,53 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
switch type {
|
||||
case "task_due_soon", "task_overdue", "task_completed", "task_assigned":
|
||||
if let taskId = intValue(for: "task_id", in: userInfo) {
|
||||
#if DEBUG
|
||||
print("Task notification for task ID: \(taskId)")
|
||||
#endif
|
||||
navigateToTask(taskId: taskId)
|
||||
}
|
||||
|
||||
case "residence_shared":
|
||||
if let residenceId = intValue(for: "residence_id", in: userInfo) {
|
||||
#if DEBUG
|
||||
print("Residence shared notification for residence ID: \(residenceId)")
|
||||
#endif
|
||||
navigateToResidence(residenceId: residenceId)
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("Residence shared notification without residence ID")
|
||||
#endif
|
||||
navigateToResidencesTab()
|
||||
}
|
||||
|
||||
case "warranty_expiring":
|
||||
if let documentId = intValue(for: "document_id", in: userInfo) {
|
||||
#if DEBUG
|
||||
print("Warranty expiring notification for document ID: \(documentId)")
|
||||
#endif
|
||||
navigateToDocument(documentId: documentId)
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("Warranty expiring notification without document ID")
|
||||
#endif
|
||||
navigateToDocumentsTab()
|
||||
}
|
||||
|
||||
default:
|
||||
#if DEBUG
|
||||
print("Unknown notification type: \(type)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Task Actions
|
||||
|
||||
private func performCompleteTask(taskId: Int) {
|
||||
#if DEBUG
|
||||
print("✅ Completing task \(taskId) from notification action")
|
||||
Task {
|
||||
#endif
|
||||
Task { [weak self] in
|
||||
guard let _ = self else { return }
|
||||
do {
|
||||
// Quick complete without photos/notes
|
||||
let request = TaskCompletionCreateRequest(
|
||||
@@ -327,84 +393,125 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
let result = try await APILayer.shared.createTaskCompletion(request: request)
|
||||
|
||||
if result is ApiResultSuccess<TaskCompletionResponse> {
|
||||
#if DEBUG
|
||||
print("✅ Task \(taskId) completed successfully")
|
||||
#endif
|
||||
// Post notification for UI refresh
|
||||
await MainActor.run {
|
||||
NotificationCenter.default.post(name: .taskActionCompleted, object: nil)
|
||||
}
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to complete task: \(error.message)")
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while completing task \(taskId)")
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error completing task: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performMarkInProgress(taskId: Int) {
|
||||
#if DEBUG
|
||||
print("🔄 Marking task \(taskId) as in progress from notification action")
|
||||
Task {
|
||||
#endif
|
||||
Task { [weak self] in
|
||||
guard let _ = self else { return }
|
||||
do {
|
||||
let result = try await APILayer.shared.markInProgress(taskId: Int32(taskId))
|
||||
|
||||
if result is ApiResultSuccess<TaskResponse> {
|
||||
#if DEBUG
|
||||
print("✅ Task \(taskId) marked as in progress")
|
||||
#endif
|
||||
await MainActor.run {
|
||||
NotificationCenter.default.post(name: .taskActionCompleted, object: nil)
|
||||
}
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to mark task in progress: \(error.message)")
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while marking task \(taskId) in progress")
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error marking task in progress: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performCancelTask(taskId: Int) {
|
||||
#if DEBUG
|
||||
print("🚫 Cancelling task \(taskId) from notification action")
|
||||
Task {
|
||||
#endif
|
||||
Task { [weak self] in
|
||||
guard let _ = self else { return }
|
||||
do {
|
||||
let result = try await APILayer.shared.cancelTask(taskId: Int32(taskId))
|
||||
|
||||
if result is ApiResultSuccess<TaskResponse> {
|
||||
#if DEBUG
|
||||
print("✅ Task \(taskId) cancelled")
|
||||
#endif
|
||||
await MainActor.run {
|
||||
NotificationCenter.default.post(name: .taskActionCompleted, object: nil)
|
||||
}
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to cancel task: \(error.message)")
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while cancelling task \(taskId)")
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error cancelling task: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performUncancelTask(taskId: Int) {
|
||||
#if DEBUG
|
||||
print("↩️ Uncancelling task \(taskId) from notification action")
|
||||
Task {
|
||||
#endif
|
||||
Task { [weak self] in
|
||||
guard let _ = self else { return }
|
||||
do {
|
||||
let result = try await APILayer.shared.uncancelTask(taskId: Int32(taskId))
|
||||
|
||||
if result is ApiResultSuccess<TaskResponse> {
|
||||
#if DEBUG
|
||||
print("✅ Task \(taskId) uncancelled")
|
||||
#endif
|
||||
await MainActor.run {
|
||||
NotificationCenter.default.post(name: .taskActionCompleted, object: nil)
|
||||
}
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to uncancel task: \(error.message)")
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while uncancelling task \(taskId)")
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error uncancelling task: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,7 +519,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
// MARK: - Navigation
|
||||
|
||||
private func navigateToTask(taskId: Int) {
|
||||
#if DEBUG
|
||||
print("📱 Navigating to task \(taskId)")
|
||||
#endif
|
||||
// Store pending navigation in case MainTabView isn't ready yet
|
||||
pendingNavigationTaskId = taskId
|
||||
NotificationCenter.default.post(
|
||||
@@ -436,7 +545,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
private func navigateToEditTask(taskId: Int) {
|
||||
#if DEBUG
|
||||
print("✏️ Navigating to edit task \(taskId)")
|
||||
#endif
|
||||
NotificationCenter.default.post(
|
||||
name: .navigateToEditTask,
|
||||
object: nil,
|
||||
@@ -445,7 +556,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
private func navigateToResidence(residenceId: Int) {
|
||||
#if DEBUG
|
||||
print("🏠 Navigating to residence \(residenceId)")
|
||||
#endif
|
||||
pendingNavigationResidenceId = residenceId
|
||||
NotificationCenter.default.post(
|
||||
name: .navigateToResidence,
|
||||
@@ -459,7 +572,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
private func navigateToDocument(documentId: Int) {
|
||||
#if DEBUG
|
||||
print("📄 Navigating to document \(documentId)")
|
||||
#endif
|
||||
pendingNavigationDocumentId = documentId
|
||||
NotificationCenter.default.post(
|
||||
name: .navigateToDocument,
|
||||
@@ -473,7 +588,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
private func navigateToHome() {
|
||||
#if DEBUG
|
||||
print("🏠 Navigating to home")
|
||||
#endif
|
||||
pendingNavigationTaskId = nil
|
||||
pendingNavigationResidenceId = nil
|
||||
pendingNavigationDocumentId = nil
|
||||
@@ -519,14 +636,22 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
let result = try await APILayer.shared.markNotificationAsRead(notificationId: notificationIdInt)
|
||||
|
||||
if result is ApiResultSuccess<MessageResponse> {
|
||||
#if DEBUG
|
||||
print("✅ Notification marked as read")
|
||||
#endif
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to mark notification as read: \(error.message)")
|
||||
#endif
|
||||
} else {
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while marking notification read")
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error marking notification as read: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +659,9 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
|
||||
func updateNotificationPreferences(_ preferences: UpdateNotificationPreferencesRequest) async -> Bool {
|
||||
guard TokenStorage.shared.getToken() != nil else {
|
||||
#if DEBUG
|
||||
print("⚠️ No auth token available")
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -542,23 +669,33 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
let result = try await APILayer.shared.updateNotificationPreferences(request: preferences)
|
||||
|
||||
if result is ApiResultSuccess<NotificationPreference> {
|
||||
#if DEBUG
|
||||
print("✅ Notification preferences updated")
|
||||
#endif
|
||||
return true
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to update preferences: \(error.message)")
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while updating notification preferences")
|
||||
#endif
|
||||
return false
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error updating notification preferences: \(error.localizedDescription)")
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getNotificationPreferences() async -> NotificationPreference? {
|
||||
guard TokenStorage.shared.getToken() != nil else {
|
||||
#if DEBUG
|
||||
print("⚠️ No auth token available")
|
||||
#endif
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -568,13 +705,19 @@ class PushNotificationManager: NSObject, ObservableObject {
|
||||
if let success = result as? ApiResultSuccess<NotificationPreference> {
|
||||
return success.data
|
||||
} else if let error = ApiResultBridge.error(from: result) {
|
||||
#if DEBUG
|
||||
print("❌ Failed to get preferences: \(error.message)")
|
||||
#endif
|
||||
return nil
|
||||
}
|
||||
#if DEBUG
|
||||
print("⚠️ Unexpected result while loading notification preferences")
|
||||
#endif
|
||||
return nil
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ Error getting notification preferences: \(error.localizedDescription)")
|
||||
#endif
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user