From e914e256de9b5ec9ca03b24e1906fc322ce6a1ca Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 26 Sep 2023 17:38:12 +0200 Subject: [PATCH 01/42] feat: base wip for typing indicator --- .../di/accountScoped/ConversationModule.kt | 6 ++++++ .../ui/home/conversations/ConversationScreen.kt | 6 ++++-- .../messages/ConversationMessagesViewModel.kt | 16 +++++++++++++++- .../messages/ConversationMessagesViewState.kt | 4 +++- .../messagecomposer/EnabledMessageComposer.kt | 10 +++++++++- .../ui/home/messagecomposer/MessageComposer.kt | 10 +++++++--- kalium | 2 +- 7 files changed, 45 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt index f5b53dfec95..8ea0286d058 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt @@ -38,6 +38,7 @@ import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetails import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase import com.wire.kalium.logic.feature.conversation.ObserveIsSelfUserMemberUseCase import com.wire.kalium.logic.feature.conversation.ObserveUserListByIdUseCase +import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase import com.wire.kalium.logic.feature.conversation.RefreshConversationsWithoutMetadataUseCase import com.wire.kalium.logic.feature.conversation.RemoveMemberFromConversationUseCase import com.wire.kalium.logic.feature.conversation.RenameConversationUseCase @@ -216,4 +217,9 @@ class ConversationModule { @Provides fun provideClearConversationContentUseCase(conversationScope: ConversationScope): ClearConversationContentUseCase = conversationScope.clearConversationContent + + @ViewModelScoped + @Provides + fun provideObserveUsersTypingUseCase(conversationScope: ConversationScope): ObserveUsersTypingUseCase = + conversationScope.observeUsersTyping } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index cdf40e69f7b..fe4274ae2f2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -580,6 +580,7 @@ private fun ConversationScreen( ConversationScreenContent( conversationId = conversationInfoViewState.conversationId, audioMessagesState = conversationMessagesViewState.audioMessagesState, + usersTyping = conversationMessagesViewState.usersTyping, lastUnreadMessageInstant = conversationMessagesViewState.firstUnreadInstant, unreadEventCount = conversationMessagesViewState.firstuUnreadEventIndex, conversationDetailsData = conversationInfoViewState.conversationDetailsData, @@ -624,6 +625,7 @@ private fun ConversationScreenContent( lastUnreadMessageInstant: Instant?, unreadEventCount: Int, audioMessagesState: Map, + usersTyping: Set, messageComposerStateHolder: MessageComposerStateHolder, messages: Flow>, onSendMessage: (MessageBundle) -> Unit, @@ -683,8 +685,8 @@ private fun ConversationScreenContent( onClearMentionSearchResult = onClearMentionSearchResult, onSendMessageBundle = onSendMessage, tempWritableVideoUri = tempWritableVideoUri, - tempWritableImageUri = tempWritableImageUri - + tempWritableImageUri = tempWritableImageUri, + usersTyping = usersTyping ) // TODO: uncomment when we have the "scroll to bottom" button implemented diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 4316c17026b..9c5dfd7761d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -54,6 +54,7 @@ import com.wire.kalium.logic.feature.asset.MessageAssetResult import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCase import com.wire.kalium.logic.feature.conversation.GetConversationUnreadEventsCountUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase +import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase import com.wire.kalium.logic.feature.message.GetMessageByIdUseCase import com.wire.kalium.logic.feature.message.ToggleReactionUseCase import com.wire.kalium.logic.feature.sessionreset.ResetSessionResult @@ -83,7 +84,8 @@ class ConversationMessagesViewModel @Inject constructor( private val toggleReaction: ToggleReactionUseCase, private val resetSession: ResetSessionUseCase, private val conversationAudioMessagePlayer: ConversationAudioMessagePlayer, - private val getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase + private val getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase, + private val observeUsersTyping: ObserveUsersTypingUseCase ) : SavedStateViewModel(savedStateHandle) { private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs() @@ -100,6 +102,18 @@ class ConversationMessagesViewModel @Inject constructor( loadPaginatedMessages() loadLastMessageInstant() observeAudioPlayerState() + observeUsersTypingState() + } + + private fun observeUsersTypingState() { + viewModelScope.launch { + observeUsersTyping.invoke(conversationId).collect { + appLogger.d("Collecting users typing: $it") + conversationViewState = conversationViewState.copy( + usersTyping = it + ) + } + } } private fun observeAudioPlayerState() { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt index 74e81bed90d..a12fcde0dec 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt @@ -24,6 +24,7 @@ import androidx.paging.PagingData import com.wire.android.media.audiomessage.AudioState import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage +import com.wire.kalium.logic.data.user.UserId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.datetime.Instant @@ -33,7 +34,8 @@ data class ConversationMessagesViewState( val firstUnreadInstant: Instant? = null, val firstuUnreadEventIndex: Int = 0, val downloadedAssetDialogState: DownloadedAssetDialogVisibilityState = DownloadedAssetDialogVisibilityState.Hidden, - val audioMessagesState: Map = emptyMap() + val audioMessagesState: Map = emptyMap(), + val usersTyping: Set = emptySet(), ) sealed class DownloadedAssetDialogVisibilityState { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 31947cbf866..18c39dadbfc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -50,7 +51,9 @@ import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType +import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.util.isPositiveNotNull @OptIn(ExperimentalLayoutApi::class) @@ -68,7 +71,8 @@ fun EnabledMessageComposer( onPingOptionClicked: () -> Unit, onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, - tempWritableImageUri: Uri? + tempWritableImageUri: Uri?, + usersTyping: Set ) { val density = LocalDensity.current val navBarHeight = BottomNavigationBarHeight() @@ -247,6 +251,10 @@ fun EnabledMessageComposer( .animateContentSize() ) } + + if (usersTyping.isNotEmpty()) { + Text(text = usersTyping.joinToString(",") { it.value.obfuscateId() }) + } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index eba90550324..ee3b1980a1d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -64,6 +64,7 @@ import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.stringWithStyledArgs import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.conversation.InteractionAvailability import com.wire.kalium.logic.feature.selfDeletingMessages.SelfDeletionTimer import kotlin.time.Duration @@ -78,7 +79,8 @@ fun MessageComposer( onSearchMentionQueryChanged: (String) -> Unit, onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, - tempWritableImageUri: Uri? + tempWritableImageUri: Uri?, + usersTyping: Set ) { with(messageComposerStateHolder) { when (messageComposerViewState.value.interactionAvailability) { @@ -132,7 +134,8 @@ fun MessageComposer( onSearchMentionQueryChanged = onSearchMentionQueryChanged, onClearMentionSearchResult = onClearMentionSearchResult, tempWritableVideoUri = tempWritableVideoUri, - tempWritableImageUri = tempWritableImageUri + tempWritableImageUri = tempWritableImageUri, + usersTyping = usersTyping ) } } @@ -221,6 +224,7 @@ fun MessageComposerPreview() { onClearMentionSearchResult = { }, onSendMessageBundle = { }, tempWritableVideoUri = null, - tempWritableImageUri = null + tempWritableImageUri = null, + usersTyping = emptySet() ) } diff --git a/kalium b/kalium index 47c310d5ce7..048b06fef00 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 47c310d5ce7d3ebde1a9fdbd3530b681c3f3606d +Subproject commit 048b06fef00aed44ea234cfcd7677cea0b865f90 From aa316e6d17e824f565a77c53182d0dd410c72ebe Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 27 Sep 2023 16:01:44 +0200 Subject: [PATCH 02/42] feat: adjustment for typing indicator --- .../com/wire/android/di/accountScoped/ConversationModule.kt | 2 +- .../conversations/messages/ConversationMessagesViewModel.kt | 6 +++--- kalium | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt index 8ea0286d058..bbf4fe4ff50 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt @@ -19,7 +19,6 @@ package com.wire.android.di.accountScoped import com.wire.android.di.CurrentAccount import com.wire.android.di.KaliumCoreLogic -import dagger.Module import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.conversation.AddMemberToConversationUseCase @@ -52,6 +51,7 @@ import com.wire.kalium.logic.feature.conversation.guestroomlink.ObserveGuestRoom import com.wire.kalium.logic.feature.conversation.guestroomlink.RevokeGuestRoomLinkUseCase import com.wire.kalium.logic.feature.conversation.messagetimer.UpdateMessageTimerUseCase import com.wire.kalium.logic.feature.team.DeleteTeamConversationUseCase +import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 9c5dfd7761d..eab10112c26 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -101,14 +101,14 @@ class ConversationMessagesViewModel @Inject constructor( init { loadPaginatedMessages() loadLastMessageInstant() - observeAudioPlayerState() observeUsersTypingState() + observeAudioPlayerState() } private fun observeUsersTypingState() { viewModelScope.launch { - observeUsersTyping.invoke(conversationId).collect { - appLogger.d("Collecting users typing: $it") + observeUsersTyping(conversationId).collect { + appLogger.d("Users typing: $it") conversationViewState = conversationViewState.copy( usersTyping = it ) diff --git a/kalium b/kalium index 048b06fef00..2a4c7e84ce2 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 048b06fef00aed44ea234cfcd7677cea0b865f90 +Subproject commit 2a4c7e84ce2e49620f45a8b67b8b683a544387ee From 125fbd793fdb75595d17a225267e035d38a125bc Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 27 Sep 2023 16:50:57 +0200 Subject: [PATCH 03/42] feat: kalium ref --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 2a4c7e84ce2..6a3fa7d2c7e 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 2a4c7e84ce2e49620f45a8b67b8b683a544387ee +Subproject commit 6a3fa7d2c7ed3684529fd1577de3eb9c5f7adb92 From f3315649ef5496d7ac4878d4677e0d015a7942d5 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 27 Sep 2023 19:18:52 +0200 Subject: [PATCH 04/42] feat: wip typing indicator --- .../wire/android/ui/home/conversations/ConversationScreen.kt | 2 +- .../conversations/messages/ConversationMessagesViewModel.kt | 2 +- .../conversations/messages/ConversationMessagesViewState.kt | 2 +- .../android/ui/home/messagecomposer/EnabledMessageComposer.kt | 4 ++-- .../wire/android/ui/home/messagecomposer/MessageComposer.kt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index a1d74b90811..049ef01b3a4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -627,7 +627,7 @@ private fun ConversationScreenContent( lastUnreadMessageInstant: Instant?, unreadEventCount: Int, audioMessagesState: Map, - usersTyping: Set, + usersTyping: Set, messageComposerStateHolder: MessageComposerStateHolder, messages: Flow>, onSendMessage: (MessageBundle) -> Unit, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index eab10112c26..e91a1d906e9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -110,7 +110,7 @@ class ConversationMessagesViewModel @Inject constructor( observeUsersTyping(conversationId).collect { appLogger.d("Users typing: $it") conversationViewState = conversationViewState.copy( - usersTyping = it + usersTyping = it.mapNotNull { it.name }.toSet() ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt index a12fcde0dec..b5c1651fce4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt @@ -35,7 +35,7 @@ data class ConversationMessagesViewState( val firstuUnreadEventIndex: Int = 0, val downloadedAssetDialogState: DownloadedAssetDialogVisibilityState = DownloadedAssetDialogVisibilityState.Hidden, val audioMessagesState: Map = emptyMap(), - val usersTyping: Set = emptySet(), + val usersTyping: Set = emptySet(), ) sealed class DownloadedAssetDialogVisibilityState { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index e05286136f3..d285e73b9d4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -75,7 +75,7 @@ fun EnabledMessageComposer( onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, tempWritableImageUri: Uri?, - usersTyping: Set + usersTyping: Set ) { val density = LocalDensity.current val navBarHeight = BottomNavigationBarHeight() @@ -261,7 +261,7 @@ fun EnabledMessageComposer( } if (usersTyping.isNotEmpty()) { - Text(text = usersTyping.joinToString(",") { it.value.obfuscateId() }) + Text(text = usersTyping.joinToString(",") { it }) } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index ee3b1980a1d..f632ab8c615 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -80,7 +80,7 @@ fun MessageComposer( onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, tempWritableImageUri: Uri?, - usersTyping: Set + usersTyping: Set ) { with(messageComposerStateHolder) { when (messageComposerViewState.value.interactionAvailability) { From 74fb69e8702f8da1766d5a65c53aa72909b47ab6 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 27 Sep 2023 19:21:03 +0200 Subject: [PATCH 05/42] feat: typing indicator user mapping --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 6a3fa7d2c7e..3b12eae946d 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 6a3fa7d2c7ed3684529fd1577de3eb9c5f7adb92 +Subproject commit 3b12eae946dc0749ea4f18a92fa504f4da873c1a From fc5d91c0e61dc70f0796dbd56580c8424f8d182b Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Thu, 28 Sep 2023 18:33:39 +0200 Subject: [PATCH 06/42] feat: mapping users summary --- .../conversations/messages/ConversationMessagesViewModel.kt | 2 +- kalium | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index e91a1d906e9..5eb7096b3d0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -110,7 +110,7 @@ class ConversationMessagesViewModel @Inject constructor( observeUsersTyping(conversationId).collect { appLogger.d("Users typing: $it") conversationViewState = conversationViewState.copy( - usersTyping = it.mapNotNull { it.name }.toSet() + usersTyping = it.filter { it.userName.orEmpty().isNotEmpty() }.map { it.userName!! }.toSet() ) } } diff --git a/kalium b/kalium index 3b12eae946d..e9d5549741f 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 3b12eae946dc0749ea4f18a92fa504f4da873c1a +Subproject commit e9d5549741f295a2fa94000554228c30ddd3b604 From f493f8b515bdcbf94fcbf9dabe5e208eb0f28ea7 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 29 Sep 2023 11:37:56 +0200 Subject: [PATCH 07/42] feat: kalium fec --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index e9d5549741f..0cfeb187578 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit e9d5549741f295a2fa94000554228c30ddd3b604 +Subproject commit 0cfeb187578e569cf0f6fc41eb465929bd742a6c From d617f947c01211a550d784f9c45589802ed87296 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 29 Sep 2023 12:04:17 +0200 Subject: [PATCH 08/42] feat: use usecase to observer users and map to ui element --- .../android/mapper/UIParticipantMapper.kt | 19 ++++++++-- .../conversations/ConversationMemberExt.kt | 19 +++------- .../home/conversations/ConversationScreen.kt | 3 +- .../messages/ConversationMessagesViewModel.kt | 7 ++-- .../messages/ConversationMessagesViewState.kt | 3 +- ...ObserveUsersTypingInConversationUseCase.kt | 36 +++++++++++++++++++ .../messagecomposer/EnabledMessageComposer.kt | 7 ++-- .../home/messagecomposer/MessageComposer.kt | 6 ++-- 8 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCase.kt diff --git a/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt index b9b2c738688..52233162583 100644 --- a/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt +++ b/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt @@ -24,6 +24,7 @@ import com.wire.android.ui.home.conversations.avatar import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.previewAsset import com.wire.android.util.ui.WireSessionImageLoader +import com.wire.kalium.logic.data.message.UserSummary import com.wire.kalium.logic.data.message.reaction.MessageReaction import com.wire.kalium.logic.data.message.receipt.DetailedReceipt import com.wire.kalium.logic.data.user.OtherUser @@ -63,7 +64,7 @@ class UIParticipantMapper @Inject constructor( id = userSummary.userId, name = userSummary.userName.orEmpty(), handle = userSummary.userHandle.orEmpty(), - avatarData = previewAsset(wireSessionImageLoader), + avatarData = userSummary.previewAsset(wireSessionImageLoader), membership = userTypeMapper.toMembership(userSummary.userType), unavailable = !userSummary.isUserDeleted && userSummary.userName.orEmpty().isEmpty(), isDeleted = userSummary.isUserDeleted, @@ -77,7 +78,7 @@ class UIParticipantMapper @Inject constructor( id = userSummary.userId, name = userSummary.userName.orEmpty(), handle = userSummary.userHandle.orEmpty(), - avatarData = previewAsset(wireSessionImageLoader), + avatarData = userSummary.previewAsset(wireSessionImageLoader), membership = userTypeMapper.toMembership(userSummary.userType), unavailable = !userSummary.isUserDeleted && userSummary.userName.orEmpty().isEmpty(), isDeleted = userSummary.isUserDeleted, @@ -86,4 +87,18 @@ class UIParticipantMapper @Inject constructor( isDefederated = false ) } + + fun toUIParticipant(userSummary: UserSummary): UIParticipant = with(userSummary) { + return UIParticipant( + id = userSummary.userId, + name = userSummary.userName.orEmpty(), + handle = userSummary.userHandle.orEmpty(), + avatarData = previewAsset(wireSessionImageLoader), + membership = userTypeMapper.toMembership(userSummary.userType), + unavailable = !userSummary.isUserDeleted && userSummary.userName.orEmpty().isEmpty(), + isDeleted = userSummary.isUserDeleted, + isSelf = false, + isDefederated = false + ) + } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt index b0a3081303f..cec9d451d0e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationMemberExt.kt @@ -24,8 +24,7 @@ import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.model.UserAvatarData import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.MemberDetails -import com.wire.kalium.logic.data.message.reaction.MessageReaction -import com.wire.kalium.logic.data.message.receipt.DetailedReceipt +import com.wire.kalium.logic.data.message.UserSummary import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.OtherUser import com.wire.kalium.logic.data.user.SelfUser @@ -70,18 +69,10 @@ val MemberDetails.userType: UserType is SelfUser -> UserType.INTERNAL } -fun MessageReaction.previewAsset( +fun UserSummary.previewAsset( wireSessionImageLoader: WireSessionImageLoader ) = UserAvatarData( - asset = this.userSummary.userPreviewAssetId?.let { UserAvatarAsset(wireSessionImageLoader, it) }, - availabilityStatus = userSummary.availabilityStatus, - connectionState = userSummary.connectionStatus -) - -fun DetailedReceipt.previewAsset( - wireSessionImageLoader: WireSessionImageLoader -) = UserAvatarData( - asset = this.userSummary.userPreviewAssetId?.let { UserAvatarAsset(wireSessionImageLoader, it) }, - availabilityStatus = userSummary.availabilityStatus, - connectionState = userSummary.connectionStatus + asset = this.userPreviewAssetId?.let { UserAvatarAsset(wireSessionImageLoader, it) }, + availabilityStatus = this.availabilityStatus, + connectionState = this.connectionStatus ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index 049ef01b3a4..89371347144 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -88,6 +88,7 @@ import com.wire.android.ui.home.conversations.call.ConversationCallViewModel import com.wire.android.ui.home.conversations.call.ConversationCallViewState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialog import com.wire.android.ui.home.conversations.details.GroupConversationDetailsNavBackArgs +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.edit.EditMessageMenuItems import com.wire.android.ui.home.conversations.info.ConversationDetailsData import com.wire.android.ui.home.conversations.info.ConversationInfoViewModel @@ -627,7 +628,7 @@ private fun ConversationScreenContent( lastUnreadMessageInstant: Instant?, unreadEventCount: Int, audioMessagesState: Map, - usersTyping: Set, + usersTyping: List, messageComposerStateHolder: MessageComposerStateHolder, messages: Flow>, onSendMessage: (MessageBundle) -> Unit, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 5eb7096b3d0..4c074a35e2b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -38,6 +38,7 @@ import com.wire.android.ui.home.conversations.ConversationSnackbarMessages.OnRes import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase +import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase import com.wire.android.ui.navArgs import com.wire.android.util.FileManager import com.wire.android.util.dispatchers.DispatcherProvider @@ -85,7 +86,7 @@ class ConversationMessagesViewModel @Inject constructor( private val resetSession: ResetSessionUseCase, private val conversationAudioMessagePlayer: ConversationAudioMessagePlayer, private val getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase, - private val observeUsersTyping: ObserveUsersTypingUseCase + private val observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase ) : SavedStateViewModel(savedStateHandle) { private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs() @@ -107,10 +108,10 @@ class ConversationMessagesViewModel @Inject constructor( private fun observeUsersTypingState() { viewModelScope.launch { - observeUsersTyping(conversationId).collect { + observeUsersTypingInConversation(conversationId).collect { appLogger.d("Users typing: $it") conversationViewState = conversationViewState.copy( - usersTyping = it.filter { it.userName.orEmpty().isNotEmpty() }.map { it.userName!! }.toSet() + usersTyping = it ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt index b5c1651fce4..1bc5c210fff 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt @@ -22,6 +22,7 @@ package com.wire.android.ui.home.conversations.messages import androidx.paging.PagingData import com.wire.android.media.audiomessage.AudioState +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.kalium.logic.data.user.UserId @@ -35,7 +36,7 @@ data class ConversationMessagesViewState( val firstuUnreadEventIndex: Int = 0, val downloadedAssetDialogState: DownloadedAssetDialogVisibilityState = DownloadedAssetDialogVisibilityState.Hidden, val audioMessagesState: Map = emptyMap(), - val usersTyping: Set = emptySet(), + val usersTyping: List = emptyList(), ) sealed class DownloadedAssetDialogVisibilityState { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCase.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCase.kt new file mode 100644 index 00000000000..00d7a810809 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCase.kt @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2023 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.usecase + +import com.wire.android.mapper.UIParticipantMapper +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class ObserveUsersTypingInConversationUseCase @Inject constructor( + private val observeUsersTyping: ObserveUsersTypingUseCase, + private val uiParticipantMapper: UIParticipantMapper +) { + + suspend operator fun invoke(conversationId: ConversationId): Flow> = + observeUsersTyping(conversationId) + .map { it.map { userSummary -> uiParticipantMapper.toUIParticipant(userSummary) } } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index d285e73b9d4..29cd9bf590d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -49,14 +49,13 @@ import androidx.compose.ui.unit.dp import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottombar.BottomNavigationBarHeight import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType -import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.data.id.ConversationId -import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.util.isPositiveNotNull @OptIn(ExperimentalLayoutApi::class, ExperimentalComposeUiApi::class) @@ -75,7 +74,7 @@ fun EnabledMessageComposer( onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, tempWritableImageUri: Uri?, - usersTyping: Set + usersTyping: List ) { val density = LocalDensity.current val navBarHeight = BottomNavigationBarHeight() @@ -261,7 +260,7 @@ fun EnabledMessageComposer( } if (usersTyping.isNotEmpty()) { - Text(text = usersTyping.joinToString(",") { it }) + Text(text = usersTyping.joinToString(",") { it.name }) } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index f632ab8c615..9b7b2e4d2d6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -51,6 +51,7 @@ import com.wire.android.ui.common.bottomsheet.WireModalSheetState import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.MessageComposerViewState +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionStateHolder import com.wire.android.ui.home.messagecomposer.state.ComposableMessageBundle.AttachmentPickedBundle import com.wire.android.ui.home.messagecomposer.state.ComposableMessageBundle.AudioMessageBundle @@ -64,7 +65,6 @@ import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.stringWithStyledArgs import com.wire.kalium.logic.data.id.ConversationId -import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.conversation.InteractionAvailability import com.wire.kalium.logic.feature.selfDeletingMessages.SelfDeletionTimer import kotlin.time.Duration @@ -80,7 +80,7 @@ fun MessageComposer( onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, tempWritableImageUri: Uri?, - usersTyping: Set + usersTyping: List ) { with(messageComposerStateHolder) { when (messageComposerViewState.value.interactionAvailability) { @@ -225,6 +225,6 @@ fun MessageComposerPreview() { onSendMessageBundle = { }, tempWritableVideoUri = null, tempWritableImageUri = null, - usersTyping = emptySet() + usersTyping = emptyList() ) } From b544794bcefde4f5614e5b76171489c33dfb89e4 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 29 Sep 2023 15:50:28 +0200 Subject: [PATCH 09/42] feat: wip --- .../ui/home/messagecomposer/EnabledMessageComposer.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 29cd9bf590d..09c8bb48446 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -144,6 +144,12 @@ fun EnabledMessageComposer( ) } + if (usersTyping.isNotEmpty()) { + Box(Modifier.wrapContentSize()) { + Text(text = usersTyping.joinToString(",") { it.name }) + } + } + if (additionalOptionStateHolder.additionalOptionsSubMenuState != AdditionalOptionSubMenuState.RecordAudio) { Box(fillRemainingSpaceOrWrapContent) { var currentSelectedLineIndex by remember { mutableStateOf(0) } @@ -258,10 +264,6 @@ fun EnabledMessageComposer( .animateContentSize() ) } - - if (usersTyping.isNotEmpty()) { - Text(text = usersTyping.joinToString(",") { it.name }) - } } } From c9ff7cb3f5e4bddd9e94209166e3acb975c002e5 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sat, 30 Sep 2023 20:49:25 +0200 Subject: [PATCH 10/42] feat: add typing indicator component v1 --- .../messagecomposer/EnabledMessageComposer.kt | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 09c8bb48446..f3d8dce70d7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -32,8 +32,11 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imeAnimationSource import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.isImeVisible +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -42,6 +45,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity @@ -49,12 +53,14 @@ import androidx.compose.ui.unit.dp import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottombar.BottomNavigationBarHeight import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType +import com.wire.android.ui.theme.wireTypography import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.util.isPositiveNotNull @@ -144,10 +150,13 @@ fun EnabledMessageComposer( ) } - if (usersTyping.isNotEmpty()) { - Box(Modifier.wrapContentSize()) { - Text(text = usersTyping.joinToString(",") { it.name }) - } + Column( + modifier = Modifier + .background(color = colorsScheme().backgroundVariant) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + UsersTypingIndicator(usersTyping) } if (additionalOptionStateHolder.additionalOptionsSubMenuState != AdditionalOptionSubMenuState.RecordAudio) { @@ -277,6 +286,33 @@ fun EnabledMessageComposer( } } +@Composable +private fun UsersTypingIndicator( + usersTyping: List, +) { + if (usersTyping.isNotEmpty()) { + Box( + modifier = Modifier + .background( + color = colorsScheme().surface, + shape = RoundedCornerShape(dimensions().corner14x) + ), + contentAlignment = Alignment.Center, + ) { + Text( + text = usersTyping.joinToString(",") { it.name }, + style = MaterialTheme.wireTypography.label01, + modifier = Modifier.padding( + top = dimensions().spacing4x, + bottom = dimensions().spacing4x, + start = dimensions().spacing8x, + end = dimensions().spacing8x, + ), + ) + } + } +} + @OptIn(ExperimentalLayoutApi::class) @Composable private fun isKeyboardMoving(): Boolean { From 58f8e5ff6dce445e6e9be46085c591299b4a0b21 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 00:12:36 +0200 Subject: [PATCH 11/42] feat: add typing indicator component animation --- .../messagecomposer/EnabledMessageComposer.kt | 58 ++++++++++++++++--- app/src/main/res/values/strings.xml | 5 ++ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index f3d8dce70d7..140104615e6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -20,10 +20,18 @@ package com.wire.android.ui.home.messagecomposer import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.InfiniteTransition +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -32,10 +40,15 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imeAnimationSource import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.isImeVisible +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -49,7 +62,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.wire.android.R import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottombar.BottomNavigationBarHeight import com.wire.android.ui.common.colorsScheme @@ -291,28 +307,56 @@ private fun UsersTypingIndicator( usersTyping: List, ) { if (usersTyping.isNotEmpty()) { - Box( + val rememberTransition = + rememberInfiniteTransition(label = stringResource(R.string.animation_label_typing_indicator_horizontal_transition)) + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier .background( color = colorsScheme().surface, - shape = RoundedCornerShape(dimensions().corner14x) - ), - contentAlignment = Alignment.Center, + shape = RoundedCornerShape(dimensions().corner14x), + ) ) { Text( - text = usersTyping.joinToString(",") { it.name }, - style = MaterialTheme.wireTypography.label01, + text = pluralStringResource( + R.plurals.typing_indicator_event_message, + usersTyping.size, + usersTyping.joinToString(", ") { it.name }), // todo. add and vs , logic + style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), modifier = Modifier.padding( top = dimensions().spacing4x, bottom = dimensions().spacing4x, start = dimensions().spacing8x, - end = dimensions().spacing8x, + end = dimensions().spacing12x, ), ) + HorizontalBouncingWritingPen(infiniteTransition = rememberTransition) } } } +@Composable +private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { + val position by infiniteTransition.animateFloat( + initialValue = 0f, targetValue = 8f, + animationSpec = infiniteRepeatable( + animation = tween(1_200, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = infiniteTransition.label + ) + + // todo. add dots + Icon( + imageVector = Icons.Default.Edit, + contentDescription = "Pen", + tint = colorsScheme().secondaryText, + modifier = Modifier + .size(dimensions().spacing12x) + .offset(x = position.dp), + ) +} + @OptIn(ExperimentalLayoutApi::class) @Composable private fun isKeyboardMoving(): Boolean { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3f3e9119bc3..73901691a29 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -676,6 +676,10 @@ "Sent a message" "Someone sent a message" + + %s is typing + %s are typing. + CONTACTS New Group @@ -727,6 +731,7 @@ Conversation could not be unarchived MessageComposeInputState transition + HorizontalBouncingWritingPen transition Collapse button rotation degree transition Open Conversation Email From c831abc5494d50e3d7c3e7aebe26be0050df71d1 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 08:14:03 +0200 Subject: [PATCH 12/42] feat: add typing indicator component animation --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 73901691a29..13779b5c103 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -678,7 +678,7 @@ "Someone sent a message" %s is typing - %s are typing. + %s are typing CONTACTS From f05ef0e535129353d35fe09ed249f083b2edc9c4 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 10:01:57 +0200 Subject: [PATCH 13/42] feat: add typing indicator component animation wip --- .../messagecomposer/EnabledMessageComposer.kt | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 140104615e6..729d31dbb9d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -48,6 +48,7 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.MoreHoriz import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -337,24 +338,33 @@ private fun UsersTypingIndicator( @Composable private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { - val position by infiniteTransition.animateFloat( - initialValue = 0f, targetValue = 8f, - animationSpec = infiniteRepeatable( - animation = tween(1_200, easing = LinearEasing), - repeatMode = RepeatMode.Reverse - ), - label = infiniteTransition.label - ) + Row(Modifier.wrapContentSize()) { // todo. add a11y caps + val position by infiniteTransition.animateFloat( + initialValue = -4f, targetValue = 2f, + animationSpec = infiniteRepeatable( + animation = tween(1_000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = infiniteTransition.label + ) - // todo. add dots - Icon( - imageVector = Icons.Default.Edit, - contentDescription = "Pen", - tint = colorsScheme().secondaryText, - modifier = Modifier - .size(dimensions().spacing12x) - .offset(x = position.dp), - ) + Icon( + imageVector = Icons.Default.MoreHoriz, + contentDescription = "More", + tint = colorsScheme().secondaryText, + modifier = Modifier + .size(dimensions().spacing12x) + .align(Alignment.Bottom) + ) + Icon( + imageVector = Icons.Default.Edit, + contentDescription = "Pen", + tint = colorsScheme().secondaryText, + modifier = Modifier + .size(dimensions().spacing12x) + .offset(x = position.dp), + ) + } } @OptIn(ExperimentalLayoutApi::class) From 57dc8608c53fe4a2058188b141a227fe7c091554 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 11:12:45 +0200 Subject: [PATCH 14/42] feat: add typing indicator component animation and extract to file --- .../conversations/UsersTypingIndicator.kt | 158 ++++++++++++++++++ .../messagecomposer/EnabledMessageComposer.kt | 88 +--------- 2 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt new file mode 100644 index 00000000000..e24d7b08cb7 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -0,0 +1,158 @@ +/* + * Wire + * Copyright (C) 2023 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 + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.InfiniteTransition +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.MoreHoriz +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.wire.android.R +import com.wire.android.model.UserAvatarData +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.android.ui.home.conversationslist.model.Membership +import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.ui.PreviewMultipleThemes +import com.wire.kalium.logic.data.id.QualifiedID +import com.wire.kalium.logic.data.user.ConnectionState + +@Composable +fun UsersTypingIndicator( + usersTyping: List, +) { + if (usersTyping.isNotEmpty()) { + val rememberTransition = + rememberInfiniteTransition(label = stringResource(R.string.animation_label_typing_indicator_horizontal_transition)) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(dimensions().spacing24x) + .background( + color = colorsScheme().surface, + shape = RoundedCornerShape(dimensions().corner14x), + ) + ) { + Text( + text = pluralStringResource( + R.plurals.typing_indicator_event_message, + usersTyping.size, + usersTyping.joinToString(", ") { it.name }), // todo. add and vs , logic + style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), + modifier = Modifier.padding( + top = dimensions().spacing4x, + bottom = dimensions().spacing4x, + start = dimensions().spacing8x, + end = dimensions().spacing8x, + ), + ) + HorizontalBouncingWritingPen(infiniteTransition = rememberTransition) + } + } +} + +@Suppress("MagicNumber") +@Composable +private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { + Row(modifier = Modifier.fillMaxHeight()) { // todo. add a11y caps + val position by infiniteTransition.animateFloat( + initialValue = -5f, targetValue = -1f, + animationSpec = infiniteRepeatable( + animation = tween(1_000, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Reverse + ), + label = infiniteTransition.label + ) + + Icon( + imageVector = Icons.Default.MoreHoriz, + contentDescription = "More", + tint = colorsScheme().secondaryText, + modifier = Modifier + .size(dimensions().spacing12x) + .offset(y = -dimensions().spacing2x) + .align(Alignment.Bottom) + ) + Icon( + imageVector = Icons.Default.Edit, + contentDescription = "Pen", + tint = colorsScheme().secondaryText, + modifier = Modifier + .size(dimensions().spacing12x) + .offset(x = position.dp) + .align(Alignment.CenterVertically), + ) + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewUsersTyping() { + Column( + modifier = Modifier + .background(color = colorsScheme().background) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + UsersTypingIndicator( + listOf( + UIParticipant( + id = QualifiedID("alice", "wire.com"), + name = "Alice Smith", + handle = "alice", + isSelf = false, + isService = false, + avatarData = UserAvatarData(), + membership = Membership.None, + connectionState = ConnectionState.ACCEPTED, + unavailable = false, + isDeleted = false, + readReceiptDate = null, + botService = null, + isDefederated = false + ) + ) + ) + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 729d31dbb9d..065c40702f8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -20,18 +20,10 @@ package com.wire.android.ui.home.messagecomposer import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.InfiniteTransition -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -40,19 +32,9 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imeAnimationSource import androidx.compose.foundation.layout.imeAnimationTarget import androidx.compose.foundation.layout.isImeVisible -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.MoreHoriz -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -63,21 +45,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.wire.android.R import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottombar.BottomNavigationBarHeight import com.wire.android.ui.common.colorsScheme -import com.wire.android.ui.common.dimensions +import com.wire.android.ui.home.conversations.UsersTypingIndicator import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType -import com.wire.android.ui.theme.wireTypography import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.util.isPositiveNotNull @@ -303,70 +281,6 @@ fun EnabledMessageComposer( } } -@Composable -private fun UsersTypingIndicator( - usersTyping: List, -) { - if (usersTyping.isNotEmpty()) { - val rememberTransition = - rememberInfiniteTransition(label = stringResource(R.string.animation_label_typing_indicator_horizontal_transition)) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .background( - color = colorsScheme().surface, - shape = RoundedCornerShape(dimensions().corner14x), - ) - ) { - Text( - text = pluralStringResource( - R.plurals.typing_indicator_event_message, - usersTyping.size, - usersTyping.joinToString(", ") { it.name }), // todo. add and vs , logic - style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), - modifier = Modifier.padding( - top = dimensions().spacing4x, - bottom = dimensions().spacing4x, - start = dimensions().spacing8x, - end = dimensions().spacing12x, - ), - ) - HorizontalBouncingWritingPen(infiniteTransition = rememberTransition) - } - } -} - -@Composable -private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { - Row(Modifier.wrapContentSize()) { // todo. add a11y caps - val position by infiniteTransition.animateFloat( - initialValue = -4f, targetValue = 2f, - animationSpec = infiniteRepeatable( - animation = tween(1_000, easing = LinearEasing), - repeatMode = RepeatMode.Reverse - ), - label = infiniteTransition.label - ) - - Icon( - imageVector = Icons.Default.MoreHoriz, - contentDescription = "More", - tint = colorsScheme().secondaryText, - modifier = Modifier - .size(dimensions().spacing12x) - .align(Alignment.Bottom) - ) - Icon( - imageVector = Icons.Default.Edit, - contentDescription = "Pen", - tint = colorsScheme().secondaryText, - modifier = Modifier - .size(dimensions().spacing12x) - .offset(x = position.dp), - ) - } -} - @OptIn(ExperimentalLayoutApi::class) @Composable private fun isKeyboardMoving(): Boolean { From 3e0f79c04b50d944fa20db25ed5ec926f3ea50dc Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 11:47:04 +0200 Subject: [PATCH 15/42] feat: add typing indicator component animation and extract strings --- .../conversations/UsersTypingIndicator.kt | 68 +++++++++++++++++-- app/src/main/res/values/strings.xml | 3 +- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index e24d7b08cb7..2a8cd10993d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -75,10 +75,7 @@ fun UsersTypingIndicator( ) ) { Text( - text = pluralStringResource( - R.plurals.typing_indicator_event_message, - usersTyping.size, - usersTyping.joinToString(", ") { it.name }), // todo. add and vs , logic + text = usersTypingIndicatorText(usersTyping), style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), modifier = Modifier.padding( top = dimensions().spacing4x, @@ -92,6 +89,21 @@ fun UsersTypingIndicator( } } +@Composable +private fun usersTypingIndicatorText( + usersTyping: List, +): String { + return pluralStringResource( + R.plurals.typing_indicator_event_message, + usersTyping.size, + if (usersTyping.size == 1) usersTyping.first().name + else { + usersTyping.first().name + }, + usersTyping.size - 1 + ) +} + @Suppress("MagicNumber") @Composable private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { @@ -128,7 +140,38 @@ private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) @PreviewMultipleThemes @Composable -fun PreviewUsersTyping() { +fun PreviewUsersTypingOne() { + Column( + modifier = Modifier + .background(color = colorsScheme().background) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + UsersTypingIndicator( + listOf( + UIParticipant( + id = QualifiedID("Alice", "wire.com"), + name = "Alice", + handle = "alice", + isSelf = false, + isService = false, + avatarData = UserAvatarData(), + membership = Membership.None, + connectionState = ConnectionState.ACCEPTED, + unavailable = false, + isDeleted = false, + readReceiptDate = null, + botService = null, + isDefederated = false + ) + ) + ) + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewUsersTypingMoreThanOne() { Column( modifier = Modifier .background(color = colorsScheme().background) @@ -137,6 +180,21 @@ fun PreviewUsersTyping() { ) { UsersTypingIndicator( listOf( + UIParticipant( + id = QualifiedID("Bob", "wire.com"), + name = "Bob", + handle = "bob", + isSelf = false, + isService = false, + avatarData = UserAvatarData(), + membership = Membership.None, + connectionState = ConnectionState.ACCEPTED, + unavailable = false, + isDeleted = false, + readReceiptDate = null, + botService = null, + isDefederated = false + ), UIParticipant( id = QualifiedID("alice", "wire.com"), name = "Alice Smith", diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 13779b5c103..275fdfa3e8c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -676,9 +676,10 @@ "Sent a message" "Someone sent a message" + %s is typing - %s are typing + %1$s and %2$d more are typing CONTACTS From 4756ce0ed343f636a56bbd0bfc1e02dc0161e9d9 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 11:52:42 +0200 Subject: [PATCH 16/42] feat: add typing indicator component animation and extract strings --- .../conversations/UsersTypingIndicator.kt | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 2a8cd10993d..bf93386b8a0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -75,7 +75,12 @@ fun UsersTypingIndicator( ) ) { Text( - text = usersTypingIndicatorText(usersTyping), + text = pluralStringResource( + R.plurals.typing_indicator_event_message, + usersTyping.size, + usersTyping.first().name, + usersTyping.size - 1 + ), style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), modifier = Modifier.padding( top = dimensions().spacing4x, @@ -89,21 +94,6 @@ fun UsersTypingIndicator( } } -@Composable -private fun usersTypingIndicatorText( - usersTyping: List, -): String { - return pluralStringResource( - R.plurals.typing_indicator_event_message, - usersTyping.size, - if (usersTyping.size == 1) usersTyping.first().name - else { - usersTyping.first().name - }, - usersTyping.size - 1 - ) -} - @Suppress("MagicNumber") @Composable private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { From 22f974fb068df2c1cd708bc83f56889939b6bb30 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 12:03:22 +0200 Subject: [PATCH 17/42] feat: add typing indicator component animation and extract strings --- .../wire/android/ui/home/conversations/UsersTypingIndicator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index bf93386b8a0..43bc5e0183c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -78,7 +78,7 @@ fun UsersTypingIndicator( text = pluralStringResource( R.plurals.typing_indicator_event_message, usersTyping.size, - usersTyping.first().name, + usersTyping.last().name, usersTyping.size - 1 ), style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), From 194955445c2cb0e081ba76367d8ce1c07ba32b63 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 12:43:28 +0200 Subject: [PATCH 18/42] feat: add typing indicator component animation and extract strings --- .../home/conversations/UsersTypingIndicator.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 43bc5e0183c..d7422a2360a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -56,7 +56,6 @@ import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.QualifiedID -import com.wire.kalium.logic.data.user.ConnectionState @Composable fun UsersTypingIndicator( @@ -96,8 +95,10 @@ fun UsersTypingIndicator( @Suppress("MagicNumber") @Composable -private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) { - Row(modifier = Modifier.fillMaxHeight()) { // todo. add a11y caps +private fun HorizontalBouncingWritingPen( + infiniteTransition: InfiniteTransition, +) { + Row(modifier = Modifier.fillMaxHeight()) { val position by infiniteTransition.animateFloat( initialValue = -5f, targetValue = -1f, animationSpec = infiniteRepeatable( @@ -109,7 +110,7 @@ private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) Icon( imageVector = Icons.Default.MoreHoriz, - contentDescription = "More", + contentDescription = null, tint = colorsScheme().secondaryText, modifier = Modifier .size(dimensions().spacing12x) @@ -118,7 +119,7 @@ private fun HorizontalBouncingWritingPen(infiniteTransition: InfiniteTransition) ) Icon( imageVector = Icons.Default.Edit, - contentDescription = "Pen", + contentDescription = null, tint = colorsScheme().secondaryText, modifier = Modifier .size(dimensions().spacing12x) @@ -147,7 +148,7 @@ fun PreviewUsersTypingOne() { isService = false, avatarData = UserAvatarData(), membership = Membership.None, - connectionState = ConnectionState.ACCEPTED, + connectionState = null, unavailable = false, isDeleted = false, readReceiptDate = null, @@ -178,7 +179,7 @@ fun PreviewUsersTypingMoreThanOne() { isService = false, avatarData = UserAvatarData(), membership = Membership.None, - connectionState = ConnectionState.ACCEPTED, + connectionState = null, unavailable = false, isDeleted = false, readReceiptDate = null, @@ -193,7 +194,7 @@ fun PreviewUsersTypingMoreThanOne() { isService = false, avatarData = UserAvatarData(), membership = Membership.None, - connectionState = ConnectionState.ACCEPTED, + connectionState = null, unavailable = false, isDeleted = false, readReceiptDate = null, From 3fb4b531a257460509adad26d532fbf7435382dc Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 15:03:27 +0200 Subject: [PATCH 19/42] feat: add typing indicator component animation and extract avatar previews --- .../conversations/UsersTypingIndicator.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index d7422a2360a..f00922c1ca1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.wire.android.R import com.wire.android.model.UserAvatarData +import com.wire.android.ui.common.UserProfileAvatar import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant @@ -73,18 +74,18 @@ fun UsersTypingIndicator( shape = RoundedCornerShape(dimensions().corner14x), ) ) { + UsersTypingAvatarPreviews(usersTyping) Text( text = pluralStringResource( R.plurals.typing_indicator_event_message, usersTyping.size, - usersTyping.last().name, + usersTyping.first().name, usersTyping.size - 1 ), style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), modifier = Modifier.padding( top = dimensions().spacing4x, bottom = dimensions().spacing4x, - start = dimensions().spacing8x, end = dimensions().spacing8x, ), ) @@ -93,6 +94,25 @@ fun UsersTypingIndicator( } } +@Suppress("MagicNumber") +@Composable +private fun UsersTypingAvatarPreviews(usersTyping: List, maxPreviewsDisplay: Int = 3) { + usersTyping.take(maxPreviewsDisplay).forEachIndexed { index, user -> + UserProfileAvatar( + avatarData = user.avatarData, + size = dimensions().spacing16x, + padding = dimensions().spacing2x, + modifier = if (usersTyping.size == 1 || maxPreviewsDisplay == 1) { + Modifier + } else { + Modifier.offset( + x = if (index == 0) dimensions().spacing8x else -(dimensions().spacing6x) + ) + } + ) + } +} + @Suppress("MagicNumber") @Composable private fun HorizontalBouncingWritingPen( From a737bdaf0108c0ad1ab37a230fa33c33a85c33a5 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Sun, 1 Oct 2023 19:01:44 +0200 Subject: [PATCH 20/42] feat: add typing indicator component animation and extract avatar previews --- .../android/ui/home/conversations/UsersTypingIndicator.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index f00922c1ca1..b9201371cb3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -98,13 +98,13 @@ fun UsersTypingIndicator( @Composable private fun UsersTypingAvatarPreviews(usersTyping: List, maxPreviewsDisplay: Int = 3) { usersTyping.take(maxPreviewsDisplay).forEachIndexed { index, user -> + val isSingleUser = usersTyping.size == 1 || maxPreviewsDisplay == 1 UserProfileAvatar( avatarData = user.avatarData, size = dimensions().spacing16x, padding = dimensions().spacing2x, - modifier = if (usersTyping.size == 1 || maxPreviewsDisplay == 1) { - Modifier - } else { + modifier = if (isSingleUser) Modifier + else { Modifier.offset( x = if (index == 0) dimensions().spacing8x else -(dimensions().spacing6x) ) From 7938ea435747e5b2fceca4de891620714413947e Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 10:00:18 +0200 Subject: [PATCH 21/42] feat: adjustment to text --- .../ui/home/conversations/UsersTypingIndicator.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index b9201371cb3..8c95881da3d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.wire.android.R import com.wire.android.model.UserAvatarData @@ -83,11 +84,15 @@ fun UsersTypingIndicator( usersTyping.size - 1 ), style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText), - modifier = Modifier.padding( - top = dimensions().spacing4x, - bottom = dimensions().spacing4x, - end = dimensions().spacing8x, - ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .weight(weight = 0.6f, fill = false) + .padding( + top = dimensions().spacing4x, + bottom = dimensions().spacing4x, + end = dimensions().spacing8x, + ) ) HorizontalBouncingWritingPen(infiniteTransition = rememberTransition) } From 378805b8f6d5129d59bb354b7297757077d7c20c Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 10:02:40 +0200 Subject: [PATCH 22/42] feat: kalium ref --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index dab0ee552ea..a7760c3e6e5 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit dab0ee552eaa2a65122afc43b9494e51f413d4ec +Subproject commit a7760c3e6e5195269e6e44472b98735f88a35701 From a9e6b9cec0bba35925f7fdf72ad0365592519140 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 10:05:46 +0200 Subject: [PATCH 23/42] feat: kalium ref --- .../wire/android/ui/home/conversations/UsersTypingIndicator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 8c95881da3d..32f5c50b662 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -87,7 +87,7 @@ fun UsersTypingIndicator( maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier - .weight(weight = 0.6f, fill = false) + .weight(weight = 1f, fill = false) .padding( top = dimensions().spacing4x, bottom = dimensions().spacing4x, From 003ded391aa6a660abad99a3b15fb6258be940c4 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 10:16:31 +0200 Subject: [PATCH 24/42] feat: detekt --- .../home/conversations/messages/ConversationMessagesViewState.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt index 1bc5c210fff..553c29ab614 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt @@ -25,7 +25,6 @@ import com.wire.android.media.audiomessage.AudioState import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage -import com.wire.kalium.logic.data.user.UserId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.datetime.Instant From 8cf4d4eb448ce7aaf644f3b121dfbbaa6491d85c Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 10:20:58 +0200 Subject: [PATCH 25/42] feat: detekt --- .../home/conversations/messages/ConversationMessagesViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 4c074a35e2b..811e0c7fb3a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -55,7 +55,6 @@ import com.wire.kalium.logic.feature.asset.MessageAssetResult import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCase import com.wire.kalium.logic.feature.conversation.GetConversationUnreadEventsCountUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase -import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase import com.wire.kalium.logic.feature.message.GetMessageByIdUseCase import com.wire.kalium.logic.feature.message.ToggleReactionUseCase import com.wire.kalium.logic.feature.sessionreset.ResetSessionResult From ed599d78bda41d4b0f7e700dc79abc160f6c4e4c Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 11:20:32 +0200 Subject: [PATCH 26/42] fix: broken testst --- .../messages/ConversationMessagesViewModelArrangement.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt index b9f4185bf5f..0caa6a8a8c9 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt @@ -30,6 +30,7 @@ import com.wire.android.ui.home.conversations.ConversationNavArgs import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase +import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase import com.wire.android.ui.navArgs import com.wire.android.util.FileManager import com.wire.kalium.logic.data.asset.AttachmentType @@ -95,6 +96,9 @@ class ConversationMessagesViewModelArrangement { @MockK lateinit var getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase + @MockK + lateinit var observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase + private val viewModel: ConversationMessagesViewModel by lazy { ConversationMessagesViewModel( savedStateHandle, @@ -108,7 +112,8 @@ class ConversationMessagesViewModelArrangement { toggleReaction, resetSession, conversationAudioMessagePlayer, - getConversationUnreadEventsCount + getConversationUnreadEventsCount, + observeUsersTypingInConversation ) } @@ -122,6 +127,7 @@ class ConversationMessagesViewModelArrangement { coEvery { getMessagesForConversationUseCase(any(), any()) } returns messagesChannel.consumeAsFlow() coEvery { getConversationUnreadEventsCount(any()) } returns GetConversationUnreadEventsCountUseCase.Result.Success(0L) coEvery { updateAssetMessageDownloadStatus(any(), any(), any()) } returns UpdateDownloadStatusResult.Success + coEvery { observeUsersTypingInConversation(any()) } returns flowOf(emptyList()) } fun withSuccessfulOpenAssetMessage( From 6c0f026016a2aacfd5fd3ab3339de7769b661655 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 11:47:00 +0200 Subject: [PATCH 27/42] fix: re-enable disabled test reset session --- .../messages/ConversationMessagesViewModelArrangement.kt | 5 +++++ .../messages/ConversationMessagesViewModelTest.kt | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt index 0caa6a8a8c9..7337bd7c40d 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt @@ -44,6 +44,7 @@ import com.wire.kalium.logic.feature.conversation.GetConversationUnreadEventsCou import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase import com.wire.kalium.logic.feature.message.GetMessageByIdUseCase import com.wire.kalium.logic.feature.message.ToggleReactionUseCase +import com.wire.kalium.logic.feature.sessionreset.ResetSessionResult import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase import com.wire.kalium.logic.functional.Either import io.mockk.MockKAnnotations @@ -170,6 +171,10 @@ class ConversationMessagesViewModelArrangement { messagesChannel.send(pagingDataFlow) } + suspend fun withResetSessionResult(resetSessionResult: ResetSessionResult = ResetSessionResult.Success) = apply { + coEvery { resetSession(any(), any(), any()) } returns resetSessionResult + } + fun withSuccessfulSaveAssetMessage( assetMimeType: String, assetName: String, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt index d7fa9652804..331710ed402 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt @@ -176,11 +176,12 @@ class ConversationMessagesViewModelTest { } @Test - @Disabled fun `given a message with failed decryption, when resetting the session, then should call ResetSessionUseCase`() = runTest { val userId = UserId("someID", "someDomain") val clientId = "someClientId" val (arrangement, viewModel) = ConversationMessagesViewModelArrangement() + .withObservableAudioMessagesState(flowOf()) + .withResetSessionResult() .arrange() viewModel.onResetSession(userId, clientId) From 456a9ce8e2bfcb60dee02ba192cb04258be1439c Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 12:26:02 +0200 Subject: [PATCH 28/42] fix: test coverage --- .../ConversationMessagesViewModelArrangement.kt | 5 +++++ .../messages/ConversationMessagesViewModelTest.kt | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt index 7337bd7c40d..5742ac84ef0 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt @@ -27,6 +27,7 @@ import com.wire.android.config.mockUri import com.wire.android.media.audiomessage.AudioState import com.wire.android.media.audiomessage.ConversationAudioMessagePlayer import com.wire.android.ui.home.conversations.ConversationNavArgs +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase @@ -175,6 +176,10 @@ class ConversationMessagesViewModelArrangement { coEvery { resetSession(any(), any(), any()) } returns resetSessionResult } + fun withObserveUsersTyping(usersTyping: List = emptyList()) = apply { + coEvery { observeUsersTypingInConversation(any()) } returns flowOf(usersTyping) + } + fun withSuccessfulSaveAssetMessage( assetMimeType: String, assetName: String, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt index 331710ed402..dd39e107f6b 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt @@ -24,9 +24,9 @@ import androidx.paging.PagingData import androidx.paging.map import app.cash.turbine.test import com.wire.android.config.CoroutineTestExtension +import com.wire.android.config.NavigationTestExtension import com.wire.android.framework.TestMessage import com.wire.android.framework.TestMessage.GENERIC_ASSET_CONTENT -import com.wire.android.config.NavigationTestExtension import com.wire.android.ui.home.conversations.mockUITextMessage import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.message.MessageContent @@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import okio.Path.Companion.toPath import org.amshove.kluent.shouldBeEqualTo -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -188,4 +187,14 @@ class ConversationMessagesViewModelTest { coVerify(exactly = 1) { arrangement.resetSession(any(), any(), any()) } } + + @Test + fun `given a conversation, when starting, then should start observing user typing events`() = runTest { + val (arrangement, _) = ConversationMessagesViewModelArrangement() + .withObservableAudioMessagesState(flowOf()) + .withObserveUsersTyping(listOf()) + .arrange() + + coVerify(exactly = 1) { arrangement.observeUsersTypingInConversation(any()) } + } } From bacd4147b5b3add2031a966414d33dbaf408b1fa Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 14:13:48 +0200 Subject: [PATCH 29/42] fix: test coverage --- ...rveUsersTypingInConversationUseCaseTest.kt | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt new file mode 100644 index 00000000000..e4a104eef2b --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt @@ -0,0 +1,106 @@ +/* + * Wire + * Copyright (C) 2023 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.usecase + +import com.wire.android.config.CoroutineTestExtension +import com.wire.android.mapper.UIParticipantMapper +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCaseTest.Arrangement.Companion.expectedUIParticipant +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.message.UserSummary +import com.wire.kalium.logic.data.user.ConnectionState +import com.wire.kalium.logic.data.user.UserAvailabilityStatus +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.data.user.type.UserType +import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(CoroutineTestExtension::class) +class ObserveUsersTypingInConversationUseCaseTest { + + @Test + fun given_UsersAreTyping_ThenObserveFlowAndReturnUIParticipant() = runTest { + val userSummaryArg = UserSummary( + expectedUIParticipant.id, + expectedUIParticipant.name, + expectedUIParticipant.handle, + null, + UserType.NONE, + false, + ConnectionState.ACCEPTED, + UserAvailabilityStatus.AVAILABLE + ) + val (arrangement, useCase) = Arrangement() + .withMapperFrom(userSummaryArg) + .withObserveUsersTypingResult(setOf(userSummaryArg)) + .arrange() + + val result = useCase(ConversationId("id", "domain")) + + assertTrue(result.first().isNotEmpty()) + assertEquals(expectedUIParticipant, result.first().first()) + coVerify(exactly = 1) { arrangement.observeUsersTyping(any()) } + } + + private class Arrangement { + + @MockK + lateinit var observeUsersTyping: ObserveUsersTypingUseCase + + @MockK + lateinit var uiParticipantMapper: UIParticipantMapper + + init { + MockKAnnotations.init(this, relaxUnitFun = true) + } + + fun withObserveUsersTypingResult(usersTyping: Set) = apply { + coEvery { observeUsersTyping(any()) } returns flowOf(usersTyping) + } + + fun withMapperFrom(userSummary: UserSummary) = apply { + every { uiParticipantMapper.toUIParticipant(eq(userSummary)) } returns expectedUIParticipant + } + + fun arrange() = this to ObserveUsersTypingInConversationUseCase( + observeUsersTyping, uiParticipantMapper + ) + + companion object { + val expectedUIParticipant = UIParticipant( + id = UserId("id", "domain"), + name = "name", + handle = "handle", + isSelf = false, + isService = false + ) + } + } +} From ed6d386fe0c6ab084f703e2ee39980ef8c63e398 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 14:24:13 +0200 Subject: [PATCH 30/42] fix: test coverage --- .../android/mapper/UIParticipantMapperTest.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt index c3b99cbec6e..0e46acb277c 100644 --- a/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt +++ b/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt @@ -30,6 +30,7 @@ import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.Conversation.Member import com.wire.kalium.logic.data.conversation.MemberDetails import com.wire.kalium.logic.data.id.TeamId +import com.wire.kalium.logic.data.message.UserSummary import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.OtherUser import com.wire.kalium.logic.data.user.SelfUser @@ -39,6 +40,7 @@ import com.wire.kalium.logic.data.user.type.UserType import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK import kotlinx.coroutines.test.runTest +import org.amshove.kluent.internal.assertEquals import org.junit.jupiter.api.Test class UIParticipantMapperTest { @@ -62,6 +64,32 @@ class UIParticipantMapperTest { } } + @Test + fun givenUserSummary_whenMappingUiParticipant_thenCorrectValuesShouldBeReturned() = runTest { + // Given + val (_, mapper) = Arrangement().arrange() + val data: List = listOf( + UserSummary( + testOtherUser(0).id, + testOtherUser(0).name, + testOtherUser(0).handle, + testOtherUser(0).previewPicture, + testOtherUser(0).userType, + testOtherUser(0).deleted, + testOtherUser(0).connectionStatus, + testOtherUser(0).availabilityStatus + ) + ) + // When + val results: List = data.map { mapper.toUIParticipant(it) } + + // Then + assertEquals(testUIParticipant(0).id, results.first().id) + assertEquals(testUIParticipant(0).name, results.first().name) + assertEquals(testUIParticipant(0).handle, results.first().handle) + assertEquals(testUIParticipant(0).avatarData.asset, results.first().avatarData.asset) + } + private fun compareResult( wireSessionImageLoader: WireSessionImageLoader, memberDetails: MemberDetails, From 5fdbe1b4ba5115b3fdd9366c1331bcf72369e0d2 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 14:36:14 +0200 Subject: [PATCH 31/42] fix: test coverage --- .../com/wire/android/mapper/UIParticipantMapperTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt index 0e46acb277c..6ce8ba62a8b 100644 --- a/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt +++ b/app/src/test/kotlin/com/wire/android/mapper/UIParticipantMapperTest.kt @@ -88,6 +88,12 @@ class UIParticipantMapperTest { assertEquals(testUIParticipant(0).name, results.first().name) assertEquals(testUIParticipant(0).handle, results.first().handle) assertEquals(testUIParticipant(0).avatarData.asset, results.first().avatarData.asset) + assertEquals(null, results.first().botService) + assertEquals(null, results.first().connectionState) + assertEquals(false, results.first().isDefederated) + assertEquals(false, results.first().isSelf) + assertEquals(false, results.first().unavailable) + assertEquals(false, results.first().isService) } private fun compareResult( From 1a2ff0e9240cedbe80616ae85fc8c7f4d44bede9 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 15:04:16 +0200 Subject: [PATCH 32/42] fix: detekt --- .../usecase/ObserveUsersTypingInConversationUseCaseTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt index e4a104eef2b..2448d71c2b7 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/ObserveUsersTypingInConversationUseCaseTest.kt @@ -37,7 +37,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* +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 From eaa11a09292a9d8f23e8ab1ab5ba36ecc7571d91 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 15:45:37 +0200 Subject: [PATCH 33/42] chore: kalium ref --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index a7760c3e6e5..f231b186e0d 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit a7760c3e6e5195269e6e44472b98735f88a35701 +Subproject commit f231b186e0d5e25a8f2f47bebeae92097cc646a6 From 77e18393ac37489847efb29a6f0e32210a348227 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 17:54:38 +0200 Subject: [PATCH 34/42] chore: address pr comments --- .../home/conversations/ConversationScreen.kt | 4 -- .../conversations/UsersTypingIndicator.kt | 13 +++- .../messages/ConversationMessagesViewModel.kt | 16 +---- .../messages/ConversationMessagesViewState.kt | 4 +- .../typing/TypingIndicatorViewModel.kt | 59 +++++++++++++++++++ .../typing/UsersTypingViewState.kt | 24 ++++++++ .../messagecomposer/EnabledMessageComposer.kt | 8 +-- .../home/messagecomposer/MessageComposer.kt | 9 +-- ...onversationMessagesViewModelArrangement.kt | 13 +--- .../ConversationMessagesViewModelTest.kt | 9 --- kalium | 2 +- 11 files changed, 104 insertions(+), 57 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/UsersTypingViewState.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index 89371347144..85917834032 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -88,7 +88,6 @@ import com.wire.android.ui.home.conversations.call.ConversationCallViewModel import com.wire.android.ui.home.conversations.call.ConversationCallViewState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialog import com.wire.android.ui.home.conversations.details.GroupConversationDetailsNavBackArgs -import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.edit.EditMessageMenuItems import com.wire.android.ui.home.conversations.info.ConversationDetailsData import com.wire.android.ui.home.conversations.info.ConversationInfoViewModel @@ -583,7 +582,6 @@ private fun ConversationScreen( ConversationScreenContent( conversationId = conversationInfoViewState.conversationId, audioMessagesState = conversationMessagesViewState.audioMessagesState, - usersTyping = conversationMessagesViewState.usersTyping, lastUnreadMessageInstant = conversationMessagesViewState.firstUnreadInstant, unreadEventCount = conversationMessagesViewState.firstuUnreadEventIndex, conversationDetailsData = conversationInfoViewState.conversationDetailsData, @@ -628,7 +626,6 @@ private fun ConversationScreenContent( lastUnreadMessageInstant: Instant?, unreadEventCount: Int, audioMessagesState: Map, - usersTyping: List, messageComposerStateHolder: MessageComposerStateHolder, messages: Flow>, onSendMessage: (MessageBundle) -> Unit, @@ -689,7 +686,6 @@ private fun ConversationScreenContent( onSendMessageBundle = onSendMessage, tempWritableVideoUri = tempWritableVideoUri, tempWritableImageUri = tempWritableImageUri, - usersTyping = usersTyping ) // TODO: uncomment when we have the "scroll to bottom" button implemented diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 32f5c50b662..b2c9e75674a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -48,21 +48,30 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import com.sebaslogen.resaca.hilt.hiltViewModelScoped import com.wire.android.R import com.wire.android.model.UserAvatarData import com.wire.android.ui.common.UserProfileAvatar import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.android.ui.home.conversations.typing.TypingIndicatorViewModel import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID @Composable -fun UsersTypingIndicator( - usersTyping: List, +fun UsersTypingIndicatorForConversation( + conversationId: ConversationId, + viewModel: TypingIndicatorViewModel = hiltViewModelScoped(conversationId), ) { + UsersTypingIndicator(usersTyping = viewModel.usersTypingViewState.usersTyping) +} + +@Composable +fun UsersTypingIndicator(usersTyping: List) { if (usersTyping.isNotEmpty()) { val rememberTransition = rememberInfiniteTransition(label = stringResource(R.string.animation_label_typing_indicator_horizontal_transition)) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 811e0c7fb3a..4316c17026b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -38,7 +38,6 @@ import com.wire.android.ui.home.conversations.ConversationSnackbarMessages.OnRes import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase -import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase import com.wire.android.ui.navArgs import com.wire.android.util.FileManager import com.wire.android.util.dispatchers.DispatcherProvider @@ -84,8 +83,7 @@ class ConversationMessagesViewModel @Inject constructor( private val toggleReaction: ToggleReactionUseCase, private val resetSession: ResetSessionUseCase, private val conversationAudioMessagePlayer: ConversationAudioMessagePlayer, - private val getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase, - private val observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase + private val getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase ) : SavedStateViewModel(savedStateHandle) { private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs() @@ -101,21 +99,9 @@ class ConversationMessagesViewModel @Inject constructor( init { loadPaginatedMessages() loadLastMessageInstant() - observeUsersTypingState() observeAudioPlayerState() } - private fun observeUsersTypingState() { - viewModelScope.launch { - observeUsersTypingInConversation(conversationId).collect { - appLogger.d("Users typing: $it") - conversationViewState = conversationViewState.copy( - usersTyping = it - ) - } - } - } - private fun observeAudioPlayerState() { viewModelScope.launch { conversationAudioMessagePlayer.observableAudioMessagesState.collect { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt index 553c29ab614..74e81bed90d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewState.kt @@ -22,7 +22,6 @@ package com.wire.android.ui.home.conversations.messages import androidx.paging.PagingData import com.wire.android.media.audiomessage.AudioState -import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import kotlinx.coroutines.flow.Flow @@ -34,8 +33,7 @@ data class ConversationMessagesViewState( val firstUnreadInstant: Instant? = null, val firstuUnreadEventIndex: Int = 0, val downloadedAssetDialogState: DownloadedAssetDialogVisibilityState = DownloadedAssetDialogVisibilityState.Hidden, - val audioMessagesState: Map = emptyMap(), - val usersTyping: List = emptyList(), + val audioMessagesState: Map = emptyMap() ) sealed class DownloadedAssetDialogVisibilityState { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt new file mode 100644 index 00000000000..f29725673f7 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModel.kt @@ -0,0 +1,59 @@ +/* + * Wire + * Copyright (C) 2023 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.typing + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.wire.android.appLogger +import com.wire.android.ui.home.conversations.ConversationNavArgs +import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase +import com.wire.android.ui.navArgs +import com.wire.kalium.logic.data.id.QualifiedID +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class TypingIndicatorViewModel @Inject constructor( + private val observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase, + savedStateHandle: SavedStateHandle, +) : ViewModel() { + + private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs() + val conversationId: QualifiedID = conversationNavArgs.conversationId + + var usersTypingViewState by mutableStateOf(UsersTypingViewState()) + private set + + init { + observeUsersTypingState() + } + + private fun observeUsersTypingState() { + viewModelScope.launch { + observeUsersTypingInConversation(conversationId).collect { + appLogger.d("Users typing: $it") + usersTypingViewState = usersTypingViewState.copy(usersTyping = it) + } + } + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/UsersTypingViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/UsersTypingViewState.kt new file mode 100644 index 00000000000..47144155f15 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/typing/UsersTypingViewState.kt @@ -0,0 +1,24 @@ +/* + * Wire + * Copyright (C) 2023 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.typing + +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant + +data class UsersTypingViewState( + val usersTyping: List = emptyList() +) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 065c40702f8..e12f0be78a8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -49,8 +49,7 @@ import androidx.compose.ui.unit.dp import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottombar.BottomNavigationBarHeight import com.wire.android.ui.common.colorsScheme -import com.wire.android.ui.home.conversations.UsersTypingIndicator -import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.android.ui.home.conversations.UsersTypingIndicatorForConversation import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState @@ -74,8 +73,7 @@ fun EnabledMessageComposer( onPingOptionClicked: () -> Unit, onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, - tempWritableImageUri: Uri?, - usersTyping: List + tempWritableImageUri: Uri? ) { val density = LocalDensity.current val navBarHeight = BottomNavigationBarHeight() @@ -151,7 +149,7 @@ fun EnabledMessageComposer( .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - UsersTypingIndicator(usersTyping) + UsersTypingIndicatorForConversation(conversationId = conversationId) } if (additionalOptionStateHolder.additionalOptionsSubMenuState != AdditionalOptionSubMenuState.RecordAudio) { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index 9b7b2e4d2d6..32faa079d52 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -79,8 +79,7 @@ fun MessageComposer( onSearchMentionQueryChanged: (String) -> Unit, onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, - tempWritableImageUri: Uri?, - usersTyping: List + tempWritableImageUri: Uri? ) { with(messageComposerStateHolder) { when (messageComposerViewState.value.interactionAvailability) { @@ -134,8 +133,7 @@ fun MessageComposer( onSearchMentionQueryChanged = onSearchMentionQueryChanged, onClearMentionSearchResult = onClearMentionSearchResult, tempWritableVideoUri = tempWritableVideoUri, - tempWritableImageUri = tempWritableImageUri, - usersTyping = usersTyping + tempWritableImageUri = tempWritableImageUri ) } } @@ -224,7 +222,6 @@ fun MessageComposerPreview() { onClearMentionSearchResult = { }, onSendMessageBundle = { }, tempWritableVideoUri = null, - tempWritableImageUri = null, - usersTyping = emptyList() + tempWritableImageUri = null ) } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt index 5742ac84ef0..b7cbad4b3e6 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelArrangement.kt @@ -27,11 +27,9 @@ import com.wire.android.config.mockUri import com.wire.android.media.audiomessage.AudioState import com.wire.android.media.audiomessage.ConversationAudioMessagePlayer import com.wire.android.ui.home.conversations.ConversationNavArgs -import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase -import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase import com.wire.android.ui.navArgs import com.wire.android.util.FileManager import com.wire.kalium.logic.data.asset.AttachmentType @@ -98,9 +96,6 @@ class ConversationMessagesViewModelArrangement { @MockK lateinit var getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase - @MockK - lateinit var observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase - private val viewModel: ConversationMessagesViewModel by lazy { ConversationMessagesViewModel( savedStateHandle, @@ -114,8 +109,7 @@ class ConversationMessagesViewModelArrangement { toggleReaction, resetSession, conversationAudioMessagePlayer, - getConversationUnreadEventsCount, - observeUsersTypingInConversation + getConversationUnreadEventsCount ) } @@ -129,7 +123,6 @@ class ConversationMessagesViewModelArrangement { coEvery { getMessagesForConversationUseCase(any(), any()) } returns messagesChannel.consumeAsFlow() coEvery { getConversationUnreadEventsCount(any()) } returns GetConversationUnreadEventsCountUseCase.Result.Success(0L) coEvery { updateAssetMessageDownloadStatus(any(), any(), any()) } returns UpdateDownloadStatusResult.Success - coEvery { observeUsersTypingInConversation(any()) } returns flowOf(emptyList()) } fun withSuccessfulOpenAssetMessage( @@ -176,10 +169,6 @@ class ConversationMessagesViewModelArrangement { coEvery { resetSession(any(), any(), any()) } returns resetSessionResult } - fun withObserveUsersTyping(usersTyping: List = emptyList()) = apply { - coEvery { observeUsersTypingInConversation(any()) } returns flowOf(usersTyping) - } - fun withSuccessfulSaveAssetMessage( assetMimeType: String, assetName: String, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt index dd39e107f6b..ba4f7327bc5 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt @@ -188,13 +188,4 @@ class ConversationMessagesViewModelTest { coVerify(exactly = 1) { arrangement.resetSession(any(), any(), any()) } } - @Test - fun `given a conversation, when starting, then should start observing user typing events`() = runTest { - val (arrangement, _) = ConversationMessagesViewModelArrangement() - .withObservableAudioMessagesState(flowOf()) - .withObserveUsersTyping(listOf()) - .arrange() - - coVerify(exactly = 1) { arrangement.observeUsersTypingInConversation(any()) } - } } diff --git a/kalium b/kalium index f231b186e0d..922fbb8fb1d 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit f231b186e0d5e25a8f2f47bebeae92097cc646a6 +Subproject commit 922fbb8fb1d812b12d997dd4ae64f0c9c8e10a0a From 3fbe18d6a2417d912538b7127c3e6ff65b360b63 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 17:56:24 +0200 Subject: [PATCH 35/42] chore: detekt --- .../com/wire/android/ui/home/messagecomposer/MessageComposer.kt | 1 - .../conversations/messages/ConversationMessagesViewModelTest.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index 32faa079d52..eba90550324 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -51,7 +51,6 @@ import com.wire.android.ui.common.bottomsheet.WireModalSheetState import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.MessageComposerViewState -import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionStateHolder import com.wire.android.ui.home.messagecomposer.state.ComposableMessageBundle.AttachmentPickedBundle import com.wire.android.ui.home.messagecomposer.state.ComposableMessageBundle.AudioMessageBundle diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt index ba4f7327bc5..42cbff0814e 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModelTest.kt @@ -187,5 +187,4 @@ class ConversationMessagesViewModelTest { coVerify(exactly = 1) { arrangement.resetSession(any(), any(), any()) } } - } From f28b2e93708ef8d8213a0060380254f18fc5fe13 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 17:57:47 +0200 Subject: [PATCH 36/42] chore: address pr comments --- .../android/ui/home/conversations/UsersTypingIndicator.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index b2c9e75674a..7fb0a68009e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -62,6 +62,8 @@ import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID +const val MAX_PREVIEWS_DISPLAY = 3 + @Composable fun UsersTypingIndicatorForConversation( conversationId: ConversationId, @@ -110,7 +112,7 @@ fun UsersTypingIndicator(usersTyping: List) { @Suppress("MagicNumber") @Composable -private fun UsersTypingAvatarPreviews(usersTyping: List, maxPreviewsDisplay: Int = 3) { +private fun UsersTypingAvatarPreviews(usersTyping: List, maxPreviewsDisplay: Int = MAX_PREVIEWS_DISPLAY) { usersTyping.take(maxPreviewsDisplay).forEachIndexed { index, user -> val isSingleUser = usersTyping.size == 1 || maxPreviewsDisplay == 1 UserProfileAvatar( From 51c78df072269701cc0c1e4145d4e454bf991546 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 17:59:08 +0200 Subject: [PATCH 37/42] chore: kalium ref --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 922fbb8fb1d..5bb647a5249 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 922fbb8fb1d812b12d997dd4ae64f0c9c8e10a0a +Subproject commit 5bb647a52490b16e4995af8d88b554c6d7b252a4 From 3297013797823ff65273c7366d5e0092bfbbfe4a Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 18:17:27 +0200 Subject: [PATCH 38/42] chore: test cov for new viewmodel --- .../typing/TypingIndicatorViewModelTest.kt | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt new file mode 100644 index 00000000000..46592ca4f5a --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt @@ -0,0 +1,71 @@ +/* + * Wire + * Copyright (C) 2023 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.typing + +import androidx.lifecycle.SavedStateHandle +import com.wire.android.config.CoroutineTestExtension +import com.wire.android.config.NavigationTestExtension +import com.wire.android.framework.TestConversation +import com.wire.android.ui.home.conversations.ConversationNavArgs +import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase +import com.wire.android.ui.navArgs +import com.wire.kalium.logic.data.id.ConversationId +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(CoroutineTestExtension::class) +@ExtendWith(NavigationTestExtension::class) +class TypingIndicatorViewModelTest { + + @Test + fun `given a conversation, when start observing, then call the right use case for observation`() = runTest { + // Given + val (arrangement, _) = Arrangement().arrange() + + // Then + coVerify { arrangement.observeUsersTypingInConversation(TestConversation.ID) } + } + + + private class Arrangement { + + @MockK + lateinit var observeUsersTypingInConversation: ObserveUsersTypingInConversationUseCase + + @MockK + lateinit var savedStateHandle: SavedStateHandle + + init { + MockKAnnotations.init(this, relaxUnitFun = true) + every { savedStateHandle.navArgs() } returns TestConversation.ID + every { savedStateHandle.navArgs() } returns ConversationNavArgs(conversationId = TestConversation.ID) + coEvery { observeUsersTypingInConversation(eq(TestConversation.ID)) } returns flowOf(emptyList()) + } + + fun arrange() = this to TypingIndicatorViewModel(observeUsersTypingInConversation, savedStateHandle) + } +} From a04f6a03e4292a3a0b49dad3e7dc8636c3519589 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 18:22:14 +0200 Subject: [PATCH 39/42] chore: test cov for new viewmodel --- .../typing/TypingIndicatorViewModelTest.kt | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt index 46592ca4f5a..7c98859e163 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt @@ -18,13 +18,17 @@ package com.wire.android.ui.home.conversations.typing import androidx.lifecycle.SavedStateHandle +import app.cash.turbine.test import com.wire.android.config.CoroutineTestExtension import com.wire.android.config.NavigationTestExtension import com.wire.android.framework.TestConversation import com.wire.android.ui.home.conversations.ConversationNavArgs +import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant +import com.wire.android.ui.home.conversations.typing.TypingIndicatorViewModelTest.Arrangement.Companion.expectedUIParticipant import com.wire.android.ui.home.conversations.usecase.ObserveUsersTypingInConversationUseCase import com.wire.android.ui.navArgs import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify @@ -33,6 +37,7 @@ import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest +import org.amshove.kluent.internal.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -44,10 +49,17 @@ class TypingIndicatorViewModelTest { @Test fun `given a conversation, when start observing, then call the right use case for observation`() = runTest { // Given - val (arrangement, _) = Arrangement().arrange() + val (arrangement, _) = Arrangement() + .withParticipantsTyping(listOf(expectedUIParticipant)) + .arrange() // Then coVerify { arrangement.observeUsersTypingInConversation(TestConversation.ID) } + arrangement.observeUsersTypingInConversation(TestConversation.ID).test { + val participants = awaitItem() + assertEquals(expectedUIParticipant, participants.first()) + awaitComplete() + } } @@ -66,6 +78,20 @@ class TypingIndicatorViewModelTest { coEvery { observeUsersTypingInConversation(eq(TestConversation.ID)) } returns flowOf(emptyList()) } + fun withParticipantsTyping(usersTyping: List = emptyList()) = apply { + coEvery { observeUsersTypingInConversation(eq(TestConversation.ID)) } returns flowOf(usersTyping) + } + fun arrange() = this to TypingIndicatorViewModel(observeUsersTypingInConversation, savedStateHandle) + + companion object { + val expectedUIParticipant = UIParticipant( + id = UserId("id", "domain"), + name = "name", + handle = "handle", + isSelf = false, + isService = false + ) + } } } From 0b84210c5ecdd28b2e9cd1153b9c3349abd747aa Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 2 Oct 2023 18:22:45 +0200 Subject: [PATCH 40/42] chore: test cov for new viewmodel --- .../ui/home/conversations/typing/TypingIndicatorViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt index 7c98859e163..32e7a60e083 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/typing/TypingIndicatorViewModelTest.kt @@ -62,7 +62,6 @@ class TypingIndicatorViewModelTest { } } - private class Arrangement { @MockK From 48eba61d043709ac7727b4ecc6f1d6d525de0655 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 3 Oct 2023 09:23:07 +0200 Subject: [PATCH 41/42] feat: not show status indicator in small preview --- .../com/wire/android/ui/common/UserProfileAvatar.kt | 11 +++++++---- .../ui/home/conversations/UsersTypingIndicator.kt | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index f1d63e2a83c..2d6b17b2e5c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -62,6 +62,7 @@ fun UserProfileAvatar( clickable: Clickable? = null, showPlaceholderIfNoAsset: Boolean = true, withCrossfadeAnimation: Boolean = false, + showStatusIndicator:Boolean = true ) { Box( contentAlignment = Alignment.Center, @@ -82,10 +83,12 @@ fun UserProfileAvatar( .testTag("User avatar"), contentScale = ContentScale.Crop ) - UserStatusIndicator( - status = avatarData.availabilityStatus, - modifier = Modifier.align(Alignment.BottomEnd) - ) + if (showStatusIndicator) { + UserStatusIndicator( + status = avatarData.availabilityStatus, + modifier = Modifier.align(Alignment.BottomEnd) + ) + } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 7fb0a68009e..e172d3fd3b1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -119,6 +119,7 @@ private fun UsersTypingAvatarPreviews(usersTyping: List, maxPrevi avatarData = user.avatarData, size = dimensions().spacing16x, padding = dimensions().spacing2x, + showStatusIndicator = false, modifier = if (isSingleUser) Modifier else { Modifier.offset( From db395c01c953997dfa45b94713c73aeea4ff034a Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 3 Oct 2023 09:24:26 +0200 Subject: [PATCH 42/42] feat: not show status indicator in small preview --- .../main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index 2d6b17b2e5c..f4f80c3b2d2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -62,7 +62,7 @@ fun UserProfileAvatar( clickable: Clickable? = null, showPlaceholderIfNoAsset: Boolean = true, withCrossfadeAnimation: Boolean = false, - showStatusIndicator:Boolean = true + showStatusIndicator: Boolean = true ) { Box( contentAlignment = Alignment.Center,