From b97e1e0f119c484156a6f113e8a7f1e7c570a83c Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Wed, 14 May 2025 15:14:34 +0000
Subject: [PATCH] ui: show new messages from support scope in main chat preview
when invitee is pending (#5909)
---
apps/ios/Shared/Model/ChatModel.swift | 2 +-
.../Chat/ComposeMessage/ComposeView.swift | 2 +-
.../Views/Chat/Group/GroupChatInfoView.swift | 2 +-
.../Views/ChatList/ChatPreviewView.swift | 24 ++++++-------------
.../platform/PlatformTextField.android.kt | 2 +-
.../chat/simplex/common/model/ChatModel.kt | 20 ++++++++++------
.../common/views/chatlist/ChatPreviewView.kt | 24 +++++++------------
.../commonMain/resources/MR/base/strings.xml | 2 +-
.../platform/PlatformTextField.desktop.kt | 2 +-
src/Simplex/Chat/Library/Subscriber.hs | 20 +++++++++-------
10 files changed, 45 insertions(+), 55 deletions(-)
diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift
index cdf3ad05a9..23a50ff07b 100644
--- a/apps/ios/Shared/Model/ChatModel.swift
+++ b/apps/ios/Shared/Model/ChatModel.swift
@@ -582,7 +582,7 @@ final class ChatModel: ObservableObject {
// update chat list
if let i = getChatIndex(cInfo.id) {
// update preview
- if cInfo.groupChatScope() == nil {
+ if cInfo.groupChatScope() == nil || cInfo.groupInfo?.membership.memberPending ?? false {
chats[i].chatItems = switch cInfo {
case .group:
if let currentPreviewItem = chats[i].chatItems.first {
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
index aec160298e..e4d1fb5a66 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
@@ -460,7 +460,7 @@ struct ComposeView: View {
if im.secondaryIMFilter == nil {
if chat.userIsPending {
- Text("reviewed by moderators")
+ Text("reviewed by admins")
.italic()
.foregroundColor(theme.colors.secondary)
.padding(.horizontal, 12)
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 622213c946..91c70f5d57 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -540,7 +540,7 @@ struct GroupChatInfoView: View {
SecondaryChatView(chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()))
} label: {
HStack {
- Label("Chat with admins", systemImage: chat.supportUnreadCount > 0 ? "flag.filled" : "flag")
+ Label("Chat with admins", systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag")
Spacer()
if chat.supportUnreadCount > 0 {
UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary)
diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
index d0a6e77566..49f629d084 100644
--- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
@@ -346,6 +346,7 @@ struct ChatPreviewView: View {
case .memRejected: chatPreviewInfoText("rejected")
case .memInvited: groupInvitationPreviewText(groupInfo)
case .memAccepted: chatPreviewInfoText("connecting…")
+ case .memPendingReview, .memPendingApproval: chatPreviewInfoText("reviewed by admins")
default: EmptyView()
}
default: EmptyView()
@@ -439,9 +440,11 @@ struct ChatPreviewView: View {
if progressByTimeout {
ProgressView()
} else if chat.chatStats.reportsCount > 0 {
- groupReportsIcon(size: size * 0.8)
+ flagIcon(size: size * 0.8, color: .red)
} else if chat.supportUnreadCount > 0 {
- GroupSupportUnreadIcon(size: size * 0.8)
+ flagIcon(size: size * 0.8, color: theme.colors.primary)
+ } else if chat.chatInfo.groupInfo?.membership.memberPending ?? false {
+ flagIcon(size: size * 0.8, color: theme.colors.secondary)
} else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
@@ -487,25 +490,12 @@ struct ChatPreviewView: View {
}
}
-func groupReportsIcon(size: CGFloat) -> some View {
+func flagIcon(size: CGFloat, color: Color) -> some View {
Image(systemName: "flag")
.resizable()
.scaledToFit()
.frame(width: size, height: size)
- .foregroundColor(.red)
-}
-
-struct GroupSupportUnreadIcon: View {
- @EnvironmentObject var theme: AppTheme
- var size: CGFloat
-
- var body: some View {
- Image(systemName: "flag")
- .resizable()
- .scaledToFit()
- .frame(width: size, height: size)
- .foregroundColor(theme.colors.primary)
- }
+ .foregroundColor(color)
}
func smallContentPreview(size: CGFloat, _ view: @escaping () -> some View) -> some View {
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt
index e6038a3fd5..3263e559b7 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt
@@ -200,7 +200,7 @@ actual fun PlatformTextField(
if (composeState.value.preview is ComposePreview.VoicePreview) {
ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding)
} else if (userIsPending) {
- ComposeOverlay(MR.strings.reviewed_by_moderators, textStyle, padding)
+ ComposeOverlay(MR.strings.reviewed_by_admins, textStyle, padding)
} else if (userIsObserver) {
ComposeOverlay(MR.strings.you_are_observer, textStyle, padding)
}
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 37c37cd211..f50b4a99ca 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
@@ -442,9 +442,9 @@ object ChatModel {
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
if (i >= 0) {
- chat = chats[i]
- // update preview
- if (cInfo.groupChatScope() == null) {
+ chat = chatsContext.chats[i]
+ // update preview (for chat from main scope to show new items for invitee in pending status)
+ if (cInfo.groupChatScope() == null || cInfo.groupInfo_?.membership?.memberPending == true) {
val newPreviewItem = when (cInfo) {
is ChatInfo.Group -> {
val currentPreviewItem = chat.chatItems.firstOrNull()
@@ -462,7 +462,7 @@ object ChatModel {
else -> cItem
}
val wasUnread = chat.unreadTag
- chats[i] = chat.copy(
+ chatsContext.chats[i] = chat.copy(
chatItems = arrayListOf(newPreviewItem),
chatStats =
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
@@ -471,11 +471,11 @@ object ChatModel {
} else
chat.chatStats
)
- updateChatTagReadInPrimaryContext(chats[i], wasUnread)
+ updateChatTagReadInPrimaryContext(chatsContext.chats[i], wasUnread)
}
// pop chat
if (appPlatform.isDesktop && cItem.chatDir.sent) {
- reorderChat(chats[i], 0)
+ reorderChat(chatsContext.chats[i], 0)
} else {
popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = i)
}
@@ -1558,7 +1558,13 @@ sealed class ChatInfo: SomeChat, NamedChat {
is Direct -> contact.activeConn == null && contact.profile.contactLink != null && contact.active
else -> false
}
- }
+
+ val groupInfo_: GroupInfo?
+ get() = when (this) {
+ is Group -> groupInfo
+ else -> null
+ }
+}
@Serializable
sealed class NetworkStatus {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
index eb7eb74103..2d375389dc 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
@@ -241,6 +241,8 @@ fun ChatPreviewView(
GroupMemberStatus.MemRejected -> Text(stringResource(MR.strings.group_preview_rejected))
GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo))
GroupMemberStatus.MemAccepted -> Text(stringResource(MR.strings.group_connection_pending), color = MaterialTheme.colors.secondary)
+ GroupMemberStatus.MemPendingReview, GroupMemberStatus.MemPendingApproval ->
+ Text(stringResource(MR.strings.reviewed_by_admins), color = MaterialTheme.colors.secondary)
else -> {}
}
else -> {}
@@ -363,9 +365,11 @@ fun ChatPreviewView(
if (progressByTimeout) {
progressView()
} else if (chat.chatStats.reportsCount > 0) {
- GroupReportsIcon()
+ FlagIcon(color = MaterialTheme.colors.error)
} else if (chat.supportUnreadCount > 0) {
- GroupSupportUnreadIcon()
+ FlagIcon(color = MaterialTheme.colors.primary)
+ } else if (chat.chatInfo.groupInfo_?.membership?.memberPending == true) {
+ FlagIcon(color = MaterialTheme.colors.secondary)
} else {
IncognitoIcon(chat.chatInfo.incognito)
}
@@ -550,23 +554,11 @@ fun IncognitoIcon(incognito: Boolean) {
}
@Composable
-fun GroupReportsIcon() {
+fun FlagIcon(color: Color) {
Icon(
painterResource(MR.images.ic_flag),
contentDescription = null,
- tint = MaterialTheme.colors.error,
- modifier = Modifier
- .size(21.sp.toDp())
- .offset(x = 2.sp.toDp())
- )
-}
-
-@Composable
-fun GroupSupportUnreadIcon() {
- Icon(
- painterResource(MR.images.ic_flag),
- contentDescription = null,
- tint = MaterialTheme.colors.primary,
+ tint = color,
modifier = Modifier
.size(21.sp.toDp())
.offset(x = 2.sp.toDp())
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 bd450d7d3c..12016794ee 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -492,7 +492,7 @@
The image cannot be decoded. Please, try a different image or contact developers.
The video cannot be decoded. Please, try a different video or contact developers.
you are observer
- reviewed by moderators
+ reviewed by admins
You can\'t send messages!
Please contact group admin.
Files and media prohibited!
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt
index c6b9a2f73e..03bc497699 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt
@@ -206,7 +206,7 @@ actual fun PlatformTextField(
if (composeState.value.preview is ComposePreview.VoicePreview) {
ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding)
} else if (userIsPending) {
- ComposeOverlay(MR.strings.reviewed_by_moderators, textStyle, padding)
+ ComposeOverlay(MR.strings.reviewed_by_admins, textStyle, padding)
} else if (userIsObserver) {
ComposeOverlay(MR.strings.you_are_observer, textStyle, padding)
}
diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs
index f9d242ff27..9761c3841d 100644
--- a/src/Simplex/Chat/Library/Subscriber.hs
+++ b/src/Simplex/Chat/Library/Subscriber.hs
@@ -2121,19 +2121,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
processUserAccepted = case acceptance of
GAAccepted -> do
membership' <- withStore' $ \db -> updateGroupMemberAccepted db user membership GSMemConnected role
- let scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo scopeInfo m) msg brokerTs (CIRcvGroupEvent RGEUserAccepted)
+ let gInfo' = gInfo {membership = membership'}
+ scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
+ (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m) msg brokerTs (CIRcvGroupEvent RGEUserAccepted)
groupMsgToView cInfo ci
- toView $ CEvtUserJoinedGroup user gInfo {membership = membership'} m
- let cd = CDGroupRcv gInfo Nothing m
+ toView $ CEvtUserJoinedGroup user gInfo' m
+ let cd = CDGroupRcv gInfo' Nothing m
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
- createGroupFeatureItems user cd CIRcvGroupFeature gInfo
- maybeCreateGroupDescrLocal gInfo m
+ createGroupFeatureItems user cd CIRcvGroupFeature gInfo'
+ maybeCreateGroupDescrLocal gInfo' m
GAPendingReview -> do
membership' <- withStore' $ \db -> updateGroupMemberAccepted db user membership GSMemPendingReview role
- let scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
- createInternalChatItem user (CDGroupSnd gInfo scopeInfo) (CISndGroupEvent SGEUserPendingReview) Nothing
- toView $ CEvtMemberAcceptedByOther user gInfo m membership'
+ let gInfo' = gInfo {membership = membership'}
+ scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
+ createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndGroupEvent SGEUserPendingReview) Nothing
+ toView $ CEvtMemberAcceptedByOther user gInfo' m membership'
GAPendingApproval ->
messageWarning "x.grp.link.acpt: unexpected group acceptance - pending approval"
introduceToRemainingMembers acceptedMember = do