Suggestion for room alias.
Rename `Mention` to `IntentionalMention` for clarity Remove dead code, there is no intentional mention for Room or RoomAlias. Rename `IntentionalMention.AtRoom` to `IntentionalMention.Room` to match Rust naming
This commit is contained in:
parent
4385a7779c
commit
4b8985e501
34 changed files with 399 additions and 222 deletions
|
|
@ -66,7 +66,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
|||
import io.element.android.features.messages.impl.actionlist.ActionListView
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.mentions.MentionSuggestionsPickerView
|
||||
import io.element.android.features.messages.impl.mentions.SuggestionsPickerView
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
|
|
@ -377,7 +377,7 @@ private fun MessagesViewContent(
|
|||
@Composable {}
|
||||
},
|
||||
sheetSwipeEnabled = state.composerState.showTextFormatting,
|
||||
sheetShape = if (state.composerState.showTextFormatting || state.composerState.memberSuggestions.isNotEmpty()) {
|
||||
sheetShape = if (state.composerState.showTextFormatting || state.composerState.suggestions.isNotEmpty()) {
|
||||
MaterialTheme.shapes.large
|
||||
} else {
|
||||
RectangleShape
|
||||
|
|
@ -427,7 +427,7 @@ private fun MessagesViewContent(
|
|||
},
|
||||
sheetContentKey = sheetResizeContentKey.intValue,
|
||||
sheetTonalElevation = 0.dp,
|
||||
sheetShadowElevation = if (state.composerState.memberSuggestions.isNotEmpty()) 16.dp else 0.dp,
|
||||
sheetShadowElevation = if (state.composerState.suggestions.isNotEmpty()) 16.dp else 0.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -439,7 +439,7 @@ private fun MessagesViewComposerBottomSheetContents(
|
|||
) {
|
||||
if (state.userEventPermissions.canSendMessage) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
MentionSuggestionsPickerView(
|
||||
SuggestionsPickerView(
|
||||
modifier = Modifier
|
||||
.heightIn(max = 230.dp)
|
||||
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
|
||||
|
|
@ -451,9 +451,9 @@ private fun MessagesViewComposerBottomSheetContents(
|
|||
roomId = state.roomId,
|
||||
roomName = state.roomName.dataOrNull(),
|
||||
roomAvatarData = state.roomAvatar.dataOrNull(),
|
||||
memberSuggestions = state.composerState.memberSuggestions,
|
||||
suggestions = state.composerState.suggestions,
|
||||
onSelectSuggestion = {
|
||||
state.composerState.eventSink(MessageComposerEvents.InsertMention(it))
|
||||
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
|
||||
}
|
||||
)
|
||||
MessageComposerView(
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
package io.element.android.features.messages.impl.mentions
|
||||
|
||||
import io.element.android.features.messages.impl.messagecomposer.RoomAliasSuggestion
|
||||
import io.element.android.libraries.core.data.filterUpTo
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
import io.element.android.libraries.textcomposer.model.SuggestionType
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ object MentionSuggestionsProcessor {
|
|||
* Process the mention suggestions.
|
||||
* @param suggestion The current suggestion input
|
||||
* @param roomMembersState The room members state, it contains the current users in the room
|
||||
* @param roomAliasSuggestions The available room alias suggestions
|
||||
* @param currentUserId The current user id
|
||||
* @param canSendRoomMention Should return true if the current user can send room mentions
|
||||
* @return The list of mentions to display
|
||||
|
|
@ -44,9 +46,10 @@ object MentionSuggestionsProcessor {
|
|||
suspend fun process(
|
||||
suggestion: Suggestion?,
|
||||
roomMembersState: MatrixRoomMembersState,
|
||||
roomAliasSuggestions: List<RoomAliasSuggestion>,
|
||||
currentUserId: UserId,
|
||||
canSendRoomMention: suspend () -> Boolean,
|
||||
): List<ResolvedMentionSuggestion> {
|
||||
): List<ResolvedSuggestion> {
|
||||
val members = roomMembersState.roomMembers()
|
||||
return when {
|
||||
members.isNullOrEmpty() || suggestion == null -> {
|
||||
|
|
@ -65,6 +68,11 @@ object MentionSuggestionsProcessor {
|
|||
)
|
||||
matchingMembers
|
||||
}
|
||||
SuggestionType.Room -> {
|
||||
roomAliasSuggestions
|
||||
.filter { it.roomAlias.value.contains(suggestion.text, ignoreCase = true) }
|
||||
.map { ResolvedSuggestion.Alias(it.roomAlias, it.roomSummary) }
|
||||
}
|
||||
else -> {
|
||||
// Clear suggestions
|
||||
emptyList()
|
||||
|
|
@ -79,7 +87,7 @@ object MentionSuggestionsProcessor {
|
|||
roomMembers: List<RoomMember>?,
|
||||
currentUserId: UserId,
|
||||
canSendRoomMention: Boolean,
|
||||
): List<ResolvedMentionSuggestion> {
|
||||
): List<ResolvedSuggestion> {
|
||||
return if (roomMembers.isNullOrEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
|
|
@ -97,10 +105,10 @@ object MentionSuggestionsProcessor {
|
|||
.filterUpTo(MAX_BATCH_ITEMS) { member ->
|
||||
isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query)
|
||||
}
|
||||
.map(ResolvedMentionSuggestion::Member)
|
||||
.map(ResolvedSuggestion::Member)
|
||||
|
||||
if ("room".contains(query) && canSendRoomMention) {
|
||||
listOf(ResolvedMentionSuggestion.AtRoom) + matchingMembers
|
||||
listOf(ResolvedSuggestion.AtRoom) + matchingMembers
|
||||
} else {
|
||||
matchingMembers
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -39,38 +40,41 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
|||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun MentionSuggestionsPickerView(
|
||||
fun SuggestionsPickerView(
|
||||
roomId: RoomId,
|
||||
roomName: String?,
|
||||
roomAvatarData: AvatarData?,
|
||||
memberSuggestions: ImmutableList<ResolvedMentionSuggestion>,
|
||||
onSelectSuggestion: (ResolvedMentionSuggestion) -> Unit,
|
||||
suggestions: ImmutableList<ResolvedSuggestion>,
|
||||
onSelectSuggestion: (ResolvedSuggestion) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
items(
|
||||
memberSuggestions,
|
||||
suggestions,
|
||||
key = { suggestion ->
|
||||
when (suggestion) {
|
||||
is ResolvedMentionSuggestion.AtRoom -> "@room"
|
||||
is ResolvedMentionSuggestion.Member -> suggestion.roomMember.userId.value
|
||||
is ResolvedSuggestion.AtRoom -> "@room"
|
||||
is ResolvedSuggestion.Member -> suggestion.roomMember.userId.value
|
||||
is ResolvedSuggestion.Alias -> suggestion.roomAlias.value
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column(modifier = Modifier.fillParentMaxWidth()) {
|
||||
RoomMemberSuggestionItemView(
|
||||
memberSuggestion = it,
|
||||
SuggestionItemView(
|
||||
suggestion = it,
|
||||
roomId = roomId.value,
|
||||
roomName = roomName,
|
||||
roomAvatar = roomAvatarData,
|
||||
|
|
@ -84,33 +88,44 @@ fun MentionSuggestionsPickerView(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomMemberSuggestionItemView(
|
||||
memberSuggestion: ResolvedMentionSuggestion,
|
||||
private fun SuggestionItemView(
|
||||
suggestion: ResolvedSuggestion,
|
||||
roomId: String,
|
||||
roomName: String?,
|
||||
roomAvatar: AvatarData?,
|
||||
onSelectSuggestion: (ResolvedMentionSuggestion) -> Unit,
|
||||
onSelectSuggestion: (ResolvedSuggestion) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(modifier = modifier.clickable { onSelectSuggestion(memberSuggestion) }, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
val avatarData = when (memberSuggestion) {
|
||||
is ResolvedMentionSuggestion.AtRoom -> roomAvatar?.copy(size = AvatarSize.Suggestion)
|
||||
?: AvatarData(roomId, roomName, null, AvatarSize.Suggestion)
|
||||
is ResolvedMentionSuggestion.Member -> AvatarData(
|
||||
id = memberSuggestion.roomMember.userId.value,
|
||||
name = memberSuggestion.roomMember.displayName,
|
||||
url = memberSuggestion.roomMember.avatarUrl,
|
||||
size = AvatarSize.Suggestion,
|
||||
Row(
|
||||
modifier = modifier.clickable { onSelectSuggestion(suggestion) },
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
val avatarSize = AvatarSize.Suggestion
|
||||
val avatarData = when (suggestion) {
|
||||
is ResolvedSuggestion.AtRoom -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize)
|
||||
is ResolvedSuggestion.Member -> AvatarData(
|
||||
suggestion.roomMember.userId.value,
|
||||
suggestion.roomMember.displayName,
|
||||
suggestion.roomMember.avatarUrl,
|
||||
avatarSize,
|
||||
)
|
||||
is ResolvedSuggestion.Alias -> AvatarData(
|
||||
suggestion.roomSummary.roomId.value,
|
||||
suggestion.roomSummary.name,
|
||||
suggestion.roomSummary.avatarUrl,
|
||||
avatarSize,
|
||||
)
|
||||
}
|
||||
val title = when (memberSuggestion) {
|
||||
is ResolvedMentionSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
|
||||
is ResolvedMentionSuggestion.Member -> memberSuggestion.roomMember.displayName
|
||||
val title = when (suggestion) {
|
||||
is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
|
||||
is ResolvedSuggestion.Member -> suggestion.roomMember.displayName
|
||||
is ResolvedSuggestion.Alias -> suggestion.roomSummary.name
|
||||
}
|
||||
|
||||
val subtitle = when (memberSuggestion) {
|
||||
is ResolvedMentionSuggestion.AtRoom -> "@room"
|
||||
is ResolvedMentionSuggestion.Member -> memberSuggestion.roomMember.userId.value
|
||||
val subtitle = when (suggestion) {
|
||||
is ResolvedSuggestion.AtRoom -> "@room"
|
||||
is ResolvedSuggestion.Member -> suggestion.roomMember.userId.value
|
||||
is ResolvedSuggestion.Alias -> suggestion.roomAlias.value
|
||||
}
|
||||
|
||||
Avatar(avatarData = avatarData, modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp))
|
||||
|
|
@ -142,7 +157,7 @@ private fun RoomMemberSuggestionItemView(
|
|||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MentionSuggestionsPickerViewPreview() {
|
||||
internal fun SuggestionsPickerViewPreview() {
|
||||
ElementPreview {
|
||||
val roomMember = RoomMember(
|
||||
userId = UserId("@alice:server.org"),
|
||||
|
|
@ -155,14 +170,24 @@ internal fun MentionSuggestionsPickerViewPreview() {
|
|||
isIgnored = false,
|
||||
role = RoomMember.Role.USER,
|
||||
)
|
||||
MentionSuggestionsPickerView(
|
||||
val anAlias = remember { RoomAlias("#room:domain.org") }
|
||||
val roomSummaryDetails = remember {
|
||||
aRoomSummaryDetails(
|
||||
name = "My room",
|
||||
)
|
||||
}
|
||||
SuggestionsPickerView(
|
||||
roomId = RoomId("!room:matrix.org"),
|
||||
roomName = "Room",
|
||||
roomAvatarData = null,
|
||||
memberSuggestions = persistentListOf(
|
||||
ResolvedMentionSuggestion.AtRoom,
|
||||
ResolvedMentionSuggestion.Member(roomMember),
|
||||
ResolvedMentionSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
|
||||
suggestions = persistentListOf(
|
||||
ResolvedSuggestion.AtRoom,
|
||||
ResolvedSuggestion.Member(roomMember),
|
||||
ResolvedSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
|
||||
ResolvedSuggestion.Alias(
|
||||
anAlias,
|
||||
roomSummaryDetails,
|
||||
)
|
||||
),
|
||||
onSelectSuggestion = {}
|
||||
)
|
||||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
|
||||
|
|
@ -44,6 +44,6 @@ sealed interface MessageComposerEvents {
|
|||
data class Error(val error: Throwable) : MessageComposerEvents
|
||||
data class TypingNotice(val isTyping: Boolean) : MessageComposerEvents
|
||||
data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents
|
||||
data class InsertMention(val mention: ResolvedMentionSuggestion) : MessageComposerEvents
|
||||
data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvents
|
||||
data object SaveDraft : MessageComposerEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
|
|
@ -72,7 +72,7 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
|
|||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.Message
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
|
|
@ -117,6 +117,7 @@ class MessageComposerPresenter @Inject constructor(
|
|||
private val analyticsService: AnalyticsService,
|
||||
private val messageComposerContext: DefaultMessageComposerContext,
|
||||
private val richTextEditorStateFactory: RichTextEditorStateFactory,
|
||||
private val roomAliasSuggestionsDataSource: RoomAliasSuggestionsDataSource,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val permalinkBuilder: PermalinkBuilder,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
|
|
@ -189,6 +190,8 @@ class MessageComposerPresenter @Inject constructor(
|
|||
|
||||
val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true)
|
||||
|
||||
val roomAliasSuggestions by roomAliasSuggestionsDataSource.getAllRoomAliasSuggestions().collectAsState(initial = emptyList())
|
||||
|
||||
LaunchedEffect(attachmentsState.value) {
|
||||
when (val attachmentStateValue = attachmentsState.value) {
|
||||
is AttachmentsState.Sending.Processing -> {
|
||||
|
|
@ -212,7 +215,7 @@ class MessageComposerPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val memberSuggestions = remember { mutableStateListOf<ResolvedMentionSuggestion>() }
|
||||
val suggestions = remember { mutableStateListOf<ResolvedSuggestion>() }
|
||||
LaunchedEffect(isMentionsEnabled) {
|
||||
if (!isMentionsEnabled) return@LaunchedEffect
|
||||
val currentUserId = room.sessionId
|
||||
|
|
@ -228,15 +231,16 @@ class MessageComposerPresenter @Inject constructor(
|
|||
val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
|
||||
merge(mentionStartTrigger, mentionCompletionTrigger)
|
||||
.combine(room.membersStateFlow) { suggestion, roomMembersState ->
|
||||
memberSuggestions.clear()
|
||||
suggestions.clear()
|
||||
val result = MentionSuggestionsProcessor.process(
|
||||
suggestion = suggestion,
|
||||
roomMembersState = roomMembersState,
|
||||
roomAliasSuggestions = roomAliasSuggestions,
|
||||
currentUserId = currentUserId,
|
||||
canSendRoomMention = ::canSendRoomMention,
|
||||
)
|
||||
if (result.isNotEmpty()) {
|
||||
memberSuggestions.addAll(result)
|
||||
suggestions.addAll(result)
|
||||
}
|
||||
}
|
||||
.collect()
|
||||
|
|
@ -362,22 +366,27 @@ class MessageComposerPresenter @Inject constructor(
|
|||
is MessageComposerEvents.SuggestionReceived -> {
|
||||
suggestionSearchTrigger.value = event.suggestion
|
||||
}
|
||||
is MessageComposerEvents.InsertMention -> {
|
||||
is MessageComposerEvents.InsertSuggestion -> {
|
||||
localCoroutineScope.launch {
|
||||
if (showTextFormatting) {
|
||||
when (val mention = event.mention) {
|
||||
is ResolvedMentionSuggestion.AtRoom -> {
|
||||
when (val suggestion = event.resolvedSuggestion) {
|
||||
is ResolvedSuggestion.AtRoom -> {
|
||||
richTextEditorState.insertAtRoomMentionAtSuggestion()
|
||||
}
|
||||
is ResolvedMentionSuggestion.Member -> {
|
||||
val text = mention.roomMember.userId.value
|
||||
val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch
|
||||
is ResolvedSuggestion.Member -> {
|
||||
val text = suggestion.roomMember.userId.value
|
||||
val link = permalinkBuilder.permalinkForUser(suggestion.roomMember.userId).getOrNull() ?: return@launch
|
||||
richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
|
||||
}
|
||||
is ResolvedSuggestion.Alias -> {
|
||||
val text = suggestion.roomAlias.value
|
||||
val link = permalinkBuilder.permalinkForRoomAlias(suggestion.roomAlias).getOrNull() ?: return@launch
|
||||
richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
|
||||
}
|
||||
}
|
||||
} else if (markdownTextEditorState.currentMentionSuggestion != null) {
|
||||
markdownTextEditorState.insertMention(
|
||||
mention = event.mention,
|
||||
} else if (markdownTextEditorState.currentSuggestion != null) {
|
||||
markdownTextEditorState.insertSuggestion(
|
||||
resolvedSuggestion = event.resolvedSuggestion,
|
||||
mentionSpanProvider = mentionSpanProvider,
|
||||
permalinkBuilder = permalinkBuilder,
|
||||
)
|
||||
|
|
@ -417,7 +426,7 @@ class MessageComposerPresenter @Inject constructor(
|
|||
canShareLocation = canShareLocation.value,
|
||||
canCreatePoll = canCreatePoll.value,
|
||||
attachmentsState = attachmentsState.value,
|
||||
memberSuggestions = memberSuggestions.toPersistentList(),
|
||||
suggestions = suggestions.toPersistentList(),
|
||||
resolveMentionDisplay = resolveMentionDisplay,
|
||||
eventSink = { handleEvents(it) },
|
||||
)
|
||||
|
|
@ -432,17 +441,21 @@ class MessageComposerPresenter @Inject constructor(
|
|||
// Reset composer right away
|
||||
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit)
|
||||
when (capturedMode) {
|
||||
is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html, mentions = message.mentions)
|
||||
is MessageComposerMode.Normal -> room.sendMessage(
|
||||
body = message.markdown,
|
||||
htmlBody = message.html,
|
||||
intentionalMentions = message.intentionalMentions
|
||||
)
|
||||
is MessageComposerMode.Edit -> {
|
||||
val eventId = capturedMode.eventId
|
||||
val transactionId = capturedMode.transactionId
|
||||
timelineController.invokeOnCurrentTimeline {
|
||||
// First try to edit the message in the current timeline
|
||||
editMessage(eventId, transactionId, message.markdown, message.html, message.mentions)
|
||||
editMessage(eventId, transactionId, message.markdown, message.html, message.intentionalMentions)
|
||||
.onFailure { cause ->
|
||||
if (cause is TimelineException.EventNotFound && eventId != null) {
|
||||
// if the event is not found in the timeline, try to edit the message directly
|
||||
room.editMessage(eventId, message.markdown, message.html, message.mentions)
|
||||
room.editMessage(eventId, message.markdown, message.html, message.intentionalMentions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -450,7 +463,7 @@ class MessageComposerPresenter @Inject constructor(
|
|||
|
||||
is MessageComposerMode.Reply -> {
|
||||
timelineController.invokeOnCurrentTimeline {
|
||||
replyMessage(capturedMode.eventId, message.markdown, message.html, message.mentions)
|
||||
replyMessage(capturedMode.eventId, message.markdown, message.html, message.intentionalMentions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -623,15 +636,15 @@ class MessageComposerPresenter @Inject constructor(
|
|||
?.let { state ->
|
||||
buildList {
|
||||
if (state.hasAtRoomMention) {
|
||||
add(Mention.AtRoom)
|
||||
add(IntentionalMention.Room)
|
||||
}
|
||||
for (userId in state.userIds) {
|
||||
add(Mention.User(UserId(userId)))
|
||||
add(IntentionalMention.User(UserId(userId)))
|
||||
}
|
||||
}
|
||||
}
|
||||
.orEmpty()
|
||||
Message(html = html, markdown = markdown, mentions = mentions)
|
||||
Message(html = html, markdown = markdown, intentionalMentions = mentions)
|
||||
} else {
|
||||
val markdown = markdownTextEditorState.getMessageMarkdown(permalinkBuilder)
|
||||
val mentions = if (withMentions) {
|
||||
|
|
@ -639,7 +652,7 @@ class MessageComposerPresenter @Inject constructor(
|
|||
} else {
|
||||
emptyList()
|
||||
}
|
||||
Message(html = null, markdown = markdown, mentions = mentions)
|
||||
Message(html = null, markdown = markdown, intentionalMentions = mentions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ package io.element.android.features.messages.impl.messagecomposer
|
|||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.wysiwyg.display.TextDisplay
|
||||
|
|
@ -35,7 +35,7 @@ data class MessageComposerState(
|
|||
val canShareLocation: Boolean,
|
||||
val canCreatePoll: Boolean,
|
||||
val attachmentsState: AttachmentsState,
|
||||
val memberSuggestions: ImmutableList<ResolvedMentionSuggestion>,
|
||||
val suggestions: ImmutableList<ResolvedSuggestion>,
|
||||
val resolveMentionDisplay: (String, String) -> TextDisplay,
|
||||
val eventSink: (MessageComposerEvents) -> Unit,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.textcomposer.aRichTextEditorState
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.wysiwyg.display.TextDisplay
|
||||
|
|
@ -41,7 +41,7 @@ fun aMessageComposerState(
|
|||
canShareLocation: Boolean = true,
|
||||
canCreatePoll: Boolean = true,
|
||||
attachmentsState: AttachmentsState = AttachmentsState.None,
|
||||
memberSuggestions: ImmutableList<ResolvedMentionSuggestion> = persistentListOf(),
|
||||
suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
|
||||
) = MessageComposerState(
|
||||
textEditorState = textEditorState,
|
||||
isFullScreen = isFullScreen,
|
||||
|
|
@ -51,7 +51,7 @@ fun aMessageComposerState(
|
|||
canShareLocation = canShareLocation,
|
||||
canCreatePoll = canCreatePoll,
|
||||
attachmentsState = attachmentsState,
|
||||
memberSuggestions = memberSuggestions,
|
||||
suggestions = suggestions,
|
||||
resolveMentionDisplay = { _, _ -> TextDisplay.Plain },
|
||||
eventSink = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.messagecomposer
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
data class RoomAliasSuggestion(
|
||||
val roomAlias: RoomAlias,
|
||||
val roomSummary: RoomSummary,
|
||||
)
|
||||
|
||||
interface RoomAliasSuggestionsDataSource {
|
||||
fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultRoomAliasSuggestionsDataSource @Inject constructor(
|
||||
private val roomListService: RoomListService,
|
||||
) : RoomAliasSuggestionsDataSource {
|
||||
override fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>> {
|
||||
return roomListService
|
||||
.allRooms
|
||||
.filteredSummaries
|
||||
.map { roomSummaries ->
|
||||
roomSummaries
|
||||
.mapNotNull { roomSummary ->
|
||||
roomSummary.canonicalAlias?.let { roomAlias ->
|
||||
RoomAliasSuggestion(
|
||||
roomAlias = roomAlias,
|
||||
roomSummary = roomSummary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -29,9 +29,10 @@ import io.element.android.features.messages.impl.draft.FakeComposerDraftService
|
|||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
|
||||
import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.TestRichTextEditorStateFactory
|
||||
import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
|
||||
import io.element.android.features.messages.impl.textcomposer.TestRichTextEditorStateFactory
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineItemIndexer
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
|
|
@ -1008,6 +1009,7 @@ class MessagesPresenterTest {
|
|||
analyticsService = analyticsService,
|
||||
messageComposerContext = DefaultMessageComposerContext(),
|
||||
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
|
||||
roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
|
||||
permissionsPresenterFactory = permissionsPresenterFactory,
|
||||
permalinkParser = FakePermalinkParser(),
|
||||
permalinkBuilder = FakePermalinkBuilder(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.messagecomposer
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeRoomAliasSuggestionsDataSource(
|
||||
initialData: List<RoomAliasSuggestion> = emptyList()
|
||||
) : RoomAliasSuggestionsDataSource {
|
||||
private val roomAliasSuggestions = MutableStateFlow(initialData)
|
||||
|
||||
override fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>> {
|
||||
return roomAliasSuggestions
|
||||
}
|
||||
|
||||
fun emitRoomAliasSuggestions(newData: List<RoomAliasSuggestion>) {
|
||||
roomAliasSuggestions.value = newData
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.messages.impl.textcomposer
|
||||
package io.element.android.features.messages.impl.messagecomposer
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -29,11 +29,6 @@ import im.vector.app.features.analytics.plan.Composer
|
|||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.messages.impl.draft.ComposerDraftService
|
||||
import io.element.android.features.messages.impl.draft.FakeComposerDraftService
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||
import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
||||
import io.element.android.features.messages.impl.utils.TextPillificationHelper
|
||||
|
|
@ -52,7 +47,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
|||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||
|
|
@ -90,7 +85,7 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenterFac
|
|||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
import io.element.android.libraries.textcomposer.model.SuggestionType
|
||||
|
|
@ -368,7 +363,7 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - edit sent message`() = runTest {
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
|
|
@ -420,13 +415,13 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - edit sent message event not found`() = runTest {
|
||||
val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
|
||||
val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.failure<Unit>(TimelineException.EventNotFound)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
this.editMessageLambda = timelineEditMessageLambda
|
||||
}
|
||||
val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<Mention> ->
|
||||
val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val fakeMatrixRoom = FakeMatrixRoom(
|
||||
|
|
@ -480,7 +475,7 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - edit not sent message`() = runTest {
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
|
|
@ -532,7 +527,7 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - reply message`() = runTest {
|
||||
val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<Mention>, _: Boolean ->
|
||||
val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
|
|
@ -974,34 +969,34 @@ class MessageComposerPresenterTest {
|
|||
|
||||
// A null suggestion (no suggestion was received) returns nothing
|
||||
initialState.eventSink(MessageComposerEvents.SuggestionReceived(null))
|
||||
assertThat(awaitItem().memberSuggestions).isEmpty()
|
||||
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, "")))
|
||||
assertThat(awaitItem().memberSuggestions)
|
||||
.containsExactly(ResolvedMentionSuggestion.AtRoom, ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
|
||||
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")))
|
||||
assertThat(awaitItem().memberSuggestions).containsExactly(ResolvedMentionSuggestion.AtRoom)
|
||||
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")))
|
||||
assertThat(awaitItem().memberSuggestions).containsExactly(ResolvedMentionSuggestion.Member(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")))
|
||||
assertThat(awaitItem().memberSuggestions).containsExactly(ResolvedMentionSuggestion.Member(david))
|
||||
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, "")))
|
||||
assertThat(awaitItem().memberSuggestions).isEmpty()
|
||||
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, "")))
|
||||
assertThat(awaitItem().memberSuggestions)
|
||||
.containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
|
||||
assertThat(awaitItem().suggestions)
|
||||
.containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1039,13 +1034,12 @@ class MessageComposerPresenterTest {
|
|||
// 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, "")))
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().memberSuggestions)
|
||||
.containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
|
||||
assertThat(awaitItem().suggestions)
|
||||
.containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - insertMention for user in rich text editor`() = runTest {
|
||||
fun `present - InsertSuggestion`() = runTest {
|
||||
val presenter = createPresenter(
|
||||
coroutineScope = this,
|
||||
permalinkBuilder = FakePermalinkBuilder(
|
||||
|
|
@ -1059,7 +1053,7 @@ class MessageComposerPresenterTest {
|
|||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.textEditorState.setHtml("Hey @bo")
|
||||
initialState.eventSink(MessageComposerEvents.InsertMention(ResolvedMentionSuggestion.Member(aRoomMember(userId = A_USER_ID_2))))
|
||||
initialState.eventSink(MessageComposerEvents.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>")
|
||||
|
|
@ -1069,17 +1063,17 @@ class MessageComposerPresenterTest {
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - send messages with intentional mentions`() = runTest {
|
||||
val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<Mention>, _: Boolean ->
|
||||
val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
this.replyMessageLambda = replyMessageLambda
|
||||
this.editMessageLambda = editMessageLambda
|
||||
}
|
||||
val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<Mention> ->
|
||||
val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
|
|
@ -1107,7 +1101,7 @@ class MessageComposerPresenterTest {
|
|||
advanceUntilIdle()
|
||||
|
||||
sendMessageResult.assertions().isCalledOnce()
|
||||
.with(value(A_MESSAGE), any(), value(listOf(Mention.User(A_USER_ID))))
|
||||
.with(value(A_MESSAGE), any(), value(listOf(IntentionalMention.User(A_USER_ID))))
|
||||
|
||||
// Check intentional mentions on reply sent
|
||||
initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
|
||||
|
|
@ -1124,7 +1118,7 @@ class MessageComposerPresenterTest {
|
|||
|
||||
assert(replyMessageLambda)
|
||||
.isCalledOnce()
|
||||
.with(any(), any(), any(), value(listOf(Mention.User(A_USER_ID_2))), value(false))
|
||||
.with(any(), any(), any(), value(listOf(IntentionalMention.User(A_USER_ID_2))), value(false))
|
||||
|
||||
// Check intentional mentions on edit message
|
||||
skipItems(1)
|
||||
|
|
@ -1142,7 +1136,7 @@ class MessageComposerPresenterTest {
|
|||
|
||||
assert(editMessageLambda)
|
||||
.isCalledOnce()
|
||||
.with(any(), any(), any(), any(), value(listOf(Mention.User(A_USER_ID_3))))
|
||||
.with(any(), any(), any(), any(), value(listOf(IntentionalMention.User(A_USER_ID_3))))
|
||||
|
||||
skipItems(1)
|
||||
}
|
||||
|
|
@ -1507,6 +1501,7 @@ class MessageComposerPresenterTest {
|
|||
analyticsService,
|
||||
DefaultMessageComposerContext(),
|
||||
TestRichTextEditorStateFactory(),
|
||||
roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
|
||||
permalinkParser = permalinkParser,
|
||||
permalinkBuilder = permalinkBuilder,
|
||||
|
|
@ -14,10 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.textcomposer
|
||||
package io.element.android.features.messages.impl.messagecomposer
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.messages.impl.messagecomposer.RichTextEditorStateFactory
|
||||
import io.element.android.wysiwyg.compose.RichTextEditorState
|
||||
import io.element.android.wysiwyg.compose.rememberRichTextEditorState
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.timeline
|
|||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
|
|
@ -162,10 +162,10 @@ class TimelineControllerTest {
|
|||
|
||||
@Test
|
||||
fun `test invokeOnCurrentTimeline use the detached timeline and not the live timeline`() = runTest {
|
||||
val lambdaForDetached = lambdaRecorder { _: String, _: String?, _: List<Mention> ->
|
||||
val lambdaForDetached = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val lambdaForLive = lambdaRecorder(ensureNeverCalled = true) { _: String, _: String?, _: List<Mention> ->
|
||||
val lambdaForLive = lambdaRecorder(ensureNeverCalled = true) { _: String, _: String?, _: List<IntentionalMention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val liveTimeline = FakeTimeline(name = "live").apply {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class SharePresenter @AssistedInject constructor(
|
|||
matrixClient.getRoom(roomId)?.sendMessage(
|
||||
body = text,
|
||||
htmlBody = null,
|
||||
mentions = emptyList(),
|
||||
intentionalMentions = emptyList(),
|
||||
)?.isSuccess.orFalse()
|
||||
}
|
||||
.all { it }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue