Unfurl profile name on remote push notifications
This commit adds support for the unfurling of author profile names on remote push notifications It also makes the following changes: - Notification extension now uses NdbNote - Some of the logic between push notifications and local notifications was unified Testing ------- Device: iPhone 15 Pro simulator iOS: 17.0.1 Damus: This commit Coverage: 1. Basic smoke tests on the app by browsing different notes and different tabs 2. Sent test push notifications for mentions and DMs to check the unfurling of profile names 3. Ran unit tests Closes: https://github.com/damus-io/damus/issues/1703 Changelog-Added: Unfurl profile name on remote push notifications Signed-off-by: Daniel D’Aquino <daniel@daquino.me> Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
committed by
William Casarin
parent
460f536fa3
commit
4171252b18
@@ -1,49 +0,0 @@
|
|||||||
//
|
|
||||||
// NostrEventInfoFromPushNotification.swift
|
|
||||||
// DamusNotificationService
|
|
||||||
//
|
|
||||||
// Created by Daniel D’Aquino on 2023-11-13.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// The representation of a JSON-encoded Nostr Event used by the push notification server
|
|
||||||
/// Needs to match with https://gitlab.com/soapbox-pub/strfry-policies/-/raw/433459d8084d1f2d6500fdf916f22caa3b4d7be5/src/types.ts
|
|
||||||
struct NostrEventInfoFromPushNotification: Codable {
|
|
||||||
let id: String // Hex-encoded
|
|
||||||
let sig: String // Hex-encoded
|
|
||||||
let kind: NostrKind
|
|
||||||
let tags: [[String]]
|
|
||||||
let pubkey: String // Hex-encoded
|
|
||||||
let content: String
|
|
||||||
let created_at: Int
|
|
||||||
|
|
||||||
static func from(dictionary: [AnyHashable: Any]) -> NostrEventInfoFromPushNotification? {
|
|
||||||
guard let id = dictionary["id"] as? String,
|
|
||||||
let sig = dictionary["sig"] as? String,
|
|
||||||
let kind_int = dictionary["kind"] as? UInt32,
|
|
||||||
let kind = NostrKind(rawValue: kind_int),
|
|
||||||
let tags = dictionary["tags"] as? [[String]],
|
|
||||||
let pubkey = dictionary["pubkey"] as? String,
|
|
||||||
let content = dictionary["content"] as? String,
|
|
||||||
let created_at = dictionary["created_at"] as? Int else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return NostrEventInfoFromPushNotification(id: id, sig: sig, kind: kind, tags: tags, pubkey: pubkey, content: content, created_at: created_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reactionEmoji() -> String? {
|
|
||||||
guard self.kind == NostrKind.like else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch self.content {
|
|
||||||
case "", "+":
|
|
||||||
return "❤️"
|
|
||||||
case "-":
|
|
||||||
return "👎"
|
|
||||||
default:
|
|
||||||
return self.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,16 +11,57 @@ import UserNotifications
|
|||||||
struct NotificationFormatter {
|
struct NotificationFormatter {
|
||||||
static var shared = NotificationFormatter()
|
static var shared = NotificationFormatter()
|
||||||
|
|
||||||
// TODO: These is a very generic notification formatter. Once we integrate NostrDB into the extension, we should reuse various functions present in `HomeModel.swift`
|
// MARK: - Formatting with NdbNote
|
||||||
func formatMessage(event: NostrEventInfoFromPushNotification) -> UNNotificationContent? {
|
|
||||||
|
// TODO: Prepare a `LocalNotification` object from `NdbNote` to reuse Notification formatting code from Local notifications
|
||||||
|
func format_message(event: NdbNote, ndb: Ndb?) -> UNMutableNotificationContent? {
|
||||||
|
guard let txn = ndb?.lookup_profile(event.pubkey),
|
||||||
|
let display_name = txn.unsafeUnownedValue?.profile?.display_name
|
||||||
|
else {
|
||||||
|
return self.format_message(event: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.format_message(event: event, display_name: display_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func format_message(event: NdbNote, display_name: String) -> UNMutableNotificationContent? {
|
||||||
|
guard let best_attempt_content: UNMutableNotificationContent = self.format_message(event: event) else { return nil }
|
||||||
|
|
||||||
|
switch event.known_kind {
|
||||||
|
case .text:
|
||||||
|
best_attempt_content.title = String(format: NSLocalizedString("%@ posted a note", comment: "Title label for push notification where a user posted a note"), display_name)
|
||||||
|
break
|
||||||
|
case .dm:
|
||||||
|
best_attempt_content.title = String(format: NSLocalizedString("New message from %@", comment: "Title label for push notifications where a direct message was sent to the user"), display_name)
|
||||||
|
break
|
||||||
|
case .like:
|
||||||
|
guard let reaction_emoji = to_reaction_emoji(ev: event) else {
|
||||||
|
best_attempt_content.title = String(format: NSLocalizedString("%@ reacted to your note", comment: "Reaction heading in local/push notification"), display_name)
|
||||||
|
best_attempt_content.body = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
best_attempt_content.title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), display_name, reaction_emoji)
|
||||||
|
best_attempt_content.body = ""
|
||||||
|
break
|
||||||
|
case .zap:
|
||||||
|
best_attempt_content.title = String(format: NSLocalizedString("%@ zapped you ⚡️", comment: "Title label for a push notification where someone zapped the user"), display_name)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_attempt_content
|
||||||
|
}
|
||||||
|
|
||||||
|
func format_message(event: NdbNote) -> UNMutableNotificationContent? {
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
if let event_json_data = try? JSONEncoder().encode(event), // Must be encoded, as the notification completion handler requires this object to conform to `NSSecureCoding`
|
if let event_json_data = try? JSONEncoder().encode(event), // Must be encoded, as the notification completion handler requires this object to conform to `NSSecureCoding`
|
||||||
let event_json_string = String(data: event_json_data, encoding: .utf8) {
|
let event_json_string = String(data: event_json_data, encoding: .utf8) {
|
||||||
content.userInfo = [
|
content.userInfo = [
|
||||||
"nostr_event_info": event_json_string
|
NDB_NOTE_JSON_USER_INFO_KEY: event_json_string
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
switch event.kind {
|
switch event.known_kind {
|
||||||
case .text:
|
case .text:
|
||||||
content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note")
|
content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note")
|
||||||
content.body = event.content
|
content.body = event.content
|
||||||
@@ -30,7 +71,7 @@ struct NotificationFormatter {
|
|||||||
content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted")
|
content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted")
|
||||||
break
|
break
|
||||||
case .like:
|
case .like:
|
||||||
guard let reactionEmoji = event.reactionEmoji() else {
|
guard let reactionEmoji = to_reaction_emoji(ev: event) else {
|
||||||
content.title = NSLocalizedString("Someone reacted to your note", comment: "Generic title label for push notifications where someone reacted to the user's post")
|
content.title = NSLocalizedString("Someone reacted to your note", comment: "Generic title label for push notifications where someone reacted to the user's post")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -45,4 +86,36 @@ struct NotificationFormatter {
|
|||||||
}
|
}
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Formatting with LocalNotification
|
||||||
|
|
||||||
|
func format_message(displayName: String, notify: LocalNotification) -> (content: UNMutableNotificationContent, identifier: String) {
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
var title = ""
|
||||||
|
var identifier = ""
|
||||||
|
|
||||||
|
switch notify.type {
|
||||||
|
case .mention:
|
||||||
|
title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName)
|
||||||
|
identifier = "myMentionNotification"
|
||||||
|
case .repost:
|
||||||
|
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
||||||
|
identifier = "myBoostNotification"
|
||||||
|
case .like:
|
||||||
|
title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), displayName, to_reaction_emoji(ev: notify.event) ?? "")
|
||||||
|
identifier = "myLikeNotification"
|
||||||
|
case .dm:
|
||||||
|
title = displayName
|
||||||
|
identifier = "myDMNotification"
|
||||||
|
case .zap, .profile_zap:
|
||||||
|
// not handled here
|
||||||
|
break
|
||||||
|
}
|
||||||
|
content.title = title
|
||||||
|
content.body = notify.content
|
||||||
|
content.sound = UNNotificationSound.default
|
||||||
|
content.userInfo = notify.to_lossy().to_user_info()
|
||||||
|
|
||||||
|
return (content, identifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
let ndb: Ndb? = try? Ndb(owns_db_file: false)
|
let ndb: Ndb? = try? Ndb(owns_db_file: false)
|
||||||
|
|
||||||
// Modify the notification content here...
|
// Modify the notification content here...
|
||||||
guard let nostrEventInfoDictionary = request.content.userInfo["nostr_event"] as? [AnyHashable: Any],
|
guard let nostrEventJSON = request.content.userInfo["nostr_event"] as? String,
|
||||||
let nostrEventInfo = NostrEventInfoFromPushNotification.from(dictionary: nostrEventInfoDictionary) else {
|
let nostrEvent = NdbNote.owned_from_json(json: nostrEventJSON)
|
||||||
|
else {
|
||||||
contentHandler(request.content)
|
contentHandler(request.content)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log that we got a push notification
|
// Log that we got a push notification
|
||||||
if let pubkey = Pubkey(hex: nostrEventInfo.pubkey),
|
if let txn = ndb?.lookup_profile(nostrEvent.pubkey) {
|
||||||
let txn = ndb?.lookup_profile(pubkey) {
|
Log.debug("Got push notification from %s (%s)", for: .push_notifications, (txn.unsafeUnownedValue?.profile?.display_name ?? "Unknown"), nostrEvent.pubkey.hex())
|
||||||
Log.debug("Got push notification from %s (%s)", for: .push_notifications, (txn.unsafeUnownedValue?.profile?.display_name ?? "Unknown"), nostrEventInfo.pubkey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let improvedContent = NotificationFormatter.shared.formatMessage(event: nostrEventInfo) {
|
if let improvedContent = NotificationFormatter.shared.format_message(event: nostrEvent, ndb: ndb) {
|
||||||
contentHandler(improvedContent)
|
contentHandler(improvedContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -438,7 +438,6 @@
|
|||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
||||||
D70A3B172B02DCE5008BD568 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; };
|
D70A3B172B02DCE5008BD568 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; };
|
||||||
D70A3B192B02DD2D008BD568 /* NostrEventInfoFromPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B182B02DD2D008BD568 /* NostrEventInfoFromPushNotification.swift */; };
|
|
||||||
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; };
|
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; };
|
||||||
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; };
|
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; };
|
||||||
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
||||||
@@ -478,6 +477,8 @@
|
|||||||
D7A343EE2AD0D77C00CED48B /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */; };
|
D7A343EE2AD0D77C00CED48B /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */; };
|
||||||
D7A343F02AD0D77C00CED48B /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343EF2AD0D77C00CED48B /* SnapshotTesting */; };
|
D7A343F02AD0D77C00CED48B /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343EF2AD0D77C00CED48B /* SnapshotTesting */; };
|
||||||
D7C6787E2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */; };
|
D7C6787E2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */; };
|
||||||
|
D7CB5D3B2B112FBB00AD4105 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; };
|
||||||
|
D7CB5D3C2B1130C600AD4105 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128B29EB19C40006FA5A /* LocalNotification.swift */; };
|
||||||
D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
|
D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
|
||||||
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
||||||
D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
||||||
@@ -541,7 +542,6 @@
|
|||||||
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
||||||
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
||||||
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||||
D7DBD4202B0307C7002A6197 /* NostrEventInfoFromPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B182B02DD2D008BD568 /* NostrEventInfoFromPushNotification.swift */; };
|
|
||||||
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
||||||
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
||||||
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; };
|
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; };
|
||||||
@@ -1260,7 +1260,6 @@
|
|||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||||
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
||||||
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = "<group>"; };
|
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = "<group>"; };
|
||||||
D70A3B182B02DD2D008BD568 /* NostrEventInfoFromPushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventInfoFromPushNotification.swift; sourceTree = "<group>"; };
|
|
||||||
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; };
|
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; };
|
||||||
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
|
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
|
||||||
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -2530,7 +2529,6 @@
|
|||||||
D79C4C162AFEB061003A41B4 /* NotificationService.swift */,
|
D79C4C162AFEB061003A41B4 /* NotificationService.swift */,
|
||||||
D79C4C182AFEB061003A41B4 /* Info.plist */,
|
D79C4C182AFEB061003A41B4 /* Info.plist */,
|
||||||
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */,
|
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */,
|
||||||
D70A3B182B02DD2D008BD568 /* NostrEventInfoFromPushNotification.swift */,
|
|
||||||
);
|
);
|
||||||
path = DamusNotificationService;
|
path = DamusNotificationService;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2946,7 +2944,6 @@
|
|||||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
||||||
4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */,
|
4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */,
|
||||||
4C86F7C62A76C51100EC0817 /* AttachedWalletNotify.swift in Sources */,
|
4C86F7C62A76C51100EC0817 /* AttachedWalletNotify.swift in Sources */,
|
||||||
D7DBD4202B0307C7002A6197 /* NostrEventInfoFromPushNotification.swift in Sources */,
|
|
||||||
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
||||||
4CB883A82975FC1800DC99E7 /* Zaps.swift in Sources */,
|
4CB883A82975FC1800DC99E7 /* Zaps.swift in Sources */,
|
||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
@@ -2970,6 +2967,7 @@
|
|||||||
BA3759932ABCCEBA0018D73B /* CameraModel.swift in Sources */,
|
BA3759932ABCCEBA0018D73B /* CameraModel.swift in Sources */,
|
||||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||||
|
D7CB5D3B2B112FBB00AD4105 /* NotificationFormatter.swift in Sources */,
|
||||||
4C4E137B2A76D5FB00BDD832 /* MuteThreadNotify.swift in Sources */,
|
4C4E137B2A76D5FB00BDD832 /* MuteThreadNotify.swift in Sources */,
|
||||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||||
4C12535A2A76C9960004F4B8 /* UnfollowNotify.swift in Sources */,
|
4C12535A2A76C9960004F4B8 /* UnfollowNotify.swift in Sources */,
|
||||||
@@ -3309,7 +3307,6 @@
|
|||||||
D7CE1B292B0BE239002EDAD4 /* node_id.c in Sources */,
|
D7CE1B292B0BE239002EDAD4 /* node_id.c in Sources */,
|
||||||
D7CE1B2E2B0BE25C002EDAD4 /* talstr.c in Sources */,
|
D7CE1B2E2B0BE25C002EDAD4 /* talstr.c in Sources */,
|
||||||
D798D2292B08686C00234419 /* ContentParsing.swift in Sources */,
|
D798D2292B08686C00234419 /* ContentParsing.swift in Sources */,
|
||||||
D70A3B192B02DD2D008BD568 /* NostrEventInfoFromPushNotification.swift in Sources */,
|
|
||||||
D798D2242B0859C900234419 /* LocalizationUtil.swift in Sources */,
|
D798D2242B0859C900234419 /* LocalizationUtil.swift in Sources */,
|
||||||
D7CE1B322B0BE6C3002EDAD4 /* NdbTxn.swift in Sources */,
|
D7CE1B322B0BE6C3002EDAD4 /* NdbTxn.swift in Sources */,
|
||||||
D7CE1B372B0BE719002EDAD4 /* Verifier.swift in Sources */,
|
D7CE1B372B0BE719002EDAD4 /* Verifier.swift in Sources */,
|
||||||
@@ -3360,6 +3357,7 @@
|
|||||||
D798D2262B085C4200234419 /* Bech32.swift in Sources */,
|
D798D2262B085C4200234419 /* Bech32.swift in Sources */,
|
||||||
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */,
|
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */,
|
||||||
D7CE1B1E2B0BE190002EDAD4 /* midl.c in Sources */,
|
D7CE1B1E2B0BE190002EDAD4 /* midl.c in Sources */,
|
||||||
|
D7CB5D3C2B1130C600AD4105 /* LocalNotification.swift in Sources */,
|
||||||
D7CE1B2D2B0BE250002EDAD4 /* take.c in Sources */,
|
D7CE1B2D2B0BE250002EDAD4 /* take.c in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
@@ -1219,6 +1219,18 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let local_notification = generate_local_notification_object(from: ev, damus_state: damus_state) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
create_local_notification(profiles: damus_state.profiles, notify: local_notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Further break down this function and related functionality so that we can use this from the Notification service extension
|
||||||
|
func generate_local_notification_object(from ev: NostrEvent, damus_state: DamusState) -> LocalNotification? {
|
||||||
|
guard let type = ev.known_kind else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if type == .text, damus_state.settings.mention_notification {
|
if type == .text, damus_state.settings.mention_notification {
|
||||||
let blocks = ev.blocks(damus_state.keypair).blocks
|
let blocks = ev.blocks(damus_state.keypair).blocks
|
||||||
for case .mention(let mention) in blocks {
|
for case .mention(let mention) in blocks {
|
||||||
@@ -1226,56 +1238,30 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: ev, profiles: damus_state.profiles, keypair: damus_state.keypair)
|
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: ev, profiles: damus_state.profiles, keypair: damus_state.keypair)
|
||||||
let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content_preview)
|
return LocalNotification(type: .mention, event: ev, target: ev, content: content_preview)
|
||||||
create_local_notification(profiles: damus_state.profiles, notify: notify )
|
|
||||||
}
|
}
|
||||||
} else if type == .boost,
|
} else if type == .boost,
|
||||||
damus_state.settings.repost_notification,
|
damus_state.settings.repost_notification,
|
||||||
let inner_ev = ev.get_inner_event(cache: damus_state.events)
|
let inner_ev = ev.get_inner_event(cache: damus_state.events)
|
||||||
{
|
{
|
||||||
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: inner_ev, profiles: damus_state.profiles, keypair: damus_state.keypair)
|
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: inner_ev, profiles: damus_state.profiles, keypair: damus_state.keypair)
|
||||||
let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: content_preview)
|
return LocalNotification(type: .repost, event: ev, target: inner_ev, content: content_preview)
|
||||||
create_local_notification(profiles: damus_state.profiles, notify: notify)
|
|
||||||
} else if type == .like,
|
} else if type == .like,
|
||||||
damus_state.settings.like_notification,
|
damus_state.settings.like_notification,
|
||||||
let evid = ev.referenced_ids.last,
|
let evid = ev.referenced_ids.last,
|
||||||
let liked_event = damus_state.events.lookup(evid)
|
let liked_event = damus_state.events.lookup(evid)
|
||||||
{
|
{
|
||||||
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: liked_event, profiles: damus_state.profiles, keypair: damus_state.keypair)
|
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: liked_event, profiles: damus_state.profiles, keypair: damus_state.keypair)
|
||||||
let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview)
|
return LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview)
|
||||||
create_local_notification(profiles: damus_state.profiles, notify: notify)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_local_notification(profiles: Profiles, notify: LocalNotification) {
|
func create_local_notification(profiles: Profiles, notify: LocalNotification) {
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
var title = ""
|
|
||||||
var identifier = ""
|
|
||||||
|
|
||||||
let displayName = event_author_name(profiles: profiles, pubkey: notify.event.pubkey)
|
let displayName = event_author_name(profiles: profiles, pubkey: notify.event.pubkey)
|
||||||
|
|
||||||
switch notify.type {
|
let (content, identifier) = NotificationFormatter.shared.format_message(displayName: displayName, notify: notify)
|
||||||
case .mention:
|
|
||||||
title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName)
|
|
||||||
identifier = "myMentionNotification"
|
|
||||||
case .repost:
|
|
||||||
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
|
||||||
identifier = "myBoostNotification"
|
|
||||||
case .like:
|
|
||||||
title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), displayName, to_reaction_emoji(ev: notify.event) ?? "")
|
|
||||||
identifier = "myLikeNotification"
|
|
||||||
case .dm:
|
|
||||||
title = displayName
|
|
||||||
identifier = "myDMNotification"
|
|
||||||
case .zap, .profile_zap:
|
|
||||||
// not handled here
|
|
||||||
break
|
|
||||||
}
|
|
||||||
content.title = title
|
|
||||||
content.body = notify.content
|
|
||||||
content.sound = UNNotificationSound.default
|
|
||||||
content.userInfo = notify.to_lossy().to_user_info()
|
|
||||||
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
let NDB_NOTE_JSON_USER_INFO_KEY = "ndb_note_json"
|
||||||
|
|
||||||
struct LossyLocalNotification {
|
struct LossyLocalNotification {
|
||||||
let type: LocalNotificationType
|
let type: LocalNotificationType
|
||||||
let mention: MentionRef
|
let mention: MentionRef
|
||||||
@@ -19,8 +21,8 @@ struct LossyLocalNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func from_user_info(user_info: [AnyHashable: Any]) -> LossyLocalNotification? {
|
static func from_user_info(user_info: [AnyHashable: Any]) -> LossyLocalNotification? {
|
||||||
if let encoded_nostr_event_push_data = user_info["nostr_event_info"] as? String {
|
if let encoded_ndb_note = user_info[NDB_NOTE_JSON_USER_INFO_KEY] as? String {
|
||||||
return self.from(encoded_nostr_event_push_data: encoded_nostr_event_push_data)
|
return self.from(json_encoded_ndb_note: encoded_ndb_note)
|
||||||
}
|
}
|
||||||
guard let id = user_info["id"] as? String,
|
guard let id = user_info["id"] as? String,
|
||||||
let target_id = MentionRef.from_bech32(str: id) else {
|
let target_id = MentionRef.from_bech32(str: id) else {
|
||||||
@@ -32,18 +34,16 @@ struct LossyLocalNotification {
|
|||||||
return LossyLocalNotification(type: type, mention: target_id)
|
return LossyLocalNotification(type: type, mention: target_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func from(encoded_nostr_event_push_data: String) -> LossyLocalNotification? {
|
static func from(json_encoded_ndb_note: String) -> LossyLocalNotification? {
|
||||||
guard let json_data = encoded_nostr_event_push_data.data(using: .utf8),
|
guard let ndb_note = NdbNote.owned_from_json(json: json_encoded_ndb_note) else {
|
||||||
let nostr_event_push_data = try? JSONDecoder().decode(NostrEventInfoFromPushNotification.self, from: json_data) else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return self.from(nostr_event_push_data: nostr_event_push_data)
|
return self.from(ndb_note: ndb_note)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func from(nostr_event_push_data: NostrEventInfoFromPushNotification) -> LossyLocalNotification? {
|
static func from(ndb_note: NdbNote) -> LossyLocalNotification? {
|
||||||
guard let type = LocalNotificationType.from(nostr_kind: nostr_event_push_data.kind) else { return nil }
|
guard let known_kind = ndb_note.known_kind, let type = LocalNotificationType.from(nostr_kind: known_kind) else { return nil }
|
||||||
guard let note_id: NoteId = NoteId.init(hex: nostr_event_push_data.id) else { return nil }
|
let target: MentionRef = .note(ndb_note.id)
|
||||||
let target: MentionRef = .note(note_id)
|
|
||||||
return LossyLocalNotification(type: type, mention: target)
|
return LossyLocalNotification(type: type, mention: target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user