From 0d22280358fe3a6727b5b4a8400d75e1d034f570 Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Fri, 22 Aug 2025 09:53:21 +0000
Subject: [PATCH] core: forward member name; ui: don't show "Member" label if
unknown member was created with name (#6211)
---
apps/ios/SimpleXChat/ChatTypes.swift | 16 +++++++++-----
.../chat/simplex/common/model/ChatModel.kt | 20 +++++++++++------
.../commonMain/resources/MR/base/strings.xml | 2 +-
src/Simplex/Chat/Library/Internal.hs | 8 ++++++-
src/Simplex/Chat/Library/Subscriber.hs | 22 ++++++++++---------
src/Simplex/Chat/Protocol.hs | 6 ++---
6 files changed, 47 insertions(+), 27 deletions(-)
diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift
index 0e4b63a2e0..aed5373664 100644
--- a/apps/ios/SimpleXChat/ChatTypes.swift
+++ b/apps/ios/SimpleXChat/ChatTypes.swift
@@ -2522,7 +2522,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
get {
let p = memberProfile
let name = p.localAlias == "" ? p.displayName : p.localAlias
- return pastMember(name)
+ return unknownMember(name)
}
}
public var fullName: String { get { memberProfile.fullName } }
@@ -2549,13 +2549,19 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
? p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)")
: p.localAlias
)
- return pastMember(name)
+ return unknownMember(name)
}
}
- private func pastMember(_ name: String) -> String {
+ private func unknownMember(_ name: String) -> String {
memberStatus == .memUnknown
- ? String.localizedStringWithFormat(NSLocalizedString("Past member %@", comment: "past/unknown group member"), name)
+ ? (
+ memberId.hasPrefix(name)
+ // unknown member was created using memberId for name
+ ? String.localizedStringWithFormat(NSLocalizedString("Member %@", comment: "past/unknown group member"), name)
+ // unknown member was created with name
+ : name
+ )
: name
}
@@ -2565,7 +2571,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
let fullName = p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)")
let name = p.localAlias == "" ? fullName : "\(p.localAlias) (\(fullName))"
- return pastMember(name)
+ return unknownMember(name)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
index c00507c0a3..047d14d9f1 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
@@ -2247,7 +2247,7 @@ data class GroupMember (
get() {
val p = memberProfile
val name = p.localAlias.ifEmpty { p.displayName }
- return pastMember(name)
+ return unknownMember(name)
}
override val fullName: String get() = memberProfile.fullName
override val shortDescr: String? get() = memberProfile.shortDescr
@@ -2262,13 +2262,19 @@ data class GroupMember (
get() {
val p = memberProfile
val name = p.localAlias.ifEmpty { p.displayName + (if (p.fullName == "" || p.fullName == p.displayName) "" else " / ${p.fullName}") }
- return pastMember(name)
+ return unknownMember(name)
}
- private fun pastMember(name: String): String {
- return if (memberStatus == GroupMemberStatus.MemUnknown)
- String.format(generalGetString(MR.strings.past_member_vName), name)
- else
+ private fun unknownMember(name: String): String {
+ return if (memberStatus == GroupMemberStatus.MemUnknown) {
+ if (memberId.startsWith(name)) {
+ // unknown member was created using memberId for name
+ String.format(generalGetString(MR.strings.past_member_vName), name)
+ } else {
+ // unknown member was created with name
+ name
+ }
+ } else
name
}
@@ -2282,7 +2288,7 @@ data class GroupMember (
} else {
fullName
}
- return pastMember(name)
+ return unknownMember(name)
}
val memberActive: Boolean get() = when (this.memberStatus) {
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index b1a9e39157..151a2019be 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -1760,7 +1760,7 @@
connecting
unknown
- Past member %1$s
+ Member %1$s
No contacts to add
diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs
index f1fdacb114..413a36f567 100644
--- a/src/Simplex/Chat/Library/Internal.hs
+++ b/src/Simplex/Chat/Library/Internal.hs
@@ -1196,9 +1196,15 @@ sendHistory user gInfo@GroupInfo {groupId, membership} m@GroupMember {activeConn
_ -> pure []
let fileDescrChatMsgs = map (ChatMessage senderVRange Nothing) fileDescrEvents
GroupMember {memberId} = sender
- msgForwardEvents = map (\cm -> XGrpMsgForward memberId cm itemTs) (xMsgNewChatMsg : fileDescrChatMsgs)
+ memberName = Just $ memberShortenedName sender
+ msgForwardEvents = map (\cm -> XGrpMsgForward memberId memberName cm itemTs) (xMsgNewChatMsg : fileDescrChatMsgs)
pure msgForwardEvents
+memberShortenedName :: GroupMember -> ContactName
+memberShortenedName GroupMember {memberProfile = LocalProfile {displayName}}
+ | T.length displayName <= 16 = displayName
+ | otherwise = T.take 16 displayName `T.snoc` '…'
+
splitFileDescr :: Int -> RcvFileDescrText -> NonEmpty FileDescr
splitFileDescr partSize rfdText = splitParts 1 rfdText
where
diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs
index e911dc8d03..3517f76152 100644
--- a/src/Simplex/Chat/Library/Subscriber.hs
+++ b/src/Simplex/Chat/Library/Subscriber.hs
@@ -974,7 +974,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XGrpPrefs ps' -> (,False) <$> xGrpPrefs gInfo' m'' ps'
-- TODO [knocking] why don't we forward these messages?
XGrpDirectInv connReq mContent_ msgScope -> memberCanSend m'' msgScope $ (Nothing, False) <$ xGrpDirectInv gInfo' m'' conn' connReq mContent_ msg brokerTs
- XGrpMsgForward memberId msg' msgTs -> (Nothing, False) <$ xGrpMsgForward gInfo' m'' memberId msg' msgTs
+ XGrpMsgForward memberId memberName msg' msgTs -> (Nothing, False) <$ xGrpMsgForward gInfo' m'' memberId memberName msg' msgTs
XInfoProbe probe -> (Nothing, False) <$ xInfoProbe (COMGroupMember m'') probe
XInfoProbeCheck probeHash -> (Nothing, False) <$ xInfoProbeCheck (COMGroupMember m'') probeHash
XInfoProbeOk probe -> (Nothing, False) <$ xInfoProbeOk (COMGroupMember m'') probe
@@ -1001,7 +1001,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
forwardMsgs groupForwardScope fwdMsgs = do
ms <- buildMemberList
let GroupMember {memberId} = m
- events = L.map (\cm -> XGrpMsgForward memberId cm brokerTs) fwdMsgs
+ memberName = Just $ memberShortenedName m
+ events = L.map (\cm -> XGrpMsgForward memberId memberName cm brokerTs) fwdMsgs
unless (null ms) $ void $ sendGroupMessages_ user gInfo ms events
where
buildMemberList = case groupForwardScope of
@@ -2924,7 +2925,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView CEvtMemberBlockedForAll {user, groupInfo = gInfo', byMember = m', member = bm', blocked}
pure $ memberEventForwardScope bm
Left (SEGroupMemberNotFoundByMemberId _) -> do
- bm <- createUnknownMember gInfo memId
+ bm <- createUnknownMember gInfo memId Nothing
bm' <- setMemberBlocked bm
toView $ CEvtUnknownMemberBlocked user gInfo m bm'
pure $ Just GFSMain
@@ -3023,7 +3024,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
forwardToMember :: GroupMember -> CM ()
forwardToMember member = do
let GroupMember {memberId} = m
- event = XGrpMsgForward memberId chatMsg brokerTs
+ memberName = Just $ memberShortenedName m
+ event = XGrpMsgForward memberId memberName chatMsg brokerTs
sendGroupMemberMessage gInfo member event Nothing (pure ())
isUserGrpFwdRelay :: GroupInfo -> Bool
@@ -3181,13 +3183,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toViewTE $ TEContactVerificationReset user ct
createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent RCEVerificationCodeReset) Nothing
- xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> CM ()
- xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId chatMsg msgTs = do
+ xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> Maybe ContactName -> ChatMessage 'Json -> UTCTime -> CM ()
+ xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId memberName chatMsg msgTs = do
when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole localDisplayName)
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case
Right author -> processForwardedMsg author
Left (SEGroupMemberNotFoundByMemberId _) -> do
- unknownAuthor <- createUnknownMember gInfo memberId
+ unknownAuthor <- createUnknownMember gInfo memberId memberName
toView $ CEvtUnknownMemberCreated user gInfo m unknownAuthor
processForwardedMsg unknownAuthor
Left e -> throwError $ ChatErrorStore e
@@ -3216,9 +3218,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XGrpPrefs ps' -> void $ xGrpPrefs gInfo author ps'
_ -> messageError $ "x.grp.msg.forward: unsupported forwarded event " <> T.pack (show $ toCMEventTag event)
- createUnknownMember :: GroupInfo -> MemberId -> CM GroupMember
- createUnknownMember gInfo memberId = do
- let name = nameFromMemberId memberId
+ createUnknownMember :: GroupInfo -> MemberId -> Maybe ContactName -> CM GroupMember
+ createUnknownMember gInfo memberId memberName = do
+ let name = fromMaybe (nameFromMemberId memberId) memberName
withStore $ \db -> createNewUnknownGroupMember db vr user gInfo memberId name
directMsgReceived :: Contact -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> CM ()
diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs
index 165d376814..d6b826491e 100644
--- a/src/Simplex/Chat/Protocol.hs
+++ b/src/Simplex/Chat/Protocol.hs
@@ -348,7 +348,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json
XGrpPrefs :: GroupPreferences -> ChatMsgEvent 'Json
XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> Maybe MsgScope -> ChatMsgEvent 'Json
- XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
+ XGrpMsgForward :: MemberId -> Maybe ContactName -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
XInfoProbe :: Probe -> ChatMsgEvent 'Json
XInfoProbeCheck :: ProbeHash -> ChatMsgEvent 'Json
XInfoProbeOk :: Probe -> ChatMsgEvent 'Json
@@ -1136,7 +1136,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
XGrpInfo_ -> XGrpInfo <$> p "groupProfile"
XGrpPrefs_ -> XGrpPrefs <$> p "groupPreferences"
XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content" <*> opt "scope"
- XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs"
+ XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> opt "memberName" <*> p "msg" <*> p "msgTs"
XInfoProbe_ -> XInfoProbe <$> p "probe"
XInfoProbeCheck_ -> XInfoProbeCheck <$> p "probeHash"
XInfoProbeOk_ -> XInfoProbeOk <$> p "probe"
@@ -1195,7 +1195,7 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en
XGrpInfo p -> o ["groupProfile" .= p]
XGrpPrefs p -> o ["groupPreferences" .= p]
XGrpDirectInv connReq content scope -> o $ ("content" .=? content) $ ("scope" .=? scope) ["connReq" .= connReq]
- XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
+ XGrpMsgForward memberId memberName msg msgTs -> o $ ("memberName" .=? memberName) ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
XInfoProbe probe -> o ["probe" .= probe]
XInfoProbeCheck probeHash -> o ["probeHash" .= probeHash]
XInfoProbeOk probe -> o ["probe" .= probe]