fix(mobile): show avatars in channel push notifications

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
This commit is contained in:
Tom Brow
2026-08-24 14:02:32 -07:00
parent 489eb436a0
commit 7d6efa9fb5
5 changed files with 19 additions and 9 deletions

View File

@@ -113,11 +113,12 @@ public struct BuzzCommunicationNotificationDescriptor: Equatable, Sendable {
public static func makeIntent(
_ descriptor: BuzzCommunicationNotificationDescriptor
) -> INSendMessageIntent {
let senderAvatar = descriptor.senderAvatarPNG.map(INImage.init(imageData:))
let sender = INPerson(
personHandle: INPersonHandle(value: descriptor.senderIdentifier, type: .unknown),
nameComponents: nil,
displayName: descriptor.senderDisplayName,
image: descriptor.senderAvatarPNG.map(INImage.init(imageData:)),
image: senderAvatar,
contactIdentifier: nil,
customIdentifier: descriptor.senderIdentifier,
isMe: false,
@@ -139,6 +140,13 @@ public struct BuzzCommunicationNotificationDescriptor: Equatable, Sendable {
let donationMetadata = INSendMessageIntentDonationMetadata()
donationMetadata.recipientCount = descriptor.recipientCount
intent.donationMetadata = donationMetadata
if let senderAvatar {
// Communication Notifications render a group conversation's image
// from the speakable-group parameter rather than INPerson.image.
// Buzz channels do not have a separate avatar, so use the verified
// sender thumbnail for the visible incoming-message avatar.
intent.setImage(senderAvatar, forParameterNamed: \.speakableGroupName)
}
}
return intent
}

View File

@@ -548,7 +548,7 @@ public final class BuzzPushNotificationResolver: BuzzPushNotificationResolving {
["stream", "forum"].contains(channelType),
let displayName = channel.displayName
else { return nil }
return (displayName, recipientCount)
return (displayName.hasPrefix("#") ? displayName : "#\(displayName)", recipientCount)
}
private static func newest(_ events: [VerifiedNostrEvent]) -> VerifiedNostrEvent? {

View File

@@ -47,7 +47,7 @@ extension BuzzPushNotificationResolverTests {
)
)
XCTAssertEqual(result.conversationDisplayName, "General")
XCTAssertEqual(result.conversationDisplayName, "#General")
XCTAssertEqual(result.conversationRecipientCount, 2)
}
@@ -160,7 +160,7 @@ extension BuzzPushNotificationResolverTests {
)
)
XCTAssertEqual(result.conversationDisplayName, "General")
XCTAssertEqual(result.conversationDisplayName, "#General")
XCTAssertEqual(result.conversationRecipientCount, 1)
XCTAssertEqual(URLProtocolStub.requests.count, 2)
}

View File

@@ -226,7 +226,7 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
XCTAssertEqual(result.title, "Alice")
XCTAssertEqual(result.senderAvatarPNG, avatar)
XCTAssertEqual(result.conversationDisplayName, "General")
XCTAssertEqual(result.conversationDisplayName, "#General")
XCTAssertEqual(result.conversationRecipientCount, 1)
XCTAssertEqual(URLProtocolStub.requests.count, 1)
}
@@ -293,7 +293,7 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
)
XCTAssertEqual(result.title, "Stale Alice")
XCTAssertEqual(result.conversationDisplayName, "Stale General")
XCTAssertEqual(result.conversationDisplayName, "#Stale General")
XCTAssertEqual(result.conversationRecipientCount, 1)
XCTAssertEqual(URLProtocolStub.requests.count, 2)
}
@@ -376,7 +376,7 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
)
XCTAssertEqual(result.title, "Newer Cached Alice")
XCTAssertEqual(result.conversationDisplayName, "Newer Cached General")
XCTAssertEqual(result.conversationDisplayName, "#Newer Cached General")
}
func testChannelOnlyRefreshIgnoresUnrequestedProfileEvent() throws {
@@ -452,7 +452,7 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
)
XCTAssertEqual(result.title, "Cached Alice")
XCTAssertEqual(result.conversationDisplayName, "Stale General")
XCTAssertEqual(result.conversationDisplayName, "#Stale General")
}
func testMissingCacheRefreshesVerifiedProfileAndChannelTogether() throws {
@@ -508,7 +508,7 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
)
XCTAssertEqual(result.title, "Fresh Alice")
XCTAssertEqual(result.conversationDisplayName, "Fresh General")
XCTAssertEqual(result.conversationDisplayName, "#Fresh General")
XCTAssertEqual(result.conversationRecipientCount, 1)
XCTAssertNil(result.senderAvatarPNG)
XCTAssertEqual(URLProtocolStub.requests.count, 2)

View File

@@ -21,6 +21,7 @@ final class BuzzCommunicationNotificationTests: XCTestCase {
XCTAssertEqual(intent.sender?.displayName, "Alice")
XCTAssertEqual(intent.sender?.customIdentifier, descriptor.senderIdentifier)
XCTAssertNotNil(intent.sender?.image)
XCTAssertNotNil(intent.image(forParameterNamed: \.speakableGroupName))
XCTAssertEqual(intent.content, "Hello Buzz")
XCTAssertEqual(intent.speakableGroupName?.spokenPhrase, "General")
XCTAssertEqual(intent.conversationIdentifier, resolution.conversationIdentifier)
@@ -46,6 +47,7 @@ final class BuzzCommunicationNotificationTests: XCTestCase {
XCTAssertEqual(intent.sender?.displayName, "Alice")
XCTAssertNotNil(intent.sender?.image)
XCTAssertNil(intent.image(forParameterNamed: \.speakableGroupName))
XCTAssertNil(intent.speakableGroupName)
XCTAssertNil(intent.donationMetadata)
}