mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-03-14 09:45:42 +00:00
android, desktop: hovered item in chat (#5684)
* android, desktop: hovered item in chat * circle background for button * icon size * change * change * comment * refactor
This commit is contained in:
parent
9dac472191
commit
7a3663f1e0
2 changed files with 52 additions and 21 deletions
|
@ -1214,6 +1214,7 @@ fun BoxScope.ChatItemsList(
|
|||
}
|
||||
}
|
||||
val highlightedItems = remember { mutableStateOf(setOf<Long>()) }
|
||||
val hoveredItemId = remember { mutableStateOf(null as Long?) }
|
||||
val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) {
|
||||
val openAroundItemId = chatModel.openAroundItemId.value
|
||||
val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() }
|
||||
|
@ -1222,6 +1223,7 @@ fun BoxScope.ChatItemsList(
|
|||
highlightedItems.value += openAroundItemId
|
||||
chatModel.openAroundItemId.value = null
|
||||
}
|
||||
hoveredItemId.value = null
|
||||
if (reportsState != null) {
|
||||
reportsListState = null
|
||||
reportsState
|
||||
|
@ -1331,7 +1333,7 @@ fun BoxScope.ChatItemsList(
|
|||
highlightedItems.value = setOf()
|
||||
}
|
||||
}
|
||||
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, 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, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
|
||||
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, 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, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1618,6 +1620,8 @@ private fun LoadLastItems(loadingMoreItems: MutableState<Boolean>, resetListStat
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: in extra rare case when after loading last items only 1 item is loaded, the view will jump like when receiving new message
|
||||
// can be reproduced by forwarding a message to notes that is (ChatPagination.INITIAL_COUNT - 1) away from bottom and going to that message
|
||||
@Composable
|
||||
private fun SmallScrollOnNewMessage(listState: State<LazyListState>, reversedChatItems: State<List<ChatItem>>) {
|
||||
val scrollDistance = with(LocalDensity.current) { -39.dp.toPx() }
|
||||
|
|
|
@ -73,6 +73,7 @@ fun ChatItemView(
|
|||
linkMode: SimplexLinkMode,
|
||||
revealed: State<Boolean>,
|
||||
highlighted: State<Boolean>,
|
||||
hoveredItemId: MutableState<Long?>,
|
||||
range: State<IntRange?>,
|
||||
selectedChatItems: MutableState<Set<Long>?>,
|
||||
searchIsNotBlank: State<Boolean>,
|
||||
|
@ -115,16 +116,8 @@ fun ChatItemView(
|
|||
val onLinkLongClick = { _: String -> showMenu.value = true }
|
||||
val live = remember { derivedStateOf { composeState.value.liveMessage != null } }.value
|
||||
|
||||
val bubbleInteractionSource = remember { MutableInteractionSource() }
|
||||
val lineInteractionSource = remember { MutableInteractionSource() }
|
||||
val bubblePressed = bubbleInteractionSource.collectIsPressedAsState()
|
||||
val linePressed = lineInteractionSource.collectIsPressedAsState()
|
||||
val lineHovered = lineInteractionSource.collectIsHoveredAsState()
|
||||
val lineActivated = remember { derivedStateOf { lineHovered.value || linePressed.value || bubblePressed.value } }
|
||||
Box(
|
||||
modifier = (if (fillMaxWidth) Modifier.fillMaxWidth() else Modifier)
|
||||
.hoverable(lineInteractionSource)
|
||||
.clickable(onClick = {}, interactionSource = lineInteractionSource, indication = null),
|
||||
modifier = (if (fillMaxWidth) Modifier.fillMaxWidth() else Modifier),
|
||||
contentAlignment = alignment,
|
||||
) {
|
||||
val info = cItem.meta.itemStatus.statusInto
|
||||
|
@ -240,20 +233,42 @@ fun ChatItemView(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun GoToItemInnerButton(alignStart: Boolean, icon: ImageResource, parentActivated: State<Boolean>, onClick: () -> Unit) {
|
||||
fun GoToItemInnerButton(alignStart: Boolean, icon: ImageResource, iconSize: Dp = 22.dp, parentActivated: State<Boolean>, onClick: () -> Unit) {
|
||||
val buttonInteractionSource = remember { MutableInteractionSource() }
|
||||
val buttonHovered = buttonInteractionSource.collectIsHoveredAsState()
|
||||
val buttonPressed = buttonInteractionSource.collectIsPressedAsState()
|
||||
val buttonActivated = remember { derivedStateOf { buttonHovered.value || buttonPressed.value } }
|
||||
|
||||
val fullyVisible = parentActivated.value || buttonActivated.value || hoveredItemId.value == cItem.id
|
||||
val mixAlpha = 0.6f
|
||||
val mixedBackgroundColor = if (fullyVisible) {
|
||||
if (MaterialTheme.colors.isLight) {
|
||||
MaterialTheme.colors.secondary.mixWith(Color.White, mixAlpha)
|
||||
} else {
|
||||
MaterialTheme.colors.secondary.mixWith(Color.Black, mixAlpha)
|
||||
}
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
val iconTint = if (fullyVisible) {
|
||||
Color.White
|
||||
} else {
|
||||
if (MaterialTheme.colors.isLight) {
|
||||
MaterialTheme.colors.secondary.mixWith(Color.White, mixAlpha)
|
||||
} else {
|
||||
MaterialTheme.colors.secondary.mixWith(Color.Black, mixAlpha)
|
||||
}
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick,
|
||||
Modifier
|
||||
.padding(start = if (alignStart) 0.dp else DEFAULT_PADDING_HALF + 3.dp, end = if (alignStart) DEFAULT_PADDING_HALF + 3.dp else 0.dp)
|
||||
.size(22.dp)
|
||||
.alpha(if (parentActivated.value || buttonActivated.value) 1f else 0.4f),
|
||||
.then(if (fullyVisible) Modifier.background(mixedBackgroundColor, CircleShape) else Modifier)
|
||||
.size(22.dp),
|
||||
interactionSource = buttonInteractionSource
|
||||
) {
|
||||
Icon(painterResource(icon), null, Modifier.size(22.dp), tint = MaterialTheme.colors.secondary)
|
||||
Icon(painterResource(icon), null, Modifier.size(iconSize), tint = iconTint)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,14 +276,14 @@ fun ChatItemView(
|
|||
fun GoToItemButton(alignStart: Boolean, parentActivated: State<Boolean>) {
|
||||
val chatTypeApiIdMsgId = cItem.meta.itemForwarded?.chatTypeApiIdMsgId
|
||||
if (searchIsNotBlank.value) {
|
||||
GoToItemInnerButton(alignStart, MR.images.ic_search, parentActivated) {
|
||||
GoToItemInnerButton(alignStart, MR.images.ic_search, 17.dp, parentActivated) {
|
||||
withBGApi {
|
||||
openChat(rhId, cInfo.chatType, cInfo.apiId, null, cItem.id)
|
||||
closeReportsIfNeeded()
|
||||
}
|
||||
}
|
||||
} else if (chatTypeApiIdMsgId != null) {
|
||||
GoToItemInnerButton(alignStart, MR.images.ic_arrow_forward, parentActivated) {
|
||||
GoToItemInnerButton(alignStart, MR.images.ic_arrow_forward, 22.dp, parentActivated) {
|
||||
val (chatType, apiId, msgId) = chatTypeApiIdMsgId
|
||||
withBGApi {
|
||||
openChat(rhId, chatType, apiId, null, msgId)
|
||||
|
@ -280,13 +295,15 @@ fun ChatItemView(
|
|||
|
||||
Column(horizontalAlignment = if (cItem.chatDir.sent) Alignment.End else Alignment.Start) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val bubbleInteractionSource = remember { MutableInteractionSource() }
|
||||
val bubbleHovered = bubbleInteractionSource.collectIsHoveredAsState()
|
||||
if (cItem.chatDir.sent) {
|
||||
GoToItemButton(true, lineActivated)
|
||||
GoToItemButton(true, bubbleHovered)
|
||||
}
|
||||
Column(Modifier.weight(1f, fill = false)) {
|
||||
val enterInteraction = remember { HoverInteraction.Enter() }
|
||||
KeyChangeEffect(highlighted.value) {
|
||||
if (highlighted.value) {
|
||||
LaunchedEffect(highlighted.value, hoveredItemId.value) {
|
||||
if (highlighted.value || hoveredItemId.value == cItem.id) {
|
||||
bubbleInteractionSource.emit(enterInteraction)
|
||||
} else {
|
||||
bubbleInteractionSource.emit(HoverInteraction.Exit(enterInteraction))
|
||||
|
@ -295,7 +312,15 @@ fun ChatItemView(
|
|||
Column(
|
||||
Modifier
|
||||
.clipChatItem(cItem, itemSeparation.largeGap, revealed.value)
|
||||
.combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick, interactionSource = bubbleInteractionSource, indication = LocalIndication.current)
|
||||
.hoverable(bubbleInteractionSource)
|
||||
.combinedClickable(
|
||||
onLongClick = { showMenu.value = true },
|
||||
onClick = {
|
||||
if (appPlatform.isAndroid && (searchIsNotBlank.value || cItem.meta.itemForwarded?.chatTypeApiIdMsgId != null)) {
|
||||
hoveredItemId.value = if (hoveredItemId.value == cItem.id) null else cItem.id
|
||||
}
|
||||
onClick()
|
||||
}, interactionSource = bubbleInteractionSource, indication = LocalIndication.current)
|
||||
.onRightClick { showMenu.value = true },
|
||||
) {
|
||||
@Composable
|
||||
|
@ -744,7 +769,7 @@ fun ChatItemView(
|
|||
}
|
||||
}
|
||||
if (!cItem.chatDir.sent) {
|
||||
GoToItemButton(false, lineActivated)
|
||||
GoToItemButton(false, bubbleHovered)
|
||||
}
|
||||
}
|
||||
if (cItem.content.msgContent != null && (cItem.meta.itemDeleted == null || revealed.value) && cItem.reactions.isNotEmpty()) {
|
||||
|
@ -1399,6 +1424,7 @@ fun PreviewChatItemView(
|
|||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||
revealed = remember { mutableStateOf(false) },
|
||||
highlighted = remember { mutableStateOf(false) },
|
||||
hoveredItemId = remember { mutableStateOf(null) },
|
||||
range = remember { mutableStateOf(0..1) },
|
||||
selectedChatItems = remember { mutableStateOf(setOf()) },
|
||||
searchIsNotBlank = remember { mutableStateOf(false) },
|
||||
|
@ -1447,6 +1473,7 @@ fun PreviewChatItemViewDeletedContent() {
|
|||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||
revealed = remember { mutableStateOf(false) },
|
||||
highlighted = remember { mutableStateOf(false) },
|
||||
hoveredItemId = remember { mutableStateOf(null) },
|
||||
range = remember { mutableStateOf(0..1) },
|
||||
selectedChatItems = remember { mutableStateOf(setOf()) },
|
||||
searchIsNotBlank = remember { mutableStateOf(false) },
|
||||
|
|
Loading…
Add table
Reference in a new issue