Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: epic typing indicator sender (WPB-4590) #2315

Merged
merged 21 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.conversation.AddMemberToConversationUseCase
import com.wire.kalium.logic.feature.conversation.AddServiceToConversationUseCase
import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase
import com.wire.kalium.logic.feature.conversation.ClearUsersTypingEventsUseCase
import com.wire.kalium.logic.feature.conversation.ConversationScope
import com.wire.kalium.logic.feature.conversation.CreateGroupConversationUseCase
import com.wire.kalium.logic.feature.conversation.GetConversationUnreadEventsCountUseCase
Expand All @@ -42,6 +43,7 @@ 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
import com.wire.kalium.logic.feature.conversation.SendTypingEventUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationAccessRoleUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationArchivedStatusUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleUseCase
Expand Down Expand Up @@ -241,4 +243,13 @@ class ConversationModule {
@Provides
fun provideObserveUsersTypingUseCase(conversationScope: ConversationScope): ObserveUsersTypingUseCase =
conversationScope.observeUsersTyping

@ViewModelScoped
@Provides
fun provideSendTypingEventUseCase(conversationScope: ConversationScope): SendTypingEventUseCase = conversationScope.sendTypingEvent

@ViewModelScoped
@Provides
fun provideClearTypingEventsUseCase(conversationScope: ConversationScope): ClearUsersTypingEventsUseCase =
conversationScope.clearUsersTypingEvents
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import com.wire.android.util.ui.UIText
import com.wire.android.util.ui.openDownloadFolder
import com.wire.kalium.logic.NetworkFailure
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.call.usecase.ConferenceCallingResult
Expand Down Expand Up @@ -347,6 +348,7 @@ fun ConversationScreen(
}
}
},
onTypingEvent = messageComposerViewModel::sendTypingEvent
)
DeleteMessageDialog(
state = messageComposerViewModel.deleteMessageDialogsState,
Expand Down Expand Up @@ -506,6 +508,7 @@ private fun ConversationScreen(
conversationScreenState: ConversationScreenState,
messageComposerStateHolder: MessageComposerStateHolder,
onLinkClick: (String) -> Unit,
onTypingEvent: (TypingIndicatorMode) -> Unit
) {
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current
Expand Down Expand Up @@ -609,7 +612,8 @@ private fun ConversationScreen(
onClearMentionSearchResult = onClearMentionSearchResult,
tempWritableImageUri = tempWritableImageUri,
tempWritableVideoUri = tempWritableVideoUri,
onLinkClick = onLinkClick
onLinkClick = onLinkClick,
onTypingEvent = onTypingEvent
)
}
}
Expand Down Expand Up @@ -652,6 +656,7 @@ private fun ConversationScreenContent(
tempWritableImageUri: Uri?,
tempWritableVideoUri: Uri?,
onLinkClick: (String) -> Unit,
onTypingEvent: (TypingIndicatorMode) -> Unit
) {
val lazyPagingMessages = messages.collectAsLazyPagingItems()

Expand Down Expand Up @@ -690,6 +695,7 @@ private fun ConversationScreenContent(
onSendMessageBundle = onSendMessage,
tempWritableVideoUri = tempWritableVideoUri,
tempWritableImageUri = tempWritableImageUri,
onTypingEvent = onTypingEvent
)

// TODO: uncomment when we have the "scroll to bottom" button implemented
Expand Down Expand Up @@ -895,6 +901,7 @@ fun PreviewConversationScreen() {
onClearMentionSearchResult = {},
conversationScreenState = conversationScreenState,
messageComposerStateHolder = messageComposerStateHolder,
onLinkClick = { _ -> }
onLinkClick = { _ -> },
onTypingEvent = {}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package com.wire.android.ui.home.conversations

import android.net.Uri
import android.webkit.URLUtil
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
Expand Down Expand Up @@ -51,6 +50,7 @@ import com.wire.android.util.getAudioLengthInMs
import com.wire.kalium.logic.configuration.FileSharingStatus
import com.wire.kalium.logic.data.asset.AttachmentType
import com.wire.kalium.logic.data.asset.KaliumFileSystem
import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCase
Expand All @@ -59,6 +59,7 @@ import com.wire.kalium.logic.feature.conversation.InteractionAvailability
import com.wire.kalium.logic.feature.conversation.IsInteractionAvailableResult
import com.wire.kalium.logic.feature.conversation.MembersToMentionUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationInteractionAvailabilityUseCase
import com.wire.kalium.logic.feature.conversation.SendTypingEventUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationReadDateUseCase
import com.wire.kalium.logic.feature.message.DeleteMessageUseCase
import com.wire.kalium.logic.feature.message.RetryFailedMessageUseCase
Expand Down Expand Up @@ -102,6 +103,7 @@ class MessageComposerViewModel @Inject constructor(
private val enqueueMessageSelfDeletion: EnqueueMessageSelfDeletionUseCase,
private val observeSelfDeletingMessages: ObserveSelfDeletionTimerSettingsForConversationUseCase,
private val persistNewSelfDeletingStatus: PersistNewSelfDeletionTimerUseCase,
private val sendTypingEvent: SendTypingEventUseCase,
private val pingRinger: PingRinger,
private val imageUtil: ImageUtil,
private val fileManager: FileManager
Expand Down Expand Up @@ -201,6 +203,7 @@ class MessageComposerViewModel @Inject constructor(
mentions = newMentions.map { it.intoMessageMention() },
)
}
sendTypingEvent(conversationId, TypingIndicatorMode.STOPPED)
}

is ComposableMessageBundle.AttachmentPickedBundle -> {
Expand All @@ -225,6 +228,7 @@ class MessageComposerViewModel @Inject constructor(
quotedMessageId = quotedMessageId
)
}
sendTypingEvent(conversationId, TypingIndicatorMode.STOPPED)
}

Ping -> {
Expand Down Expand Up @@ -364,8 +368,6 @@ class MessageComposerViewModel @Inject constructor(
}
}

fun isLinkValid(link: String) = URLUtil.isValidUrl(link)

fun clearMentionSearchResult() {
messageComposerViewState.value =
messageComposerViewState.value.copy(mentionSearchResult = emptyList())
Expand Down Expand Up @@ -440,6 +442,12 @@ class MessageComposerViewModel @Inject constructor(
invalidLinkDialogState = InvalidLinkDialogState.Hidden
}

fun sendTypingEvent(typingIndicatorMode: TypingIndicatorMode) {
viewModelScope.launch {
sendTypingEvent(conversationId, typingIndicatorMode)
}
}

companion object {
private const val sizeOf1MB = 1024 * 1024
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.wire.android.ui.home.conversations

import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.InfiniteTransition
import androidx.compose.animation.core.RepeatMode
Expand All @@ -25,6 +26,7 @@ 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.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
Expand All @@ -44,6 +46,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
Expand Down Expand Up @@ -113,20 +116,15 @@ fun UsersTypingIndicator(usersTyping: List<UIParticipant>) {
@Suppress("MagicNumber")
@Composable
private fun UsersTypingAvatarPreviews(usersTyping: List<UIParticipant>, maxPreviewsDisplay: Int = MAX_PREVIEWS_DISPLAY) {
usersTyping.take(maxPreviewsDisplay).forEachIndexed { index, user ->
val isSingleUser = usersTyping.size == 1 || maxPreviewsDisplay == 1
UserProfileAvatar(
avatarData = user.avatarData,
size = dimensions().spacing16x,
padding = dimensions().spacing2x,
showStatusIndicator = false,
modifier = if (isSingleUser) Modifier
else {
Modifier.offset(
x = if (index == 0) dimensions().spacing8x else -(dimensions().spacing6x)
)
}
)
Row(horizontalArrangement = Arrangement.spacedBy((-14).dp)) {
usersTyping.take(maxPreviewsDisplay).forEach { user ->
UserProfileAvatar(
avatarData = user.avatarData,
size = dimensions().spacing16x,
padding = dimensions().spacing2x,
showStatusIndicator = false,
)
}
}
}

Expand All @@ -137,20 +135,31 @@ private fun HorizontalBouncingWritingPen(
) {
Row(modifier = Modifier.fillMaxHeight()) {
val position by infiniteTransition.animateFloat(
initialValue = -5f, targetValue = -1f,
initialValue = -10f, targetValue = -2f,
animationSpec = infiniteRepeatable(
animation = tween(1_000, easing = FastOutSlowInEasing),
animation = tween(1_500, easing = FastOutSlowInEasing),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to extract these values to some Dimens variable for easier concurrent modification?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll take a look on how we are doing for some animations,
I tried to look for dimensions that fit, but didn't find anything so far

repeatMode = RepeatMode.Reverse
),
label = infiniteTransition.label
)

val iconAlpha: Float by infiniteTransition.animateFloat(
initialValue = 0.1f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1_500, easing = FastOutLinearInEasing),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same than above

repeatMode = RepeatMode.Reverse,
),
label = infiniteTransition.label
)

Icon(
imageVector = Icons.Default.MoreHoriz,
contentDescription = null,
tint = colorsScheme().secondaryText,
modifier = Modifier
.size(dimensions().spacing12x)
.alpha(iconAlpha)
.offset(y = -dimensions().spacing2x)
.align(Alignment.Bottom)
)
Expand Down Expand Up @@ -237,6 +246,21 @@ fun PreviewUsersTypingMoreThanOne() {
readReceiptDate = null,
botService = null,
isDefederated = false
),
UIParticipant(
id = QualifiedID("alice", "wire.com"),
name = "Patty",
handle = "patty",
isSelf = false,
isService = false,
avatarData = UserAvatarData(),
membership = Membership.None,
connectionState = null,
unavailable = false,
isDeleted = false,
readReceiptDate = null,
botService = null,
isDefederated = false
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.feature.asset.MessageAssetResult
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCase
import com.wire.kalium.logic.feature.conversation.ClearUsersTypingEventsUseCase
import com.wire.kalium.logic.feature.conversation.GetConversationUnreadEventsCountUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase
import com.wire.kalium.logic.feature.message.GetMessageByIdUseCase
Expand Down Expand Up @@ -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 clearUsersTypingEvents: ClearUsersTypingEventsUseCase
) : SavedStateViewModel(savedStateHandle) {

private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs()
Expand All @@ -97,11 +99,16 @@ class ConversationMessagesViewModel @Inject constructor(
val infoMessage = _infoMessage.asSharedFlow()

init {
clearOrphanedTypingEvents()
loadPaginatedMessages()
loadLastMessageInstant()
observeAudioPlayerState()
}

private fun clearOrphanedTypingEvents() {
viewModelScope.launch { clearUsersTypingEvents() }
}

private fun observeAudioPlayerState() {
viewModelScope.launch {
conversationAudioMessagePlayer.observableAudioMessagesState.collect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ 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.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.util.isPositiveNotNull

Expand All @@ -67,6 +68,7 @@ fun EnabledMessageComposer(
messageListContent: @Composable () -> Unit,
onChangeSelfDeletionClicked: () -> Unit,
onSearchMentionQueryChanged: (String) -> Unit,
onTypingEvent: (Conversation.TypingIndicatorMode) -> Unit,
onSendButtonClicked: () -> Unit,
onAttachmentPicked: (UriAsset) -> Unit,
onAudioRecorded: (UriAsset) -> Unit,
Expand Down Expand Up @@ -137,12 +139,6 @@ fun EnabledMessageComposer(
modifier = fillRemainingSpaceOrWrapContent
.fillMaxWidth()
) {
Box(Modifier.wrapContentSize()) {
SecurityClassificationBannerForConversation(
conversationId = conversationId
)
}

Column(
modifier = Modifier
.background(color = colorsScheme().backgroundVariant)
Expand All @@ -152,6 +148,12 @@ fun EnabledMessageComposer(
UsersTypingIndicatorForConversation(conversationId = conversationId)
}

Box(Modifier.wrapContentSize()) {
SecurityClassificationBannerForConversation(
conversationId = conversationId
)
}

if (additionalOptionStateHolder.additionalOptionsSubMenuState != AdditionalOptionSubMenuState.RecordAudio) {
Box(fillRemainingSpaceOrWrapContent) {
var currentSelectedLineIndex by remember { mutableStateOf(0) }
Expand All @@ -170,7 +172,8 @@ fun EnabledMessageComposer(
messageCompositionHolder.setMessageText(
messageTextFieldValue = it,
onSearchMentionQueryChanged = onSearchMentionQueryChanged,
onClearMentionSearchResult = onClearMentionSearchResult
onClearMentionSearchResult = onClearMentionSearchResult,
onTypingEvent = onTypingEvent
)
},
onChangeSelfDeletionClicked = onChangeSelfDeletionClicked,
Expand Down Expand Up @@ -220,7 +223,8 @@ fun EnabledMessageComposer(
onMentionButtonClicked = {
messageCompositionHolder.startMention(
onSearchMentionQueryChanged,
onClearMentionSearchResult
onClearMentionSearchResult,
onTypingEvent
)
},
onOnSelfDeletingOptionClicked = {
Expand Down