From 80538850f1bd38b0fa7e84caca7c7ecd6dc7afb1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 17 Jun 2026 19:10:37 +0100 Subject: [PATCH] ui: show badges in more contexts (#7084) * core: fix delivery cursor not advancing to maximum group member id for posgtgres (#7043) * ui: show badge in user picker above message entry * core: send badge with channel owner profile --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../ContextProfilePickerView.swift | 11 ++++-- .../chat/ComposeContextProfilePickerView.kt | 5 ++- src/Simplex/Chat/Library/Commands.hs | 4 +- tests/ChatTests/ChatRelays.hs | 37 +++++++++++++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift index 427a600627..9047eaf84b 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift @@ -163,10 +163,13 @@ struct ContextProfilePickerView: View { } label: { HStack { ProfileImage(imageStr: user.image, size: 38) - Text(user.chatViewName) - .fontWeight(selectedUser == user && !incognitoDefault ? .medium : .regular) - .foregroundColor(theme.colors.onBackground) - .lineLimit(1) + NameWithBadge( + Text(user.chatViewName) + .fontWeight(selectedUser == user && !incognitoDefault ? .medium : .regular) + .foregroundColor(theme.colors.onBackground), + user.profile.localBadge + ) + .lineLimit(1) Spacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt index ce023e83c9..b7be49055e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt @@ -146,9 +146,10 @@ fun ComposeContextProfilePickerView( ) { ProfileImage(size = USER_ROW_AVATAR_SIZE, image = user.image) TextIconSpaced(false) - Text( + NameWithBadge( user.chatViewName, - modifier = Modifier.align(Alignment.CenterVertically), + user.profile.localBadge, + Modifier.align(Alignment.CenterVertically), fontWeight = if (selectedUser.value.userId == user.userId && !incognitoDefault) FontWeight.Medium else FontWeight.Normal ) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 43f480b5c4..9fe2342dd6 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -3989,9 +3989,9 @@ processChatCommand cxt nm = \case pure (relayMember, conn, groupRelay) let GroupMember {memberRole = userRole, memberId = userMemberId} = membership allowSimplexLinks = groupFeatureUserAllowed SGFSimplexLinks gInfo - membershipProfile = redactedMemberProfile allowSimplexLinks $ fromLocalProfile $ memberProfile membership GroupMember {memberId = relayMemberId} = relayMember - relayInv = GroupRelayInvitation { + membershipProfile <- presentUserBadge user (incognitoMembershipProfile gInfo) $ redactedMemberProfile allowSimplexLinks $ fromLocalProfile $ memberProfile membership + let relayInv = GroupRelayInvitation { fromMember = MemberIdRole userMemberId userRole, fromMemberProfile = membershipProfile, relayMemberId, diff --git a/tests/ChatTests/ChatRelays.hs b/tests/ChatTests/ChatRelays.hs index 4b09347dcf..e5c2598f41 100644 --- a/tests/ChatTests/ChatRelays.hs +++ b/tests/ChatTests/ChatRelays.hs @@ -6,6 +6,7 @@ module ChatTests.ChatRelays where import ChatClient import ChatTests.DBUtils import ChatTests.Groups (memberJoinChannel, memberJoinChannel', prepareChannel, prepareChannel', prepareChannel1Relay, setupRelay) +import ChatTests.Profiles (addTestBadge, issueTestBadge, testBadgeKeys) import ChatTests.Utils import Control.Concurrent (threadDelay) import qualified Data.Aeson as J @@ -14,8 +15,10 @@ import qualified Data.ByteString.Lazy.Char8 as LB import Data.Maybe (fromMaybe) import qualified Data.Text as T import ProtocolTests (testGroupProfile) +import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Protocol (LinkOwnerSig, MsgChatLink (..), MsgContent (..)) import Simplex.Chat.Types (GroupProfile (..)) +import Simplex.Messaging.Crypto.BBS (bbsKeyGen) import Simplex.Messaging.Encoding.String (StrEncoding (..)) import Simplex.Messaging.Util (decodeJSON) import Test.Hspec hiding (it) @@ -32,6 +35,40 @@ chatRelayTests = do it "share channel card in direct chat" testShareChannelDirect it "share channel card in group" testShareChannelGroup it "share channel card in channel" testShareChannelChannel + describe "channel badges" $ do + it "subscriber and owner see each other's badges forwarded by the relay" testChannelMemberBadges + +-- A channel owner and a subscriber each hold a supporter badge; their member profiles only reach +-- each other forwarded by the relay. Both sides should still see the other's active badge. +testChannelMemberBadges :: HasCallStack => TestParams -> IO () +testChannelMemberBadges ps = do + Right (pk, sk) <- bbsKeyGen + let cfg = testCfg {badgePublicKeys = testBadgeKeys pk} + withNewTestChatCfgOpts ps cfg testOpts "alice" aliceProfile $ \alice -> + withNewTestChatCfgOpts ps cfg relayTestOpts "bob" bobProfile $ \bob -> + withNewTestChatCfgOpts ps cfg testOpts "cath" cathProfile $ \cath -> do + addTestBadge alice =<< issueTestBadge sk Nothing + addTestBadge cath =<< issueTestBadge sk Nothing + (shortLink, fullLink) <- prepareChannel1Relay "team" alice bob + memberJoinChannel "team" [bob] [alice] shortLink fullLink cath + -- a channel message lets the relay-forwarded member profiles settle on both sides + alice #> "#team hi" + bob <# "#team> hi" + cath <# "#team> hi [>>]" + threadDelay 1000000 + -- owner and subscriber are connected only via the relay, so /i shows the badge then "member not connected" for both + alice ##> "/i #team cath" + alice <## "group ID: 1" + alice <##. "member ID: " + alice <## "supporter badge - active" + alice <## "no expiry" + alice <## "member not connected" + cath ##> "/i #team alice" + cath <## "group ID: 1" + cath <##. "member ID: " + cath <## "supporter badge - active" + cath <## "no expiry" + cath <## "member not connected" testGetSetChatRelays :: HasCallStack => TestParams -> IO () testGetSetChatRelays ps =