diff --git a/app/src/main/kotlin/com/wire/android/mapper/RegularMessageContentMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/RegularMessageContentMapper.kt index fe5457f6e1..9724ed2d2f 100644 --- a/app/src/main/kotlin/com/wire/android/mapper/RegularMessageContentMapper.kt +++ b/app/src/main/kotlin/com/wire/android/mapper/RegularMessageContentMapper.kt @@ -28,8 +28,6 @@ import com.wire.android.ui.home.conversations.model.MessageButton import com.wire.android.ui.home.conversations.model.UIMessageContent import com.wire.android.ui.home.conversations.model.UIQuotedMessage import com.wire.android.ui.home.conversations.model.messagetypes.image.VisualMediaParams -import com.wire.android.ui.markdown.toMarkdownDocument -import com.wire.android.ui.markdown.toMarkdownTextWithMentions import com.wire.android.ui.theme.Accent import com.wire.android.util.time.ISOFormatter import com.wire.android.util.ui.UIText @@ -110,11 +108,7 @@ class RegularMessageMapper @Inject constructor( MessageBody( message = UIText.DynamicString(textContent.value, content.textContent?.mentions.orEmpty()), - quotedMessage = quotedMessage, - markdownDocument = UIText.DynamicString( - textContent.value, - content.textContent?.mentions.orEmpty() - ).toMarkdownTextWithMentions().second.toMarkdownDocument() + quotedMessage = quotedMessage ) } @@ -199,16 +193,9 @@ class RegularMessageMapper @Inject constructor( else -> UIText.StringResource(R.string.sent_a_message_with_unknown_content) } - val markdownDocument = if (uiText is UIText.DynamicString) { - uiText.toMarkdownTextWithMentions().second.toMarkdownDocument() - } else { - null - } - return MessageBody( message = uiText, - quotedMessage = quotedMessage, - markdownDocument = markdownDocument + quotedMessage = quotedMessage ).let { messageBody -> UIMessageContent.TextMessage( messageBody = messageBody, 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 507195c12a..39ef378296 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 @@ -230,6 +230,8 @@ import com.wire.android.ui.common.R as commonR */ private const val MAXIMUM_SCROLLED_MESSAGES_UNTIL_AUTOSCROLL_STOPS = 5 +private const val SCOPED_VIEW_MODEL_PREFETCH_WINDOW = 3 + /** * The maximum number of participants to start a call without showing a confirmation dialog. */ @@ -1349,11 +1351,22 @@ fun MessageList( } } - val audioMessageKeysInScope = remember(lazyPagingMessages.itemSnapshotList.items) { - lazyPagingMessages.itemSnapshotList.items.mapNotNull { it.audioMessageScopedKeyOrNull() }.distinct() + val scopedMessages by remember(lazyListState, lazyPagingMessages) { + derivedStateOf { + lazyPagingMessages.peekVisibleWindowItems(lazyListState, SCOPED_VIEW_MODEL_PREFETCH_WINDOW) + } + } + val playingAudioMessageKey = (playingAudioMessage as? PlayingAudioMessage.Some)?.let { + AudioMessageArgs(it.conversationId, it.messageId).key } - val assetLocalPathKeysInScope = remember(lazyPagingMessages.itemSnapshotList.items) { - lazyPagingMessages.itemSnapshotList.items + val audioMessageKeysInScope = remember(scopedMessages, playingAudioMessageKey) { + buildList { + scopedMessages.mapNotNullTo(this) { it.audioMessageScopedKeyOrNull() } + playingAudioMessageKey?.let(::add) + }.distinct() + } + val assetLocalPathKeysInScope = remember(scopedMessages) { + scopedMessages .flatMap { it.assetLocalPathScopedKeys() } .distinct() } @@ -1651,6 +1664,27 @@ private fun BoxScope.ScrollDateOverlay( private fun LazyPagingItems.peekOrNull(index: Int): UIMessage? = if (index in 0 until itemCount) peek(index) else null +private fun LazyPagingItems.peekVisibleWindowItems( + lazyListState: LazyListState, + prefetchWindow: Int +): List { + val visibleItems = lazyListState.layoutInfo.visibleItemsInfo + return if (itemCount == 0 || visibleItems.isEmpty()) { + emptyList() + } else { + val firstVisibleIndex = visibleItems.minOf { it.index } + val lastVisibleIndex = visibleItems.maxOf { it.index } + val firstIndex = (firstVisibleIndex - prefetchWindow).coerceAtLeast(0) + val lastIndex = (lastVisibleIndex + prefetchWindow).coerceAtMost(itemCount - 1) + + if (firstIndex > lastIndex) { + emptyList() + } else { + (firstIndex..lastIndex).mapNotNull { index -> peekOrNull(index) } + } + } +} + @Composable private fun MessageGroupDateTime( now: Long, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypes.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypes.kt index d53987364d..d1150693e7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypes.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypes.kt @@ -40,6 +40,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.DpSize @@ -106,9 +107,14 @@ internal fun MessageBody( clickable: Boolean = true, messageStyle: MessageStyle = MessageStyle.NORMAL ) { - val (displayMentions, text) = messageBody?.message?.let { - mapToDisplayMentions(it, LocalContext.current.resources) - } ?: Pair(emptyList(), null) + val resources = LocalContext.current.resources + val configuration = LocalConfiguration.current + val message = messageBody?.message + val (displayMentions, text) = remember(message, configuration) { + message?.let { + mapToDisplayMentions(it, resources) + } ?: Pair(emptyList(), null) + } val color = when (messageStyle) { MessageStyle.BUBBLE_SELF -> colorsScheme().selfBubble.onPrimary diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt index 41b28e31c6..20a3d7bcbd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt @@ -644,8 +644,18 @@ enum class MessageSource { @Serializable data class MessageTime(val instant: Instant) { - val utcISO: String = instant.toIsoDateTimeString() - val formattedDate: String = utcISO.uiMessageDateTime() ?: "" + @Transient + private val utcISOValue: Lazy = lazy(LazyThreadSafetyMode.PUBLICATION) { + instant.toIsoDateTimeString() + } + + @Transient + private val formattedDateValue: Lazy = lazy(LazyThreadSafetyMode.PUBLICATION) { + utcISO.uiMessageDateTime() ?: "" + } + + val utcISO: String get() = utcISOValue.value + val formattedDate: String get() = formattedDateValue.value fun getFormattedDateGroup(now: Long): MessageDateTimeGroup? = utcISO.groupedUIMessageDateTime(now = now) fun shouldDisplayDatesDifferenceDivider(previousDate: String): Boolean = utcISO.shouldDisplayDatesDifferenceDivider(previousDate = previousDate) diff --git a/app/src/test/kotlin/com/wire/android/mapper/RegularMessageContentMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/RegularMessageContentMapperTest.kt index 408851eb17..7e42514212 100644 --- a/app/src/test/kotlin/com/wire/android/mapper/RegularMessageContentMapperTest.kt +++ b/app/src/test/kotlin/com/wire/android/mapper/RegularMessageContentMapperTest.kt @@ -52,6 +52,7 @@ import kotlinx.coroutines.test.runTest import okio.Path import okio.Path.Companion.toPath import okio.buffer +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -83,6 +84,18 @@ class RegularMessageContentMapperTest { } } + @Test + fun givenTextContent_whenMappingToTextMessageContent_thenMarkdownDocumentShouldBeParsedByUi() = runTest { + // Given + val (_, mapper) = Arrangement().arrange() + + // When + val result = mapper.toText(TestConversation.ID, TestMessage.TEXT_MESSAGE.content, userMembers, DeliveryStatus.CompleteDelivery) + + // Then + assertNull(result.messageBody.markdownDocument) + } + @Test fun givenAssetContent_whenMappingToUIMessageContent_thenCorrectValuesShouldBeReturned() = runTest { // Given