Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sample] Icons from notification types #1294

Merged
merged 4 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 61 additions & 57 deletions Example/PNDecryptionService/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

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
func handle(content: UNNotificationContent, pushMessage: NotifyMessage, subscription: NotifySubscription, topic: String) -> UNNotificationContent {

var senderAvatar: INImage?

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()
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct SubscriptionsViewModel: Identifiable {
}

var hasMessage: Bool {
return messagesCount != 0
/* return messagesCount != 0 Badge disabled */
return false
flypaper0 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ struct NotificationsView: View {
.listRowSeparator(.hidden)
}
.listStyle(PlainListStyle())
.safeAreaInset(edge: .bottom) {
Spacer().frame(height: 16)
}
}
.task {
try? await presenter.fetch()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
14 changes: 14 additions & 0 deletions Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift
Original file line number Diff line number Diff line change
@@ -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]
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))!
Expand Down
Loading