diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index a49a5455f..91dc10250 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -216,6 +216,7 @@ 5455147CAC63F71E48F7D699 /* NSELogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D455BC2423D911A62ACFB2 /* NSELogger.swift */; }; 54AE8860D668AFD96E7E177B /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; }; 54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */; }; + 55CDD3968D95D1A820B5491E /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; }; 564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */; }; 565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; }; 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; }; @@ -594,6 +595,7 @@ D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */; }; D5C805F49B2C75DC3793E780 /* EmojiItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */; }; D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; }; + D63974A88CF2BC721F109C77 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = DCA3C4A997AD28E6918D4CE5 /* Compound */; }; D6661A94DBD97658B2ADBD6A /* MapTilerStaticMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A4D29F2683F5772AC72406F /* MapTilerStaticMap.swift */; }; D7CDBAE82782BD0529DECB5F /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; }; D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; }; @@ -609,6 +611,7 @@ DE0BBA736557B42BC0DA6CBF /* TimelineEventProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B62EE933FC3D5651AF4607 /* TimelineEventProxy.swift */; }; DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */; }; + DF05F9C9D3D977EB77E13692 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A593735D882778FD2C9A185B /* DesignKit */; }; DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */; }; DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4959CECEC984B3995616F427 /* DataProtectionManager.swift */; }; DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */; }; @@ -1430,6 +1433,8 @@ 53DEF39F0C4DE02E3FC56D91 /* KeychainAccess in Frameworks */, F06CE9132855E81EBB6DDC32 /* Kingfisher in Frameworks */, 67D6E0700A9C1E676F6231F8 /* Collections in Frameworks */, + D63974A88CF2BC721F109C77 /* Compound in Frameworks */, + DF05F9C9D3D977EB77E13692 /* DesignKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3582,6 +3587,8 @@ 800631D7250B7F93195035F1 /* KeychainAccess */, 940C605265DD82DA0C655E23 /* Kingfisher */, AD544C0FA48DFFB080920061 /* Collections */, + DCA3C4A997AD28E6918D4CE5 /* Compound */, + A593735D882778FD2C9A185B /* DesignKit */, ); productName = NSE; productReference = 0D8F620C8B314840D8602E3F /* NSE.appex */; @@ -3849,6 +3856,7 @@ 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */, 5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */, B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */, + 55CDD3968D95D1A820B5491E /* PlaceholderAvatarImage.swift in Sources */, 414F50CFCFEEE2611127DCFB /* RestorationToken.swift in Sources */, 7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */, 6C5A2C454E6C198AB39ED760 /* SharedUserDefaultsKeys.swift in Sources */, @@ -5337,6 +5345,10 @@ package = 395DE6AE429B7ACC7C7FE31D /* XCRemoteSwiftPackageReference "KZFileWatchers" */; productName = KZFileWatchers; }; + A593735D882778FD2C9A185B /* DesignKit */ = { + isa = XCSwiftPackageProductDependency; + productName = DesignKit; + }; A5A56C4F47C368EBE5C5E870 /* DesignKit */ = { isa = XCSwiftPackageProductDependency; productName = DesignKit; @@ -5391,6 +5403,11 @@ package = AC3475112CA40C2C6E78D1EB /* XCRemoteSwiftPackageReference "matrix-analytics-events" */; productName = AnalyticsEvents; }; + DCA3C4A997AD28E6918D4CE5 /* Compound */ = { + isa = XCSwiftPackageProductDependency; + package = 9754C4B03F6255F67FC15E52 /* XCRemoteSwiftPackageReference "compound-ios" */; + productName = Compound; + }; DE8DC9B3FBA402117DC4C49F /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */; diff --git a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift index 5aebcea8c..10001200c 100644 --- a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift +++ b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift @@ -16,15 +16,21 @@ import Foundation import Intents +import SwiftUI import UserNotifications struct NotificationIcon { + struct GroupInfo { + let name: String + let id: String + } + let mediaSource: MediaSourceProxy? // Required as the key to set images for groups - let groupName: String? + let groupInfo: GroupInfo? var shouldDisplayAsGroup: Bool { - groupName != nil + groupInfo != nil } } @@ -99,11 +105,12 @@ extension UNMutableNotificationContent { senderID: String, senderName: String, icon: NotificationIcon) async throws -> UNMutableNotificationContent { - var image = INImage(named: "") + var fetchedImage: INImage? + let image: INImage if let mediaSource = icon.mediaSource { switch await mediaProvider?.loadImageDataFromSource(mediaSource) { case .success(let data): - image = INImage(imageData: data) + fetchedImage = INImage(imageData: data) case .failure(let error): MXLog.error("Couldn't add sender icon: \(error)") case .none: @@ -111,6 +118,15 @@ extension UNMutableNotificationContent { } } + if let fetchedImage { + image = fetchedImage + } else if let data = await getPlaceholderAvatarImageData(name: icon.groupInfo?.name ?? senderName, + id: icon.groupInfo?.name ?? senderName) { + image = INImage(imageData: data) + } else { + image = INImage(named: "") + } + let senderHandle = INPersonHandle(value: senderID, type: .unknown) let sender = INPerson(personHandle: senderHandle, nameComponents: nil, @@ -122,10 +138,10 @@ extension UNMutableNotificationContent { // These are required to show the group name as subtitle var speakableGroupName: INSpeakableString? var recipients: [INPerson]? - if let groupName = icon.groupName { + if let groupInfo = icon.groupInfo { let meHandle = INPersonHandle(value: receiverID, type: .unknown) let me = INPerson(personHandle: meHandle, nameComponents: nil, displayName: nil, image: nil, contactIdentifier: nil, customIdentifier: nil, isMe: true) - speakableGroupName = INSpeakableString(spokenPhrase: groupName) + speakableGroupName = INSpeakableString(spokenPhrase: groupInfo.name) recipients = [sender, me] } @@ -157,4 +173,13 @@ extension UNMutableNotificationContent { // swiftlint:disable:next force_cast return updatedContent.mutableCopy() as! UNMutableNotificationContent } + + private func getPlaceholderAvatarImageData(name: String, id: String) async -> Data? { + let image = PlaceholderAvatarImage(name: name, + contentID: id) + .clipShape(Circle()) + .frame(width: 100, height: 100) + let renderer = await ImageRenderer(content: image) + return await renderer.uiImage?.jpegData(compressionQuality: 0.8) + } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift index 511bfc26c..1a81054a8 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift @@ -16,8 +16,11 @@ import SwiftUI +import Compound +import DesignKit + struct PlaceholderAvatarImage: View { - @Environment(\.redactionReasons) var redactionReasons + @Environment(\.redactionReasons) private var redactionReasons private let textForImage: String private let contentID: String? diff --git a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift index 251edefc8..fa91d1fb2 100644 --- a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift +++ b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift @@ -185,6 +185,15 @@ extension NotificationItemProxyProtocol { } } + var icon: NotificationIcon { + if isDirect { + return NotificationIcon(mediaSource: senderAvatarMediaSource, groupInfo: nil) + } else { + return NotificationIcon(mediaSource: roomAvatarMediaSource, + groupInfo: .init(name: roomDisplayName, id: roomID)) + } + } + /// Process the receiver item proxy /// - Parameters: /// - receiverId: identifier of the user that has received the notification @@ -236,13 +245,10 @@ extension NotificationItemProxyProtocol { notification.categoryIdentifier = NotificationConstants.Category.invite - let icon: NotificationIcon let body: String if !isDirect { - icon = NotificationIcon(mediaSource: roomAvatarMediaSource, groupName: roomDisplayName) body = L10n.notificationRoomInviteBody } else { - icon = NotificationIcon(mediaSource: senderAvatarMediaSource, groupName: nil) body = L10n.notificationInviteBody } @@ -292,17 +298,9 @@ extension NotificationItemProxyProtocol { } notification.categoryIdentifier = NotificationConstants.Category.message - let senderName = senderDisplayName ?? roomDisplayName - let icon: NotificationIcon - if !isDirect { - icon = NotificationIcon(mediaSource: roomAvatarMediaSource, groupName: roomDisplayName) - } else { - icon = NotificationIcon(mediaSource: senderAvatarMediaSource, groupName: nil) - } - notification = try await notification.addSenderIcon(using: mediaProvider, senderID: event.senderID, - senderName: senderName, + senderName: senderDisplayName ?? roomDisplayName, icon: icon) return notification } diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index c5e973afd..7f7420d6b 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -36,6 +36,8 @@ targets: - package: KeychainAccess - package: Kingfisher - package: Collections + - package: Compound + - package: DesignKit info: path: ../SupportingFiles/Info.plist @@ -91,3 +93,4 @@ targets: - path: ../../ElementX/Sources/Other/Extensions/UNNotificationContent.swift - path: ../../ElementX/Sources/Other/UserPreference.swift - path: ../../ElementX/Sources/Other/SharedUserDefaultsKeys.swift + - path: ../../ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift diff --git a/changelog.d/1168.feature b/changelog.d/1168.feature new file mode 100644 index 000000000..92e9c3c6c --- /dev/null +++ b/changelog.d/1168.feature @@ -0,0 +1 @@ +Push Notifications of rooms/dm without avatars will now display the default placeholder used in app. \ No newline at end of file