Merge pull request #5710 from element-hq/feature/bma/textComposerLayout

Fix layout issue in text composer
This commit is contained in:
Benoit Marty
2025-11-12 11:29:57 +01:00
committed by GitHub
13 changed files with 226 additions and 221 deletions

View File

@@ -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
}
}

View File

@@ -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)
)
}
}

View File

@@ -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
@@ -52,7 +53,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
@@ -183,27 +184,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(
@@ -256,7 +257,7 @@ fun MessagesView(
roomAvatarData = state.roomAvatar,
suggestions = state.composerState.suggestions,
onSelectSuggestion = {
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
state.composerState.eventSink(MessageComposerEvent.InsertSuggestion(it))
}
)
}
@@ -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,

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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<Suggestion?>(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)
}

View File

@@ -26,5 +26,5 @@ data class MessageComposerState(
val suggestions: ImmutableList<ResolvedSuggestion>,
val resolveMentionDisplay: (String, String) -> TextDisplay,
val resolveAtRoomMentionDisplay: () -> TextDisplay,
val eventSink: (MessageComposerEvents) -> Unit,
val eventSink: (MessageComposerEvent) -> Unit,
)

View File

@@ -32,7 +32,7 @@ fun aMessageComposerState(
showAttachmentSourcePicker: Boolean = false,
canShareLocation: Boolean = true,
suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
eventSink: (MessageComposerEvents) -> Unit = {},
eventSink: (MessageComposerEvent) -> Unit = {},
) = MessageComposerState(
textEditorState = textEditorState,
isFullScreen = isFullScreen,

View File

@@ -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()

View File

@@ -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
}
}

View File

@@ -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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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<MessageComposerEvents>()
val composerRecorder = EventsRecorder<MessageComposerEvent>()
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,

View File

@@ -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 href='https://matrix.to/#/${A_USER_ID_2.value}'>${A_USER_ID_2.value}</a>")
@@ -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<MessageComposerState>.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)

View File

@@ -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,