From 522f99aadd20d6fe376b97678af85a74d76e5e43 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 20 Nov 2024 22:39:13 +0000 Subject: [PATCH 1/4] directory service: notify admins about group registration events (#5223) --- .../src/Directory/Service.hs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 2c18d4df27..afcdb233e8 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -105,8 +105,11 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe SDRSuperUser -> deSuperUserCommand ct ciId cmd DELogChatResponse r -> logInfo r where + withAdminUsers action = void . forkIO $ do + forM_ superUsers $ \KnownContact {contactId} -> action contactId + forM_ adminUsers $ \KnownContact {contactId} -> action contactId withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId - notifySuperUsers s = withSuperUsers $ \contactId -> sendMessage' cc contactId s + notifyAdminUsers s = withAdminUsers $ \contactId -> sendMessage' cc contactId s notifyOwner GroupReg {dbContactId} = sendMessage' cc dbContactId ctId `isOwner` GroupReg {dbContactId} = ctId == dbContactId withGroupReg GroupInfo {groupId, localDisplayName} err action = do @@ -288,16 +291,16 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe notifyOwner gr $ "The group profile is updated " <> userGroupRef <> ", but no link is added to the welcome message.\n\nThe group will remain hidden from the directory until the group link is added and the group is re-approved." GPServiceLinkRemoved -> do notifyOwner gr $ "The group link for " <> userGroupRef <> " is removed from the welcome message.\n\nThe group is hidden from the directory until the group link is added and the group is re-approved." - notifySuperUsers $ "The group link is removed from " <> groupRef <> ", de-listed." + notifyAdminUsers $ "The group link is removed from " <> groupRef <> ", de-listed." GPServiceLinkAdded -> do setGroupStatus st gr $ GRSPendingApproval n' notifyOwner gr $ "The group link is added to " <> userGroupRef <> "!\nIt is hidden from the directory until approved." - notifySuperUsers $ "The group link is added to " <> groupRef <> "." + notifyAdminUsers $ "The group link is added to " <> groupRef <> "." checkRolesSendToApprove gr n' GPHasServiceLink -> do setGroupStatus st gr $ GRSPendingApproval n' notifyOwner gr $ "The group " <> userGroupRef <> " is updated!\nIt is hidden from the directory until approved." - notifySuperUsers $ "The group " <> groupRef <> " is updated." + notifyAdminUsers $ "The group " <> groupRef <> " is updated." checkRolesSendToApprove gr n' GPServiceLinkError -> logError $ "Error: no group link for " <> groupRef <> " pending approval." groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) @@ -329,7 +332,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe maybe ("The group ID " <> tshow dbGroupId <> " submitted: ") (\c -> localDisplayName' c <> " submitted the group ID " <> tshow dbGroupId <> ": ") ct_ <> ("\n" <> groupInfoText p <> "\n" <> membersStr <> "\nTo approve send:") msg = maybe (MCText text) (\image -> MCImage {text, image}) image' - withSuperUsers $ \cId -> do + withAdminUsers $ \cId -> do sendComposedMessage' cc cId Nothing msg sendMessage' cc cId $ "/approve " <> tshow dbGroupId <> ":" <> viewName displayName <> " " <> tshow gaId @@ -344,14 +347,14 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe GRSSuspendedBadRoles -> when (rStatus == GRSOk) $ do setGroupStatus st gr GRSActive notifyOwner gr $ uCtRole <> ".\n\nThe group is listed in the directory again." - notifySuperUsers $ "The group " <> groupRef <> " is listed " <> suCtRole + notifyAdminUsers $ "The group " <> groupRef <> " is listed " <> suCtRole GRSPendingApproval gaId -> when (rStatus == GRSOk) $ do sendToApprove g gr gaId notifyOwner gr $ uCtRole <> ".\n\nThe group is submitted for approval." GRSActive -> when (rStatus /= GRSOk) $ do setGroupStatus st gr GRSSuspendedBadRoles notifyOwner gr $ uCtRole <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupRef <> " is de-listed " <> suCtRole + notifyAdminUsers $ "The group " <> groupRef <> " is de-listed " <> suCtRole _ -> pure () where rStatus = groupRolesStatus contactRole serviceRole @@ -370,7 +373,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe whenContactIsOwner gr $ do setGroupStatus st gr GRSActive notifyOwner gr $ uSrvRole <> ".\n\nThe group is listed in the directory again." - notifySuperUsers $ "The group " <> groupRef <> " is listed " <> suSrvRole + notifyAdminUsers $ "The group " <> groupRef <> " is listed " <> suSrvRole GRSPendingApproval gaId -> when (serviceRole == GRAdmin) $ whenContactIsOwner gr $ do sendToApprove g gr gaId @@ -378,7 +381,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe GRSActive -> when (serviceRole /= GRAdmin) $ do setGroupStatus st gr GRSSuspendedBadRoles notifyOwner gr $ uSrvRole <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupRef <> " is de-listed " <> suSrvRole + notifyAdminUsers $ "The group " <> groupRef <> " is de-listed " <> suSrvRole _ -> pure () where groupRef = groupReference g @@ -395,7 +398,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe when (ctId `isOwner` gr) $ do setGroupStatus st gr GRSRemoved notifyOwner gr $ "You are removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (group owner is removed)." + notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner is removed)." deContactLeftGroup :: ContactId -> GroupInfo -> IO () deContactLeftGroup ctId g = do @@ -404,7 +407,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe when (ctId `isOwner` gr) $ do setGroupStatus st gr GRSRemoved notifyOwner gr $ "You left the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (group owner left)." + notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner left)." deServiceRemovedFromGroup :: GroupInfo -> IO () deServiceRemovedFromGroup g = do @@ -412,7 +415,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe withGroupReg g "service removed" $ \gr -> do setGroupStatus st gr GRSRemoved notifyOwner gr $ serviceName <> " is removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." + notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." deUserCommand :: ServiceState -> Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO () deUserCommand env@ServiceState {searchRequests} ct ciId = \case From 1083a0727a4750ede208ee8fe0cdc4acd1deee4d Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Fri, 22 Nov 2024 08:31:58 +0000 Subject: [PATCH 2/4] flatpak: update metainfo (#5146) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index c3c7954836..bc90e4e041 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,34 @@ + + https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html + +

New in v6.1 - v6.1.1:

+
    +
  • Misc fixes
  • +
+

New in v6.1:

+

Better security:

+
    +
  • SimpleX protocols reviewed by Trail of Bits.
  • +
  • security improvements (don't worry, there is nothing critical there).
  • +
+

Better calls:

+
    +
  • you can switch audio and video during the call.
  • +
  • share the screen from desktop app.
  • +
+

Better user experience:

+
    +
  • switch chat profile for 1-time invitations.
  • +
  • customizable message shape.
  • +
  • better message dates.
  • +
  • forward up to 20 messages at once.
  • +
  • delete or moderate up to 200 messages.
  • +
+
+
https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html From ea9ee987cfb3e89618b8edc6d6e0a15da8e70c7f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:59:39 +0700 Subject: [PATCH 3/4] android, desktop: better message info screen (#5227) - changed tabBar style to leading icon - made tabBar the same size as AppBars - made background color as theme background --- .../common/views/chat/ChatItemInfoView.kt | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index 8b4f7f8ec9..d6744a0a0d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.* import chat.simplex.common.ui.theme.* @@ -477,28 +478,33 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools selection.value = CIInfoTab.Delivery(ciInfo.memberDeliveryStatuses) } } - TabRow( - selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class }, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - availableTabs.forEach { ciInfoTab -> - Tab( - selected = selection.value::class == ciInfoTab::class, - onClick = { - selection.value = ciInfoTab - }, - text = { Text(tabTitle(ciInfoTab), fontSize = 13.sp) }, - icon = { - Icon( - painterResource(tabIcon(ciInfoTab)), - tabTitle(ciInfoTab) - ) - }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) + val oneHandUI = remember { appPrefs.oneHandUI.state } + Box(Modifier.offset(x = 0.dp, y = if (oneHandUI.value) -AppBarHeight * fontSizeSqrtMultiplier else 0.dp)) { + TabRow( + selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class }, + Modifier.height(AppBarHeight * fontSizeSqrtMultiplier), + backgroundColor = MaterialTheme.colors.background, + contentColor = MaterialTheme.colors.primary, + ) { + availableTabs.forEach { ciInfoTab -> + LeadingIconTab( + selected = selection.value::class == ciInfoTab::class, + onClick = { + selection.value = ciInfoTab + }, + text = { Text(tabTitle(ciInfoTab), fontSize = 13.sp) }, + icon = { + Icon( + painterResource(tabIcon(ciInfoTab)), + tabTitle(ciInfoTab) + ) + }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } } + Divider() } } } else { From bff2d7d3b6230d138e14f85e2381a66c5f2fd484 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:34:43 +0700 Subject: [PATCH 4/4] android, desktop: highlight quoted messaged on click to scroll to it (#5229) --- .../simplex/common/views/chat/ChatView.kt | 25 ++++++++++++++++--- .../common/views/chat/item/ChatItemView.kt | 16 +++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 8dd3e42440..2aee98acba 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -980,8 +980,9 @@ fun BoxScope.ChatItemsList( } val chatInfoUpdated = rememberUpdatedState(chatInfo) + val highlightedItems = remember { mutableStateOf(setOf()) } val scope = rememberCoroutineScope() - val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } + val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } LoadLastItems(loadingMoreItems, remoteHostId, chatInfo) SmallScrollOnNewMessage(listState, chatModel.chatItems) @@ -1031,7 +1032,17 @@ fun BoxScope.ChatItemsList( tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + val highlighted = remember { derivedStateOf { highlightedItems.value.contains(cItem.id) } } + LaunchedEffect(Unit) { + snapshotFlow { highlighted.value } + .distinctUntilChanged() + .filter { it } + .collect { + delay(500) + highlightedItems.value = setOf() + } + } + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } @@ -1810,6 +1821,7 @@ private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State, de private fun scrollToItem( loadingMoreItems: MutableState, + highlightedItems: MutableState>, chatInfo: State, maxHeight: State, scope: CoroutineScope, @@ -1840,8 +1852,13 @@ private fun scrollToItem( index = mergedItems.value.indexInParentItems[itemId] ?: -1 } if (index != -1) { - withContext(scope.coroutineContext) { - listState.value.animateScrollToItem(min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) + if (listState.value.layoutInfo.visibleItemsInfo.any { it.index == index && it.offset + it.size <= maxHeight.value }) { + highlightedItems.value = setOf(itemId) + } else { + withContext(scope.coroutineContext) { + listState.value.animateScrollToItem(min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) + highlightedItems.value = setOf(itemId) + } } } } finally { 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 5096992c29..f0c85736af 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 @@ -2,6 +2,8 @@ package chat.simplex.common.views.chat.item import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.* import androidx.compose.material.* @@ -57,6 +59,7 @@ fun ChatItemView( useLinkPreviews: Boolean, linkMode: SimplexLinkMode, revealed: State, + highlighted: State, range: State, selectedChatItems: MutableState?>, fillMaxWidth: Boolean = true, @@ -135,10 +138,19 @@ fun ChatItemView( } Column(horizontalAlignment = if (cItem.chatDir.sent) Alignment.End else Alignment.Start) { + val interactionSource = remember { MutableInteractionSource() } + val enterInteraction = remember { HoverInteraction.Enter() } + KeyChangeEffect(highlighted.value) { + if (highlighted.value) { + interactionSource.emit(enterInteraction) + } else { + interactionSource.emit(HoverInteraction.Exit(enterInteraction)) + } + } Column( Modifier .clipChatItem(cItem, itemSeparation.largeGap, revealed.value) - .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick) + .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick, interactionSource = interactionSource, indication = LocalIndication.current) .onRightClick { showMenu.value = true }, ) { @Composable @@ -1064,6 +1076,7 @@ fun PreviewChatItemView( linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, revealed = remember { mutableStateOf(false) }, + highlighted = remember { mutableStateOf(false) }, range = remember { mutableStateOf(0..1) }, selectedChatItems = remember { mutableStateOf(setOf()) }, selectChatItem = {}, @@ -1106,6 +1119,7 @@ fun PreviewChatItemViewDeletedContent() { linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, revealed = remember { mutableStateOf(false) }, + highlighted = remember { mutableStateOf(false) }, range = remember { mutableStateOf(0..1) }, selectedChatItems = remember { mutableStateOf(setOf()) }, selectChatItem = {},