diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/CellsModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/CellsModule.kt index e2baa24aa24..b177375721d 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/CellsModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/CellsModule.kt @@ -66,6 +66,7 @@ import com.wire.kalium.cells.domain.usecase.GetConversationNameUseCase import com.wire.kalium.cells.domain.usecase.GetUserNameUseCase import com.wire.kalium.cells.domain.usecase.offline.DeleteOfflineFileUseCase import com.wire.kalium.cells.domain.usecase.offline.GetOfflineFileUseCase +import com.wire.kalium.cells.domain.usecase.offline.ObserveOfflineFilesByConversationUseCase import com.wire.kalium.cells.domain.usecase.offline.ObserveOfflineFilesUseCase import com.wire.kalium.cells.domain.usecase.offline.SaveOfflineFileUseCase import com.wire.kalium.cells.paginatedConversationsFlowUseCase @@ -235,6 +236,10 @@ class CellsModule { @Provides fun provideObserveOfflineFilesUseCase(cellsScope: CellsScope): ObserveOfflineFilesUseCase = cellsScope.observeOfflineFiles + @Provides + fun provideObserveOfflineFilesByConversationUseCase(cellsScope: CellsScope): ObserveOfflineFilesByConversationUseCase = + cellsScope.observeOfflineFilesByConversation + @Provides fun provideGetOfflineFileUseCase(cellsScope: CellsScope): GetOfflineFileUseCase = cellsScope.getOfflineFile diff --git a/app/src/main/kotlin/com/wire/android/ui/common/multipart/MultipartAttachmentUi.kt b/app/src/main/kotlin/com/wire/android/ui/common/multipart/MultipartAttachmentUi.kt index 48eb23aba01..f036b8449b1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/multipart/MultipartAttachmentUi.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/multipart/MultipartAttachmentUi.kt @@ -40,18 +40,19 @@ data class MultipartAttachmentUi( val transferStatus: AssetTransferStatus, val progress: Float? = null, val isEditSupported: Boolean = false, + val isAvailableOffline: Boolean = false, ) enum class AssetSource { CELL, ASSET_STORAGE } -fun MessageAttachment.toUiModel(progress: Float? = null) = when (this) { - is AssetContent -> this.toUiModel(progress) - is CellAssetContent -> this.toUiModel(progress) +fun MessageAttachment.toUiModel(progress: Float? = null, isAvailableOffline: Boolean = false) = when (this) { + is AssetContent -> this.toUiModel(progress, isAvailableOffline) + is CellAssetContent -> this.toUiModel(progress, isAvailableOffline) } -fun CellAssetContent.toUiModel(progress: Float?) = MultipartAttachmentUi( +fun CellAssetContent.toUiModel(progress: Float?, isAvailableOffline: Boolean = false) = MultipartAttachmentUi( uuid = this.id, source = AssetSource.CELL, fileName = this.assetPath?.substringAfterLast("/"), @@ -67,9 +68,10 @@ fun CellAssetContent.toUiModel(progress: Float?) = MultipartAttachmentUi( progress = progress, contentHash = contentHash, isEditSupported = isEditSupported, + isAvailableOffline = isAvailableOffline, ) -fun AssetContent.toUiModel(progress: Float?) = MultipartAttachmentUi( +fun AssetContent.toUiModel(progress: Float?, isAvailableOffline: Boolean = false) = MultipartAttachmentUi( uuid = this.remoteData.assetId, source = AssetSource.ASSET_STORAGE, fileName = this.name, @@ -84,4 +86,5 @@ fun AssetContent.toUiModel(progress: Float?) = MultipartAttachmentUi( contentHash = null, contentUrl = null, isEditSupported = false, + isAvailableOffline = isAvailableOffline, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelFactory.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelFactory.kt index 5041381034d..96f56d5d8cb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelFactory.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelFactory.kt @@ -66,7 +66,9 @@ import com.wire.kalium.cells.domain.usecase.GetWireCellConfigurationUseCase import com.wire.kalium.cells.domain.usecase.ObserveAttachmentDraftsUseCase import com.wire.kalium.cells.domain.usecase.RemoveAttachmentDraftUseCase import com.wire.kalium.cells.domain.usecase.RetryAttachmentUploadUseCase +import com.wire.kalium.cells.domain.usecase.offline.ObserveOfflineFilesByConversationUseCase import com.wire.kalium.logic.data.id.QualifiedIdMapper +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.asset.KaliumFileSystem import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase @@ -192,6 +194,7 @@ class ConversationCoreViewModelFactory @Inject constructor( private val onlineEditor: OnlineEditor, private val featureFlags: KaliumConfigs, private val getWireCellsConfig: GetWireCellConfigurationUseCase, + private val observeOfflineFilesByConversation: ObserveOfflineFilesByConversationUseCase, private val networkStateObserver: NetworkStateObserver, @CurrentAccount private val selfUserId: UserId, ) { @@ -346,7 +349,8 @@ class ConversationCoreViewModelFactory @Inject constructor( selfUserId = selfUserId, ) - fun multipartAttachmentsViewModel() = MultipartAttachmentsViewModelImpl( + fun multipartAttachmentsViewModel(conversationId: ConversationId) = MultipartAttachmentsViewModelImpl( + conversationId = conversationId, refreshHelper = refreshHelper, download = downloadCellFile, getEditorUrl = getEditorUrl, @@ -355,5 +359,6 @@ class ConversationCoreViewModelFactory @Inject constructor( kaliumFileSystem = kaliumFileSystem, featureFlags = featureFlags, getWireCellsConfig = getWireCellsConfig, + observeOfflineFilesByConversation = observeOfflineFilesByConversation ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelGraph.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelGraph.kt index c3f3be26dfe..8ef29950efe 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelGraph.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelGraph.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.ViewModel import com.wire.android.di.metro.MetroViewModelGraph import com.wire.android.di.metro.metroSavedStateViewModel import com.wire.android.di.metro.metroViewModel +import com.wire.kalium.logic.data.id.ConversationId import com.wire.android.ui.home.conversations.attachment.MessageAttachmentsViewModel import com.wire.android.ui.home.conversations.banner.ConversationBannerViewModel import com.wire.android.ui.home.conversations.composer.MessageComposerViewModel @@ -115,9 +116,9 @@ fun conversationInfoViewModel(): ConversationInfoViewModel = conversationSavedStateViewModel { this.conversationInfoViewModel(it) } @Composable -fun multipartAttachmentsViewModel(conversationKey: String): MultipartAttachmentsViewModel = - conversationViewModel(key = conversationKey) { - this.multipartAttachmentsViewModel() +fun multipartAttachmentsViewModel(conversationId: ConversationId): MultipartAttachmentsViewModel = + conversationViewModel(key = conversationId.value) { + this.multipartAttachmentsViewModel(conversationId) } @Composable diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsView.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsView.kt index 232a5aed1c5..1061f45a6a1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsView.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsView.kt @@ -25,18 +25,20 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onVisibilityChanged import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode +import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.decode.Decoder import coil3.request.ImageRequest import coil3.request.crossfade import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.multipart.MultipartAttachmentUi -import com.wire.android.ui.common.multipart.toUiModel import com.wire.android.ui.home.conversations.multipartAttachmentsViewModel import com.wire.android.ui.home.conversations.messages.item.MessageStyle import com.wire.android.ui.home.conversations.model.messagetypes.multipart.grid.AssetGridPreview @@ -59,13 +61,19 @@ fun MultipartAttachmentsView( modifier: Modifier = Modifier, viewModel: MultipartAttachmentsViewModel = when { LocalInspectionMode.current -> MultipartAttachmentsViewModelPreview - else -> multipartAttachmentsViewModel(conversationId.value) + else -> multipartAttachmentsViewModel(conversationId) } ) { + // Collect to trigger recomposition when offline availability changes. + val offlineAttachmentIds by viewModel.offlineAttachmentIds.collectAsStateWithLifecycle() // TODO I found out that empty attachments list is not handled here and it shows empty message with no information if (attachments.size == 1) { - attachments.first().toUiModel().let { + val attachment = attachments.first() + val item = remember(attachment, offlineAttachmentIds) { + viewModel.mapAttachment(attachment) + } + item.let { AssetPreview( modifier = modifier .onVisibilityChanged { visible -> @@ -86,7 +94,9 @@ fun MultipartAttachmentsView( ) } } else { - val groups = viewModel.mapAttachments(attachments) + val groups = remember(attachments, offlineAttachmentIds) { + viewModel.mapAttachments(attachments = attachments) + } Column( modifier = modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt index 6bfeaf1cc8c..3cfadb3a980 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModel.kt @@ -32,44 +32,65 @@ import com.wire.android.util.FileManager import com.wire.kalium.cells.domain.usecase.GetEditorUrlUseCase import com.wire.kalium.cells.domain.usecase.GetWireCellConfigurationUseCase import com.wire.kalium.cells.domain.usecase.download.DownloadCellFileUseCase +import com.wire.kalium.cells.domain.usecase.offline.ObserveOfflineFilesByConversationUseCase import com.wire.kalium.common.functional.onSuccess import com.wire.kalium.logic.data.asset.AssetTransferStatus import com.wire.kalium.logic.data.asset.KaliumFileSystem import com.wire.kalium.logic.data.featureConfig.CollaboraEdition +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.message.AssetContent import com.wire.kalium.logic.data.message.CellAssetContent import com.wire.kalium.logic.data.message.MessageAttachment import com.wire.kalium.logic.featureFlags.KaliumConfigs import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import okio.Path.Companion.toPath interface MultipartAttachmentsViewModel { + val offlineAttachmentIds: StateFlow> fun onClick(attachment: MultipartAttachmentUi, openInImageViewer: (String) -> Unit) + fun mapAttachment(attachment: MessageAttachment): MultipartAttachmentUi { + val isAvailableOffline = attachment.assetId() in offlineAttachmentIds.value + return attachment.toUiModel(isAvailableOffline = isAvailableOffline) + } + fun mapAttachments( - attachments: List + attachments: List, ): List { + val offlineIds = offlineAttachmentIds.value val result = mutableListOf() var group: MultipartAttachmentGroup? = null attachments.forEach { + val isAvailableOffline = it.assetId() in offlineIds if (it.isMediaAttachment()) { group = when (group) { - null -> MultipartAttachmentGroup.Media(listOf(it.toUiModel())) - is MultipartAttachmentGroup.Media -> group.copy(group.attachments + it.toUiModel()) + null -> MultipartAttachmentGroup.Media(listOf(it.toUiModel(isAvailableOffline = isAvailableOffline))) + is MultipartAttachmentGroup.Media -> { + val newAttachment = it.toUiModel(isAvailableOffline = isAvailableOffline) + group.copy(attachments = group.attachments + newAttachment) + } else -> { result.add(group) - MultipartAttachmentGroup.Media(listOf(it.toUiModel())) + MultipartAttachmentGroup.Media(listOf(it.toUiModel(isAvailableOffline = isAvailableOffline))) } } } else { group = when (group) { - null -> MultipartAttachmentGroup.Files(listOf(it.toUiModel())) - is MultipartAttachmentGroup.Files -> group.copy(group.attachments + it.toUiModel()) + null -> MultipartAttachmentGroup.Files(listOf(it.toUiModel(isAvailableOffline = isAvailableOffline))) + is MultipartAttachmentGroup.Files -> { + val newAttachment = it.toUiModel(isAvailableOffline = isAvailableOffline) + group.copy(attachments = group.attachments + newAttachment) + } else -> { result.add(group) - MultipartAttachmentGroup.Files(listOf(it.toUiModel())) + MultipartAttachmentGroup.Files(listOf(it.toUiModel(isAvailableOffline = isAvailableOffline))) } } } @@ -93,12 +114,15 @@ interface MultipartAttachmentsViewModel { @Suppress("EmptyFunctionBlock") object MultipartAttachmentsViewModelPreview : MultipartAttachmentsViewModel { + override val offlineAttachmentIds: StateFlow> = MutableStateFlow(emptySet()) override fun onClick(attachment: MultipartAttachmentUi, openInImageViewer: (String) -> Unit) {} override fun onAttachmentsVisible(attachments: List) {} override fun onAttachmentsHidden(attachments: List) {} } +@Suppress("LongParameterList") class MultipartAttachmentsViewModelImpl( + private val conversationId: ConversationId, private val refreshHelper: CellAssetRefreshHelper, private val download: DownloadCellFileUseCase, private val getEditorUrl: GetEditorUrlUseCase, @@ -107,9 +131,13 @@ class MultipartAttachmentsViewModelImpl( private val kaliumFileSystem: KaliumFileSystem, private val featureFlags: KaliumConfigs, private val getWireCellsConfig: GetWireCellConfigurationUseCase, + observeOfflineFilesByConversation: ObserveOfflineFilesByConversationUseCase, ) : ViewModel(), MultipartAttachmentsViewModel { private val uploadProgress = mutableStateMapOf() + override val offlineAttachmentIds: StateFlow> = observeOfflineFilesByConversation(conversationId) + .map { offlineFiles -> offlineFiles.mapTo(mutableSetOf()) { it.id } } + .stateIn(viewModelScope, SharingStarted.Eagerly, emptySet()) private var isCollaboraEnabled: Boolean = false @@ -117,7 +145,10 @@ class MultipartAttachmentsViewModelImpl( loadWireCellConfig() } - override fun onClick(attachment: MultipartAttachmentUi, openInImageViewer: (String) -> Unit) { + override fun onClick( + attachment: MultipartAttachmentUi, + openInImageViewer: (String) -> Unit, + ) { when { attachment.isImage() && !attachment.fileNotFound() -> openInImageViewer(attachment.uuid) attachment.isEditSupported && isCollaboraEnabled && featureFlags.collaboraIntegration -> @@ -171,7 +202,7 @@ class MultipartAttachmentsViewModelImpl( download( assetId = attachment.uuid, - conversationId = null, // TODO to replace with real conversation id in next PR + conversationId = conversationId.value, outFilePath = path, assetSize = attachment.assetSize ?: 0, ) { progress -> @@ -205,7 +236,7 @@ class MultipartAttachmentsViewModelImpl( } } -private fun MessageAttachment.assetId() = +internal fun MessageAttachment.assetId() = when (this) { is AssetContent -> remoteData.assetId is CellAssetContent -> id diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/grid/AssetGridPreview.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/grid/AssetGridPreview.kt index e2764c41859..b60849448a0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/grid/AssetGridPreview.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/grid/AssetGridPreview.kt @@ -28,11 +28,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import com.wire.android.feature.cells.R import com.wire.android.feature.cells.domain.model.AttachmentFileType import com.wire.android.ui.common.applyIf import com.wire.android.ui.common.colorsScheme @@ -92,6 +95,20 @@ internal fun AssetGridPreview( } } + if (item.isAvailableOffline) { + Icon( + modifier = Modifier + .padding( + end = dimensions().spacing6x, + top = dimensions().spacing6x + ) + .align(Alignment.TopEnd), + painter = painterResource(R.drawable.ic_downloaded), + contentDescription = null, + tint = colorsScheme().secondaryText, + ) + } + item.progress?.let { CircularProgressIndicator( modifier = Modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/standalone/AssetPreview.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/standalone/AssetPreview.kt index 15f2098244d..a6b46b4d354 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/standalone/AssetPreview.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/standalone/AssetPreview.kt @@ -22,12 +22,17 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable +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.res.painterResource import androidx.compose.ui.unit.Dp +import com.wire.android.feature.cells.R import com.wire.android.feature.cells.domain.model.AttachmentFileType import com.wire.android.ui.common.applyIf import com.wire.android.ui.common.colorsScheme @@ -76,6 +81,19 @@ fun AssetPreview( item.isEditSupported -> EditableAssetPreview(item, messageStyle) else -> FileAssetPreview(item, messageStyle) } + if (item.isAvailableOffline) { + Icon( + modifier = Modifier + .padding( + end = dimensions().spacing6x, + top = dimensions().spacing6x + ) + .align(Alignment.TopEnd), + painter = painterResource(R.drawable.ic_downloaded), + contentDescription = null, + tint = colorsScheme().secondaryText, + ) + } } else { AssetNotAvailablePreview(messageStyle = messageStyle) } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModelTest.kt index f23b308cf30..58cb093250a 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/model/messagetypes/multipart/MultipartAttachmentsViewModelTest.kt @@ -17,31 +17,41 @@ */ package com.wire.android.ui.home.conversations.model.messagetypes.multipart +import com.wire.android.config.CoroutineTestExtension import com.wire.android.feature.cells.domain.model.AttachmentFileType import com.wire.android.feature.cells.ui.edit.OnlineEditor import com.wire.android.framework.FakeKaliumFileSystem import com.wire.android.ui.common.multipart.AssetSource import com.wire.android.ui.common.multipart.MultipartAttachmentUi import com.wire.android.util.FileManager -import com.wire.kalium.cells.domain.usecase.download.DownloadCellFileUseCase import com.wire.kalium.cells.domain.usecase.GetEditorUrlUseCase import com.wire.kalium.cells.domain.usecase.GetWireCellConfigurationUseCase +import com.wire.kalium.cells.domain.usecase.download.DownloadCellFileUseCase +import com.wire.kalium.cells.domain.usecase.offline.ObserveOfflineFilesByConversationUseCase +import com.wire.kalium.cells.domain.usecase.offline.OfflineFileInfo import com.wire.kalium.common.functional.right import com.wire.kalium.logic.data.asset.AssetTransferStatus import com.wire.kalium.logic.data.asset.KaliumFileSystem +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.message.CellAssetContent import com.wire.kalium.logic.featureFlags.KaliumConfigs import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith typealias OpenImageCallback = (s: String) -> Unit +@ExtendWith(CoroutineTestExtension::class) class MultipartAttachmentsViewModelTest { @Test @@ -137,6 +147,38 @@ class MultipartAttachmentsViewModelTest { ) } + @Test + fun `with offline attachment id when mapped then attachment is marked as available offline`() = runTest { + val (_, viewModel) = Arrangement() + .withOfflineAttachmentId("asset_1") + .arrange() + + advanceUntilIdle() + assertTrue( + viewModel.offlineAttachmentIds.value.contains("asset_1") + ) + + val result = viewModel.mapAttachments( + listOf(testAssetContent.copy(id = "asset_1", mimeType = "application/pdf")), + ) + + assertEquals( + listOf( + MultipartAttachmentsViewModel.MultipartAttachmentGroup.Files( + attachments = listOf( + testAttachmentUi.copy( + uuid = "asset_1", + mimeType = "application/pdf", + assetType = AttachmentFileType.PDF, + isAvailableOffline = true, + ), + ) + ) + ), + result + ) + } + @Test fun `with image attachment when clicked then image opened in internal viewer`() = runTest { val (_, viewModel) = Arrangement() @@ -251,17 +293,40 @@ class MultipartAttachmentsViewModelTest { @MockK lateinit var getWireCellsConfig: GetWireCellConfigurationUseCase + @MockK + lateinit var observeOfflineFilesByConversation: ObserveOfflineFilesByConversationUseCase + val kaliumFileSystem: KaliumFileSystem = FakeKaliumFileSystem() + private var offlineFiles: List = emptyList() + + fun withOfflineAttachmentId(assetId: String) = apply { + offlineFiles = listOf( + OfflineFileInfo( + id = assetId, + conversationId = testConversationId.value, + name = "file", + owner = "owner", + localPath = "local/path", + size = 1L, + downloadedAt = 1L, + ) + ) + } - suspend fun arrange(): Pair { + fun arrange(): Pair { coEvery { refreshHelper.refresh(any()) } returns Unit coEvery { fileManager.openWithExternalApp(any(), any(), any(), any()) } returns Unit coEvery { fileManager.openUrlWithExternalApp(any(), any(), any()) } returns Unit coEvery { download(any(), any(), any(), any(), any(), any(), any(), any()) } returns Unit.right() coEvery { getWireCellsConfig() } returns null + every { observeOfflineFilesByConversation(testConversationId) } returns flowOf(offlineFiles) + every { + observeOfflineFilesByConversation(ConversationId("other-conversation", "test-domain")) + } returns flowOf(offlineFiles) return this to MultipartAttachmentsViewModelImpl( + conversationId = testConversationId, refreshHelper = refreshHelper, download = download, fileManager = fileManager, @@ -270,11 +335,14 @@ class MultipartAttachmentsViewModelTest { kaliumFileSystem = kaliumFileSystem, featureFlags = kaliumConfigs, getWireCellsConfig = getWireCellsConfig, + observeOfflineFilesByConversation = observeOfflineFilesByConversation, ) } } private companion object { + val testConversationId = ConversationId("test-conversation-id", "test-domain") + val testAssetContent = CellAssetContent( id = "assetId1", versionId = "1", diff --git a/kalium b/kalium index e996a3aced8..09e868d171c 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit e996a3aced8de5a826c8dc7f071fccf5d35f6ef2 +Subproject commit 09e868d171cb71f123a69afd90fd07c6c488bdf8