Skip to content

Commit

Permalink
feat: media files tab - Part 2 (WPB-5378) (#2523)
Browse files Browse the repository at this point in the history
Co-authored-by: Jakub Żerko <[email protected]>
  • Loading branch information
alexandreferris and Garzas authored Dec 22, 2023
1 parent 375902d commit 45f95ce
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ 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.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 +43,7 @@ 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.getPaginatedFlowOfMessagesByConversation
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfMessagesBySearchQueryAndConversation
import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase
Expand Down Expand Up @@ -154,8 +156,15 @@ 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
Expand Down
34 changes: 34 additions & 0 deletions app/src/main/kotlin/com/wire/android/ui/common/Extensions.kt
Original file line number Diff line number Diff line change
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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ private fun ConversationScreenContent(
}

@Composable
private fun SnackBarMessage(
fun SnackBarMessage(
composerMessages: SharedFlow<SnackBarMessage>,
conversationMessages: SharedFlow<SnackBarMessage>
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ 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.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.GetImageAssetMessagesForConversationUseCase
import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.feature.asset.MessageAssetResult
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -44,7 +45,8 @@ import javax.inject.Inject
class ConversationAssetMessagesViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
private val dispatchers: DispatcherProvider,
private val getAssets: GetAssetMessagesForConversationUseCase,
private val getImageMessages: GetImageAssetMessagesForConversationUseCase,
private val getAssetMessages: GetAssetMessagesFromConversationUseCase,
private val getPrivateAsset: GetMessageAssetUseCase,
private val assetMapper: UIAssetMapper,
) : SavedStateViewModel(savedStateHandle) {
Expand All @@ -60,37 +62,49 @@ class ConversationAssetMessagesViewModel @Inject constructor(
private var currentOffset: Int = 0

init {
loadImages()
loadAssets()
}

private fun loadAssets() = viewModelScope.launch {
val assetsResult = getAssetMessages.invoke(
conversationId = conversationId,
initialOffset = 0
)

viewState = viewState.copy(
assetMessages = assetsResult
)
}

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

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

// imitate loading new asset batch
viewState = viewState.copy(messages = viewState.messages.plus(uiAssetList.map {
viewState = viewState.copy(imageMessages = viewState.imageMessages.plus(uiAssetList.map {
it.copy(
downloadStatus = if (it.assetPath == null && it.downloadStatus != Message.DownloadStatus.FAILED_DOWNLOAD) {
Message.DownloadStatus.DOWNLOAD_IN_PROGRESS
Expand All @@ -117,7 +131,7 @@ class ConversationAssetMessagesViewModel @Inject constructor(
currentOffset += BATCH_SIZE

viewState = viewState.copy(
messages = viewState.messages.dropLast(uiMessages.size).plus(uiMessages).toImmutableList(),
imageMessages = viewState.imageMessages.dropLast(uiMessages.size).plus(uiMessages).toImmutableList(),
)
} else {
continueLoading = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@
package com.wire.android.ui.home.conversations.media

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

@Stable
data class ConversationAssetMessagesViewState(
val messages: ImmutableList<UIAssetMessage> = persistentListOf()
val imageMessages: ImmutableList<UIAssetMessage> = persistentListOf(),
val assetMessages: Flow<PagingData<UIPagingItem>> = emptyFlow()
)
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph
import com.wire.android.R
import com.wire.android.media.audiomessage.AudioState
import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.style.PopUpNavigationAnimation
Expand All @@ -56,7 +57,10 @@ import com.wire.android.ui.common.topBarElevation
import com.wire.android.ui.common.topappbar.NavigationIconType
import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar
import com.wire.android.ui.destinations.MediaGalleryScreenDestination
import com.wire.android.ui.home.conversations.model.messagetypes.asset.UIAssetMessage
import com.wire.android.ui.home.conversations.DownloadedAssetDialog
import com.wire.android.ui.home.conversations.MessageComposerViewModel
import com.wire.android.ui.home.conversations.SnackBarMessage
import com.wire.android.ui.home.conversations.messages.ConversationMessagesViewModel
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.util.ui.PreviewMultipleThemes
Expand All @@ -69,9 +73,13 @@ import kotlinx.coroutines.launch
style = PopUpNavigationAnimation::class
)
@Composable
fun ConversationMediaScreen(navigator: Navigator) {
val viewModel: ConversationAssetMessagesViewModel = hiltViewModel()
val state: ConversationAssetMessagesViewState = viewModel.viewState
fun ConversationMediaScreen(
navigator: Navigator,
conversationAssetMessagesViewModel: ConversationAssetMessagesViewModel = hiltViewModel(),
conversationMessagesViewModel: ConversationMessagesViewModel = hiltViewModel(),
messageComposerViewModel: MessageComposerViewModel = hiltViewModel()
) {
val state: ConversationAssetMessagesViewState = conversationAssetMessagesViewModel.viewState

Content(
state = state,
Expand All @@ -89,8 +97,23 @@ fun ConversationMediaScreen(navigator: Navigator) {
)
},
continueAssetLoading = { shouldContinue ->
viewModel.continueLoading(shouldContinue)
}
conversationAssetMessagesViewModel.continueLoading(shouldContinue)
},
onAssetItemClicked = conversationMessagesViewModel::downloadOrFetchAssetAndShowDialog,
audioMessagesState = conversationMessagesViewModel.conversationViewState.audioMessagesState,
onAudioItemClicked = conversationMessagesViewModel::audioClick,
)

DownloadedAssetDialog(
downloadedAssetDialogState = conversationMessagesViewModel.conversationViewState.downloadedAssetDialogState,
onSaveFileToExternalStorage = conversationMessagesViewModel::downloadAssetExternally,
onOpenFileWithExternalApp = conversationMessagesViewModel::downloadAndOpenAsset,
hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog
)

SnackBarMessage(
messageComposerViewModel.infoMessage,
conversationMessagesViewModel.infoMessage
)
}

Expand All @@ -100,7 +123,10 @@ private fun Content(
state: ConversationAssetMessagesViewState,
onNavigationPressed: () -> Unit = {},
onImageFullScreenMode: (conversationId: ConversationId, messageId: String, isSelfAsset: Boolean) -> Unit,
continueAssetLoading: (shouldContinue: Boolean) -> Unit
continueAssetLoading: (shouldContinue: Boolean) -> Unit,
audioMessagesState: Map<String, AudioState> = emptyMap(),
onAudioItemClicked: (String) -> Unit,
onAssetItemClicked: (String) -> Unit
) {
val scope = rememberCoroutineScope()
val lazyListStates: List<LazyListState> = ConversationMediaScreenTabItem.entries.map { rememberLazyListState() }
Expand Down Expand Up @@ -139,12 +165,17 @@ private fun Content(
.padding(padding)
) { pageIndex ->
when (ConversationMediaScreenTabItem.entries[pageIndex]) {
ConversationMediaScreenTabItem.PICTURES -> PicturesContent(
uiAssetMessageList = state.messages,
ConversationMediaScreenTabItem.PICTURES -> ImageAssetsContent(
groupedImageMessageList = state.imageMessages,
onImageFullScreenMode = onImageFullScreenMode,
continueAssetLoading = continueAssetLoading
)
ConversationMediaScreenTabItem.FILES -> FilesContent()
ConversationMediaScreenTabItem.FILES -> FileAssetsContent(
groupedAssetMessageList = state.assetMessages,
audioMessagesState = audioMessagesState,
onAudioItemClicked = onAudioItemClicked,
onAssetItemClicked = onAssetItemClicked
)
}
}

Expand All @@ -157,32 +188,6 @@ private fun Content(
}
}

@Composable
private fun PicturesContent(
uiAssetMessageList: List<UIAssetMessage>,
onImageFullScreenMode: (conversationId: ConversationId, messageId: String, isSelfAsset: Boolean) -> Unit,
continueAssetLoading: (shouldContinue: Boolean) -> Unit
) {
if (uiAssetMessageList.isEmpty()) {
EmptyMediaContentScreen(
text = stringResource(R.string.label_conversation_pictures_empty)
)
} else {
AssetGrid(
uiAssetMessageList = uiAssetMessageList,
onImageFullScreenMode = onImageFullScreenMode,
continueAssetLoading = continueAssetLoading
)
}
}

@Composable
private fun FilesContent() {
EmptyMediaContentScreen(
text = stringResource(R.string.label_conversation_files_empty)
)
}

enum class ConversationMediaScreenTabItem(@StringRes override val titleResId: Int) : TabItem {
PICTURES(R.string.label_conversation_pictures),
FILES(R.string.label_conversation_files);
Expand All @@ -194,8 +199,10 @@ fun previewConversationMediaScreenEmptyContent() {
WireTheme {
Content(
state = ConversationAssetMessagesViewState(),
onImageFullScreenMode = {_, _, _ -> },
continueAssetLoading = {}
onImageFullScreenMode = { _, _, _ -> },
continueAssetLoading = { },
onAudioItemClicked = { },
onAssetItemClicked = { }
)
}
}
Loading

0 comments on commit 45f95ce

Please sign in to comment.