Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d8ac3f7
feat: base for link previews
yamilmedina Jun 1, 2026
8353d6f
feat: base for link previews, mapping to ui
yamilmedina Jun 1, 2026
8c1312e
feat: base for link previews, mapping to ui
yamilmedina Jun 1, 2026
fd2bda2
feat: base for link previews, mapping to ui
yamilmedina Jun 1, 2026
9e029a6
feat: base for link previews, mapping to ui
yamilmedina Jun 1, 2026
78fad6d
wip link previews
yamilmedina Jun 3, 2026
a7f4c4c
Merge branch 'develop' into feat/preview/pr2-image-resolution
yamilmedina Jun 4, 2026
309209f
Merge branch 'develop' into feat/preview/pr2-image-resolution
yamilmedina Jun 8, 2026
fcf7f60
merge rebase
yamilmedina Jun 8, 2026
299a953
feat: implement feature flag aware gate for link previews
yamilmedina Jun 8, 2026
c14e338
feat: improvement of ui for previews, todo linkable and colors
yamilmedina Jun 16, 2026
b51af69
Merge branch 'develop' into feat/preview/featureflagpreviews
yamilmedina Jun 29, 2026
7ed6b95
revertable, ui improvs
yamilmedina Jul 1, 2026
7a91216
feat: user config prop
yamilmedina Jul 6, 2026
aa98bd3
Merge branch 'develop' into feat/preview/featureflagpreviews
yamilmedina Jul 7, 2026
db50a45
feat: simplify generation
yamilmedina Jul 7, 2026
73d9766
feat: fetch metadata early
yamilmedina Jul 8, 2026
392100d
feat: fetch metadata early
yamilmedina Jul 8, 2026
3727edd
feat: cleanup
yamilmedina Jul 8, 2026
2db4973
feat: cleanup
yamilmedina Jul 8, 2026
0c2ac29
feat: stability and kalium ref
yamilmedina Jul 9, 2026
11fd383
Merge branch 'develop' into feat/preview/featureflagpreviews
yamilmedina Jul 10, 2026
b43a291
kalium re
yamilmedina Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class KaliumConfigsModule {
domainWithFaultyKeysMap = BuildConfig.DOMAIN_REMOVAL_KEYS_FOR_REPAIR,
isDebug = BuildConfig.DEBUG,
blockCrossBackendLogin = BuildConfig.BLOCK_CROSS_BACKEND_LOGIN,
linkPreviewEnabled = BuildConfig.LINK_PREVIEW_ENABLED,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import com.wire.kalium.logic.feature.message.fetchOlderMessagesByConversationId
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfAssetMessageByConversationId
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfMessagesByConversation
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfMessagesBySearchQueryAndConversation
import com.wire.kalium.logic.feature.message.linkpreview.DetectLinkPreviewTargetUseCase
import com.wire.kalium.logic.feature.message.linkpreview.GenerateLinkPreviewUseCase
import com.wire.kalium.logic.feature.message.observePaginatedImageAssetMessageByConversationId
import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase
import dev.zacsweers.metro.BindingContainer
Expand Down Expand Up @@ -102,6 +104,14 @@ class MessageModule {
@Provides
fun provideSendTextMessageUseCase(messageScope: MessageScope): SendTextMessageUseCase = messageScope.sendTextMessage

@Provides
fun provideGenerateLinkPreviewUseCase(messageScope: MessageScope): GenerateLinkPreviewUseCase =
messageScope.generateLinkPreview

@Provides
fun provideDetectLinkPreviewTargetUseCase(messageScope: MessageScope): DetectLinkPreviewTargetUseCase =
messageScope.detectLinkPreviewTarget

@Provides
fun provideSendEditTextMessageUseCase(messageScope: MessageScope): SendEditTextMessageUseCase =
messageScope.sendEditTextMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import com.wire.kalium.logic.feature.user.UploadUserAvatarUseCase
import com.wire.kalium.logic.feature.user.UserScope
import com.wire.kalium.logic.feature.user.readReceipts.ObserveReadReceiptsEnabledUseCase
import com.wire.kalium.logic.feature.user.readReceipts.PersistReadReceiptsStatusConfigUseCase
import com.wire.kalium.logic.feature.user.linkPreviews.ObserveLinkPreviewsEnabledUseCase
import com.wire.kalium.logic.feature.user.linkPreviews.PersistLinkPreviewsStatusConfigUseCase
import com.wire.kalium.logic.feature.user.typingIndicator.ObserveTypingIndicatorEnabledUseCase
import com.wire.kalium.logic.feature.user.typingIndicator.PersistTypingIndicatorStatusConfigUseCase
import com.wire.kalium.logic.sync.ForegroundActionsUseCase
Expand Down Expand Up @@ -116,6 +118,14 @@ class UserModule {
fun providePersistReadReceiptsStatusConfig(userScope: UserScope): PersistReadReceiptsStatusConfigUseCase =
userScope.persistReadReceiptsStatusConfig

@Provides
fun provideObserveLinkPreviewsEnabled(userScope: UserScope): ObserveLinkPreviewsEnabledUseCase =
userScope.observeLinkPreviewsEnabled

@Provides
fun providePersistLinkPreviewsStatusConfig(userScope: UserScope): PersistLinkPreviewsStatusConfigUseCase =
userScope.persistLinkPreviewsStatusConfig

@Provides
fun provideFinalizeMLSClientAfterE2EIEnrollmentUseCase(userScope: UserScope): FinalizeMLSClientAfterE2EIEnrollmentUseCase =
userScope.finalizeMLSClientAfterE2EIEnrollment
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ class MessageMapper @Inject constructor(
source = if (sender is SelfUser) MessageSource.Self else MessageSource.OtherUser,
header = provideMessageHeader(sender, message),
messageFooter = footer,
userAvatarData = getUserAvatarData(sender)
userAvatarData = getUserAvatarData(sender),
linkPreviews = if (message is Message.Regular) message.linkPreviews else emptyList()
)

is UIMessageContent.SystemMessage ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletion
import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase
import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.FetchConversationMLSVerificationStatusUseCase
import com.wire.kalium.logic.feature.message.linkpreview.DetectLinkPreviewTargetUseCase
import com.wire.kalium.logic.feature.message.linkpreview.GenerateLinkPreviewUseCase
import com.wire.kalium.logic.feature.user.IsFileSharingEnabledUseCase
import com.wire.kalium.logic.featureFlags.KaliumConfigs
import com.wire.kalium.network.NetworkStateObserver
Expand Down Expand Up @@ -197,6 +199,8 @@ class ConversationCoreViewModelFactory @Inject constructor(
private val getWireCellsConfig: GetWireCellConfigurationUseCase,
private val observeOfflineFilesByConversation: ObserveOfflineFilesByConversationUseCase,
private val isSelfUserViewerOnConversation: IsSelfUserViewerOnConversationUseCase,
private val generateLinkPreview: GenerateLinkPreviewUseCase,
private val detectLinkPreviewTarget: DetectLinkPreviewTargetUseCase,
private val networkStateObserver: NetworkStateObserver,
@CurrentAccount private val selfUserId: UserId,
) {
Expand Down Expand Up @@ -266,6 +270,8 @@ class ConversationCoreViewModelFactory @Inject constructor(
analyticsManager = analyticsManager,
isWireCellsEnabledForConversation = isWireCellsEnabledForConversation,
sharedState = messageSharedState,
generateLinkPreview = generateLinkPreview,
detectLinkPreviewTarget = detectLinkPreviewTarget
)

fun messageDraftViewModel(savedStateHandle: SavedStateHandle) = MessageDraftViewModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ import com.wire.android.ui.common.progress.WireCircularProgressIndicator
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.snackbar.SwipeableSnackbar
import com.wire.android.ui.common.spacers.HorizontalSpace
import com.wire.android.ui.common.textfield.textAsFlow
import com.wire.android.ui.common.visbility.rememberVisibilityState
import com.wire.android.ui.emoji.EmojiPickerBottomSheet
import com.wire.android.ui.home.conversations.AuthorHeaderHelper.rememberShouldHaveSmallBottomPadding
Expand Down Expand Up @@ -205,6 +206,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
Expand Down Expand Up @@ -337,6 +340,28 @@ fun ConversationScreen(
}
}

LaunchedEffect(
messageComposerStateHolder.messageCompositionInputStateHolder.messageTextState,
messageAttachmentsViewModel.attachments,
) {
if (messageAttachmentsViewModel.attachments.isNotEmpty()) {
sendMessageViewModel.clearLinkPreview()
return@LaunchedEffect
}

messageComposerStateHolder.messageCompositionInputStateHolder.messageTextState
.textAsFlow()
.distinctUntilChanged()
.collectLatest { text ->
sendMessageViewModel.updateLinkPreview(
text = text.toString(),
mentions = messageComposerStateHolder.messageComposition.value.selectedMentions.map {
it.intoMessageMention()
}
)
}
}

conversationMigrationViewModel.migratedConversationId?.let { migratedConversationId ->
navigator.navigate(
NavigationCommand(
Expand Down Expand Up @@ -420,7 +445,9 @@ fun ConversationScreen(
NavigationCommand(MessageDetailsScreenDestination(conversationInfoViewModel.conversationId, messageId, isSelfMessage))
)
},
onSendMessage = { sendMessageViewModel.trySendMessage(it) },
onSendMessage = {
sendMessageViewModel.trySendMessage(it.withPrefetchedLinkPreview(sendMessageViewModel.currentLinkPreview))
},
onPingOptionClicked = {
if (conversationCallViewModel.conversationCallViewState.participantsCount > MAX_GROUP_SIZE_FOR_PING) {
showDialog.value = ConversationScreenDialogType.PING_CONFIRMATION
Expand Down Expand Up @@ -761,6 +788,15 @@ fun ConversationScreen(
}
}

private fun MessageBundle.withPrefetchedLinkPreview(
linkPreview: com.wire.kalium.logic.data.message.linkpreview.MessageLinkPreview?
): MessageBundle {
return when (this) {
is ComposableMessageBundle.SendTextMessageBundle -> copy(prefetchedLinkPreview = linkPreview)
else -> this
}
}

private fun conversationScreenOnBackButtonClick(
messageComposerViewModel: MessageComposerViewModel,
messageComposerStateHolder: MessageComposerStateHolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,21 @@ fun MessageBubbleItem(
Modifier.width(normalizedSize.width)
}

is UIMessageContent.TextMessage -> {
val previewParams = message.linkPreviewParams
if (previewParams != null) {
val normalizedSize = previewParams.normalizedSize(
maxBounds = MaxBounds.ScreenFraction(
maxWFraction = dimensions().messageVisualMaxFractionWidth,
maxHFraction = dimensions().messageVisualMaxFractionHeight
)
)
Modifier.width(normalizedSize.width)
} else {
Modifier
}
}

else -> {
Modifier
}
Expand Down Expand Up @@ -201,7 +216,11 @@ fun MessageBubbleItem(
Column {
val contentModifier = if (message.hasAssetParams) {
Modifier.padding(
bottom = if (useSmallBottomPadding) dimensions().spacing0x else paddingValue
bottom = if (useSmallBottomPadding && message.linkPreviewParams == null) {
dimensions().spacing0x
} else {
paddingValue
}
)
} else {
Modifier.padding(all = paddingValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import com.wire.android.ui.common.applyIf
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.spacers.HorizontalSpace
import com.wire.android.ui.common.spacers.VerticalSpace
import com.wire.android.ui.home.conversations.info.ConversationDetailsData
import com.wire.android.ui.home.conversations.conversationAssetPathsViewModel
import com.wire.android.ui.home.conversations.info.ConversationDetailsData
import com.wire.android.ui.home.conversations.messages.QuotedMessage
import com.wire.android.ui.home.conversations.messages.QuotedMessageStyle
import com.wire.android.ui.home.conversations.messages.QuotedStyle
Expand All @@ -58,6 +58,7 @@ import com.wire.android.ui.home.conversations.model.messagetypes.audio.AudioMess
import com.wire.android.ui.home.conversations.model.messagetypes.location.LocationMessageContent
import com.wire.android.ui.home.conversations.model.messagetypes.multipart.MultipartAttachmentsView
import com.wire.android.ui.home.conversations.model.messagetypes.video.VideoMessage
import com.wire.android.ui.home.messagecomposer.LinkPreviewCard
import com.wire.android.ui.theme.Accent
import com.wire.android.util.launchGeoIntent
import com.wire.kalium.logic.data.asset.AssetTransferStatus
Expand Down Expand Up @@ -214,44 +215,67 @@ private fun MessageContent(
}

is UIMessageContent.TextMessage -> {
val hasLinkPreview = message.linkPreviews.isNotEmpty()
Column {
messageContent.messageBody.quotedMessage?.let {
VerticalSpace.x4()
when (it) {
is UIQuotedMessage.UIQuotedData -> QuotedMessage(
conversationId = message.conversationId,
messageData = it,
style = QuotedMessageStyle(
quotedStyle = QuotedStyle.COMPLETE,
messageStyle = messageStyle,
selfAccent = accent,
senderAccent = it.senderAccent
),
clickable = onReplyClick
message.linkPreviews.firstOrNull()?.let { preview ->
LinkPreviewCard(
preview = preview,
isAvailable = !message.isPending && message.isAvailable,
messageStyle = messageStyle,
onClick = { onLinkClick(preview.url) }
)
}
Column(
modifier = if (hasLinkPreview) {
Modifier.padding(
start = dimensions().spacing10x,
end = dimensions().spacing10x,
top = dimensions().spacing8x
)
} else {
Modifier
}
) {
messageContent.messageBody.quotedMessage?.let {
if (!hasLinkPreview) {
VerticalSpace.x4()
}
when (it) {
is UIQuotedMessage.UIQuotedData -> QuotedMessage(
conversationId = message.conversationId,
messageData = it,
style = QuotedMessageStyle(
quotedStyle = QuotedStyle.COMPLETE,
messageStyle = messageStyle,
selfAccent = accent,
senderAccent = it.senderAccent
),
clickable = onReplyClick
)

UIQuotedMessage.UnavailableData -> QuotedUnavailable(
style = QuotedMessageStyle(
quotedStyle = QuotedStyle.COMPLETE,
messageStyle = messageStyle,
selfAccent = accent,
senderAccent = Accent.Unknown
UIQuotedMessage.UnavailableData -> QuotedUnavailable(
style = QuotedMessageStyle(
quotedStyle = QuotedStyle.COMPLETE,
messageStyle = messageStyle,
selfAccent = accent,
senderAccent = Accent.Unknown
)
)
)
}
VerticalSpace.x4()
}
VerticalSpace.x4()
MessageBody(
messageBody = messageContent.messageBody,
searchQuery = searchQuery,
isAvailable = !message.isPending && message.isAvailable,
onOpenProfile = onOpenProfile,
buttonList = null,
messageId = message.header.messageId,
onLinkClick = onLinkClick,
messageStyle = messageStyle,
accent = accent,
)
}
MessageBody(
messageBody = messageContent.messageBody,
searchQuery = searchQuery,
isAvailable = !message.isPending && message.isAvailable,
onOpenProfile = onOpenProfile,
buttonList = null,
messageId = message.header.messageId,
onLinkClick = onLinkClick,
messageStyle = messageStyle,
accent = accent
)
}
}

Expand Down Expand Up @@ -291,7 +315,7 @@ private fun MessageContent(
messageId = message.header.messageId,
onLinkClick = onLinkClick,
messageStyle = messageStyle,
accent = accent
accent = accent,
)
}
}
Expand Down Expand Up @@ -414,7 +438,7 @@ private fun MessageContent(
messageId = message.header.messageId,
onLinkClick = onLinkClick,
messageStyle = messageStyle,
accent = accent
accent = accent,
)
Spacer(modifier = Modifier.height(dimensions().spacing8x))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.asset.AssetTransferStatus
import com.wire.kalium.logic.data.message.Message
import com.wire.kalium.logic.data.message.linkpreview.MessageLinkPreview
import kotlinx.datetime.Instant
import kotlin.time.DurationUnit
import kotlin.time.toDuration
Expand Down Expand Up @@ -459,3 +460,26 @@ fun PreviewBubbleSelfImageFailedDownload() {
}
}
}

@PreviewMultipleThemes
@Composable
fun PreviewMessageWithLinkPreviews() = WireTheme {
Box(modifier = Modifier.background(color = colorsScheme().surface)) {
RegularMessageItem(
message = mockMessageWithText.copy(
linkPreviews = listOf(
MessageLinkPreview(
url = "https://wire.com",
urlOffset = 0,
permanentUrl = "https://wire.com",
title = "Wire",
summary = "Secure collaboration for teams with messaging, file sharing, and calling."
)
)
),
conversationDetailsData = ConversationDetailsData.None(null),
clickActions = MessageClickActions.Content(),
isBubbleUiEnabled = true
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal fun MessageBody(
onLinkClick: (String) -> Unit,
searchQuery: String = "",
clickable: Boolean = true,
messageStyle: MessageStyle = MessageStyle.NORMAL
messageStyle: MessageStyle = MessageStyle.NORMAL,
) {
val resources = LocalContext.current.resources
val configuration = LocalConfiguration.current
Expand Down Expand Up @@ -156,6 +156,7 @@ internal fun MessageBody(
clickable
)
}

buttonList?.also {
VerticalSpace.x4()
MessageButtonsContent(
Expand Down
Loading
Loading