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 a45d384638..8cdac06333 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
@@ -2569,7 +2569,7 @@ data class ChatItem (
else -> CIMergeCategory.RcvGroupEvent
}
is CIContent.SndGroupEventContent -> when (content.sndGroupEvent) {
- is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft -> null
+ is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft, is SndGroupEvent.UserPendingReview -> null
else -> CIMergeCategory.SndGroupEvent
}
else -> {
@@ -4349,6 +4349,7 @@ sealed class SndGroupEvent() {
@Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): SndGroupEvent()
@Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent()
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): SndGroupEvent()
+ @Serializable @SerialName("userPendingReview") class UserPendingReview(): SndGroupEvent()
val text: String get() = when (this) {
is MemberRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_member_role), profile.profileViewName, role.text)
@@ -4361,6 +4362,7 @@ sealed class SndGroupEvent() {
is MemberDeleted -> String.format(generalGetString(MR.strings.snd_group_event_member_deleted), profile.profileViewName)
is UserLeft -> generalGetString(MR.strings.snd_group_event_user_left)
is GroupUpdated -> generalGetString(MR.strings.snd_group_event_group_profile_updated)
+ is UserPendingReview -> generalGetString(MR.strings.snd_group_event_user_pending_review)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
index 3ea75a74a7..1f4615d5fe 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
@@ -636,6 +636,15 @@ fun ChatItemView(
CIEventView(eventItemViewText(reversedChatItems))
}
+ @Composable fun UserPendingReviewEventItemView() {
+ Text(
+ buildAnnotatedString {
+ withStyle(chatEventStyle.copy(fontWeight = FontWeight.Bold)) { append(cItem.content.text) }
+ },
+ Modifier.padding(horizontal = 6.dp, vertical = 6.dp)
+ )
+ }
+
@Composable
fun DeletedItem() {
MarkedDeletedItemView(chatsCtx, cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp)
@@ -717,7 +726,10 @@ fun ChatItemView(
MsgContentItemDropdownMenu()
}
is CIContent.SndGroupEventContent -> {
- EventItemView()
+ when (c.sndGroupEvent) {
+ is SndGroupEvent.UserPendingReview -> UserPendingReviewEventItemView()
+ else -> EventItemView()
+ }
MsgContentItemDropdownMenu()
}
is CIContent.RcvConnEventContent -> {
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 a3f7ae9dfd..d9f789a202 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -1594,6 +1594,7 @@
you removed %1$s
you left
group profile updated
+ Please wait for group moderators to review your request to join the group.
%s connected
%s and %s connected
diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs
index 13e58eb36d..66cc5c1767 100644
--- a/src/Simplex/Chat/Library/Internal.hs
+++ b/src/Simplex/Chat/Library/Internal.hs
@@ -967,10 +967,17 @@ profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$>
ExistingIncognito lp -> fromLocalProfile lp
introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToModerators vr user gInfo m = do
+introduceToModerators vr user gInfo@GroupInfo {groupId} m = do
+ when (maxVersion (memberChatVRange m) < groupKnockingVersion) $ sendPendingReviewMessage
modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo
let rcpModMs = filter memberCurrent modMs
introduceMember vr user gInfo m rcpModMs (Just $ MSMember $ memberId' m)
+ where
+ sendPendingReviewMessage = case memberConn m of
+ Just conn -> do
+ let event = XMsgNew $ MCSimple $ extMsgContent (MCText pendingReviewMessage) Nothing
+ sendGroupMemberMessages user conn [event] groupId
+ Nothing -> pure ()
introduceToAll :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
introduceToAll vr user gInfo m = do
diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs
index 41d7bdad72..35b6a0b822 100644
--- a/src/Simplex/Chat/Library/Subscriber.hs
+++ b/src/Simplex/Chat/Library/Subscriber.hs
@@ -796,9 +796,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
when (connChatVersion < batchSend2Version) sendGroupAutoReply
case mStatus of
GSMemPendingApproval -> pure ()
- -- edge case: reviews were turned off mid connection;
- -- options: proceed to review as declared, or introduce to group and send XGrpLinkAcpt;
- -- choosing first option for simplicity, same edge case for approval is also not considered
GSMemPendingReview -> introduceToModerators vr user gInfo' m'
_ -> do
introduceToAll vr user gInfo' m'
@@ -2470,7 +2467,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
pure (announcedMember', Just scopeInfo)
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> CM ()
- xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do
+ xGrpMemIntro gInfo@GroupInfo {membership, chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do
case memberCategory m of
GCHostMember ->
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
@@ -2481,6 +2478,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
Nothing -> messageError "x.grp.mem.intro: member chat version range incompatible"
Just (ChatVersionRange mcvr)
| maxVersion mcvr >= groupDirectInvVersion -> do
+ memCount <- withStore' $ \db -> getGroupMembersCount db user gInfo
+ -- only create SGEUserPendingReview item on the first introduction - when only 2 members are user and host
+ when (memberPending membership && memCount == 2) $ do
+ (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
+ createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndGroupEvent SGEUserPendingReview) Nothing
subMode <- chatReadVar subscriptionMode
-- [async agent commands] commands should be asynchronous, continuation is to send XGrpMemInv - have to remember one has completed and process on second
groupConnIds <- createConn subMode
diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs
index 886639aa07..9e93db39b5 100644
--- a/src/Simplex/Chat/Messages/CIContent.hs
+++ b/src/Simplex/Chat/Messages/CIContent.hs
@@ -341,6 +341,12 @@ sndGroupEventToText = \case
SGEMemberDeleted _ p -> "removed " <> profileToText p
SGEUserLeft -> "left"
SGEGroupUpdated _ -> "group profile updated"
+ SGEUserPendingReview -> "please wait for group moderators to review your request to join the group"
+
+-- used to send to members with old version
+pendingReviewMessage :: Text
+pendingReviewMessage =
+ "Please wait for group moderators to review your request to join the group."
rcvConnEventToText :: RcvConnEvent -> Text
rcvConnEventToText = \case
diff --git a/src/Simplex/Chat/Messages/CIContent/Events.hs b/src/Simplex/Chat/Messages/CIContent/Events.hs
index ca076e5b8a..36b3a6ebdd 100644
--- a/src/Simplex/Chat/Messages/CIContent/Events.hs
+++ b/src/Simplex/Chat/Messages/CIContent/Events.hs
@@ -40,6 +40,7 @@ data SndGroupEvent
| SGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRUserDeletedMembers
| SGEUserLeft -- CRLeftMemberUser
| SGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated
+ | SGEUserPendingReview
deriving (Show)
data RcvConnEvent
diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs
index 24f598a7e6..2e82b1dc27 100644
--- a/src/Simplex/Chat/Store/Groups.hs
+++ b/src/Simplex/Chat/Store/Groups.hs
@@ -56,6 +56,7 @@ module Simplex.Chat.Store.Groups
getGroupMembers,
getGroupModerators,
getGroupMembersForExpiration,
+ getGroupMembersCount,
getGroupCurrentMembersCount,
deleteGroupChatItems,
deleteGroupMembers,
@@ -944,6 +945,11 @@ toContactMember :: VersionRangeChat -> User -> (GroupMemberRow :. MaybeConnectio
toContactMember vr User {userContactId} (memberRow :. connRow) =
(toGroupMember userContactId memberRow) {activeConn = toMaybeConnection vr connRow}
+getGroupMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int
+getGroupMembersCount db User {userId} GroupInfo {groupId} =
+ fromOnly . head
+ <$> DB.query db "SELECT COUNT(1) FROM group_members WHERE group_id = ? AND user_id = ?" (groupId, userId)
+
getGroupCurrentMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int
getGroupCurrentMembersCount db User {userId} GroupInfo {groupId} = do
statuses :: [GroupMemberStatus] <-
diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt
index 3b63f346d5..743bfc53bf 100644
--- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt
+++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt
@@ -5650,6 +5650,10 @@ Query: SELECT COUNT(1) FROM contacts WHERE user_id = ? AND chat_item_ttl > 0
Plan:
SEARCH contacts USING INDEX idx_contacts_chat_ts (user_id=?)
+Query: SELECT COUNT(1) FROM group_members WHERE group_id = ? AND user_id = ?
+Plan:
+SEARCH group_members USING COVERING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
+
Query: SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0
Plan:
SEARCH groups USING INDEX idx_groups_chat_ts (user_id=?)