fix: show verified seal next to sender names in chat (#1506)

Surface fingerprint verification in the message timeline so a verified
contact is distinguishable from an impersonator without opening the
fingerprint sheet.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Taksh Kothari 2026-07-30 22:26:48 +05:30 committed by GitHub
parent 4ef5558d7b
commit e7f4ef0912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -41,7 +41,9 @@ final class ChatMessageFormatter {
}()
let isDark = colorScheme == .dark
if let cachedText = message.getCachedFormattedText(isDark: isDark, isSelf: isSelf, variant: theme.formatCacheVariant) {
let isVerifiedSender = !isSelf && isVerifiedSender(of: message)
let cacheVariant = theme.formatCacheVariant + (isVerifiedSender ? "-vf" : "")
if let cachedText = message.getCachedFormattedText(isDark: isDark, isSelf: isSelf, variant: cacheVariant) {
return cachedText
}
@ -66,6 +68,9 @@ final class ChatMessageFormatter {
suffixStyle.foregroundColor = baseColor.opacity(0.6)
result.append(AttributedString(suffix).mergingAttributes(suffixStyle))
}
if isVerifiedSender {
appendVerifiedSeal(to: &result, baseColor: baseColor, design: design)
}
result.append(AttributedString("> ").mergingAttributes(senderStyle))
let content = message.content
@ -335,7 +340,7 @@ final class ChatMessageFormatter {
result.append(timestamp.mergingAttributes(timestampStyle))
}
message.setCachedFormattedText(result, isDark: isDark, isSelf: isSelf, variant: theme.formatCacheVariant)
message.setCachedFormattedText(result, isDark: isDark, isSelf: isSelf, variant: cacheVariant)
return result
}
@ -356,6 +361,7 @@ final class ChatMessageFormatter {
let isDark = colorScheme == .dark
let baseColor: Color = isSelf ? .orange : peerColor(for: message, isDark: isDark)
let isVerifiedSender = !isSelf && isVerifiedSender(of: message)
if message.sender == "system" {
var style = AttributeContainer()
@ -381,6 +387,9 @@ final class ChatMessageFormatter {
suffixStyle.foregroundColor = baseColor.opacity(0.6)
result.append(AttributedString(suffix).mergingAttributes(suffixStyle))
}
if isVerifiedSender {
appendVerifiedSeal(to: &result, baseColor: baseColor, design: design)
}
result.append(AttributedString("> ").mergingAttributes(senderStyle))
return result
}
@ -427,6 +436,29 @@ final class ChatMessageFormatter {
}
private extension ChatMessageFormatter {
/// Whether the message sender has a fingerprint the user has verified.
/// Used for the in-chat seal next to `<@name>` so verification is visible
/// without opening the fingerprint sheet (#1439).
func isVerifiedSender(of message: BitchatMessage) -> Bool {
guard let peerID = message.senderPeerID,
let fingerprint = viewModel.getFingerprint(for: peerID) else {
return false
}
return viewModel.peerIdentityStore.isVerified(fingerprint)
}
func appendVerifiedSeal(
to result: inout AttributedString,
baseColor: Color,
design: Font.Design
) {
var sealStyle = AttributeContainer()
// Match the peer-list verified seal: filled checkmark in the sender tint.
sealStyle.foregroundColor = baseColor
sealStyle.font = .bitchatSystem(size: 11, weight: .semibold, design: design)
result.append(AttributedString("").mergingAttributes(sealStyle))
}
func peerColor(for message: BitchatMessage, isDark: Bool) -> Color {
if let spid = message.senderPeerID {
if spid.isGeoChat || spid.isGeoDM {