From e78f8459daf08256cd7c798a0d645799ee4deaf4 Mon Sep 17 00:00:00 2001 From: Alexandre Ferris Date: Mon, 27 Nov 2023 12:46:39 +0100 Subject: [PATCH] feat: Highlight selected searched message (WPB-5506) (#2441) --- .../home/conversations/ConversationScreen.kt | 11 +++++--- .../ui/home/conversations/MessageItem.kt | 27 +++++++++++++++++-- .../messages/ConversationMessagesViewModel.kt | 17 +++++++++--- .../wire/android/ui/theme/WireColorScheme.kt | 7 +++-- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index e2598da54c3..67251aa3edf 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -663,6 +663,7 @@ private fun ConversationScreen( lastUnreadMessageInstant = conversationMessagesViewState.firstUnreadInstant, unreadEventCount = conversationMessagesViewState.firstuUnreadEventIndex, conversationDetailsData = conversationInfoViewState.conversationDetailsData, + selectedMessageId = conversationMessagesViewState.searchedMessageId, messageComposerStateHolder = messageComposerStateHolder, messages = conversationMessagesViewState.messages, onSendMessage = onSendMessage, @@ -705,6 +706,7 @@ private fun ConversationScreenContent( lastUnreadMessageInstant: Instant?, unreadEventCount: Int, audioMessagesState: Map, + selectedMessageId: String?, messageComposerStateHolder: MessageComposerStateHolder, messages: Flow>, onSendMessage: (MessageBundle) -> Unit, @@ -757,7 +759,8 @@ private fun ConversationScreenContent( conversationDetailsData = conversationDetailsData, onFailedMessageCancelClicked = onFailedMessageCancelClicked, onFailedMessageRetryClicked = onFailedMessageRetryClicked, - onLinkClick = onLinkClick + onLinkClick = onLinkClick, + selectedMessageId = selectedMessageId ) }, onChangeSelfDeletionClicked = onChangeSelfDeletionClicked, @@ -822,7 +825,8 @@ fun MessageList( conversationDetailsData: ConversationDetailsData, onFailedMessageRetryClicked: (String) -> Unit, onFailedMessageCancelClicked: (String) -> Unit, - onLinkClick: (String) -> Unit + onLinkClick: (String) -> Unit, + selectedMessageId: String? ) { val mostRecentMessage = lazyPagingMessages.itemCount.takeIf { it > 0 }?.let { lazyPagingMessages[0] } @@ -893,7 +897,8 @@ fun MessageList( onSelfDeletingMessageRead = onSelfDeletingMessageRead, onFailedMessageCancelClicked = onFailedMessageCancelClicked, onFailedMessageRetryClicked = onFailedMessageRetryClicked, - onLinkClick = onLinkClick + onLinkClick = onLinkClick, + isSelectedMessage = (message.header.messageId == selectedMessageId) ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt index 6b97c328944..e23af8337fb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.home.conversations +import androidx.compose.animation.Animatable import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -40,10 +41,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -113,7 +116,8 @@ fun MessageItem( onMessageClick: (messageId: String) -> Unit = {}, defaultBackgroundColor: Color = Color.Transparent, shouldDisplayMessageStatus: Boolean = true, - shouldDisplayFooter: Boolean = true + shouldDisplayFooter: Boolean = true, + isSelectedMessage: Boolean = false ) { with(message) { val selfDeletionTimerState = rememberSelfDeletionTimer(header.messageStatus.expirationStatus) @@ -129,7 +133,7 @@ fun MessageItem( ) } - val backgroundColorModifier = if (message.sendingFailed || message.decryptionFailed) { + var backgroundColorModifier = if (message.sendingFailed || message.decryptionFailed) { Modifier.background(colorsScheme().messageErrorBackgroundColor) } else if (selfDeletionTimerState is SelfDeletionTimerHelper.SelfDeletionTimerState.Expirable && !message.isDeleted) { val color by animateColorAsState( @@ -143,6 +147,23 @@ fun MessageItem( Modifier.background(defaultBackgroundColor) } + val colorAnimation = remember { Animatable(Color.Transparent) } + val highlightColor = colorsScheme().selectedMessageHighlightColor + val transparentColor = colorsScheme().primary.copy(alpha = 0F) + LaunchedEffect(isSelectedMessage) { + if (isSelectedMessage) { + colorAnimation.snapTo(highlightColor) + colorAnimation.animateTo( + transparentColor, + tween(SELECTED_MESSAGE_ANIMATION_DURATION) + ) + } + } + + if (isSelectedMessage) { + backgroundColorModifier = Modifier.drawBehind { drawRect(colorAnimation.value) } + } + Box( backgroundColorModifier .clickable(enabled = isContentClickable, onClick = { @@ -644,3 +665,5 @@ private fun Message.DownloadStatus.isSaved(): Boolean { internal val DeliveryStatusContent.expandable get() = this is DeliveryStatusContent.PartialDelivery && !this.isSingleUserFailure + +private const val SELECTED_MESSAGE_ANIMATION_DURATION = 2000 diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 02a1e5894fe..8282ba6a50c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -60,6 +60,7 @@ import com.wire.kalium.logic.feature.message.ToggleReactionUseCase import com.wire.kalium.logic.feature.sessionreset.ResetSessionResult import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.flowOn @@ -69,6 +70,7 @@ import kotlinx.datetime.Instant import okio.Path import javax.inject.Inject import kotlin.math.max +import kotlin.time.Duration.Companion.seconds @HiltViewModel @Suppress("LongParameterList", "TooManyFunctions") @@ -127,10 +129,6 @@ class ConversationMessagesViewModel @Inject constructor( private fun loadPaginatedMessages() = viewModelScope.launch { val lastReadIndex = conversationViewState.searchedMessageId?.let { messageId -> - conversationViewState = conversationViewState.copy( - searchedMessageId = null - ) - when (val result = getSearchedConversationMessagePosition( conversationId = conversationId, messageId = messageId @@ -150,6 +148,17 @@ class ConversationMessagesViewModel @Inject constructor( messages = paginatedMessagesFlow, firstuUnreadEventIndex = max(lastReadIndex - 1, 0) ) + + handleSelectedSearchedMessageHighlighting() + } + + private suspend fun handleSelectedSearchedMessageHighlighting() { + viewModelScope.launch { + delay(3.seconds) + conversationViewState = conversationViewState.copy( + searchedMessageId = null + ) + } } private fun loadLastMessageInstant() = viewModelScope.launch { diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt b/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt index d60f552071c..5fee40987c9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt +++ b/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt @@ -104,6 +104,7 @@ data class WireColorScheme( val onScrollToBottomButtonColor: Color, val validE2eiStatusColor: Color, val mlsVerificationTextColor: Color, + val selectedMessageHighlightColor: Color ) { fun toColorScheme(): ColorScheme = ColorScheme( primary = primary, @@ -237,7 +238,8 @@ private val LightWireColorScheme = WireColorScheme( scrollToBottomButtonColor = WireColorPalette.Gray70, onScrollToBottomButtonColor = Color.White, validE2eiStatusColor = WireColorPalette.LightGreen550, - mlsVerificationTextColor = WireColorPalette.DarkGreen700 + mlsVerificationTextColor = WireColorPalette.DarkGreen700, + selectedMessageHighlightColor = WireColorPalette.DarkBlue50 ) // Dark WireColorScheme @@ -345,7 +347,8 @@ private val DarkWireColorScheme = WireColorScheme( scrollToBottomButtonColor = WireColorPalette.Gray60, onScrollToBottomButtonColor = Color.Black, validE2eiStatusColor = WireColorPalette.DarkGreen550, - mlsVerificationTextColor = WireColorPalette.DarkGreen700 + mlsVerificationTextColor = WireColorPalette.DarkGreen700, + selectedMessageHighlightColor = WireColorPalette.DarkBlue50 ) @PackagePrivate