Skip to content

Commit

Permalink
feat: media files tab epic [WPB-4914] (#2556)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>
  • Loading branch information
Garzas and alexandreferris committed Dec 28, 2023
1 parent d4a218c commit d141853
Show file tree
Hide file tree
Showing 16 changed files with 935 additions and 201 deletions.
Expand Up @@ -21,8 +21,10 @@ import com.wire.android.di.CurrentAccount
import com.wire.android.di.KaliumCoreLogic
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.asset.GetAssetMessagesForConversationUseCase
import com.wire.kalium.logic.feature.asset.GetImageAssetMessagesForConversationUseCase
import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.feature.asset.ObservePaginatedAssetImageMessages
import com.wire.kalium.logic.feature.asset.GetPaginatedFlowOfAssetMessageByConversationIdUseCase
import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCase
import com.wire.kalium.logic.feature.message.DeleteMessageUseCase
Expand All @@ -42,6 +44,8 @@ import com.wire.kalium.logic.feature.message.SendTextMessageUseCase
import com.wire.kalium.logic.feature.message.ToggleReactionUseCase
import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase
import com.wire.kalium.logic.feature.message.ephemeral.EnqueueMessageSelfDeletionUseCase
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfAssetMessageByConversationId
import com.wire.kalium.logic.feature.message.observePaginatedImageAssetMessageByConversationId
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfMessagesByConversation
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfMessagesBySearchQueryAndConversation
import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase
Expand Down Expand Up @@ -154,8 +158,22 @@ class MessageModule {

@ViewModelScoped
@Provides
fun provideGetAssetMessagesUseCase(messageScope: MessageScope): GetAssetMessagesForConversationUseCase =
messageScope.getAssetMessagesByConversation
fun provideGetImageAssetMessagesByConversationUseCase(messageScope: MessageScope): GetImageAssetMessagesForConversationUseCase =
messageScope.getImageAssetMessagesByConversation

@ViewModelScoped
@Provides
fun provideGetPaginatedFlowOfAssetMessageByConversationId(
messageScope: MessageScope
): GetPaginatedFlowOfAssetMessageByConversationIdUseCase =
messageScope.getPaginatedFlowOfAssetMessageByConversationId

@ViewModelScoped
@Provides
fun provideGetPaginatedFlowOfImageAssetMessageByConversationId(
messageScope: MessageScope
): ObservePaginatedAssetImageMessages =
messageScope.observePaginatedImageAssetMessageByConversationId

@ViewModelScoped
@Provides
Expand Down
34 changes: 34 additions & 0 deletions app/src/main/kotlin/com/wire/android/ui/common/Extensions.kt
Expand Up @@ -50,11 +50,20 @@ import com.google.accompanist.placeholder.shimmer
import com.wire.android.R
import com.wire.android.model.ClickBlockParams
import com.wire.android.model.Clickable
import com.wire.android.ui.home.conversations.model.messagetypes.asset.UIAssetMessage
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.util.LocalSyncStateObserver
import com.wire.kalium.logic.data.message.Message
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import java.time.format.TextStyle
import java.util.Locale
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

Expand Down Expand Up @@ -145,3 +154,28 @@ fun <T : R, R> Flow<T>.collectAsStateLifecycleAware(
fun <T> StateFlow<T>.collectAsStateLifecycleAware(
context: CoroutineContext = EmptyCoroutineContext
): State<T> = collectAsStateLifecycleAware(value, context)

fun monthYearHeader(month: Int, year: Int): String {
val currentYear = Instant.fromEpochMilliseconds(System.currentTimeMillis()).toLocalDateTime(
TimeZone.currentSystemDefault()).year
val monthYearInstant = LocalDateTime(year = year, monthNumber = month, 1, 0, 0, 0)

val monthName = monthYearInstant.month.getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault())
return if (year == currentYear) {
// If it's the current year, display only the month name
monthName
} else {
// If it's not the current year, display both the month name and the year
"$monthName $year"
}
}

fun List<UIAssetMessage>.toImageAssetGroupedByMonthAndYear(timeZone: TimeZone) = this.groupBy { asset ->
val localDateTime = asset.time.toLocalDateTime(timeZone)
monthYearHeader(year = localDateTime.year, month = localDateTime.monthNumber)
}

fun List<Message.Standalone>.toGenericAssetGroupedByMonthAndYear(timeZone: TimeZone) = this.groupBy { message ->
val localDateTime = message.date.toInstant().toLocalDateTime(timeZone)
monthYearHeader(year = localDateTime.year, month = localDateTime.monthNumber)
}
Expand Up @@ -784,7 +784,7 @@ private fun ConversationScreenContent(
}

@Composable
private fun SnackBarMessage(
fun SnackBarMessage(
composerMessages: SharedFlow<SnackBarMessage>,
conversationMessages: SharedFlow<SnackBarMessage>
) {
Expand Down
Expand Up @@ -23,30 +23,22 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.wire.android.mapper.UIAssetMapper
import com.wire.android.navigation.SavedStateViewModel
import com.wire.android.ui.home.conversations.ConversationNavArgs
import com.wire.android.ui.home.conversations.usecase.GetAssetMessagesFromConversationUseCase
import com.wire.android.ui.home.conversations.usecase.ObserveImageAssetMessagesFromConversationUseCase
import com.wire.android.ui.navArgs
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.message.Message
import com.wire.kalium.logic.feature.asset.GetAssetMessagesForConversationUseCase
import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.feature.asset.MessageAssetResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

@HiltViewModel
@Suppress("LongParameterList", "TooManyFunctions")
class ConversationAssetMessagesViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
private val dispatchers: DispatcherProvider,
private val getAssets: GetAssetMessagesForConversationUseCase,
private val getPrivateAsset: GetMessageAssetUseCase,
private val assetMapper: UIAssetMapper,
private val getImageMessages: ObserveImageAssetMessagesFromConversationUseCase,
private val getAssetMessages: GetAssetMessagesFromConversationUseCase,
) : SavedStateViewModel(savedStateHandle) {

private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs()
Expand All @@ -55,80 +47,30 @@ class ConversationAssetMessagesViewModel @Inject constructor(
var viewState by mutableStateOf(ConversationAssetMessagesViewState())
private set

private var continueLoading = true
private var isLoading = false
private var currentOffset: Int = 0

init {
loadImages()
loadAssets()
}

fun continueLoading(shouldContinue: Boolean) {
if (shouldContinue) {
if (!continueLoading) {
continueLoading = true
loadAssets()
}
} else {
continueLoading = false
}
}

private fun loadAssets() = viewModelScope.launch {
if (isLoading) {
return@launch
}
isLoading = true
try {
while (continueLoading) {
val uiAssetList = withContext(dispatchers.io()) {
getAssets.invoke(
conversationId = conversationId,
limit = BATCH_SIZE,
offset = currentOffset
).map(assetMapper::toUIAsset)
}
val assetsResult = getAssetMessages.invoke(
conversationId = conversationId,
initialOffset = 0
)

// imitate loading new asset batch
viewState = viewState.copy(messages = viewState.messages.plus(uiAssetList.map {
it.copy(
downloadStatus = if (it.assetPath == null && it.downloadStatus != Message.DownloadStatus.FAILED_DOWNLOAD) {
Message.DownloadStatus.DOWNLOAD_IN_PROGRESS
} else {
it.downloadStatus
}
)
}).toImmutableList())

if (uiAssetList.size >= BATCH_SIZE) {
val uiMessages = uiAssetList.map { uiAsset ->
if (uiAsset.assetPath == null) {
val assetPath = withContext(dispatchers.io()) {
when (val asset = getPrivateAsset.invoke(uiAsset.conversationId, uiAsset.messageId).await()) {
is MessageAssetResult.Failure -> null
is MessageAssetResult.Success -> asset.decodedAssetPath
}
}
uiAsset.copy(assetPath = assetPath)
} else {
uiAsset
}
}
currentOffset += BATCH_SIZE

viewState = viewState.copy(
messages = viewState.messages.dropLast(uiMessages.size).plus(uiMessages).toImmutableList(),
)
} else {
continueLoading = false
}
}
} finally {
isLoading = false
}
viewState = viewState.copy(
assetMessages = assetsResult
)
}

companion object {
const val BATCH_SIZE = 5
private fun loadImages() = viewModelScope.launch {
val imageAssetsResult = getImageMessages.invoke(
conversationId = conversationId,
initialOffset = 0
)

viewState = viewState.copy(
imageMessages = imageAssetsResult
)
}
}
Expand Up @@ -21,11 +21,14 @@
package com.wire.android.ui.home.conversations.media

import androidx.compose.runtime.Stable
import com.wire.android.ui.home.conversations.model.messagetypes.asset.UIAssetMessage
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import androidx.paging.PagingData
import com.wire.android.ui.home.conversations.usecase.UIImageAssetPagingItem
import com.wire.android.ui.home.conversations.usecase.UIPagingItem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow

@Stable
data class ConversationAssetMessagesViewState(
val messages: ImmutableList<UIAssetMessage> = persistentListOf()
val imageMessages: Flow<PagingData<UIImageAssetPagingItem>> = emptyFlow(),
val assetMessages: Flow<PagingData<UIPagingItem>> = emptyFlow()
)

0 comments on commit d141853

Please sign in to comment.