Skip to content

Commit a942fd4

Browse files
authored
Merge pull request #3322 from element-hq/feature/bma/roomAliasCompletion
Suggestion for room alias (disabled for now)
2 parents 2678834 + e704af4 commit a942fd4

File tree

42 files changed

+814
-274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+814
-274
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
6666
import io.element.android.features.messages.impl.actionlist.ActionListView
6767
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
6868
import io.element.android.features.messages.impl.attachments.Attachment
69-
import io.element.android.features.messages.impl.mentions.MentionSuggestionsPickerView
7069
import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet
7170
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
7271
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
7372
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
73+
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsPickerView
7474
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
7575
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
7676
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults
@@ -377,7 +377,7 @@ private fun MessagesViewContent(
377377
@Composable {}
378378
},
379379
sheetSwipeEnabled = state.composerState.showTextFormatting,
380-
sheetShape = if (state.composerState.showTextFormatting || state.composerState.memberSuggestions.isNotEmpty()) {
380+
sheetShape = if (state.composerState.showTextFormatting || state.composerState.suggestions.isNotEmpty()) {
381381
MaterialTheme.shapes.large
382382
} else {
383383
RectangleShape
@@ -427,7 +427,7 @@ private fun MessagesViewContent(
427427
},
428428
sheetContentKey = sheetResizeContentKey.intValue,
429429
sheetTonalElevation = 0.dp,
430-
sheetShadowElevation = if (state.composerState.memberSuggestions.isNotEmpty()) 16.dp else 0.dp,
430+
sheetShadowElevation = if (state.composerState.suggestions.isNotEmpty()) 16.dp else 0.dp,
431431
)
432432
}
433433
}
@@ -439,7 +439,7 @@ private fun MessagesViewComposerBottomSheetContents(
439439
) {
440440
if (state.userEventPermissions.canSendMessage) {
441441
Column(modifier = Modifier.fillMaxWidth()) {
442-
MentionSuggestionsPickerView(
442+
SuggestionsPickerView(
443443
modifier = Modifier
444444
.heightIn(max = 230.dp)
445445
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
@@ -451,9 +451,9 @@ private fun MessagesViewComposerBottomSheetContents(
451451
roomId = state.roomId,
452452
roomName = state.roomName.dataOrNull(),
453453
roomAvatarData = state.roomAvatar.dataOrNull(),
454-
memberSuggestions = state.composerState.memberSuggestions,
454+
suggestions = state.composerState.suggestions,
455455
onSelectSuggestion = {
456-
state.composerState.eventSink(MessageComposerEvents.InsertMention(it))
456+
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
457457
}
458458
)
459459
MessageComposerView(

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer
1818

1919
import android.net.Uri
2020
import androidx.compose.runtime.Immutable
21-
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
21+
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
2222
import io.element.android.libraries.textcomposer.model.MessageComposerMode
2323
import io.element.android.libraries.textcomposer.model.Suggestion
2424

@@ -44,6 +44,6 @@ sealed interface MessageComposerEvents {
4444
data class Error(val error: Throwable) : MessageComposerEvents
4545
data class TypingNotice(val isTyping: Boolean) : MessageComposerEvents
4646
data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents
47-
data class InsertMention(val mention: ResolvedMentionSuggestion) : MessageComposerEvents
47+
data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvents
4848
data object SaveDraft : MessageComposerEvents
4949
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import im.vector.app.features.analytics.plan.Interaction
4040
import io.element.android.features.messages.impl.attachments.Attachment
4141
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
4242
import io.element.android.features.messages.impl.draft.ComposerDraftService
43-
import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor
43+
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
4444
import io.element.android.features.messages.impl.timeline.TimelineController
4545
import io.element.android.features.messages.impl.utils.TextPillificationHelper
4646
import io.element.android.libraries.architecture.Presenter
@@ -55,8 +55,8 @@ import io.element.android.libraries.matrix.api.core.UserId
5555
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
5656
import io.element.android.libraries.matrix.api.permalink.PermalinkData
5757
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
58+
import io.element.android.libraries.matrix.api.room.IntentionalMention
5859
import io.element.android.libraries.matrix.api.room.MatrixRoom
59-
import io.element.android.libraries.matrix.api.room.Mention
6060
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
6161
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
6262
import io.element.android.libraries.matrix.api.room.isDm
@@ -72,7 +72,7 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
7272
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
7373
import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme
7474
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
75-
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
75+
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
7676
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
7777
import io.element.android.libraries.textcomposer.model.Message
7878
import io.element.android.libraries.textcomposer.model.MessageComposerMode
@@ -117,6 +117,7 @@ class MessageComposerPresenter @Inject constructor(
117117
private val analyticsService: AnalyticsService,
118118
private val messageComposerContext: DefaultMessageComposerContext,
119119
private val richTextEditorStateFactory: RichTextEditorStateFactory,
120+
private val roomAliasSuggestionsDataSource: RoomAliasSuggestionsDataSource,
120121
private val permalinkParser: PermalinkParser,
121122
private val permalinkBuilder: PermalinkBuilder,
122123
permissionsPresenterFactory: PermissionsPresenter.Factory,
@@ -125,6 +126,7 @@ class MessageComposerPresenter @Inject constructor(
125126
private val mentionSpanProvider: MentionSpanProvider,
126127
private val pillificationHelper: TextPillificationHelper,
127128
private val roomMemberProfilesCache: RoomMemberProfilesCache,
129+
private val suggestionsProcessor: SuggestionsProcessor,
128130
) : Presenter<MessageComposerState> {
129131
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
130132
private var pendingEvent: MessageComposerEvents? = null
@@ -149,8 +151,10 @@ class MessageComposerPresenter @Inject constructor(
149151
}
150152
val markdownTextEditorState = rememberMarkdownTextEditorState(initialText = null, initialFocus = false)
151153
var isMentionsEnabled by remember { mutableStateOf(false) }
154+
var isRoomAliasSuggestionsEnabled by remember { mutableStateOf(false) }
152155
LaunchedEffect(Unit) {
153156
isMentionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.Mentions)
157+
isRoomAliasSuggestionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.RoomAliasSuggestions)
154158
}
155159

156160
val cameraPermissionState = cameraPermissionPresenter.present()
@@ -189,6 +193,8 @@ class MessageComposerPresenter @Inject constructor(
189193

190194
val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true)
191195

196+
val roomAliasSuggestions by roomAliasSuggestionsDataSource.getAllRoomAliasSuggestions().collectAsState(initial = emptyList())
197+
192198
LaunchedEffect(attachmentsState.value) {
193199
when (val attachmentStateValue = attachmentsState.value) {
194200
is AttachmentsState.Sending.Processing -> {
@@ -212,7 +218,7 @@ class MessageComposerPresenter @Inject constructor(
212218
}
213219
}
214220

215-
val memberSuggestions = remember { mutableStateListOf<ResolvedMentionSuggestion>() }
221+
val suggestions = remember { mutableStateListOf<ResolvedSuggestion>() }
216222
LaunchedEffect(isMentionsEnabled) {
217223
if (!isMentionsEnabled) return@LaunchedEffect
218224
val currentUserId = room.sessionId
@@ -228,15 +234,16 @@ class MessageComposerPresenter @Inject constructor(
228234
val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
229235
merge(mentionStartTrigger, mentionCompletionTrigger)
230236
.combine(room.membersStateFlow) { suggestion, roomMembersState ->
231-
memberSuggestions.clear()
232-
val result = MentionSuggestionsProcessor.process(
237+
suggestions.clear()
238+
val result = suggestionsProcessor.process(
233239
suggestion = suggestion,
234240
roomMembersState = roomMembersState,
241+
roomAliasSuggestions = if (isRoomAliasSuggestionsEnabled) roomAliasSuggestions else emptyList(),
235242
currentUserId = currentUserId,
236243
canSendRoomMention = ::canSendRoomMention,
237244
)
238245
if (result.isNotEmpty()) {
239-
memberSuggestions.addAll(result)
246+
suggestions.addAll(result)
240247
}
241248
}
242249
.collect()
@@ -362,22 +369,27 @@ class MessageComposerPresenter @Inject constructor(
362369
is MessageComposerEvents.SuggestionReceived -> {
363370
suggestionSearchTrigger.value = event.suggestion
364371
}
365-
is MessageComposerEvents.InsertMention -> {
372+
is MessageComposerEvents.InsertSuggestion -> {
366373
localCoroutineScope.launch {
367374
if (showTextFormatting) {
368-
when (val mention = event.mention) {
369-
is ResolvedMentionSuggestion.AtRoom -> {
375+
when (val suggestion = event.resolvedSuggestion) {
376+
is ResolvedSuggestion.AtRoom -> {
370377
richTextEditorState.insertAtRoomMentionAtSuggestion()
371378
}
372-
is ResolvedMentionSuggestion.Member -> {
373-
val text = mention.roomMember.userId.value
374-
val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch
379+
is ResolvedSuggestion.Member -> {
380+
val text = suggestion.roomMember.userId.value
381+
val link = permalinkBuilder.permalinkForUser(suggestion.roomMember.userId).getOrNull() ?: return@launch
382+
richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
383+
}
384+
is ResolvedSuggestion.Alias -> {
385+
val text = suggestion.roomAlias.value
386+
val link = permalinkBuilder.permalinkForRoomAlias(suggestion.roomAlias).getOrNull() ?: return@launch
375387
richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
376388
}
377389
}
378-
} else if (markdownTextEditorState.currentMentionSuggestion != null) {
379-
markdownTextEditorState.insertMention(
380-
mention = event.mention,
390+
} else if (markdownTextEditorState.currentSuggestion != null) {
391+
markdownTextEditorState.insertSuggestion(
392+
resolvedSuggestion = event.resolvedSuggestion,
381393
mentionSpanProvider = mentionSpanProvider,
382394
permalinkBuilder = permalinkBuilder,
383395
)
@@ -417,7 +429,7 @@ class MessageComposerPresenter @Inject constructor(
417429
canShareLocation = canShareLocation.value,
418430
canCreatePoll = canCreatePoll.value,
419431
attachmentsState = attachmentsState.value,
420-
memberSuggestions = memberSuggestions.toPersistentList(),
432+
suggestions = suggestions.toPersistentList(),
421433
resolveMentionDisplay = resolveMentionDisplay,
422434
eventSink = { handleEvents(it) },
423435
)
@@ -432,25 +444,29 @@ class MessageComposerPresenter @Inject constructor(
432444
// Reset composer right away
433445
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit)
434446
when (capturedMode) {
435-
is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html, mentions = message.mentions)
447+
is MessageComposerMode.Normal -> room.sendMessage(
448+
body = message.markdown,
449+
htmlBody = message.html,
450+
intentionalMentions = message.intentionalMentions
451+
)
436452
is MessageComposerMode.Edit -> {
437453
val eventId = capturedMode.eventId
438454
val transactionId = capturedMode.transactionId
439455
timelineController.invokeOnCurrentTimeline {
440456
// First try to edit the message in the current timeline
441-
editMessage(eventId, transactionId, message.markdown, message.html, message.mentions)
457+
editMessage(eventId, transactionId, message.markdown, message.html, message.intentionalMentions)
442458
.onFailure { cause ->
443459
if (cause is TimelineException.EventNotFound && eventId != null) {
444460
// if the event is not found in the timeline, try to edit the message directly
445-
room.editMessage(eventId, message.markdown, message.html, message.mentions)
461+
room.editMessage(eventId, message.markdown, message.html, message.intentionalMentions)
446462
}
447463
}
448464
}
449465
}
450466

451467
is MessageComposerMode.Reply -> {
452468
timelineController.invokeOnCurrentTimeline {
453-
replyMessage(capturedMode.eventId, message.markdown, message.html, message.mentions)
469+
replyMessage(capturedMode.eventId, message.markdown, message.html, message.intentionalMentions)
454470
}
455471
}
456472
}
@@ -623,23 +639,23 @@ class MessageComposerPresenter @Inject constructor(
623639
?.let { state ->
624640
buildList {
625641
if (state.hasAtRoomMention) {
626-
add(Mention.AtRoom)
642+
add(IntentionalMention.Room)
627643
}
628644
for (userId in state.userIds) {
629-
add(Mention.User(UserId(userId)))
645+
add(IntentionalMention.User(UserId(userId)))
630646
}
631647
}
632648
}
633649
.orEmpty()
634-
Message(html = html, markdown = markdown, mentions = mentions)
650+
Message(html = html, markdown = markdown, intentionalMentions = mentions)
635651
} else {
636652
val markdown = markdownTextEditorState.getMessageMarkdown(permalinkBuilder)
637653
val mentions = if (withMentions) {
638654
markdownTextEditorState.getMentions()
639655
} else {
640656
emptyList()
641657
}
642-
Message(html = null, markdown = markdown, mentions = mentions)
658+
Message(html = null, markdown = markdown, intentionalMentions = mentions)
643659
}
644660
}
645661

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package io.element.android.features.messages.impl.messagecomposer
1919
import androidx.compose.runtime.Immutable
2020
import androidx.compose.runtime.Stable
2121
import io.element.android.features.messages.impl.attachments.Attachment
22-
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
22+
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
2323
import io.element.android.libraries.textcomposer.model.MessageComposerMode
2424
import io.element.android.libraries.textcomposer.model.TextEditorState
2525
import io.element.android.wysiwyg.display.TextDisplay
@@ -35,7 +35,7 @@ data class MessageComposerState(
3535
val canShareLocation: Boolean,
3636
val canCreatePoll: Boolean,
3737
val attachmentsState: AttachmentsState,
38-
val memberSuggestions: ImmutableList<ResolvedMentionSuggestion>,
38+
val suggestions: ImmutableList<ResolvedSuggestion>,
3939
val resolveMentionDisplay: (String, String) -> TextDisplay,
4040
val eventSink: (MessageComposerEvents) -> Unit,
4141
)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer
1818

1919
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
2020
import io.element.android.libraries.textcomposer.aRichTextEditorState
21-
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
21+
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
2222
import io.element.android.libraries.textcomposer.model.MessageComposerMode
2323
import io.element.android.libraries.textcomposer.model.TextEditorState
2424
import io.element.android.wysiwyg.display.TextDisplay
@@ -41,7 +41,7 @@ fun aMessageComposerState(
4141
canShareLocation: Boolean = true,
4242
canCreatePoll: Boolean = true,
4343
attachmentsState: AttachmentsState = AttachmentsState.None,
44-
memberSuggestions: ImmutableList<ResolvedMentionSuggestion> = persistentListOf(),
44+
suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
4545
) = MessageComposerState(
4646
textEditorState = textEditorState,
4747
isFullScreen = isFullScreen,
@@ -51,7 +51,7 @@ fun aMessageComposerState(
5151
canShareLocation = canShareLocation,
5252
canCreatePoll = canCreatePoll,
5353
attachmentsState = attachmentsState,
54-
memberSuggestions = memberSuggestions,
54+
suggestions = suggestions,
5555
resolveMentionDisplay = { _, _ -> TextDisplay.Plain },
5656
eventSink = {},
5757
)

0 commit comments

Comments
 (0)