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