Merge branch 'master' into server-operators

This commit is contained in:
Evgeny Poberezkin
2024-11-22 15:35:43 +00:00
5 changed files with 106 additions and 38 deletions
@@ -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 {
@@ -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 {
@@ -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 = {},