Merge branch 'main' into wallet

# Conflicts:
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt
#	libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt
This commit is contained in:
Cobb 2026-04-16 22:05:16 -07:00
commit 0ef6b69a79
912 changed files with 17051 additions and 4425 deletions

View file

@ -134,7 +134,7 @@ private fun ReplyToModeView(
modifier
.clip(RoundedCornerShape(6.dp))
.background(ElementTheme.colors.bgCanvasDefault)
.border(1.dp, ElementTheme.colors.borderInteractiveSecondary, RoundedCornerShape(6.dp))
.border(1.dp, ElementTheme.colors.separatorPrimary, RoundedCornerShape(6.dp))
.padding(4.dp)
) {
InReplyToView(

View file

@ -14,6 +14,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
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.room.RoomMember
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
@Immutable
sealed interface ResolvedSuggestion {
@ -33,11 +34,7 @@ sealed interface ResolvedSuggestion {
)
}
/**
* A slash command suggestion (e.g., /pay).
*/
data class Command(
val command: String,
val description: String,
val command: SlashCommandSuggestion,
) : ResolvedSuggestion
}

View file

@ -61,21 +61,29 @@ class MarkdownTextEditorState(
}
is ResolvedSuggestion.Member -> {
val currentText = SpannableStringBuilder(text.value())
val mentionSpan = mentionSpanProvider.createUserMentionSpan(resolvedSuggestion.roomMember.userId)
val userId = resolvedSuggestion.roomMember.userId
val mentionSpan = mentionSpanProvider.createUserMentionSpan(userId)
currentText.replace(suggestion.start, suggestion.end, "@ ")
val end = suggestion.start + 1
currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
this.text.update(currentText, true)
this.selection = IntRange(end + 1, end + 1)
text.update(currentText, true)
selection = IntRange(end + 1, end + 1)
}
is ResolvedSuggestion.Alias -> {
val currentText = SpannableStringBuilder(text.value())
val mentionSpan = mentionSpanProvider.createRoomMentionSpan(resolvedSuggestion.roomAlias.toRoomIdOrAlias())
val roomAlias = resolvedSuggestion.roomAlias
val mentionSpan = mentionSpanProvider.createRoomMentionSpan(roomAlias.toRoomIdOrAlias())
currentText.replace(suggestion.start, suggestion.end, "# ")
val end = suggestion.start + 1
currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
this.text.update(currentText, true)
this.selection = IntRange(end + 1, end + 1)
text.update(currentText, true)
selection = IntRange(end + 1, end + 1)
}
is ResolvedSuggestion.Command -> {
// Just insert the command text
text.update("${resolvedSuggestion.command.command} ", true)
val length = resolvedSuggestion.command.command.length + 1
selection = IntRange(length, length)
}
is ResolvedSuggestion.Command -> {
// Insert the command text with a trailing space

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_a11y_add_attachment">"添付ファイルを追加"</string>
<string name="rich_text_editor_bullet_list">"箇条リスト"</string>
<string name="rich_text_editor_close_formatting_options">"書式設定を中止して閉じる"</string>
<string name="rich_text_editor_code_block">"コードブロックを切替"</string>
<string name="rich_text_editor_composer_caption_placeholder">"キャプションを追加"</string>
<string name="rich_text_editor_composer_encrypted_placeholder">"暗号化されたメッセージ…"</string>
<string name="rich_text_editor_composer_placeholder">"メッセージ…"</string>
<string name="rich_text_editor_composer_unencrypted_placeholder">"暗号化されていないメッセージ…"</string>
<string name="rich_text_editor_create_link">"リンクを作成"</string>
<string name="rich_text_editor_edit_link">"リンクを編集"</string>
<string name="rich_text_editor_format_action">"%1$s の状態: %2$s"</string>
<string name="rich_text_editor_format_bold">"太字"</string>
<string name="rich_text_editor_format_italic">"斜体"</string>
<string name="rich_text_editor_format_state_disabled">"無効"</string>
<string name="rich_text_editor_format_state_off">"オフ"</string>
<string name="rich_text_editor_format_state_on">"オン"</string>
<string name="rich_text_editor_format_strikethrough">"取り消し線を追加"</string>
<string name="rich_text_editor_format_underline">"下線を追加"</string>
<string name="rich_text_editor_full_screen_toggle">"全画面モードの切替"</string>
<string name="rich_text_editor_indent">"インデント"</string>
<string name="rich_text_editor_inline_code">"コード部の書式"</string>
<string name="rich_text_editor_link">"リンクを設定"</string>
<string name="rich_text_editor_numbered_list">"番号リスト"</string>
<string name="rich_text_editor_open_compose_options">"記述設定を開く"</string>
<string name="rich_text_editor_quote">"引用の表示切替"</string>
<string name="rich_text_editor_remove_link">"リンクを削除"</string>
<string name="rich_text_editor_unindent">"インデントを削除"</string>
<string name="rich_text_editor_url_placeholder">"リンク"</string>
<string name="screen_media_upload_preview_caption_warning">"古いアプリケーションを使用しているユーザーはキャプションを見られない可能性があります。"</string>
<string name="screen_room_voice_message_tooltip">"長押しで録音"</string>
</resources>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_a11y_add_attachment">"Thêm tệp đính kèm"</string>
<string name="rich_text_editor_bullet_list">"Chuyển đổi danh sách dấu đầu dòng"</string>
<string name="rich_text_editor_close_formatting_options">"Hủy và đóng định dạng văn bản"</string>
<string name="rich_text_editor_code_block">"Bật/tắt khối mã"</string>
<string name="rich_text_editor_composer_placeholder">"Tin nhắn…"</string>
<string name="rich_text_editor_create_link">"Tạo liên kết"</string>
<string name="rich_text_editor_edit_link">"Sửa liên kết"</string>
<string name="rich_text_editor_format_bold">"Áp dụng định dạng in đậm"</string>
<string name="rich_text_editor_format_italic">"Áp dụng định dạng in nghiêng"</string>
<string name="rich_text_editor_format_strikethrough">"Áp dụng định dạng gạch ngang"</string>
<string name="rich_text_editor_format_underline">"Áp dụng định dạng gạch chân"</string>
<string name="rich_text_editor_full_screen_toggle">"Bật/tắt chế độ toàn màn hình"</string>
<string name="rich_text_editor_indent">"Thụt lề"</string>
<string name="rich_text_editor_inline_code">"Áp dụng định dạng mã trong dòng"</string>
<string name="rich_text_editor_link">"Đặt liên kết"</string>
<string name="rich_text_editor_numbered_list">"Chuyển đổi danh sách được đánh số"</string>
<string name="rich_text_editor_open_compose_options">"Mở tùy chọn soạn tin"</string>
<string name="rich_text_editor_quote">"Chuyển sang Trích dẫn"</string>
<string name="rich_text_editor_remove_link">"Xóa liên kết"</string>
<string name="rich_text_editor_unindent">"Bỏ thụt lề"</string>
<string name="rich_text_editor_url_placeholder">"Liên kết"</string>
<string name="screen_room_voice_message_tooltip">"Nhấn giữ để ghi âm"</string>
</resources>

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
import io.element.android.libraries.textcomposer.impl.mentions.aMentionSpanProvider
import io.element.android.libraries.textcomposer.mentions.MentionSpan
import io.element.android.libraries.textcomposer.mentions.MentionType
@ -42,6 +43,7 @@ class MarkdownTextEditorStateTest {
val mentionSpanProvider = aMentionSpanProvider()
state.insertSuggestion(suggestion, mentionSpanProvider)
assertThat(state.getMentions()).isEmpty()
assertThat(state.text.value().toString()).isEqualTo("Hello @")
}
@Test
@ -53,6 +55,7 @@ class MarkdownTextEditorStateTest {
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
state.insertSuggestion(suggestion, mentionSpanProvider)
assertThat(state.text.value().toString()).isEqualTo("Hello # ")
}
@Test
@ -64,6 +67,19 @@ class MarkdownTextEditorStateTest {
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
state.insertSuggestion(suggestion, mentionSpanProvider)
assertThat(state.text.value().toString()).isEqualTo("Hello # ")
}
@Test
fun `insertSuggestion - command`() {
val state = aMarkdownTextEditorState(initialText = "/rai", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 0, end = 3, type = SuggestionType.Command, text = "/rainbow")
}
val suggestion = aSlashCommandSuggestion()
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
state.insertSuggestion(suggestion, mentionSpanProvider)
assertThat(state.text.value().toString()).isEqualTo("/rainbow ")
}
@Test
@ -74,6 +90,7 @@ class MarkdownTextEditorStateTest {
val mentionSpanProvider = aMentionSpanProvider()
state.insertSuggestion(mention, mentionSpanProvider)
assertThat(state.getMentions()).isEmpty()
assertThat(state.text.value().toString()).isEqualTo("Hello @")
}
@Test
@ -91,6 +108,7 @@ class MarkdownTextEditorStateTest {
val mentions = state.getMentions()
assertThat(mentions).isNotEmpty()
assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId).isEqualTo(member.userId)
assertThat(state.text.value().toString()).isEqualTo("Hello @ ")
}
@Test
@ -107,15 +125,14 @@ class MarkdownTextEditorStateTest {
val mentions = state.getMentions()
assertThat(mentions).isNotEmpty()
assertThat(mentions.firstOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
assertThat(state.text.value().toString()).isEqualTo("Hello @ ")
}
@Test
fun `getMessageMarkdown - when there are no MentionSpans returns the same text`() {
val text = "No mentions here"
val state = aMarkdownTextEditorState(initialText = text, initialFocus = true)
val markdown = state.getMessageMarkdown(FakePermalinkBuilder())
assertThat(markdown).isEqualTo(text)
}
@ -128,19 +145,17 @@ class MarkdownTextEditorStateTest {
)
val state = aMarkdownTextEditorState(initialText = text, initialFocus = true)
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
val markdown = state.getMessageMarkdown(permalinkBuilder = permalinkBuilder)
assertThat(markdown).isEqualTo(
"Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room" +
" and a room [#room:domain.org](https://matrix.to/#/#room:domain.org)"
)
assertThat(state.text.value().toString()).isEqualTo("Hello @ and everyone in @ and a room #room:domain.org")
}
@Test
fun `getMentions - when there are no MentionSpans returns empty list of mentions`() {
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
assertThat(state.getMentions()).isEmpty()
}
@ -148,9 +163,7 @@ class MarkdownTextEditorStateTest {
fun `getMentions - when there are MentionSpans returns a list of mentions`() {
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
val mentions = state.getMentions()
assertThat(mentions).isNotEmpty()
assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId?.value).isEqualTo("@alice:matrix.org")
assertThat(mentions.lastOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
@ -184,4 +197,14 @@ class MarkdownTextEditorStateTest {
roomAvatarUrl = null
)
}
private fun aSlashCommandSuggestion(): ResolvedSuggestion.Command {
return ResolvedSuggestion.Command(
command = SlashCommandSuggestion(
command = "/rainbow",
parameters = "param",
description = "Make the text colorful 🌈",
),
)
}
}