Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: media files tab - Part 2 (WPB-5378) #2523

Merged
merged 30 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9ebb87e
feat: add strings for empty state
alexandreferris Dec 8, 2023
190171e
feat: add empty state screen for media
alexandreferris Dec 8, 2023
4dd91a5
feat: add empty state screens for media
alexandreferris Dec 8, 2023
627f6dd
feat: center text in vertical and horizontal center of screen and add…
alexandreferris Dec 8, 2023
b4548d7
Merge branch 'develop' into feat/media_files_tab_part2
alexandreferris Dec 11, 2023
5c25f17
chore: update kalium reference to in development for media files
alexandreferris Dec 13, 2023
e615df8
feat: add new use case to get only image assets from conversation to …
alexandreferris Dec 13, 2023
b4b4679
feat: move group by month and year to extensions file
alexandreferris Dec 13, 2023
f5cb759
feat: add new usecase to get asset messages and combine with user info
alexandreferris Dec 13, 2023
2d27227
feat: add initial display of asset messages
alexandreferris Dec 13, 2023
95a8430
Merge branch 'feat/media_files_tab_epic' into feat/media_files_tab_part2
alexandreferris Dec 13, 2023
42ad6b8
chore: fix merge conflicts
alexandreferris Dec 13, 2023
dc759aa
chore: update to kalium reference for development
alexandreferris Dec 13, 2023
9e7028e
Merge branch 'develop' into feat/media_files_tab_part2
alexandreferris Dec 13, 2023
244fab3
chore: adjust naming of extension functions
alexandreferris Dec 13, 2023
ac64870
chore: detekt
alexandreferris Dec 13, 2023
4e3af11
chore: adjust detekt
alexandreferris Dec 14, 2023
1a5f74d
chore: tentative of pagination for media files result
alexandreferris Dec 15, 2023
e44d40f
Merge branch 'feat/media_files_tab_epic' into feat/media_files_tab_part2
alexandreferris Dec 18, 2023
385cbeb
chore: change return type of AR usecase to return pagingData
alexandreferris Dec 19, 2023
4499e2d
chore: add logic to save/open asset file
alexandreferris Dec 19, 2023
4d96d9c
feat: add clicks and states for file open/save and audio messages
alexandreferris Dec 19, 2023
1477654
chore: get latest changes from kalium branch
alexandreferris Dec 19, 2023
04965ea
feat: date time label in media files
Garzas Dec 20, 2023
5710d64
chore: adjust function naming on message module
alexandreferris Dec 20, 2023
9a5c60b
chore: remove blank line
alexandreferris Dec 20, 2023
7b6d735
chore: detekt max line length
alexandreferris Dec 20, 2023
384b98a
Merge branch 'develop' into feat/media_files_tab_part2
alexandreferris Dec 20, 2023
effe696
Merge branch 'feat/media_files_tab_epic' into feat/media_files_tab_part2
alexandreferris Dec 20, 2023
50d5938
chore: update kalium with latest changes from feature branch
alexandreferris Dec 20, 2023
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 @@ -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
Loading