From d922126f4b0c0a8dac73673653e43a3beb714bec Mon Sep 17 00:00:00 2001 From: jonnyandrew Date: Wed, 8 Feb 2023 17:47:22 +0000 Subject: [PATCH] [Rich text editor] Add code block, quote and list indentation actions (#8045) * Add remaining rich text editor actions * Render code blocks in the timeline * Hide indentation buttons when not in a list --- changelog.d/8045.feature | 1 + dependencies.gradle | 2 +- .../src/main/res/values/strings.xml | 4 ++ .../features/html/EventHtmlRendererTest.kt | 4 +- .../detail/composer/RichTextComposerLayout.kt | 47 +++++++++++++------ .../app/features/html/EventHtmlRenderer.kt | 3 +- .../app/features/html/HtmlCodeHandlers.kt | 14 +++++- .../main/res/drawable/ic_composer_bold.xml | 8 ++-- .../res/drawable/ic_composer_bullet_list.xml | 13 +++-- .../res/drawable/ic_composer_code_block.xml | 9 ++++ .../res/drawable/ic_composer_collapse.xml | 4 +- .../res/drawable/ic_composer_full_screen.xml | 4 +- .../main/res/drawable/ic_composer_indent.xml | 12 +++++ .../res/drawable/ic_composer_inline_code.xml | 18 +++---- .../main/res/drawable/ic_composer_italic.xml | 8 ++-- .../main/res/drawable/ic_composer_link.xml | 12 ++--- .../drawable/ic_composer_numbered_list.xml | 36 +++++++------- .../main/res/drawable/ic_composer_quote.xml | 18 +++++++ .../ic_composer_rich_text_editor_close.xml | 6 +-- .../ic_composer_rich_text_editor_edit.xml | 12 ++--- .../drawable/ic_composer_strikethrough.xml | 12 ++--- .../res/drawable/ic_composer_underlined.xml | 13 +++-- .../res/drawable/ic_composer_unindent.xml | 12 +++++ .../src/main/res/drawable/ic_microphone.xml | 2 +- .../res/drawable/ic_rich_composer_add.xml | 4 +- 25 files changed, 182 insertions(+), 96 deletions(-) create mode 100644 changelog.d/8045.feature create mode 100644 vector/src/main/res/drawable/ic_composer_code_block.xml create mode 100644 vector/src/main/res/drawable/ic_composer_indent.xml create mode 100644 vector/src/main/res/drawable/ic_composer_quote.xml create mode 100644 vector/src/main/res/drawable/ic_composer_unindent.xml diff --git a/changelog.d/8045.feature b/changelog.d/8045.feature new file mode 100644 index 0000000000..89b9111def --- /dev/null +++ b/changelog.d/8045.feature @@ -0,0 +1 @@ +[Rich text editor] Add code block, quote and indentation actions \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index a876db4fc5..a87fd3f868 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.23.0" + 'wysiwyg' : "io.element.android:wysiwyg:1.0.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index de3fa20916..17fbadd776 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3503,7 +3503,11 @@ Set link Toggle numbered list Toggle bullet list + Indent + Unindent + Toggle quote Apply inline code format + Toggle code block Toggle full screen mode Text diff --git a/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt b/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt index 7f3293e7d1..c095b33b44 100644 --- a/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt @@ -21,6 +21,7 @@ import androidx.core.text.toSpanned import androidx.test.platform.app.InstrumentationRegistry import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.toTestSpan import im.vector.app.features.settings.VectorPreferences import io.mockk.every @@ -40,9 +41,10 @@ class EventHtmlRendererTest { every { it.isRichTextEditorEnabled() } returns false } private val fakeSessionHolder = mockk() + private val fakeDimensionConverter = mockk() private val renderer = EventHtmlRenderer( - MatrixHtmlPluginConfigure(ColorProvider(context), context.resources, fakeVectorPreferences), + MatrixHtmlPluginConfigure(ColorProvider(context), context.resources, fakeVectorPreferences, fakeDimensionConverter), context, fakeVectorPreferences, fakeSessionHolder, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 2e0ef1f70a..a13ef25d62 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -232,6 +232,27 @@ internal class RichTextComposerLayout @JvmOverloads constructor( addRichTextMenuItem(R.drawable.ic_composer_strikethrough, R.string.rich_text_editor_format_strikethrough, ComposerAction.STRIKE_THROUGH) { views.richTextComposerEditText.toggleInlineFormat(InlineFormat.StrikeThrough) } + addRichTextMenuItem(R.drawable.ic_composer_bullet_list, R.string.rich_text_editor_bullet_list, ComposerAction.UNORDERED_LIST) { + views.richTextComposerEditText.toggleList(ordered = false) + } + addRichTextMenuItem(R.drawable.ic_composer_numbered_list, R.string.rich_text_editor_numbered_list, ComposerAction.ORDERED_LIST) { + views.richTextComposerEditText.toggleList(ordered = true) + } + addRichTextMenuItem(R.drawable.ic_composer_indent, R.string.rich_text_editor_indent, ComposerAction.INDENT) { + views.richTextComposerEditText.indent() + } + addRichTextMenuItem(R.drawable.ic_composer_unindent, R.string.rich_text_editor_unindent, ComposerAction.UNINDENT) { + views.richTextComposerEditText.unindent() + } + addRichTextMenuItem(R.drawable.ic_composer_quote, R.string.rich_text_editor_quote, ComposerAction.QUOTE) { + views.richTextComposerEditText.toggleQuote() + } + addRichTextMenuItem(R.drawable.ic_composer_inline_code, R.string.rich_text_editor_inline_code, ComposerAction.INLINE_CODE) { + views.richTextComposerEditText.toggleInlineFormat(InlineFormat.InlineCode) + } + addRichTextMenuItem(R.drawable.ic_composer_code_block, R.string.rich_text_editor_code_block, ComposerAction.CODE_BLOCK) { + views.richTextComposerEditText.toggleCodeBlock() + } addRichTextMenuItem(R.drawable.ic_composer_link, R.string.rich_text_editor_link, ComposerAction.LINK) { views.richTextComposerEditText.getLinkAction()?.let { when (it) { @@ -240,15 +261,6 @@ internal class RichTextComposerLayout @JvmOverloads constructor( } } } - addRichTextMenuItem(R.drawable.ic_composer_bullet_list, R.string.rich_text_editor_bullet_list, ComposerAction.UNORDERED_LIST) { - views.richTextComposerEditText.toggleList(ordered = false) - } - addRichTextMenuItem(R.drawable.ic_composer_numbered_list, R.string.rich_text_editor_numbered_list, ComposerAction.ORDERED_LIST) { - views.richTextComposerEditText.toggleList(ordered = true) - } - addRichTextMenuItem(R.drawable.ic_composer_inline_code, R.string.rich_text_editor_inline_code, ComposerAction.INLINE_CODE) { - views.richTextComposerEditText.toggleInlineFormat(InlineFormat.InlineCode) - } } fun setLink(link: String?) = @@ -331,11 +343,11 @@ internal class RichTextComposerLayout @JvmOverloads constructor( * Updates the non-active input with the contents of the active input. */ private fun syncEditTexts() = - if (isTextFormattingEnabled) { - views.plainTextComposerEditText.setText(views.richTextComposerEditText.getMarkdown()) - } else { - views.richTextComposerEditText.setMarkdown(views.plainTextComposerEditText.text.toString()) - } + if (isTextFormattingEnabled) { + views.plainTextComposerEditText.setText(views.richTextComposerEditText.getMarkdown()) + } else { + views.richTextComposerEditText.setMarkdown(views.plainTextComposerEditText.text.toString()) + } private fun addRichTextMenuItem(@DrawableRes iconId: Int, @StringRes description: Int, action: ComposerAction, onClick: () -> Unit) { val inflater = LayoutInflater.from(context) @@ -355,6 +367,13 @@ internal class RichTextComposerLayout @JvmOverloads constructor( val stateForAction = menuState[action] button.isEnabled = stateForAction != ActionState.DISABLED button.isSelected = stateForAction == ActionState.REVERSED + + if (action == ComposerAction.INDENT || action == ComposerAction.UNINDENT) { + val indentationButtonIsVisible = + menuState[ComposerAction.ORDERED_LIST] == ActionState.REVERSED || + menuState[ComposerAction.UNORDERED_LIST] == ActionState.REVERSED + button.isVisible = indentationButtonIsVisible + } } fun estimateCollapsedHeight(): Int { diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index bc9ba0b85a..e75d12f1d8 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -240,6 +240,7 @@ class MatrixHtmlPluginConfigure @Inject constructor( private val colorProvider: ColorProvider, private val resources: Resources, private val vectorPreferences: VectorPreferences, + private val dimensionConverter: DimensionConverter, ) : HtmlPlugin.HtmlConfigure { override fun configureHtml(plugin: HtmlPlugin) { @@ -248,7 +249,7 @@ class MatrixHtmlPluginConfigure @Inject constructor( .addHandler(FontTagHandler()) .addHandler(ParagraphHandler(DimensionConverter(resources))) .addHandler(MxReplyTagHandler()) - .addHandler(CodePostProcessorTagHandler(vectorPreferences)) + .addHandler(CodePostProcessorTagHandler(vectorPreferences, dimensionConverter)) .addHandler(CodePreTagHandler()) .addHandler(CodeTagHandler()) .addHandler(SpanHandler(colorProvider)) diff --git a/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt b/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt index 295b74c7a9..3175996ba1 100644 --- a/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt +++ b/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt @@ -16,7 +16,9 @@ package im.vector.app.features.html +import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.settings.VectorPreferences +import io.element.android.wysiwyg.spans.CodeBlockSpan import io.element.android.wysiwyg.spans.InlineCodeSpan import io.noties.markwon.MarkwonVisitor import io.noties.markwon.SpannableBuilder @@ -68,6 +70,7 @@ internal class CodePreTagHandler : TagHandler() { internal class CodePostProcessorTagHandler( private val vectorPreferences: VectorPreferences, + private val dimensionConverter: DimensionConverter, ) : TagHandler() { override fun supportedTags() = listOf(HtmlRootTagPlugin.ROOT_TAG_NAME) @@ -90,6 +93,7 @@ internal class CodePostProcessorTagHandler( val intermediateCodeSpan = code.what as IntermediateCodeSpan val theme = visitor.configuration().theme() val span = intermediateCodeSpan.toFinalCodeSpan(theme) + SpannableBuilder.setSpans( visitor.builder(), span, code.start, code.end ) @@ -98,9 +102,15 @@ internal class CodePostProcessorTagHandler( private fun IntermediateCodeSpan.toFinalCodeSpan( markwonTheme: MarkwonTheme - ): Any = if (vectorPreferences.isRichTextEditorEnabled() && !isBlock) { - InlineCodeSpan() + ): Any = if (vectorPreferences.isRichTextEditorEnabled()) { + toRichTextEditorSpan() } else { HtmlCodeSpan(markwonTheme, isBlock) } + + private fun IntermediateCodeSpan.toRichTextEditorSpan() = if (isBlock) { + CodeBlockSpan(dimensionConverter.dpToPx(10), dimensionConverter.dpToPx(4)) + } else { + InlineCodeSpan() + } } diff --git a/vector/src/main/res/drawable/ic_composer_bold.xml b/vector/src/main/res/drawable/ic_composer_bold.xml index 3d9a10d16b..2624bbddd6 100644 --- a/vector/src/main/res/drawable/ic_composer_bold.xml +++ b/vector/src/main/res/drawable/ic_composer_bold.xml @@ -3,8 +3,8 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - + diff --git a/vector/src/main/res/drawable/ic_composer_bullet_list.xml b/vector/src/main/res/drawable/ic_composer_bullet_list.xml index f6febc88f0..81e57753b0 100644 --- a/vector/src/main/res/drawable/ic_composer_bullet_list.xml +++ b/vector/src/main/res/drawable/ic_composer_bullet_list.xml @@ -3,11 +3,10 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - - - - + + + + diff --git a/vector/src/main/res/drawable/ic_composer_code_block.xml b/vector/src/main/res/drawable/ic_composer_code_block.xml new file mode 100644 index 0000000000..ca4ab42a46 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_code_block.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_composer_collapse.xml b/vector/src/main/res/drawable/ic_composer_collapse.xml index 724a833761..d123caac5f 100644 --- a/vector/src/main/res/drawable/ic_composer_collapse.xml +++ b/vector/src/main/res/drawable/ic_composer_collapse.xml @@ -4,6 +4,6 @@ android:viewportWidth="20" android:viewportHeight="20"> + android:fillColor="?vctr_content_quaternary" + android:pathData="M10.708,10Q10.438,10 10.219,9.781Q10,9.562 10,9.292V4.542Q10,4.354 10.146,4.219Q10.292,4.083 10.458,4.083Q10.646,4.083 10.781,4.219Q10.917,4.354 10.917,4.542V8.438L16.375,3Q16.5,2.854 16.688,2.854Q16.875,2.854 17,3Q17.146,3.125 17.146,3.312Q17.146,3.5 17,3.625L11.562,9.083H15.458Q15.646,9.083 15.781,9.229Q15.917,9.375 15.917,9.542Q15.917,9.729 15.781,9.865Q15.646,10 15.458,10ZM3,17Q2.854,16.875 2.854,16.688Q2.854,16.5 3,16.375L8.438,10.917H4.542Q4.354,10.917 4.219,10.771Q4.083,10.625 4.083,10.458Q4.083,10.271 4.219,10.135Q4.354,10 4.542,10H9.292Q9.562,10 9.781,10.219Q10,10.438 10,10.708V15.458Q10,15.646 9.854,15.781Q9.708,15.917 9.542,15.917Q9.354,15.917 9.219,15.781Q9.083,15.646 9.083,15.458V11.562L3.625,17Q3.5,17.146 3.312,17.146Q3.125,17.146 3,17Z" /> diff --git a/vector/src/main/res/drawable/ic_composer_full_screen.xml b/vector/src/main/res/drawable/ic_composer_full_screen.xml index de1862c09b..6c7d7d6731 100644 --- a/vector/src/main/res/drawable/ic_composer_full_screen.xml +++ b/vector/src/main/res/drawable/ic_composer_full_screen.xml @@ -4,6 +4,6 @@ android:viewportWidth="20" android:viewportHeight="20"> + android:fillColor="?vctr_content_quaternary" + android:pathData="M3.625,17.083Q3.354,17.083 3.135,16.865Q2.917,16.646 2.917,16.375V11.625Q2.917,11.438 3.062,11.302Q3.208,11.167 3.375,11.167Q3.562,11.167 3.698,11.302Q3.833,11.438 3.833,11.625V15.5L15.5,3.833H11.625Q11.438,3.833 11.302,3.688Q11.167,3.542 11.167,3.375Q11.167,3.188 11.302,3.052Q11.438,2.917 11.625,2.917H16.375Q16.646,2.917 16.865,3.135Q17.083,3.354 17.083,3.625V8.375Q17.083,8.562 16.938,8.698Q16.792,8.833 16.625,8.833Q16.438,8.833 16.302,8.698Q16.167,8.562 16.167,8.375V4.5L4.5,16.167H8.375Q8.562,16.167 8.698,16.312Q8.833,16.458 8.833,16.625Q8.833,16.812 8.698,16.948Q8.562,17.083 8.375,17.083Z" /> diff --git a/vector/src/main/res/drawable/ic_composer_indent.xml b/vector/src/main/res/drawable/ic_composer_indent.xml new file mode 100644 index 0000000000..5aa8cd73da --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_indent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_composer_inline_code.xml b/vector/src/main/res/drawable/ic_composer_inline_code.xml index 1743b757af..e041523d1c 100644 --- a/vector/src/main/res/drawable/ic_composer_inline_code.xml +++ b/vector/src/main/res/drawable/ic_composer_inline_code.xml @@ -3,13 +3,13 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - - - + + + diff --git a/vector/src/main/res/drawable/ic_composer_italic.xml b/vector/src/main/res/drawable/ic_composer_italic.xml index faa4f89cd4..7e293577ea 100644 --- a/vector/src/main/res/drawable/ic_composer_italic.xml +++ b/vector/src/main/res/drawable/ic_composer_italic.xml @@ -3,8 +3,8 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - + diff --git a/vector/src/main/res/drawable/ic_composer_link.xml b/vector/src/main/res/drawable/ic_composer_link.xml index 6d0f731ed9..5a38a6d2c3 100644 --- a/vector/src/main/res/drawable/ic_composer_link.xml +++ b/vector/src/main/res/drawable/ic_composer_link.xml @@ -3,10 +3,10 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - + diff --git a/vector/src/main/res/drawable/ic_composer_numbered_list.xml b/vector/src/main/res/drawable/ic_composer_numbered_list.xml index d6a860c4c8..47522233a4 100644 --- a/vector/src/main/res/drawable/ic_composer_numbered_list.xml +++ b/vector/src/main/res/drawable/ic_composer_numbered_list.xml @@ -3,22 +3,22 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - - - - - - + + + + + + diff --git a/vector/src/main/res/drawable/ic_composer_quote.xml b/vector/src/main/res/drawable/ic_composer_quote.xml new file mode 100644 index 0000000000..42336fbecd --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_quote.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_composer_rich_text_editor_close.xml b/vector/src/main/res/drawable/ic_composer_rich_text_editor_close.xml index c461470de5..c432cc3cfb 100644 --- a/vector/src/main/res/drawable/ic_composer_rich_text_editor_close.xml +++ b/vector/src/main/res/drawable/ic_composer_rich_text_editor_close.xml @@ -3,7 +3,7 @@ android:height="12dp" android:viewportWidth="12" android:viewportHeight="12"> - + diff --git a/vector/src/main/res/drawable/ic_composer_rich_text_editor_edit.xml b/vector/src/main/res/drawable/ic_composer_rich_text_editor_edit.xml index 4556974221..155f3be031 100644 --- a/vector/src/main/res/drawable/ic_composer_rich_text_editor_edit.xml +++ b/vector/src/main/res/drawable/ic_composer_rich_text_editor_edit.xml @@ -3,10 +3,10 @@ android:height="12dp" android:viewportWidth="12" android:viewportHeight="12"> - - + + diff --git a/vector/src/main/res/drawable/ic_composer_strikethrough.xml b/vector/src/main/res/drawable/ic_composer_strikethrough.xml index 3970c95381..7ad78919ac 100644 --- a/vector/src/main/res/drawable/ic_composer_strikethrough.xml +++ b/vector/src/main/res/drawable/ic_composer_strikethrough.xml @@ -3,10 +3,10 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - - + + diff --git a/vector/src/main/res/drawable/ic_composer_underlined.xml b/vector/src/main/res/drawable/ic_composer_underlined.xml index fe18d60185..170edd6641 100644 --- a/vector/src/main/res/drawable/ic_composer_underlined.xml +++ b/vector/src/main/res/drawable/ic_composer_underlined.xml @@ -3,11 +3,10 @@ android:height="44dp" android:viewportWidth="44" android:viewportHeight="44"> - - - - + + + + diff --git a/vector/src/main/res/drawable/ic_composer_unindent.xml b/vector/src/main/res/drawable/ic_composer_unindent.xml new file mode 100644 index 0000000000..0585f21b03 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_unindent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_microphone.xml b/vector/src/main/res/drawable/ic_microphone.xml index 270fe456fa..d1366aeca9 100644 --- a/vector/src/main/res/drawable/ic_microphone.xml +++ b/vector/src/main/res/drawable/ic_microphone.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:fillColor="?vctr_content_tertiary"/> diff --git a/vector/src/main/res/drawable/ic_rich_composer_add.xml b/vector/src/main/res/drawable/ic_rich_composer_add.xml index 3a90a40902..8d082ed3b3 100644 --- a/vector/src/main/res/drawable/ic_rich_composer_add.xml +++ b/vector/src/main/res/drawable/ic_rich_composer_add.xml @@ -5,11 +5,11 @@ android:viewportHeight="36"> + android:fillColor="?vctr_system"/>