From 35049369732b5667298bb8c2981fa3bafe5a9dd3 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 14:43:33 +0300 Subject: [PATCH 1/4] NotificationService notofocation types icon --- .../NotificationService.swift | 110 +++++++++--------- .../Client/Wallet/NotifyClientFactory.swift | 18 +-- .../Wallet/NotifyDecryptionService.swift | 15 ++- .../Client/Wallet/NotifySqliteFactory.swift | 25 ++++ .../Client/Wallet/NotifyStorageFactory.swift | 8 ++ 5 files changed, 102 insertions(+), 74 deletions(-) create mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift create mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift index 9fae80990..87ac13e5f 100644 --- a/Example/PNDecryptionService/NotificationService.swift +++ b/Example/PNDecryptionService/NotificationService.swift @@ -75,11 +75,11 @@ class NotificationService: UNNotificationServiceExtension { private func handleNotifyNotification(content: UNNotificationContent, topic: String, ciphertext: String) -> UNMutableNotificationContent { do { let service = NotifyDecryptionService(groupIdentifier: "group.com.walletconnect.sdk") - let (pushMessage, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext) + let (pushMessage, subscription, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext) log("message decrypted", account: account, topic: topic, message: pushMessage) - let updatedContent = handle(content: content, pushMessage: pushMessage, topic: topic) + let updatedContent = handle(content: content, pushMessage: pushMessage, subscription: subscription, topic: topic) let mutableContent = updatedContent.mutableCopy() as! UNMutableNotificationContent mutableContent.title = pushMessage.title @@ -114,62 +114,66 @@ class NotificationService: UNNotificationServiceExtension { private extension NotificationService { - func handle(content: UNNotificationContent, pushMessage: NotifyMessage, topic: String) -> UNNotificationContent { - do { - let iconUrl = try pushMessage.icon.asURL() + func handle(content: UNNotificationContent, pushMessage: NotifyMessage, subscription: NotifySubscription, topic: String) -> UNNotificationContent { + + let icon = subscription.scope[pushMessage.type]?.imageUrls?.sm ?? pushMessage.icon + var senderAvatar: INImage? + + do { + let iconUrl = try icon.asURL() let senderThumbnailImageData = try Data(contentsOf: iconUrl) let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) - let senderAvatar = INImage(imageData: senderThumbnailImageFileData) - - var personNameComponents = PersonNameComponents() - personNameComponents.nickname = pushMessage.title - - let senderPerson = INPerson( - personHandle: INPersonHandle(value: topic, type: .unknown), - nameComponents: personNameComponents, - displayName: pushMessage.title, - image: senderAvatar, - contactIdentifier: nil, - customIdentifier: topic, - isMe: false, - suggestionType: .none - ) - - let selfPerson = INPerson( - personHandle: INPersonHandle(value: "0", type: .unknown), - nameComponents: nil, - displayName: nil, - image: nil, - contactIdentifier: nil, - customIdentifier: nil, - isMe: true, - suggestionType: .none - ) - - let incomingMessagingIntent = INSendMessageIntent( - recipients: [selfPerson], - outgoingMessageType: .outgoingMessageText, - content: pushMessage.body, - speakableGroupName: nil, - conversationIdentifier: pushMessage.type, - serviceName: nil, - sender: senderPerson, - attachments: [] - ) - - incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender) - - let interaction = INInteraction(intent: incomingMessagingIntent, response: nil) - interaction.direction = .incoming - interaction.donate(completion: nil) - - return try content.updating(from: incomingMessagingIntent) - } - catch { - return content + senderAvatar = INImage(imageData: senderThumbnailImageFileData) + } catch { + log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage) } + + var personNameComponents = PersonNameComponents() + personNameComponents.nickname = pushMessage.title + + let senderPerson = INPerson( + personHandle: INPersonHandle(value: topic, type: .unknown), + nameComponents: personNameComponents, + displayName: pushMessage.title, + image: senderAvatar, + contactIdentifier: nil, + customIdentifier: topic, + isMe: false, + suggestionType: .none + ) + + let selfPerson = INPerson( + personHandle: INPersonHandle(value: "0", type: .unknown), + nameComponents: nil, + displayName: nil, + image: nil, + contactIdentifier: nil, + customIdentifier: nil, + isMe: true, + suggestionType: .none + ) + + let incomingMessagingIntent = INSendMessageIntent( + recipients: [selfPerson], + outgoingMessageType: .outgoingMessageText, + content: pushMessage.body, + speakableGroupName: nil, + conversationIdentifier: pushMessage.type, + serviceName: nil, + sender: senderPerson, + attachments: [] + ) + + incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender) + + let interaction = INInteraction(intent: incomingMessagingIntent, response: nil) + interaction.direction = .incoming + interaction.donate(completion: nil) + + let updated = try? content.updating(from: incomingMessagingIntent) + return updated ?? content } func downloadAttachment(data: Data, fileName: String) throws -> URL { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 14fa2fdef..c18b8749f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -7,8 +7,7 @@ public struct NotifyClientFactory { let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier) let groupKeychainService = GroupKeychainStorage(serviceIdentifier: groupIdentifier) - let databasePath = databasePath(appGroup: groupIdentifier, database: "notify_v\(version).db") - let sqlite = DiskSqlite(path: databasePath) + let sqlite = NotifySqliteFactory.create(appGroup: groupIdentifier) return NotifyClientFactory.create( projectId: projectId, @@ -97,19 +96,4 @@ public struct NotifyClientFactory { subscriptionWatcher: subscriptionWatcher ) } - - static func databasePath(appGroup: String, database: String) -> String { - guard let path = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? - .appendingPathComponent(database) else { - - fatalError("Database path not exists") - } - - return path.absoluteString - } - - static var version: String { - return "1" - } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift index 272a37cea..e53b53042 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift @@ -3,29 +3,36 @@ import Foundation public class NotifyDecryptionService { enum Errors: Error { case malformedNotifyMessage + case subsctiptionNotFound } private let serializer: Serializing + private let database: NotifyDatabase private static let notifyTags: [UInt] = [4002] - init(serializer: Serializing) { + init(serializer: Serializing, database: NotifyDatabase) { self.serializer = serializer + self.database = database } public init(groupIdentifier: String) { let keychainStorage = GroupKeychainStorage(serviceIdentifier: groupIdentifier) let kms = KeyManagementService(keychain: keychainStorage) - self.serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) + let logger = ConsoleLogger(prefix: "🔐", loggingLevel: .off) + let sqlite = NotifySqliteFactory.create(appGroup: groupIdentifier) + self.serializer = Serializer(kms: kms, logger: logger) + self.database = NotifyDatabase(sqlite: sqlite, logger: logger) } public static func canHandle(tag: UInt) -> Bool { return notifyTags.contains(tag) } - public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, Account) { + public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, NotifySubscription, Account) { let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) guard let params = rpcRequest.params else { throw Errors.malformedNotifyMessage } let wrapper = try params.get(NotifyMessagePayload.Wrapper.self) let (messagePayload, _) = try NotifyMessagePayload.decodeAndVerify(from: wrapper) - return (messagePayload.message, messagePayload.account) + guard let subscription = database.getSubscription(topic: topic) else { throw Errors.subsctiptionNotFound } + return (messagePayload.message, subscription, messagePayload.account) } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift new file mode 100644 index 000000000..cd1b0dd59 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift @@ -0,0 +1,25 @@ +import Foundation + +struct NotifySqliteFactory { + + static func create(appGroup: String) -> Sqlite { + let databasePath = databasePath(appGroup: appGroup, database: "notify_v\(version).db") + let sqlite = DiskSqlite(path: databasePath) + return sqlite + } + + static func databasePath(appGroup: String, database: String) -> String { + guard let path = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? + .appendingPathComponent(database) else { + + fatalError("Database path not exists") + } + + return path.absoluteString + } + + static var version: String { + return "1" + } +} diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift new file mode 100644 index 000000000..3d5e56c21 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift @@ -0,0 +1,8 @@ +import Foundation + +struct NotifyStorageFactory { + + static func create() -> NotifyStorage { + + } +} From 96f2339f7195b26f24a4e5325e7ccd4e59c4aead Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 18:32:24 +0300 Subject: [PATCH 2/4] Icons from notification types --- .../NotificationService.swift | 20 +++++++++---------- .../Models/NotifyMessageViewModel.swift | 4 ++++ .../PushMessages/SubscriptionPresenter.swift | 5 +++++ .../PushMessages/SubscriptionView.swift | 4 ++-- .../Client/Wallet/NotifyImageUrls.swift | 14 +++++++++++++ .../Client/Wallet/NotifyStorageFactory.swift | 8 -------- .../DataStructures/NotifySubscription.swift | 4 ++++ 7 files changed, 39 insertions(+), 20 deletions(-) delete mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift index 87ac13e5f..d3f535f8f 100644 --- a/Example/PNDecryptionService/NotificationService.swift +++ b/Example/PNDecryptionService/NotificationService.swift @@ -115,19 +115,19 @@ class NotificationService: UNNotificationServiceExtension { private extension NotificationService { func handle(content: UNNotificationContent, pushMessage: NotifyMessage, subscription: NotifySubscription, topic: String) -> UNNotificationContent { - - let icon = subscription.scope[pushMessage.type]?.imageUrls?.sm ?? pushMessage.icon var senderAvatar: INImage? - do { - let iconUrl = try icon.asURL() - let senderThumbnailImageData = try Data(contentsOf: iconUrl) - let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) - let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) - senderAvatar = INImage(imageData: senderThumbnailImageFileData) - } catch { - log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage) + if let icon = subscription.messageIcons(ofType: pushMessage.type).md { + do { + let iconUrl = try icon.asURL() + let senderThumbnailImageData = try Data(contentsOf: iconUrl) + let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) + let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) + senderAvatar = INImage(imageData: senderThumbnailImageFileData) + } catch { + log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage) + } } var personNameComponents = PersonNameComponents() diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift index 9e937154c..342bac31e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift @@ -24,4 +24,8 @@ struct NotifyMessageViewModel: Identifiable { var publishedAt: String { return pushMessageRecord.publishedAt.formatted(.relative(presentation: .named, unitsStyle: .wide)) } + + var type: String { + return pushMessageRecord.message.type + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index bfddfee6e..2f9bb1a21 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -52,6 +52,11 @@ final class SubscriptionPresenter: ObservableObject { } } + func messageIconUrl(message: NotifyMessageViewModel) -> URL? { + let icons = subscription.messageIcons(ofType: message.type) + return try? icons.md?.asURL() + } + func unsubscribe() { interactor.deleteSubscription(subscription) router.dismiss() diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index e68fcb5b6..e168a8814 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -49,12 +49,12 @@ struct SubscriptionView: View { private func notificationView(pushMessage: NotifyMessageViewModel) -> some View { VStack(alignment: .center) { HStack(spacing: 12) { - CacheAsyncImage(url: URL(string: pushMessage.imageUrl) ?? presenter.subscriptionViewModel.imageUrl) { phase in + CacheAsyncImage(url: presenter.messageIconUrl(message: pushMessage)) { phase in if let image = phase.image { image .resizable() .frame(width: 48, height: 48) - .background(Color.black) + .background(Color.black.opacity(0.05)) .cornerRadius(10, corners: .allCorners) } else { Color.black diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift index 124dc1def..c5963bae2 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift @@ -1,7 +1,21 @@ import Foundation public struct NotifyImageUrls: Codable, Equatable { + public let sm: String? public let md: String? public let lg: String? + + public init(sm: String? = nil, md: String? = nil, lg: String? = nil) { + self.sm = sm + self.md = md + self.lg = lg + } + + public init?(icons: [String]) { + guard icons.count == 3 else { return nil } + self.sm = icons[0] + self.md = icons[1] + self.lg = icons[2] + } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift deleted file mode 100644 index 3d5e56c21..000000000 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -struct NotifyStorageFactory { - - static func create() -> NotifyStorage { - - } -} diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index 0cc7f313e..54be8d136 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -15,6 +15,10 @@ public struct NotifySubscription: Codable, Equatable, SqliteRow { return "\(account.absoluteString)-\(metadata.url)" } + public func messageIcons(ofType type: String) -> NotifyImageUrls { + return scope[type]?.imageUrls ?? NotifyImageUrls(icons: metadata.icons) ?? NotifyImageUrls() + } + public init(decoder: SqliteRowDecoder) throws { self.topic = try decoder.decodeString(at: 0) self.account = try Account(decoder.decodeString(at: 1))! From 17107eab8d57751826c68c60efb19516cbdc2162 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 18:34:45 +0300 Subject: [PATCH 3/4] Badge disabled --- .../Wallet/Notifications/Models/SubscriptionsViewModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift index 6e0f12f42..cced6706c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift @@ -45,6 +45,7 @@ struct SubscriptionsViewModel: Identifiable { } var hasMessage: Bool { - return messagesCount != 0 + /* return messagesCount != 0 Badge disabled */ + return false } } From 6aa35d52828afe7e68c1defe381e8e8969be117a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 18:41:09 +0300 Subject: [PATCH 4/4] Bottom padding --- .../Wallet/Notifications/NotificationsView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index a3146cbd8..7cf63ed8f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -62,6 +62,9 @@ struct NotificationsView: View { .listRowSeparator(.hidden) } .listStyle(PlainListStyle()) + .safeAreaInset(edge: .bottom) { + Spacer().frame(height: 16) + } } .task { try? await presenter.fetch()