From c0d0034d21bcd5bf16c73a70092a5436c3e8971f Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 14:01:42 +0300 Subject: [PATCH] feat: show verification seal on private message sender rows DM-only filled seal next to verified peers' names closes the remaining UI gap from #1439 without dressing public mesh timelines. Co-authored-by: Cursor --- bitchat/App/ConversationUIModel.swift | 12 ++++++++++++ bitchat/Views/Components/TextMessageView.swift | 9 +++++++++ bitchat/Views/Media/MediaMessageView.swift | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/bitchat/App/ConversationUIModel.swift b/bitchat/App/ConversationUIModel.swift index 91f0afef..9311c152 100644 --- a/bitchat/App/ConversationUIModel.swift +++ b/bitchat/App/ConversationUIModel.swift @@ -128,6 +128,18 @@ final class ConversationUIModel: ObservableObject { message.sender == currentNickname || message.senderPeerID == chatViewModel.meshService.myPeerID } + /// Whether a private-message row should show the filled verification seal + /// next to the sender name (#1439). Scoped to DMs only — public timelines + /// have different trust semantics and stay undressed. + func showsVerifiedSeal(for message: BitchatMessage) -> Bool { + guard message.isPrivate, + message.sender != "system", + !isSentByCurrentUser(message), + let peerID = message.senderPeerID else { return false } + guard let fingerprint = chatViewModel.getFingerprint(for: peerID) else { return false } + return chatViewModel.peerIdentityStore.isVerified(fingerprint) + } + func senderDisplayName(for peerID: PeerID, fallbackMessages: [BitchatMessage]) -> String? { if peerID.isGeoDM || peerID.isGeoChat { return chatViewModel.geohashDisplayName(for: peerID) diff --git a/bitchat/Views/Components/TextMessageView.swift b/bitchat/Views/Components/TextMessageView.swift index a16e3ac6..ed376acf 100644 --- a/bitchat/Views/Components/TextMessageView.swift +++ b/bitchat/Views/Components/TextMessageView.swift @@ -50,6 +50,15 @@ struct TextMessageView: View { .padding(.trailing, 4) .accessibilityHidden(true) } + if conversationUIModel.showsVerifiedSeal(for: message) { + Image(systemName: "checkmark.seal.fill") + .font(.bitchatSystem(size: 8)) + .foregroundColor(Color.green.opacity(0.85)) + .padding(.trailing, 4) + .accessibilityLabel( + String(localized: "content.accessibility.verified_sender", defaultValue: "Verified sender", comment: "Accessibility label for the seal next to a verified peer's name on a private message") + ) + } if message.isBridged { Image(systemName: "network") .font(.bitchatSystem(size: 8)) diff --git a/bitchat/Views/Media/MediaMessageView.swift b/bitchat/Views/Media/MediaMessageView.swift index 2e2d1429..29711f38 100644 --- a/bitchat/Views/Media/MediaMessageView.swift +++ b/bitchat/Views/Media/MediaMessageView.swift @@ -48,6 +48,15 @@ struct MediaMessageView: View { .padding(.trailing, 4) .accessibilityHidden(true) } + if conversationUIModel.showsVerifiedSeal(for: message) { + Image(systemName: "checkmark.seal.fill") + .font(.bitchatSystem(size: 8)) + .foregroundColor(Color.green.opacity(0.85)) + .padding(.trailing, 4) + .accessibilityLabel( + String(localized: "content.accessibility.verified_sender", defaultValue: "Verified sender", comment: "Accessibility label for the seal next to a verified peer's name on a private message") + ) + } VStack(alignment: .leading, spacing: 2) { HStack(alignment: .center, spacing: 4) { Text(conversationUIModel.formatMessageHeader(message, colorScheme: colorScheme, theme: theme))