diff --git a/NotificationExtension/NotificationService.swift b/NotificationExtension/NotificationService.swift index c33f72563c..5acec490e1 100644 --- a/NotificationExtension/NotificationService.swift +++ b/NotificationExtension/NotificationService.swift @@ -15,9 +15,13 @@ import Pachyderm import Intents import HTMLStreamer import WebURL +import UIKit +import TuskerPreferences private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "NotificationService") +private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) + class NotificationService: UNNotificationServiceExtension { private static let textConverter = TextConverter(configuration: .init(insertNewlines: false), callbacks: HTMLCallbacks.self) @@ -225,8 +229,33 @@ class NotificationService: UNNotificationServiceExtension { } let updatedContent: UNMutableNotificationContent + + let contentProviding: any UNNotificationContentProviding + if #available(iOS 18.0, *), + await Preferences.shared.hasFeatureFlag(.pushNotifCustomEmoji) { + let attributedString = NSMutableAttributedString(string: content.body) + + for match in emojiRegex.matches(in: content.body, range: NSRange(location: 0, length: content.body.utf16.count)).reversed() { + let emojiName = (content.body as NSString).substring(with: match.range(at: 1)) + guard let emoji = notification.status?.emojis.first(where: { $0.shortcode == emojiName }), + let url = URL(emoji.url), + let (data, _) = try? await URLSession.shared.data(from: url), + let image = UIImage(data: data) else { + continue + } + let attachment = NSTextAttachment(image: image) + let attachmentStr = NSAttributedString(attachment: attachment) + attributedString.replaceCharacters(in: match.range, with: attachmentStr) + } + + let attributedCtx = UNNotificationAttributedMessageContext(sendMessageIntent: intent, attributedContent: attributedString) + contentProviding = attributedCtx + } else { + contentProviding = intent + } + do { - let newContent = try content.updating(from: intent) + let newContent = try content.updating(from: contentProviding) if let newMutableContent = newContent.mutableCopy() as? UNMutableNotificationContent { pendingRequest?.0 = newMutableContent updatedContent = newMutableContent diff --git a/Packages/TuskerPreferences/Sources/TuskerPreferences/Supporting Types/FeatureFlag.swift b/Packages/TuskerPreferences/Sources/TuskerPreferences/Supporting Types/FeatureFlag.swift index e8ed874e24..e6bfe67215 100644 --- a/Packages/TuskerPreferences/Sources/TuskerPreferences/Supporting Types/FeatureFlag.swift +++ b/Packages/TuskerPreferences/Sources/TuskerPreferences/Supporting Types/FeatureFlag.swift @@ -9,4 +9,5 @@ import Foundation public enum FeatureFlag: String, Codable { case iPadBrowserNavigation = "ipad-browser-navigation" + case pushNotifCustomEmoji = "push-notif-custom-emoji" } diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index e65fce0558..e9b86e8a32 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -77,6 +77,7 @@ D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; }; D6210D762C0B924F009BB569 /* RemoveProfileSuggestionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6210D752C0B924F009BB569 /* RemoveProfileSuggestionService.swift */; }; D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D621733228F1D5ED004C7DB1 /* ReblogService.swift */; }; + D62220472C7EA8DF003E43B7 /* TuskerPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = D62220462C7EA8DF003E43B7 /* TuskerPreferences */; }; D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; }; D623A53D2635F5590095BD04 /* StatusPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A53C2635F5590095BD04 /* StatusPollView.swift */; }; D623A5412635FB3C0095BD04 /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A5402635FB3C0095BD04 /* PollOptionView.swift */; }; @@ -828,6 +829,7 @@ buildActionMask = 2147483647; files = ( D630C4252BC7845800208903 /* WebURL in Frameworks */, + D62220472C7EA8DF003E43B7 /* TuskerPreferences in Frameworks */, D630C4232BC7842C00208903 /* HTMLStreamer in Frameworks */, D630C3E52BC6313400208903 /* Pachyderm in Frameworks */, D630C3DF2BC61C4900208903 /* PushNotifications in Frameworks */, @@ -1795,6 +1797,7 @@ D630C3E42BC6313400208903 /* Pachyderm */, D630C4222BC7842C00208903 /* HTMLStreamer */, D630C4242BC7845800208903 /* WebURL */, + D62220462C7EA8DF003E43B7 /* TuskerPreferences */, ); productName = NotificationExtension; productReference = D630C3D12BC61B6000208903 /* NotificationExtension.appex */; @@ -3299,6 +3302,10 @@ isa = XCSwiftPackageProductDependency; productName = Pachyderm; }; + D62220462C7EA8DF003E43B7 /* TuskerPreferences */ = { + isa = XCSwiftPackageProductDependency; + productName = TuskerPreferences; + }; D630C3C72BC43AFD00208903 /* PushNotifications */ = { isa = XCSwiftPackageProductDependency; productName = PushNotifications;