Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ dependencies {
implementationWithCoverage(projects.core.uiCommon)
implementationWithCoverage(projects.core.di)
implementationWithCoverage(projects.core.media)
implementationWithCoverage(projects.core.mediaPlayer)
implementationWithCoverage(projects.core.notification)
implementationWithCoverage(projects.core.search)
implementationWithCoverage(projects.features.cells)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.wire.android.di.accountScoped.TeamModule
import com.wire.android.di.accountScoped.UserModule
import com.wire.android.feature.cells.ui.CellsMetroViewModelBindings
import com.wire.android.feature.meetings.ui.MeetingsMetroViewModelBindings
import com.wire.android.mediaplayer.MediaPlayerMetroViewModelBindings
import com.wire.android.notification.broadcastreceivers.EndOngoingCallReceiver
import com.wire.android.notification.broadcastreceivers.IncomingCallActionReceiver
import com.wire.android.notification.broadcastreceivers.NomadLogoutReceiver
Expand Down Expand Up @@ -107,6 +108,7 @@ import dev.zacsweers.metrox.viewmodel.ViewModelGraph
MeetingsMetroViewModelBindings::class,
CoreUICommonMetroViewModelBindings::class,
SearchMetroViewModelBindings::class,
MediaPlayerMetroViewModelBindings::class,
]
)
@Suppress("TooManyFunctions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import com.ramcosta.composedestinations.generated.app.destinations.MessageDetail
import com.ramcosta.composedestinations.generated.app.destinations.OtherUserProfileScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.SelfUserProfileScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ServiceDetailsScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.VideoPlayerScreenDestination
import com.ramcosta.composedestinations.generated.sketch.destinations.DrawingCanvasScreenDestination
import com.ramcosta.composedestinations.result.NavResult.Canceled
import com.ramcosta.composedestinations.result.NavResult.Value
Expand Down Expand Up @@ -512,6 +513,17 @@ fun ConversationScreen(
updateImageOnFullscreenMode(message)
}
},
onVideoClick = { localPath, contentUrl, fileName ->
navigator.navigate(
NavigationCommand(
VideoPlayerScreenDestination(
localPath = localPath,
contentUrl = contentUrl,
fileName = fileName,
)
)
)
},
onStartCall = {
conversationCallViewModel.startCallIfPossible(conversationInfoViewModel.conversationInfoViewState.conversationType)
},
Expand Down Expand Up @@ -827,6 +839,7 @@ private fun ConversationScreen(
onDeleteMessage: (String, Boolean) -> Unit,
onAssetItemClicked: (String) -> Unit,
onImageFullScreenMode: (UIMessage.Regular, Boolean, String?) -> Unit,
onVideoClick: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit,
onStartCall: () -> Unit,
onJoinCall: () -> Unit,
onReactionClick: (messageId: String, reactionEmoji: String) -> Unit,
Expand Down Expand Up @@ -937,6 +950,7 @@ private fun ConversationScreen(
onAudioRecorded = onAudioRecorded,
onAssetItemClicked = onAssetItemClicked,
onImageFullScreenMode = onImageFullScreenMode,
onVideoClick = onVideoClick,
onReactionClicked = onReactionClick,
onResetSessionClicked = onResetSessionClick,
onOpenProfile = onOpenProfile,
Expand Down Expand Up @@ -1025,6 +1039,7 @@ private fun ConversationScreenContent(
onAudioRecorded: (UriAsset) -> Unit,
onAssetItemClicked: (String) -> Unit,
onImageFullScreenMode: (UIMessage.Regular, Boolean, String?) -> Unit,
onVideoClick: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit,
onReactionClicked: (String, String) -> Unit,
onResetSessionClicked: (senderUserId: UserId, clientId: String?) -> Unit,
onOpenProfile: (senderId: MessageSenderId) -> Unit,
Expand Down Expand Up @@ -1081,6 +1096,7 @@ private fun ConversationScreenContent(
onReactionClicked = onReactionClicked,
onAssetClicked = onAssetItemClicked,
onImageClicked = onImageFullScreenMode,
onVideoClicked = onVideoClick,
onLinkClicked = onLinkClick,
onReplyClicked = onNavigateToReplyOriginalMessage,
onResetSessionClicked = onResetSessionClicked,
Expand Down Expand Up @@ -1837,6 +1853,7 @@ fun PreviewConversationScreen() = WireTheme {
onDeleteMessage = { _, _ -> },
onAssetItemClicked = { },
onImageFullScreenMode = { _, _, _ -> },
onVideoClick = { _, _, _ -> },
onStartCall = { },
onJoinCall = { },
onReactionClick = { _, _ -> },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
open val onReactionClicked: (String, String) -> Unit = { _, _ -> }
open val onAssetClicked: (String) -> Unit = {}
open val onImageClicked: (UIMessage.Regular, Boolean, String?) -> Unit = { _, _, _ -> }
open val onVideoClicked: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit = { _, _, _ -> }

Check warning on line 32 in app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/item/MessageClickActions.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/item/MessageClickActions.kt#L32

Added line #L32 was not covered by tests
open val onLinkClicked: (String) -> Unit = {}
open val onReplyClicked: (UIMessage.Regular) -> Unit = {}
open val onResetSessionClicked: (senderUserId: UserId, clientId: String?) -> Unit = { _, _ -> }
Expand All @@ -46,6 +47,7 @@
override val onReactionClicked: (String, String) -> Unit = { _, _ -> },
override val onAssetClicked: (String) -> Unit = {},
override val onImageClicked: (UIMessage.Regular, Boolean, String?) -> Unit = { _, _, _ -> },
override val onVideoClicked: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit = { _, _, _ -> },

Check warning on line 50 in app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/item/MessageClickActions.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/item/MessageClickActions.kt#L50

Added line #L50 was not covered by tests
override val onLinkClicked: (String) -> Unit = {},
override val onReplyClicked: (UIMessage.Regular) -> Unit = {},
override val onResetSessionClicked: (senderUserId: UserId, clientId: String?) -> Unit = { _, _ -> },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ internal fun UIMessage.Regular.MessageContentAndStatus(
messageStyle: MessageStyle,
onAssetClicked: (String) -> Unit,
onImageClicked: (UIMessage.Regular, Boolean, String?) -> Unit,
onVideoClicked: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit,
onProfileClicked: (senderId: MessageSenderId) -> Unit,
onLinkClicked: (String) -> Unit,
onReplyClicked: (UIMessage.Regular) -> Unit,
Expand Down Expand Up @@ -119,6 +120,7 @@ internal fun UIMessage.Regular.MessageContentAndStatus(
onAssetClick = onAssetClickable,
onImageClick = onImageClickable,
onMultipartImageClick = onMultipartImageClickable,
onMultipartVideoClick = onVideoClicked,
onOpenProfile = onProfileClicked,
onLinkClick = onLinkClicked,
onReplyClick = onReplyClickable,
Expand Down Expand Up @@ -167,6 +169,7 @@ private fun MessageContent(
onAssetClick: Clickable,
onImageClick: Clickable,
onMultipartImageClick: (String) -> Unit,
onMultipartVideoClick: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit,
onOpenProfile: (senderId: MessageSenderId) -> Unit,
onLinkClick: (String) -> Unit,
onReplyClick: Clickable,
Expand Down Expand Up @@ -451,7 +454,8 @@ private fun MessageContent(
conversationId = message.conversationId,
attachments = messageContent.attachments,
messageStyle = messageStyle,
onImageAttachmentClick = onMultipartImageClick
onImageAttachmentClick = onMultipartImageClick,
onVideoAttachmentClick = onMultipartVideoClick,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ fun MessageContentItem(
messageStyle = messageStyle,
onAssetClicked = clickActions.onAssetClicked,
onImageClicked = clickActions.onImageClicked,
onVideoClicked = clickActions.onVideoClicked,
searchQuery = searchQuery,
accent = accent,
onProfileClicked = clickActions.onProfileClicked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fun MultipartAttachmentsView(
attachments: List<MessageAttachment>,
messageStyle: MessageStyle,
onImageAttachmentClick: (String) -> Unit,
onVideoAttachmentClick: (localPath: String?, contentUrl: String?, fileName: String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: MultipartAttachmentsViewModel = when {
LocalInspectionMode.current -> MultipartAttachmentsViewModelPreview
Expand Down Expand Up @@ -89,6 +90,9 @@ fun MultipartAttachmentsView(
viewModel.onClick(
attachment = it,
openInImageViewer = onImageAttachmentClick,
openInVideoPlayer = { att ->
onVideoAttachmentClick(att.localPath, att.contentUrl, att.fileName)
},
)
},
)
Expand Down Expand Up @@ -119,6 +123,9 @@ fun MultipartAttachmentsView(
viewModel.onClick(
attachment = it,
openInImageViewer = onImageAttachmentClick,
openInVideoPlayer = { att ->
onVideoAttachmentClick(att.localPath, att.contentUrl, att.fileName)
},
)
},
)
Expand All @@ -131,6 +138,9 @@ fun MultipartAttachmentsView(
viewModel.onClick(
attachment = it,
openInImageViewer = onImageAttachmentClick,
openInVideoPlayer = { att ->
onVideoAttachmentClick(att.localPath, att.contentUrl, att.fileName)
},
)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@

interface MultipartAttachmentsViewModel {
val offlineAttachmentIds: StateFlow<Set<String>>
fun onClick(attachment: MultipartAttachmentUi, openInImageViewer: (String) -> Unit)
fun onClick(
attachment: MultipartAttachmentUi,
openInImageViewer: (String) -> Unit,
openInVideoPlayer: (MultipartAttachmentUi) -> Unit,
)
fun mapAttachment(attachment: MessageAttachment): MultipartAttachmentUi {
val isAvailableOffline = attachment.assetId() in offlineAttachmentIds.value
return attachment.toUiModel(isAvailableOffline = isAvailableOffline)
Expand Down Expand Up @@ -115,7 +119,11 @@
@Suppress("EmptyFunctionBlock")
object MultipartAttachmentsViewModelPreview : MultipartAttachmentsViewModel {
override val offlineAttachmentIds: StateFlow<Set<String>> = MutableStateFlow(emptySet<String>())
override fun onClick(attachment: MultipartAttachmentUi, openInImageViewer: (String) -> Unit) {}
override fun onClick(
attachment: MultipartAttachmentUi,
openInImageViewer: (String) -> Unit,
openInVideoPlayer: (MultipartAttachmentUi) -> Unit,
) {}

Check warning on line 126 in app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt#L126

Added line #L126 was not covered by tests
override fun onAttachmentsVisible(attachments: List<MessageAttachment>) {}
override fun onAttachmentsHidden(attachments: List<MessageAttachment>) {}
}
Expand Down Expand Up @@ -148,6 +156,7 @@
override fun onClick(
attachment: MultipartAttachmentUi,
openInImageViewer: (String) -> Unit,
openInVideoPlayer: (MultipartAttachmentUi) -> Unit,
) {
when {
attachment.isImage() && !attachment.fileNotFound() -> openInImageViewer(attachment.uuid)
Expand All @@ -158,6 +167,9 @@
refreshHelper.refresh(attachment.uuid)
}

attachment.isVideo() && (attachment.localFileAvailable() || attachment.canOpenWithUrl()) ->
openInVideoPlayer(attachment)

Check warning on line 171 in app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt#L171

Added line #L171 was not covered by tests

attachment.localFileAvailable() -> openLocalFile(attachment)
attachment.canOpenWithUrl() -> openUrl(attachment)
else -> downloadAsset(attachment)
Expand Down Expand Up @@ -250,6 +262,8 @@

private fun MultipartAttachmentUi.isImage() = AttachmentFileType.fromMimeType(mimeType) == IMAGE

private fun MultipartAttachmentUi.isVideo() = assetType == VIDEO

private fun MessageAttachment.isMediaAttachment() =
when (AttachmentFileType.fromMimeType(mimeType())) {
IMAGE, VIDEO -> true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations.videoplayer

data class VideoPlayerNavArgs(
val localPath: String? = null,
val contentUrl: String? = null,
val fileName: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations.videoplayer

import androidx.compose.runtime.Composable
import com.wire.android.mediaplayer.VideoPlayer
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.annotation.app.WireRootDestination
import com.wire.android.navigation.style.PopUpNavigationAnimation

/**
* App navigation entry point for the shared [VideoPlayer]. Lets chat (and any app screen) play a
* downloaded video in-app instead of handing it off to an external application.
*/
@WireRootDestination(
navArgs = VideoPlayerNavArgs::class,
style = PopUpNavigationAnimation::class,
)
@Composable
fun VideoPlayerScreen(
navigator: Navigator,
navArgs: VideoPlayerNavArgs,
) {
VideoPlayer(
localPath = navArgs.localPath,
contentUrl = navArgs.contentUrl,
fileName = navArgs.fileName,
onNavigateBack = navigator::navigateBack,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class MultipartAttachmentsViewModelTest {

val callback = mockk<OpenImageCallback>(relaxed = true)

viewModel.onClick(testAttachmentUi, callback)
viewModel.onClick(testAttachmentUi, callback, {})

coVerify(exactly = 1) { callback.invoke(testAttachmentUi.uuid) }
}
Expand All @@ -202,7 +202,8 @@ class MultipartAttachmentsViewModelTest {
attachment = testAttachmentUi.copy(
transferStatus = AssetTransferStatus.NOT_FOUND,
),
openInImageViewer = callback
openInImageViewer = callback,
openInVideoPlayer = { }
)

coVerify(exactly = 0) { callback.invoke(testAttachmentUi.uuid) }
Expand All @@ -221,7 +222,8 @@ class MultipartAttachmentsViewModelTest {
mimeType = "application/pdf",
transferStatus = AssetTransferStatus.NOT_FOUND,
),
openInImageViewer = callback
openInImageViewer = callback,
openInVideoPlayer = { }
)

coVerify(exactly = 0) { callback.invoke(testAttachmentUi.uuid) }
Expand All @@ -240,7 +242,8 @@ class MultipartAttachmentsViewModelTest {
mimeType = "application/pdf",
localPath = "local/path",
),
openInImageViewer = callback
openInImageViewer = callback,
openInVideoPlayer = { }
)

coVerify(exactly = 1) { arrangement.fileManager.openWithExternalApp(any(), any(), any(), any()) }
Expand All @@ -258,7 +261,8 @@ class MultipartAttachmentsViewModelTest {
mimeType = "application/pdf",
contentUrl = "content/url",
),
openInImageViewer = callback
openInImageViewer = callback,
openInVideoPlayer = { }
)

coVerify(exactly = 1) { arrangement.fileManager.openUrlWithExternalApp(any(), any(), any()) }
Expand Down
Loading
Loading