From a9025332473b37a962c1f09aae69e7d86708aa56 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 10 Nov 2025 16:29:30 +0100 Subject: [PATCH 1/3] Rename MessageComposerEvents to MessageComposerEvent --- .../features/messages/impl/MessagesNode.kt | 4 +- .../messages/impl/MessagesPresenter.kt | 10 +- .../features/messages/impl/MessagesView.kt | 4 +- .../messagecomposer/AttachmentsBottomSheet.kt | 16 +-- ...poserEvents.kt => MessageComposerEvent.kt} | 30 ++-- .../MessageComposerPresenter.kt | 46 +++--- .../messagecomposer/MessageComposerState.kt | 2 +- .../MessageComposerStateProvider.kt | 2 +- .../messagecomposer/MessageComposerView.kt | 16 +-- .../impl/threads/ThreadedMessagesNode.kt | 4 +- .../messages/impl/MessagesPresenterTest.kt | 38 ++--- .../MessageComposerPresenterTest.kt | 134 +++++++++--------- 12 files changed, 153 insertions(+), 153 deletions(-) rename features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/{MessageComposerEvents.kt => MessageComposerEvent.kt} (70%) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index ef20ccf398..168a4d6b3d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -33,7 +33,7 @@ import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerR import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents @@ -242,7 +242,7 @@ class MessagesNode( OnLifecycleEvent { _, event -> when (event) { - Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvents.SaveDraft) + Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvent.SaveDraft) else -> Unit } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 05d557d0e4..e912722c6d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -34,7 +34,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.link.LinkState -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.MarkAsFullyRead @@ -479,7 +479,7 @@ class MessagesPresenter( }.orEmpty(), ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } } @@ -494,7 +494,7 @@ class MessagesPresenter( content = "", ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } @@ -507,7 +507,7 @@ class MessagesPresenter( content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(), ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } @@ -524,7 +524,7 @@ class MessagesPresenter( hideImage = timelineProtectionState.hideMediaContent(targetEvent.eventId), ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 2f77972aea..b3deafc2eb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -52,7 +52,7 @@ import io.element.android.features.messages.impl.link.LinkEvents import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet import io.element.android.features.messages.impl.messagecomposer.DisabledComposerView -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerView import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsPickerView import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState @@ -256,7 +256,7 @@ fun MessagesView( roomAvatarData = state.roomAvatar, suggestions = state.composerState.suggestions, onSelectSuggestion = { - state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it)) + state.composerState.eventSink(MessageComposerEvent.InsertSuggestion(it)) } ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt index 567a63c0ef..1fdb61f484 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt @@ -64,7 +64,7 @@ internal fun AttachmentsBottomSheet( // Send 'DismissAttachmentMenu' event when the bottomsheet was just hidden LaunchedEffect(isVisible) { if (!isVisible) { - state.eventSink(MessageComposerEvents.DismissAttachmentMenu) + state.eventSink(MessageComposerEvent.DismissAttachmentMenu) } } @@ -99,25 +99,25 @@ private fun AttachmentSourcePickerMenu( .imePadding() ) { ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.TakePhoto())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) }, style = ListItemStyle.Primary, ) ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.VideoCall())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) }, style = ListItemStyle.Primary, ) ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Image())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) }, style = ListItemStyle.Primary, ) ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Attachment())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_files)) }, style = ListItemStyle.Primary, @@ -125,7 +125,7 @@ private fun AttachmentSourcePickerMenu( if (state.canShareLocation) { ListItem( modifier = Modifier.clickable { - state.eventSink(MessageComposerEvents.PickAttachmentSource.Location) + state.eventSink(MessageComposerEvent.PickAttachmentSource.Location) onSendLocationClick() }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.LocationPin())), @@ -135,7 +135,7 @@ private fun AttachmentSourcePickerMenu( } ListItem( modifier = Modifier.clickable { - state.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) + state.eventSink(MessageComposerEvent.PickAttachmentSource.Poll) onCreatePollClick() }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Polls())), @@ -144,7 +144,7 @@ private fun AttachmentSourcePickerMenu( ) if (enableTextFormatting) { ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.ToggleTextFormatting(enabled = true)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.TextFormatting())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) }, style = ListItemStyle.Primary, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvent.kt similarity index 70% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvent.kt index 3630cc210d..ae82c60f2a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvent.kt @@ -13,15 +13,15 @@ import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion -sealed interface MessageComposerEvents { - data object ToggleFullScreenState : MessageComposerEvents - data object SendMessage : MessageComposerEvents - data class SendUri(val uri: Uri) : MessageComposerEvents - data object CloseSpecialMode : MessageComposerEvents - data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvents - data object AddAttachment : MessageComposerEvents - data object DismissAttachmentMenu : MessageComposerEvents - sealed interface PickAttachmentSource : MessageComposerEvents { +sealed interface MessageComposerEvent { + data object ToggleFullScreenState : MessageComposerEvent + data object SendMessage : MessageComposerEvent + data class SendUri(val uri: Uri) : MessageComposerEvent + data object CloseSpecialMode : MessageComposerEvent + data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvent + data object AddAttachment : MessageComposerEvent + data object DismissAttachmentMenu : MessageComposerEvent + sealed interface PickAttachmentSource : MessageComposerEvent { data object FromGallery : PickAttachmentSource data object FromFiles : PickAttachmentSource data object PhotoFromCamera : PickAttachmentSource @@ -30,10 +30,10 @@ sealed interface MessageComposerEvents { data object Poll : PickAttachmentSource } - data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvents - data class Error(val error: Throwable) : MessageComposerEvents - data class TypingNotice(val isTyping: Boolean) : MessageComposerEvents - data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents - data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvents - data object SaveDraft : MessageComposerEvents + data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvent + data class Error(val error: Throwable) : MessageComposerEvent + data class TypingNotice(val isTyping: Boolean) : MessageComposerEvent + data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvent + data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvent + data object SaveDraft : MessageComposerEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 7e01572a42..04d7146ad9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -132,7 +132,7 @@ class MessageComposerPresenter( private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode()) private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) - private var pendingEvent: MessageComposerEvents? = null + private var pendingEvent: MessageComposerEvent? = null private val suggestionSearchTrigger = MutableStateFlow(null) // Used to disable some UI related elements in tests @@ -186,8 +186,8 @@ class MessageComposerPresenter( LaunchedEffect(cameraPermissionState.permissionGranted) { if (cameraPermissionState.permissionGranted) { when (pendingEvent) { - is MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> cameraPhotoPicker.launch() - is MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> cameraVideoPicker.launch() + is MessageComposerEvent.PickAttachmentSource.PhotoFromCamera -> cameraPhotoPicker.launch() + is MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> cameraVideoPicker.launch() else -> Unit } pendingEvent = null @@ -228,10 +228,10 @@ class MessageComposerPresenter( } } - fun handleEvent(event: MessageComposerEvents) { + fun handleEvent(event: MessageComposerEvent) { when (event) { - MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value - MessageComposerEvents.CloseSpecialMode -> { + MessageComposerEvent.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value + MessageComposerEvent.CloseSpecialMode -> { if (messageComposerContext.composerMode.isEditing) { localCoroutineScope.launch { resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = true) @@ -240,13 +240,13 @@ class MessageComposerPresenter( messageComposerContext.composerMode = MessageComposerMode.Normal } } - is MessageComposerEvents.SendMessage -> { + is MessageComposerEvent.SendMessage -> { sessionCoroutineScope.sendMessage( markdownTextEditorState = markdownTextEditorState, richTextEditorState = richTextEditorState, ) } - is MessageComposerEvents.SendUri -> { + is MessageComposerEvent.SendUri -> { val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId sessionCoroutineScope.sendAttachment( attachment = Attachment.Media( @@ -263,22 +263,22 @@ class MessageComposerPresenter( // Reset composer since the attachment has been sent messageComposerContext.composerMode = MessageComposerMode.Normal } - is MessageComposerEvents.SetMode -> { + is MessageComposerEvent.SetMode -> { localCoroutineScope.setMode(event.composerMode, markdownTextEditorState, richTextEditorState) } - MessageComposerEvents.AddAttachment -> localCoroutineScope.launch { + MessageComposerEvent.AddAttachment -> localCoroutineScope.launch { showAttachmentSourcePicker = true } - MessageComposerEvents.DismissAttachmentMenu -> showAttachmentSourcePicker = false - MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launch { + MessageComposerEvent.DismissAttachmentMenu -> showAttachmentSourcePicker = false + MessageComposerEvent.PickAttachmentSource.FromGallery -> localCoroutineScope.launch { showAttachmentSourcePicker = false galleryMediaPicker.launch() } - MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launch { + MessageComposerEvent.PickAttachmentSource.FromFiles -> localCoroutineScope.launch { showAttachmentSourcePicker = false filesPicker.launch() } - MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch { + MessageComposerEvent.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch { showAttachmentSourcePicker = false if (cameraPermissionState.permissionGranted) { cameraPhotoPicker.launch() @@ -287,7 +287,7 @@ class MessageComposerPresenter( cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) } } - MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch { + MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch { showAttachmentSourcePicker = false if (cameraPermissionState.permissionGranted) { cameraVideoPicker.launch() @@ -296,32 +296,32 @@ class MessageComposerPresenter( cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) } } - MessageComposerEvents.PickAttachmentSource.Location -> { + MessageComposerEvent.PickAttachmentSource.Location -> { showAttachmentSourcePicker = false // Navigation to the location picker screen is done at the view layer } - MessageComposerEvents.PickAttachmentSource.Poll -> { + MessageComposerEvent.PickAttachmentSource.Poll -> { showAttachmentSourcePicker = false // Navigation to the create poll screen is done at the view layer } - is MessageComposerEvents.ToggleTextFormatting -> { + is MessageComposerEvent.ToggleTextFormatting -> { showAttachmentSourcePicker = false localCoroutineScope.toggleTextFormatting(event.enabled, markdownTextEditorState, richTextEditorState) } - is MessageComposerEvents.Error -> { + is MessageComposerEvent.Error -> { analyticsService.trackError(event.error) } - is MessageComposerEvents.TypingNotice -> { + is MessageComposerEvent.TypingNotice -> { if (sendTypingNotifications) { localCoroutineScope.launch { room.typingNotice(event.isTyping) } } } - is MessageComposerEvents.SuggestionReceived -> { + is MessageComposerEvent.SuggestionReceived -> { suggestionSearchTrigger.value = event.suggestion } - is MessageComposerEvents.InsertSuggestion -> { + is MessageComposerEvent.InsertSuggestion -> { localCoroutineScope.launch { if (showTextFormatting) { when (val suggestion = event.resolvedSuggestion) { @@ -348,7 +348,7 @@ class MessageComposerPresenter( } } } - MessageComposerEvents.SaveDraft -> { + MessageComposerEvent.SaveDraft -> { val draft = createDraftFromState(markdownTextEditorState, richTextEditorState) sessionCoroutineScope.updateDraft(draft, isVolatile = false) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index 47fe84504c..424e8c07b9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -26,5 +26,5 @@ data class MessageComposerState( val suggestions: ImmutableList, val resolveMentionDisplay: (String, String) -> TextDisplay, val resolveAtRoomMentionDisplay: () -> TextDisplay, - val eventSink: (MessageComposerEvents) -> Unit, + val eventSink: (MessageComposerEvent) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index dc044b13ad..a06bf30dad 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -32,7 +32,7 @@ fun aMessageComposerState( showAttachmentSourcePicker: Boolean = false, canShareLocation: Boolean = true, suggestions: ImmutableList = persistentListOf(), - eventSink: (MessageComposerEvents) -> Unit = {}, + eventSink: (MessageComposerEvent) -> Unit = {}, ) = MessageComposerState( textEditorState = textEditorState, isFullScreen = isFullScreen, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 3c294c881e..292a6b39a8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -38,36 +38,36 @@ internal fun MessageComposerView( ) { val view = LocalView.current fun sendMessage() { - state.eventSink(MessageComposerEvents.SendMessage) + state.eventSink(MessageComposerEvent.SendMessage) } fun sendUri(uri: Uri) { - state.eventSink(MessageComposerEvents.SendUri(uri)) + state.eventSink(MessageComposerEvent.SendUri(uri)) } fun onAddAttachment() { - state.eventSink(MessageComposerEvents.AddAttachment) + state.eventSink(MessageComposerEvent.AddAttachment) } fun onCloseSpecialMode() { - state.eventSink(MessageComposerEvents.CloseSpecialMode) + state.eventSink(MessageComposerEvent.CloseSpecialMode) } fun onDismissTextFormatting() { view.clearFocus() - state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = false)) + state.eventSink(MessageComposerEvent.ToggleTextFormatting(enabled = false)) } fun onSuggestionReceived(suggestion: Suggestion?) { - state.eventSink(MessageComposerEvents.SuggestionReceived(suggestion)) + state.eventSink(MessageComposerEvent.SuggestionReceived(suggestion)) } fun onError(error: Throwable) { - state.eventSink(MessageComposerEvents.Error(error)) + state.eventSink(MessageComposerEvent.Error(error)) } fun onTyping(typing: Boolean) { - state.eventSink(MessageComposerEvents.TypingNotice(typing)) + state.eventSink(MessageComposerEvent.TypingNotice(typing)) } val coroutineScope = rememberCoroutineScope() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index 49e6903b06..10922ca5e7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -33,7 +33,7 @@ import io.element.android.features.messages.impl.MessagesView import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents @@ -234,7 +234,7 @@ class ThreadedMessagesNode( val state = presenter.present() OnLifecycleEvent { _, event -> when (event) { - Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvents.SaveDraft) + Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvent.SaveDraft) else -> Unit } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index a3465f570b..4562214aa4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -20,7 +20,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.link.aLinkState -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState @@ -304,7 +304,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -313,7 +313,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent())) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -335,7 +335,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to an image media message`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -364,7 +364,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -376,7 +376,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a video media message`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -406,7 +406,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -418,7 +418,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a file media message`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -441,7 +441,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -453,7 +453,7 @@ class MessagesPresenterTest { @Test fun `present - handle action edit`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -462,7 +462,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent())) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Edit( eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = (aMessageEvent().content as TimelineItemTextContent).body @@ -892,7 +892,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a poll`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -904,7 +904,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll)) skipItems(1) composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -1009,7 +1009,7 @@ class MessagesPresenterTest { caption = A_CAPTION, ) ) - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -1018,7 +1018,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditCaption, messageEvent)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.EditCaption( eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = A_CAPTION, @@ -1030,7 +1030,7 @@ class MessagesPresenterTest { @Test fun `present - handle action add caption`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -1044,7 +1044,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.AddCaption, messageEvent)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.EditCaption( eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = "", @@ -1231,7 +1231,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply in a thread with threads disabled`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( featureFlagService = FakeFeatureFlagService( initialState = mapOf(FeatureFlags.Threads.key to false) @@ -1243,7 +1243,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent())) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 8a4f5f7bec..e9ebe0ca2e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -154,10 +154,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) + initialState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState) val fullscreenState = awaitItem() assertThat(fullscreenState.isFullScreen).isTrue() - fullscreenState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) + fullscreenState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState) val notFullscreenState = awaitItem() assertThat(notFullscreenState.isFullScreen).isFalse() } @@ -196,7 +196,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = anEditMode(message = ANOTHER_MESSAGE) - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) @@ -238,7 +238,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = anEditCaptionMode(caption = A_CAPTION) - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo(A_CAPTION) @@ -280,11 +280,11 @@ class MessageComposerPresenterTest { presenter.test { var state = awaitFirstItem() val mode = anEditCaptionMode(caption = A_CAPTION) - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_CAPTION) - state.eventSink.invoke(MessageComposerEvents.SendMessage) + state.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo("") waitForPredicate { analyticsService.capturedEvents.size == 1 } @@ -321,13 +321,13 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val editMode = anEditMode(message = ANOTHER_MESSAGE) - state.eventSink.invoke(MessageComposerEvents.SetMode(editMode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(editMode)) state = awaitItem() assertThat(state.mode).isEqualTo(editMode) assertThat(state.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) val replyMode = aReplyMode() - state.eventSink.invoke(MessageComposerEvents.SetMode(replyMode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(replyMode)) state = awaitItem() assertThat(state.mode).isEqualTo(replyMode) assertThat(state.textEditorState.messageHtml()).isEmpty() @@ -350,7 +350,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = aReplyMode() - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo("") @@ -366,7 +366,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = aReplyMode() - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) state.textEditorState.setHtml(A_REPLY) @@ -395,7 +395,7 @@ class MessageComposerPresenterTest { initialState.textEditorState.setHtml(A_MESSAGE) val withMessageState = awaitItem() assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) - withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") waitForPredicate { analyticsService.capturedEvents.size == 1 } @@ -432,7 +432,7 @@ class MessageComposerPresenterTest { val withMessageState = awaitItem() assertThat(withMessageState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(withMessageState.textEditorState.messageHtml()).isNull() - withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo("") waitForPredicate { analyticsService.capturedEvents.size == 1 } @@ -469,14 +469,14 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = anEditMode() - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withEditedMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) skipItems(1) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -524,14 +524,14 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = anEditMode() - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withEditedMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) skipItems(1) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -579,14 +579,14 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = anEditMode(eventOrTransactionId = A_TRANSACTION_ID.toEventOrTransactionId()) - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withEditedMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) skipItems(1) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -629,13 +629,13 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = aReplyMode() - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo("") state.textEditorState.setHtml(A_REPLY) assertThat(state.textEditorState.messageHtml()).isEqualTo(A_REPLY) - state.eventSink.invoke(MessageComposerEvents.SendMessage) + state.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -664,7 +664,7 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.showAttachmentSourcePicker).isFalse() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) assertThat(awaitItem().showAttachmentSourcePicker).isTrue() } } @@ -676,10 +676,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) skipItems(1) - initialState.eventSink(MessageComposerEvents.DismissAttachmentMenu) + initialState.eventSink(MessageComposerEvent.DismissAttachmentMenu) assertThat(awaitItem().showAttachmentSourcePicker).isFalse() } } @@ -719,7 +719,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -760,7 +760,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -776,7 +776,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) // No crashes here, otherwise it fails } } @@ -798,7 +798,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -813,10 +813,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) val attachmentOpenState = awaitItem() assertThat(attachmentOpenState.showAttachmentSourcePicker).isTrue() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.Poll) val finalState = awaitItem() assertThat(finalState.showAttachmentSourcePicker).isFalse() } @@ -832,10 +832,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) val attachmentOpenState = awaitItem() assertThat(attachmentOpenState.showAttachmentSourcePicker).isTrue() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.Location) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.Location) val finalState = awaitItem() assertThat(finalState.showAttachmentSourcePicker).isFalse() } @@ -860,7 +860,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -884,7 +884,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) permissionPresenter.setPermissionGranted() onPreviewAttachmentLambda.assertions().isCalledOnce() cancelAndIgnoreRemainingEvents() @@ -910,7 +910,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -934,7 +934,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) val permissionState = awaitItem() assertThat(permissionState.showAttachmentSourcePicker).isFalse() permissionPresenter.setPermissionGranted() @@ -951,7 +951,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.Error(testException)) + initialState.eventSink(MessageComposerEvent.Error(testException)) assertThat(analyticsService.trackedErrors).containsExactly(testException) } } @@ -964,10 +964,10 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.showTextFormatting).isFalse() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) val composerOptions = awaitItem() assertThat(composerOptions.showAttachmentSourcePicker).isTrue() - composerOptions.eventSink(MessageComposerEvents.ToggleTextFormatting(true)) + composerOptions.eventSink(MessageComposerEvent.ToggleTextFormatting(true)) skipItems(2) // composer options closed val showTextFormatting = awaitItem() assertThat(showTextFormatting.showAttachmentSourcePicker).isFalse() @@ -976,7 +976,7 @@ class MessageComposerPresenterTest { Interaction(index = null, interactionType = null, name = Interaction.Name.MobileRoomComposerFormattingEnabled) ) analyticsService.capturedEvents.clear() - showTextFormatting.eventSink(MessageComposerEvents.ToggleTextFormatting(false)) + showTextFormatting.eventSink(MessageComposerEvent.ToggleTextFormatting(false)) skipItems(1) val finished = awaitItem() assertThat(finished.showTextFormatting).isFalse() @@ -1009,33 +1009,33 @@ class MessageComposerPresenterTest { val initialState = awaitItem() // A null suggestion (no suggestion was received) returns nothing - initialState.eventSink(MessageComposerEvents.SuggestionReceived(null)) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(null)) assertThat(awaitItem().suggestions).isEmpty() // An empty suggestion returns the room and joined members that are not the current user - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) assertThat(awaitItem().suggestions) .containsExactly(ResolvedSuggestion.AtRoom, ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david)) // A suggestion containing a part of "room" will also return the room mention - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "roo"))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "roo"))) assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.AtRoom) // A non-empty suggestion will return those joined members whose user id matches it - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "bob"))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "bob"))) assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.Member(bob)) // A non-empty suggestion will return those joined members whose display name matches it - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "dave"))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "dave"))) assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.Member(david)) // If the suggestion isn't a mention, no suggestions are returned - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Command, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Command, ""))) assertThat(awaitItem().suggestions).isEmpty() // If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned canUserTriggerRoomNotificationResult = false - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) assertThat(awaitItem().suggestions) .containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david)) } @@ -1070,7 +1070,7 @@ class MessageComposerPresenterTest { val initialState = awaitItem() // An empty suggestion returns the joined members that are not the current user, but not the room - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) skipItems(1) assertThat(awaitItem().suggestions) .containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david)) @@ -1090,7 +1090,7 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() initialState.textEditorState.setHtml("Hey @bo") - initialState.eventSink(MessageComposerEvents.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) + initialState.eventSink(MessageComposerEvent.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) assertThat(initialState.textEditorState.messageHtml()) .isEqualTo("Hey ${A_USER_ID_2.value}") @@ -1133,7 +1133,7 @@ class MessageComposerPresenterTest { hasAtRoomMention = false ) initialState.textEditorState.setHtml(A_MESSAGE) - initialState.eventSink(MessageComposerEvents.SendMessage) + initialState.eventSink(MessageComposerEvent.SendMessage) advanceUntilIdle() @@ -1141,7 +1141,7 @@ class MessageComposerPresenterTest { .with(value(A_MESSAGE), any(), value(listOf(IntentionalMention.User(A_USER_ID)))) // Check intentional mentions on reply sent - initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) + initialState.eventSink(MessageComposerEvent.SetMode(aReplyMode())) val mentionUser2 = listOf(A_USER_ID_2.value) (awaitItem().textEditorState as? TextEditorState.Rich)?.richTextEditorState?.mentionsState = MentionsState( userIds = mentionUser2, @@ -1150,7 +1150,7 @@ class MessageComposerPresenterTest { hasAtRoomMention = false ) - initialState.eventSink(MessageComposerEvents.SendMessage) + initialState.eventSink(MessageComposerEvent.SendMessage) advanceUntilIdle() assert(replyMessageLambda) @@ -1159,7 +1159,7 @@ class MessageComposerPresenterTest { // Check intentional mentions on edit message skipItems(1) - initialState.eventSink(MessageComposerEvents.SetMode(anEditMode())) + initialState.eventSink(MessageComposerEvent.SetMode(anEditMode())) val mentionUser3 = listOf(A_USER_ID_3.value) (awaitItem().textEditorState as? TextEditorState.Rich)?.richTextEditorState?.mentionsState = MentionsState( userIds = mentionUser3, @@ -1168,7 +1168,7 @@ class MessageComposerPresenterTest { hasAtRoomMention = false ) - initialState.eventSink(MessageComposerEvents.SendMessage) + initialState.eventSink(MessageComposerEvent.SendMessage) advanceUntilIdle() assert(editMessageLambda) @@ -1196,7 +1196,7 @@ class MessageComposerPresenterTest { remember(state, state.textEditorState.messageHtml()) { state } }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(MessageComposerEvents.SendUri(Uri.parse("content://uri"))) + initialState.eventSink.invoke(MessageComposerEvent.SendUri(Uri.parse("content://uri"))) waitForPredicate { mediaPreProcessor.processCallCount == 1 } } } @@ -1213,8 +1213,8 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() typingNoticeResult.assertions().isNeverCalled() - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(false)) advanceUntilIdle() typingNoticeResult.assertions().isCalledExactly(2) .withSequence( @@ -1239,8 +1239,8 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() typingNoticeResult.assertions().isNeverCalled() - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(false)) typingNoticeResult.assertions().isNeverCalled() } } @@ -1422,7 +1422,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(MessageComposerEvents.SaveDraft) + initialState.eventSink.invoke(MessageComposerEvent.SaveDraft) advanceUntilIdle() assert(saveDraftLambda) .isCalledOnce() @@ -1452,26 +1452,26 @@ class MessageComposerPresenterTest { val withMessageState = awaitItem() assertThat(withMessageState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) - withMessageState.eventSink(MessageComposerEvents.SaveDraft) + withMessageState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() - withMessageState.eventSink(MessageComposerEvents.ToggleTextFormatting(true)) + withMessageState.eventSink(MessageComposerEvent.ToggleTextFormatting(true)) skipItems(1) val withFormattingState = awaitItem() assertThat(withFormattingState.showTextFormatting).isTrue() - withFormattingState.eventSink(MessageComposerEvents.SaveDraft) + withFormattingState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() - withFormattingState.eventSink(MessageComposerEvents.SetMode(anEditMode())) + withFormattingState.eventSink(MessageComposerEvent.SetMode(anEditMode())) val withEditModeState = awaitItem() assertThat(withEditModeState.mode).isEqualTo(anEditMode()) - withEditModeState.eventSink(MessageComposerEvents.SaveDraft) + withEditModeState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() - withEditModeState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) + withEditModeState.eventSink(MessageComposerEvent.SetMode(aReplyMode())) val withReplyModeState = awaitItem() assertThat(withReplyModeState.mode).isEqualTo(aReplyMode()) - withReplyModeState.eventSink(MessageComposerEvents.SaveDraft) + withReplyModeState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() assert(saveDraftLambda) @@ -1514,7 +1514,7 @@ class MessageComposerPresenterTest { } private suspend fun ReceiveTurbine.backToNormalMode(state: MessageComposerState, skipCount: Int = 0): MessageComposerState { - state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode) + state.eventSink.invoke(MessageComposerEvent.CloseSpecialMode) skipItems(skipCount) val normalState = awaitItem() assertThat(normalState.mode).isEqualTo(MessageComposerMode.Normal) From c62f834917d2f560a25150f35d6477ac4622bce8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 10 Nov 2025 16:34:14 +0100 Subject: [PATCH 2/3] Format - no other change. --- .../features/messages/impl/MessagesView.kt | 42 +++++----- .../libraries/textcomposer/TextComposer.kt | 84 +++++++++---------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index b3deafc2eb..4f18421679 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -183,27 +183,27 @@ fun MessagesView( Scaffold( contentWindowInsets = WindowInsets.statusBars, topBar = { - if (state.timelineState.timelineMode is Timeline.Mode.Thread) { - ThreadTopBar( - roomName = state.roomName, - roomAvatarData = state.roomAvatar, - heroes = state.heroes, - isTombstoned = state.isTombstoned, - onBackClick = onBackClick, - ) - } else { - MessagesViewTopBar( - roomName = state.roomName, - roomAvatar = state.roomAvatar, - isTombstoned = state.isTombstoned, - heroes = state.heroes, - roomCallState = state.roomCallState, - dmUserIdentityState = state.dmUserVerificationState, - onBackClick = { hidingKeyboard { onBackClick() } }, - onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } }, - onJoinCallClick = onJoinCallClick, - ) - } + if (state.timelineState.timelineMode is Timeline.Mode.Thread) { + ThreadTopBar( + roomName = state.roomName, + roomAvatarData = state.roomAvatar, + heroes = state.heroes, + isTombstoned = state.isTombstoned, + onBackClick = onBackClick, + ) + } else { + MessagesViewTopBar( + roomName = state.roomName, + roomAvatar = state.roomAvatar, + isTombstoned = state.isTombstoned, + heroes = state.heroes, + roomCallState = state.roomCallState, + dmUserIdentityState = state.dmUserVerificationState, + onBackClick = { hidingKeyboard { onBackClick() } }, + onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } }, + onJoinCallClick = onJoinCallClick, + ) + } }, content = { padding -> Box( diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 9f1bf0bffa..5553fd0b01 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -140,8 +140,8 @@ fun TextComposer( } val layoutModifier = modifier - .fillMaxSize() - .height(IntrinsicSize.Min) + .fillMaxSize() + .height(IntrinsicSize.Min) val composerOptionsButton: @Composable () -> Unit = remember(composerMode) { @Composable { @@ -178,18 +178,18 @@ fun TextComposer( @Composable { TextInputBox( modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { - coroutineScope.launch { - state.requestFocus() - view.showKeyboard() - } + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + coroutineScope.launch { + state.requestFocus() + view.showKeyboard() } - .semantics { - hideFromAccessibility() - }, + } + .semantics { + hideFromAccessibility() + }, composerMode = composerMode, onResetComposerMode = onResetComposerMode, isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(), @@ -199,8 +199,8 @@ fun TextComposer( placeholder = placeholder, registerStateUpdates = true, modifier = Modifier - .padding(top = 6.dp, bottom = 6.dp) - .fillMaxWidth(), + .padding(top = 6.dp, bottom = 6.dp) + .fillMaxWidth(), style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.richTextEditorState.hasFocus), resolveMentionDisplay = resolveMentionDisplay, resolveRoomMentionDisplay = resolveAtRoomMentionDisplay, @@ -420,8 +420,8 @@ private fun StandardLayout( if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) { Box( modifier = Modifier - .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) - .size(48.dp), + .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) + .size(48.dp), contentAlignment = Alignment.Center, ) { voiceDeleteButton() @@ -431,8 +431,8 @@ private fun StandardLayout( } Box( modifier = Modifier - .padding(bottom = 8.dp, top = 8.dp) - .weight(1f) + .padding(bottom = 8.dp, top = 8.dp) + .weight(1f) ) { voiceRecording() } @@ -445,17 +445,17 @@ private fun StandardLayout( } Box( modifier = Modifier - .padding(bottom = 8.dp, top = 8.dp) - .weight(1f) + .padding(bottom = 8.dp, top = 8.dp) + .weight(1f) ) { textInput() } } Box( Modifier - .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) - .size(48.dp) - .clearAndSetSemantics(endButtonA11y), + .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) + .size(48.dp) + .clearAndSetSemantics(endButtonA11y), contentAlignment = Alignment.Center, ) { endButton() @@ -506,8 +506,8 @@ private fun TextFormattingLayout( } Box( modifier = Modifier - .weight(1f) - .padding(horizontal = 12.dp) + .weight(1f) + .padding(horizontal = 12.dp) ) { textInput() } @@ -526,11 +526,11 @@ private fun TextFormattingLayout( } Box( modifier = Modifier - .padding( - start = 14.dp, - end = 6.dp, - ) - .clearAndSetSemantics(endButtonA11y) + .padding( + start = 14.dp, + end = 6.dp, + ) + .clearAndSetSemantics(endButtonA11y) ) { sendButton() } @@ -552,12 +552,12 @@ private fun TextInputBox( Column( modifier = Modifier - .clip(roundedCorners) - .border(0.5.dp, borderColor, roundedCorners) - .background(color = bgColor) - .requiredHeightIn(min = 42.dp) - .fillMaxSize() - .then(modifier), + .clip(roundedCorners) + .border(0.5.dp, borderColor, roundedCorners) + .background(color = bgColor) + .requiredHeightIn(min = 42.dp) + .fillMaxSize() + .then(modifier), ) { if (composerMode is MessageComposerMode.Special) { ComposerModeView( @@ -567,8 +567,8 @@ private fun TextInputBox( } Box( modifier = Modifier - .padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp) - .then(Modifier.testTag(TestTags.textEditor)), + .padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp) + .then(Modifier.testTag(TestTags.textEditor)), contentAlignment = Alignment.CenterStart, ) { textInput() @@ -576,9 +576,9 @@ private fun TextInputBox( var showBottomSheet by remember { mutableStateOf(false) } Icon( modifier = Modifier - .clickable { showBottomSheet = true } - .padding(horizontal = 8.dp, vertical = 4.dp) - .align(Alignment.CenterEnd), + .clickable { showBottomSheet = true } + .padding(horizontal = 8.dp, vertical = 4.dp) + .align(Alignment.CenterEnd), imageVector = CompoundIcons.InfoSolid(), tint = ElementTheme.colors.iconCriticalPrimary, contentDescription = null, From 30d39a32909f4b4ed3edee753cdd8993af273d17 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 10 Nov 2025 16:59:36 +0100 Subject: [PATCH 3/3] Ensure bottom sheet is collapsed when leaving RTE mode. Closes #5635 --- .../features/messages/impl/MessagesView.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 4f18421679..1f62adeb23 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow @@ -278,14 +279,13 @@ fun MessagesView( }, ) }, - sheetDragHandle = if (state.composerState.showTextFormatting) { - @Composable { toggleAction -> + sheetDragHandle = @Composable { toggleAction -> + if (state.composerState.showTextFormatting) { val expandA11yLabel = stringResource(CommonStrings.a11y_expand_message_text_field) val collapseA11yLabel = stringResource(CommonStrings.a11y_collapse_message_text_field) BottomSheetDragHandle( modifier = Modifier.semantics { role = Role.Button - // Accessibility action to toggle the bottom sheet state val label = when (expandableState.position) { ExpandableBottomSheetLayoutState.Position.COLLAPSED, ExpandableBottomSheetLayoutState.Position.DRAGGING -> expandA11yLabel @@ -297,9 +297,14 @@ fun MessagesView( } } ) + } else { + LaunchedEffect(Unit) { + // Ensure that the bottom sheet is collapsed + if (expandableState.position == ExpandableBottomSheetLayoutState.Position.EXPANDED) { + toggleAction() + } + } } - } else { - @Composable {} }, isSwipeGestureEnabled = state.composerState.showTextFormatting, state = expandableState,