DamusPurpleNotificationManagement.swift (3347B)
1 // 2 // DamusPurpleNotificationManagement.swift 3 // damus 4 // 5 // Created by Daniel D’Aquino on 2024-02-26. 6 // 7 8 import Foundation 9 10 /// A definition of how many days in advance to notify the user of impending expiration. (e.g. 3 days before expiration AND 2 days before expiration AND 1 day before expiration) 11 fileprivate let PURPLE_IMPENDING_EXPIRATION_NOTIFICATION_SCHEDULE: Set<Int> = [7, 3, 1] 12 fileprivate let ONE_DAY: TimeInterval = 60 * 60 * 24 13 14 extension DamusPurple { 15 typealias NotificationHandlerFunction = (DamusAppNotification) async -> Void 16 17 func check_and_send_app_notifications_if_needed(handler: NotificationHandlerFunction) async { 18 await self.check_and_send_purple_expiration_notifications_if_needed(handler: handler) 19 } 20 21 /// Checks if we need to send a DamusPurple impending expiration notification to the user, and sends them if needed. 22 /// 23 /// **Note:** To keep things simple at this point, this function uses a "best effort" strategy, and silently fails if something is wrong, as it is not an essential component of the app — to avoid adding more error handling complexity to the app 24 private func check_and_send_purple_expiration_notifications_if_needed(handler: NotificationHandlerFunction) async { 25 if self.storekit_manager.recorded_purchased_products.count > 0 { 26 // If user has a recurring IAP purchase, there no need to notify them of impending expiration 27 return 28 } 29 guard let purple_expiration_date: Date = try? await self.get_maybe_cached_account(pubkey: self.keypair.pubkey)?.expiry else { 30 return // If there are no expiry dates (e.g. The user is not a Purple user) or we cannot get it for some reason (e.g. server is temporarily down and we have no cache), don't bother sending notifications 31 } 32 33 let days_to_expiry: Int = round_days_to_date(purple_expiration_date, from: Date.now) 34 35 let applicable_impending_expiry_notification_schedule_items: [Int] = PURPLE_IMPENDING_EXPIRATION_NOTIFICATION_SCHEDULE.filter({ $0 >= days_to_expiry }) 36 37 for applicable_impending_expiry_notification_schedule_item in applicable_impending_expiry_notification_schedule_items { 38 // Send notifications predicted by the schedule 39 // Note: The `insert_app_notification` has built-in logic to prevent us from sending two identical notifications, so we need not worry about it here. 40 await handler(.init( 41 content: .purple_impending_expiration( 42 days_remaining: applicable_impending_expiry_notification_schedule_item, 43 expiry_date: UInt64(purple_expiration_date.timeIntervalSince1970) 44 ), 45 timestamp: purple_expiration_date.addingTimeInterval(-Double(applicable_impending_expiry_notification_schedule_item) * ONE_DAY)) 46 ) 47 } 48 49 if days_to_expiry < 0 { 50 await handler(.init( 51 content: .purple_expired(expiry_date: UInt64(purple_expiration_date.timeIntervalSince1970)), 52 timestamp: purple_expiration_date) 53 ) 54 } 55 } 56 } 57 58 fileprivate func round_days_to_date(_ target_date: Date, from from_date: Date) -> Int { 59 return Int(round(target_date.timeIntervalSince(from_date) / ONE_DAY)) 60 }