NotificationFormatter.swift (7441B)
1 // 2 // NotificationFormatter.swift 3 // DamusNotificationService 4 // 5 // Created by Daniel D’Aquino on 2023-11-13. 6 // 7 8 import Foundation 9 import UserNotifications 10 11 struct NotificationFormatter { 12 static var shared = NotificationFormatter() 13 14 // MARK: - Formatting with NdbNote 15 16 func format_message(event: NdbNote) -> UNMutableNotificationContent? { 17 let content = UNMutableNotificationContent() 18 if let event_json_data = try? JSONEncoder().encode(event), // Must be encoded, as the notification completion handler requires this object to conform to `NSSecureCoding` 19 let event_json_string = String(data: event_json_data, encoding: .utf8) { 20 content.userInfo = [ 21 NDB_NOTE_JSON_USER_INFO_KEY: event_json_string 22 ] 23 } 24 switch event.known_kind { 25 case .text: 26 content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note") 27 content.body = event.content 28 break 29 case .dm: 30 content.title = NSLocalizedString("New message", comment: "Title label for push notifications where a direct message was sent to the user") 31 content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted") 32 break 33 case .like: 34 guard let reactionEmoji = to_reaction_emoji(ev: event) else { 35 content.title = NSLocalizedString("Someone reacted to your note", comment: "Generic title label for push notifications where someone reacted to the user's post") 36 break 37 } 38 content.title = NSLocalizedString("New note reaction", comment: "Title label for push notifications where someone reacted to the user's post with a specific emoji") 39 content.body = String(format: NSLocalizedString("Someone reacted to your note with %@", comment: "Body label for push notifications where someone reacted to the user's post with a specific emoji"), reactionEmoji) 40 break 41 case .zap: 42 content.title = NSLocalizedString("Someone zapped you ⚡️", comment: "Title label for a push notification where someone zapped the user") 43 break 44 default: 45 return nil 46 } 47 return content 48 } 49 50 // MARK: - Formatting with LocalNotification 51 52 func format_message(displayName: String, notify: LocalNotification) -> (content: UNMutableNotificationContent, identifier: String)? { 53 let content = UNMutableNotificationContent() 54 var title = "" 55 var identifier = "" 56 57 switch notify.type { 58 case .tagged: 59 title = String(format: NSLocalizedString("Tagged by %@", comment: "Tagged by heading in local notification"), displayName) 60 identifier = "myMentionNotification" 61 case .mention: 62 title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName) 63 identifier = "myMentionNotification" 64 case .repost: 65 title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName) 66 identifier = "myBoostNotification" 67 case .like: 68 title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), displayName, to_reaction_emoji(ev: notify.event) ?? "") 69 identifier = "myLikeNotification" 70 case .dm: 71 title = displayName 72 identifier = "myDMNotification" 73 case .zap, .profile_zap: 74 // not handled here. Try `format_message(displayName: String, notify: LocalNotification, state: HeadlessDamusState) async -> (content: UNMutableNotificationContent, identifier: String)?` 75 return nil 76 case .reply: 77 title = String(format: NSLocalizedString("%@ replied to your note", comment: "Heading for local notification indicating a new reply"), displayName) 78 identifier = "myReplyNotification" 79 } 80 content.title = title 81 content.body = notify.content 82 content.sound = UNNotificationSound.default 83 content.userInfo = notify.to_lossy().to_user_info() 84 85 return (content, identifier) 86 } 87 88 func format_message(displayName: String, notify: LocalNotification, state: HeadlessDamusState) async -> (content: UNMutableNotificationContent, identifier: String)? { 89 // Try sync method first and return if it works 90 if let sync_formatted_message = self.format_message(displayName: displayName, notify: notify) { 91 return sync_formatted_message 92 } 93 94 // If it does not work, try async formatting methods 95 let content = UNMutableNotificationContent() 96 97 switch notify.type { 98 case .zap, .profile_zap: 99 guard let zap = await get_zap(from: notify.event, state: state) else { 100 Log.debug("format_message: async get_zap failed", for: .push_notifications) 101 return nil 102 } 103 content.title = Self.zap_notification_title(zap) 104 content.body = Self.zap_notification_body(profiles: state.profiles, zap: zap) 105 content.sound = UNNotificationSound.default 106 content.userInfo = LossyLocalNotification(type: .zap, mention: .note(notify.event.id)).to_user_info() 107 return (content, "myZapNotification") 108 default: 109 // The sync method should have taken care of this. 110 return nil 111 } 112 } 113 114 // MARK: - Formatting zap utility notifications 115 116 static func zap_notification_title(_ zap: Zap) -> String { 117 if zap.private_request != nil { 118 return NSLocalizedString("Private Zap", comment: "Title of notification when a private zap is received.") 119 } else { 120 return NSLocalizedString("Zap", comment: "Title of notification when a non-private zap is received.") 121 } 122 } 123 124 static func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String { 125 let src = zap.request.ev 126 let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey 127 128 let profile_txn = profiles.lookup(id: pk) 129 let profile = profile_txn?.unsafeUnownedValue 130 let name = Profile.displayName(profile: profile, pubkey: pk).displayName.truncate(maxLength: 50) 131 132 let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0)) 133 let formattedSats = format_msats_abbrev(zap.invoice.amount) 134 135 if src.content.isEmpty { 136 let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale) 137 return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats, name) 138 } else { 139 let format = localizedStringFormat(key: "zap_notification_with_message", locale: locale) 140 return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats, name, src.content) 141 } 142 } 143 }