mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-30 03:10:11 +00:00
Merge branch 'master' into server-operators
This commit is contained in:
+27
-21
@@ -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 {
|
||||
|
||||
+21
-4
@@ -980,8 +980,9 @@ fun BoxScope.ChatItemsList(
|
||||
}
|
||||
|
||||
val chatInfoUpdated = rememberUpdatedState(chatInfo)
|
||||
val highlightedItems = remember { mutableStateOf(setOf<Long>()) }
|
||||
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<Int>, de
|
||||
|
||||
private fun scrollToItem(
|
||||
loadingMoreItems: MutableState<Boolean>,
|
||||
highlightedItems: MutableState<Set<Long>>,
|
||||
chatInfo: State<ChatInfo>,
|
||||
maxHeight: State<Int>,
|
||||
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 {
|
||||
|
||||
+15
-1
@@ -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<Boolean>,
|
||||
highlighted: State<Boolean>,
|
||||
range: State<IntRange?>,
|
||||
selectedChatItems: MutableState<Set<Long>?>,
|
||||
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 = {},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -38,6 +38,34 @@
|
||||
</description>
|
||||
|
||||
<releases>
|
||||
<release version="6.1.1" date="2024-10-18">
|
||||
<url type="details">https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html</url>
|
||||
<description>
|
||||
<p>New in v6.1 - v6.1.1:</p>
|
||||
<ul>
|
||||
<li>Misc fixes</li>
|
||||
</ul>
|
||||
<p>New in v6.1:</p>
|
||||
<p>Better security:</p>
|
||||
<ul>
|
||||
<li>SimpleX protocols reviewed by Trail of Bits.</li>
|
||||
<li>security improvements (don't worry, there is nothing critical there).</li>
|
||||
</ul>
|
||||
<p>Better calls:</p>
|
||||
<ul>
|
||||
<li>you can switch audio and video during the call.</li>
|
||||
<li>share the screen from desktop app.</li>
|
||||
</ul>
|
||||
<p>Better user experience:</p>
|
||||
<ul>
|
||||
<li>switch chat profile for 1-time invitations.</li>
|
||||
<li>customizable message shape.</li>
|
||||
<li>better message dates.</li>
|
||||
<li>forward up to 20 messages at once.</li>
|
||||
<li>delete or moderate up to 200 messages.</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="6.1.0" date="2024-10-14">
|
||||
<url type="details">https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html</url>
|
||||
<description>
|
||||
|
||||
Reference in New Issue
Block a user