From e976055253fc21fdc5c01362f7f749b323e9cc0a Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Mon, 28 Oct 2019 18:33:15 +0100
Subject: [PATCH 1/8] Support spoilers in messages

---
 CHANGES.md                                    |  1 +
 .../detail/timeline/item/MessageTextItem.kt   | 21 ++++++---
 .../vector/riotx/features/html/SpoilerSpan.kt | 44 +++++++++++++++++++
 3 files changed, 61 insertions(+), 5 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt

diff --git a/CHANGES.md b/CHANGES.md
index 4a7c0c8fdf..093d2c0b86 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,7 @@ Features ✨:
 Improvements 🙌:
  - Search reaction by name or keyword in emoji picker
  - Handle code tags (#567)
+ - Support spoiler messages
 
 Other changes:
  - Markdown set to off by default (#412)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
index 564adc50f2..e43503aad8 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -31,6 +31,7 @@ import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import me.saket.bettermovementmethod.BetterLinkMovementMethod
+import java.net.URL
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@@ -49,15 +50,25 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
     private val mvmtMethod = BetterLinkMovementMethod.newInstance().also {
         it.setOnLinkClickListener { _, url ->
             // Return false to let android manage the click on the link, or true if the link is handled by the application
-            urlClickCallback?.onUrlClicked(url) == true
+            try {
+                (URL(url))
+                urlClickCallback?.onUrlClicked(url) == true
+            } catch (t: Throwable) {
+                false
+            }
         }
         // We need also to fix the case when long click on link will trigger long click on cell
         it.setOnLinkLongClickListener { tv, url ->
             // Long clicks are handled by parent, return true to block android to do something with url
-            if (urlClickCallback?.onUrlLongClicked(url) == true) {
-                tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0))
-                true
-            } else {
+            try {
+                (URL(url))
+                if (urlClickCallback?.onUrlLongClicked(url) == true) {
+                    tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0))
+                    true
+                } else {
+                    false
+                }
+            } catch (t: Throwable) {
                 false
             }
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt b/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
new file mode 100644
index 0000000000..f57984c4af
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 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 im.vector.riotx.features.html
+
+import android.content.Context
+import android.graphics.Color
+import android.text.TextPaint
+import android.text.style.ClickableSpan
+import android.view.View
+import im.vector.riotx.R
+import im.vector.riotx.features.themes.ThemeUtils
+
+class SpoilerSpan(val bgColor: Int, val context: Context) : ClickableSpan() {
+
+    override fun onClick(widget: View) {
+        isHidden = !isHidden
+        widget.invalidate()
+    }
+
+    var isHidden = true
+
+    override fun updateDrawState(tp: TextPaint) {
+        tp.bgColor = bgColor
+        if (isHidden) {
+            tp.color = Color.TRANSPARENT
+        } else {
+            tp.color = ThemeUtils.getColor(context, R.attr.riotx_text_primary)
+        }
+    }
+}

From 42e0d0f7696fb7070d43e4cdca55eb3348e37d05 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 29 Oct 2019 15:35:15 +0100
Subject: [PATCH 2/8] Improve code to check url validity

---
 .../im/vector/riotx/core/utils/UrlUtils.kt    | 28 +++++++++++++++++++
 .../detail/timeline/item/MessageTextItem.kt   | 22 ++++-----------
 2 files changed, 34 insertions(+), 16 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt

diff --git a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt
new file mode 100644
index 0000000000..3de555f66e
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 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 im.vector.riotx.core.utils
+
+import java.net.URL
+
+fun String.isValidUrl(): Boolean {
+    return try {
+        URL(this)
+        true
+    } catch (t: Throwable) {
+        false
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
index e43503aad8..45a6e2e743 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -24,6 +24,7 @@ import androidx.core.widget.TextViewCompat
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.riotx.R
+import im.vector.riotx.core.utils.isValidUrl
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotx.features.html.PillImageSpan
 import kotlinx.coroutines.Dispatchers
@@ -31,7 +32,6 @@ import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import me.saket.bettermovementmethod.BetterLinkMovementMethod
-import java.net.URL
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@@ -50,25 +50,15 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
     private val mvmtMethod = BetterLinkMovementMethod.newInstance().also {
         it.setOnLinkClickListener { _, url ->
             // Return false to let android manage the click on the link, or true if the link is handled by the application
-            try {
-                (URL(url))
-                urlClickCallback?.onUrlClicked(url) == true
-            } catch (t: Throwable) {
-                false
-            }
+            url.isValidUrl() && urlClickCallback?.onUrlClicked(url) == true
         }
         // We need also to fix the case when long click on link will trigger long click on cell
         it.setOnLinkLongClickListener { tv, url ->
             // Long clicks are handled by parent, return true to block android to do something with url
-            try {
-                (URL(url))
-                if (urlClickCallback?.onUrlLongClicked(url) == true) {
-                    tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0))
-                    true
-                } else {
-                    false
-                }
-            } catch (t: Throwable) {
+            if (url.isValidUrl() && urlClickCallback?.onUrlLongClicked(url) == true) {
+                tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0))
+                true
+            } else {
                 false
             }
         }

From 86667a6d8aa755e1e75d10a18e06bc1440a76d84 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 29 Oct 2019 15:51:18 +0100
Subject: [PATCH 3/8] Passes text color instead of context

---
 .../java/im/vector/riotx/features/html/SpoilerSpan.kt    | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt b/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
index f57984c4af..7dd76dd71b 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
@@ -16,29 +16,26 @@
 
 package im.vector.riotx.features.html
 
-import android.content.Context
 import android.graphics.Color
 import android.text.TextPaint
 import android.text.style.ClickableSpan
 import android.view.View
-import im.vector.riotx.R
-import im.vector.riotx.features.themes.ThemeUtils
 
-class SpoilerSpan(val bgColor: Int, val context: Context) : ClickableSpan() {
+class SpoilerSpan(private val bgColor: Int, private val textColor: Int) : ClickableSpan() {
 
     override fun onClick(widget: View) {
         isHidden = !isHidden
         widget.invalidate()
     }
 
-    var isHidden = true
+    private var isHidden = true
 
     override fun updateDrawState(tp: TextPaint) {
         tp.bgColor = bgColor
         if (isHidden) {
             tp.color = Color.TRANSPARENT
         } else {
-            tp.color = ThemeUtils.getColor(context, R.attr.riotx_text_primary)
+            tp.color = textColor
         }
     }
 }

From 24f1262005b51963deb9c50746d42ab0170b5ef9 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 1 Nov 2019 12:34:22 +0100
Subject: [PATCH 4/8] Merge refactoring

---
 .../riotx/features/html/EventHtmlRenderer.kt  |  1 +
 .../vector/riotx/features/html/SpanHandler.kt | 48 +++++++++++++++++++
 2 files changed, 49 insertions(+)
 create mode 100644 vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt

diff --git a/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
index dc9e21e440..136d667c44 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
@@ -58,5 +58,6 @@ class MatrixHtmlPluginConfigure @Inject constructor(private val context: Context
                 .addHandler(FontTagHandler())
                 .addHandler(MxLinkTagHandler(GlideApp.with(context), context, avatarRenderer, session))
                 .addHandler(MxReplyTagHandler())
+                .addHandler(SpanHandler(context))
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt
new file mode 100644
index 0000000000..33d8f87271
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 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 im.vector.riotx.features.html
+
+import android.content.Context
+import im.vector.riotx.R
+import im.vector.riotx.features.themes.ThemeUtils
+import io.noties.markwon.MarkwonVisitor
+import io.noties.markwon.SpannableBuilder
+import io.noties.markwon.html.HtmlTag
+import io.noties.markwon.html.MarkwonHtmlRenderer
+import io.noties.markwon.html.TagHandler
+
+class SpanHandler(context: Context) : TagHandler() {
+
+    override fun supportedTags() = listOf("span")
+
+    private val spoilerBgColor: Int = ThemeUtils.getColor(context, R.attr.vctr_markdown_block_background_color)
+
+    private val textColor: Int = ThemeUtils.getColor(context, R.attr.riotx_text_primary)
+
+    override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
+        val mxSpoiler = tag.attributes()["data-mx-spoiler"]
+        if (mxSpoiler != null) {
+            SpannableBuilder.setSpans(
+                    visitor.builder(),
+                    SpoilerSpan(spoilerBgColor, textColor),
+                    tag.start(),
+                    tag.end()
+            )
+        } else {
+            // default thing?
+        }
+    }
+}

From 3c4c0ed46acd853f717b81f226efb9574c57c2bf Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 1 Nov 2019 12:37:33 +0100
Subject: [PATCH 5/8] Add /spoiler command

---
 .../java/im/vector/riotx/features/command/Command.kt   |  3 ++-
 .../im/vector/riotx/features/command/CommandParser.kt  |  5 +++++
 .../im/vector/riotx/features/command/ParsedCommand.kt  |  1 +
 .../features/home/room/detail/RoomDetailViewModel.kt   | 10 ++++++++++
 vector/src/main/res/values/strings.xml                 |  2 ++
 5 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt
index 2d4cecdf26..7d745b925b 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt
@@ -37,5 +37,6 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
     KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user),
     CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
     MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
-    CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token);
+    CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
+    SPOILER("/spoiler", "<message>", R.string.command_description_spoiler);
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
index 9cf6510fdb..d108f56f5a 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
@@ -207,6 +207,11 @@ object CommandParser {
                         ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN)
                     }
                 }
+                Command.SPOILER.command                -> {
+                    val message = textMessage.substring(Command.SPOILER.command.length).trim()
+
+                    return ParsedCommand.SendSpoiler(message)
+                }
                 else                                   -> {
                     // Unknown command
                     return ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt
index f6bbed2889..02f5abe540 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt
@@ -45,4 +45,5 @@ sealed class ParsedCommand {
     class ChangeDisplayName(val displayName: String) : ParsedCommand()
     class SetMarkdown(val enable: Boolean) : ParsedCommand()
     object ClearScalarToken : ParsedCommand()
+    class SendSpoiler(val message: String) : ParsedCommand()
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index b1c6aa02fb..2436b7a8b4 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -49,6 +49,7 @@ import im.vector.riotx.BuildConfig
 import im.vector.riotx.R
 import im.vector.riotx.core.extensions.postLiveEvent
 import im.vector.riotx.core.platform.VectorViewModel
+import im.vector.riotx.core.resources.StringProvider
 import im.vector.riotx.core.resources.UserPreferencesProvider
 import im.vector.riotx.core.utils.LiveEvent
 import im.vector.riotx.core.utils.subscribeLogError
@@ -66,6 +67,7 @@ import java.util.concurrent.TimeUnit
 class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
                                                       userPreferencesProvider: UserPreferencesProvider,
                                                       private val vectorPreferences: VectorPreferences,
+                                                      private val stringProvider: StringProvider,
                                                       private val session: Session
 ) : VectorViewModel<RoomDetailViewState>(initialState) {
 
@@ -327,6 +329,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                             _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
                             popDraft()
                         }
+                        is ParsedCommand.SendSpoiler              -> {
+                            room.sendFormattedTextMessage(
+                                    "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})",
+                                    "<span data-mx-spoiler>${slashCommandResult.message}</span>"
+                            )
+                            _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
+                            popDraft()
+                        }
                         is ParsedCommand.ChangeTopic              -> {
                             handleChangeTopicSlashCommand(slashCommandResult)
                             popDraft()
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index eb1eaf1368..be0d97c147 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1168,6 +1168,8 @@
     <string name="command_description_nick">Changes your display nickname</string>
     <string name="command_description_markdown">On/Off markdown</string>
     <string name="command_description_clear_scalar_token">To fix Matrix Apps management</string>
+    <string name="command_description_spoiler">Sends the given message as a spoiler</string>
+    <string name="spoiler">Spoiler</string>
 
     <string name="markdown_has_been_enabled">Markdown has been enabled.</string>
     <string name="markdown_has_been_disabled">Markdown has been disabled.</string>

From b3233d3eb7e5b468b37bf05945147889095caef0 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Mon, 4 Nov 2019 16:46:30 +0100
Subject: [PATCH 6/8] Change spoiler bg colors

---
 .../main/java/im/vector/riotx/features/html/SpanHandler.kt | 5 +++--
 .../main/java/im/vector/riotx/features/html/SpoilerSpan.kt | 7 +++++--
 vector/src/main/res/values/attrs.xml                       | 1 +
 vector/src/main/res/values/theme_dark.xml                  | 1 +
 vector/src/main/res/values/theme_light.xml                 | 1 +
 5 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt
index 33d8f87271..dbc09cf0a9 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/SpanHandler.kt
@@ -28,7 +28,8 @@ class SpanHandler(context: Context) : TagHandler() {
 
     override fun supportedTags() = listOf("span")
 
-    private val spoilerBgColor: Int = ThemeUtils.getColor(context, R.attr.vctr_markdown_block_background_color)
+    private val spoilerBgColorHidden: Int = ThemeUtils.getColor(context, R.attr.vctr_spoiler_background_color)
+    private val spoilerBgColorRevealed: Int = ThemeUtils.getColor(context, R.attr.vctr_markdown_block_background_color)
 
     private val textColor: Int = ThemeUtils.getColor(context, R.attr.riotx_text_primary)
 
@@ -37,7 +38,7 @@ class SpanHandler(context: Context) : TagHandler() {
         if (mxSpoiler != null) {
             SpannableBuilder.setSpans(
                     visitor.builder(),
-                    SpoilerSpan(spoilerBgColor, textColor),
+                    SpoilerSpan(spoilerBgColorHidden, spoilerBgColorRevealed, textColor),
                     tag.start(),
                     tag.end()
             )
diff --git a/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt b/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
index 7dd76dd71b..d8236f0746 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/SpoilerSpan.kt
@@ -21,7 +21,9 @@ import android.text.TextPaint
 import android.text.style.ClickableSpan
 import android.view.View
 
-class SpoilerSpan(private val bgColor: Int, private val textColor: Int) : ClickableSpan() {
+class SpoilerSpan(private val bgColorHidden: Int,
+                  private val bgColorRevealed: Int,
+                  private val textColor: Int) : ClickableSpan() {
 
     override fun onClick(widget: View) {
         isHidden = !isHidden
@@ -31,10 +33,11 @@ class SpoilerSpan(private val bgColor: Int, private val textColor: Int) : Clicka
     private var isHidden = true
 
     override fun updateDrawState(tp: TextPaint) {
-        tp.bgColor = bgColor
         if (isHidden) {
+            tp.bgColor = bgColorHidden
             tp.color = Color.TRANSPARENT
         } else {
+            tp.bgColor = bgColorRevealed
             tp.color = textColor
         }
     }
diff --git a/vector/src/main/res/values/attrs.xml b/vector/src/main/res/values/attrs.xml
index e9a4296add..c30a1d99d9 100644
--- a/vector/src/main/res/values/attrs.xml
+++ b/vector/src/main/res/values/attrs.xml
@@ -34,6 +34,7 @@
         <attr name="vctr_unread_marker_line_color" format="color" />
         <attr name="vctr_markdown_block_background_color" format="color" />
         <attr name="vctr_room_activity_divider_color" format="color" />
+        <attr name="vctr_spoiler_background_color" format="color" />
 
         <!-- tab bar colors -->
         <attr name="vctr_tab_bar_inverted_background_color" format="color" />
diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml
index f09cb0c874..f61a89482a 100644
--- a/vector/src/main/res/values/theme_dark.xml
+++ b/vector/src/main/res/values/theme_dark.xml
@@ -101,6 +101,7 @@
         <item name="vctr_search_mode_room_name_text_color">#CCC3C3C3</item>
         <item name="vctr_unread_marker_line_color">@color/accent_color_dark</item>
         <item name="vctr_markdown_block_background_color">@android:color/black</item>
+        <item name="vctr_spoiler_background_color">#FFFFFFFF</item>
         <item name="vctr_room_activity_divider_color">#565656</item>
 
         <!-- tab bar colors -->
diff --git a/vector/src/main/res/values/theme_light.xml b/vector/src/main/res/values/theme_light.xml
index 1da010b8ff..aa343a11fc 100644
--- a/vector/src/main/res/values/theme_light.xml
+++ b/vector/src/main/res/values/theme_light.xml
@@ -101,6 +101,7 @@
         <item name="vctr_search_mode_room_name_text_color">#333C3C3C</item>
         <item name="vctr_unread_marker_line_color">@color/accent_color_light</item>
         <item name="vctr_markdown_block_background_color">#FFEEEEEE</item>
+        <item name="vctr_spoiler_background_color">#FF000000</item>
         <item name="vctr_room_activity_divider_color">#FFF2F2F2</item>
 
         <!-- tab bar colors -->

From 7206d84a6b0eee2992b8a2e4f1c78c2d3cffcd15 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Mon, 4 Nov 2019 16:51:45 +0100
Subject: [PATCH 7/8] Add FIXME

---
 .../main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
index 136d667c44..327677ade1 100644
--- a/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
+++ b/vector/src/main/java/im/vector/riotx/features/html/EventHtmlRenderer.kt
@@ -58,6 +58,7 @@ class MatrixHtmlPluginConfigure @Inject constructor(private val context: Context
                 .addHandler(FontTagHandler())
                 .addHandler(MxLinkTagHandler(GlideApp.with(context), context, avatarRenderer, session))
                 .addHandler(MxReplyTagHandler())
+                // FIXME (P3) SpanHandler is not recreated when theme is change and it depends on theme colors
                 .addHandler(SpanHandler(context))
     }
 }

From e4b829f0cf9459d6525f2007ddb7c2c676ec45fa Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Mon, 4 Nov 2019 16:53:51 +0100
Subject: [PATCH 8/8] Lift of 'return'

---
 .../riotx/features/command/CommandParser.kt   | 33 ++++++++++---------
 1 file changed, 17 insertions(+), 16 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
index d108f56f5a..a9c20a9ec5 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
@@ -56,11 +56,12 @@ object CommandParser {
                 return ParsedCommand.ErrorEmptySlashCommand
             }
 
-            when (val slashCommand = messageParts.first()) {
+
+            return when (val slashCommand = messageParts.first()) {
                 Command.CHANGE_DISPLAY_NAME.command    -> {
                     val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim()
 
-                    return if (newDisplayName.isNotEmpty()) {
+                    if (newDisplayName.isNotEmpty()) {
                         ParsedCommand.ChangeDisplayName(newDisplayName)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME)
@@ -69,7 +70,7 @@ object CommandParser {
                 Command.TOPIC.command                  -> {
                     val newTopic = textMessage.substring(Command.TOPIC.command.length).trim()
 
-                    return if (newTopic.isNotEmpty()) {
+                    if (newTopic.isNotEmpty()) {
                         ParsedCommand.ChangeTopic(newTopic)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.TOPIC)
@@ -78,12 +79,12 @@ object CommandParser {
                 Command.EMOTE.command                  -> {
                     val message = textMessage.substring(Command.EMOTE.command.length).trim()
 
-                    return ParsedCommand.SendEmote(message)
+                    ParsedCommand.SendEmote(message)
                 }
                 Command.JOIN_ROOM.command              -> {
                     val roomAlias = textMessage.substring(Command.JOIN_ROOM.command.length).trim()
 
-                    return if (roomAlias.isNotEmpty()) {
+                    if (roomAlias.isNotEmpty()) {
                         ParsedCommand.JoinRoom(roomAlias)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
@@ -92,14 +93,14 @@ object CommandParser {
                 Command.PART.command                   -> {
                     val roomAlias = textMessage.substring(Command.PART.command.length).trim()
 
-                    return if (roomAlias.isNotEmpty()) {
+                    if (roomAlias.isNotEmpty()) {
                         ParsedCommand.PartRoom(roomAlias)
                     } else {
                         ParsedCommand.ErrorSyntax(Command.PART)
                     }
                 }
                 Command.INVITE.command                 -> {
-                    return if (messageParts.size == 2) {
+                    if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
                         if (MatrixPatterns.isUserId(userId)) {
@@ -112,7 +113,7 @@ object CommandParser {
                     }
                 }
                 Command.KICK_USER.command              -> {
-                    return if (messageParts.size >= 2) {
+                    if (messageParts.size >= 2) {
                         val userId = messageParts[1]
                         if (MatrixPatterns.isUserId(userId)) {
                             val reason = textMessage.substring(Command.KICK_USER.command.length
@@ -128,7 +129,7 @@ object CommandParser {
                     }
                 }
                 Command.BAN_USER.command               -> {
-                    return if (messageParts.size >= 2) {
+                    if (messageParts.size >= 2) {
                         val userId = messageParts[1]
                         if (MatrixPatterns.isUserId(userId)) {
                             val reason = textMessage.substring(Command.BAN_USER.command.length
@@ -144,7 +145,7 @@ object CommandParser {
                     }
                 }
                 Command.UNBAN_USER.command             -> {
-                    return if (messageParts.size == 2) {
+                    if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
                         if (MatrixPatterns.isUserId(userId)) {
@@ -157,7 +158,7 @@ object CommandParser {
                     }
                 }
                 Command.SET_USER_POWER_LEVEL.command   -> {
-                    return if (messageParts.size == 3) {
+                    if (messageParts.size == 3) {
                         val userId = messageParts[1]
                         if (MatrixPatterns.isUserId(userId)) {
                             val powerLevelsAsString = messageParts[2]
@@ -177,7 +178,7 @@ object CommandParser {
                     }
                 }
                 Command.RESET_USER_POWER_LEVEL.command -> {
-                    return if (messageParts.size == 2) {
+                    if (messageParts.size == 2) {
                         val userId = messageParts[1]
 
                         if (MatrixPatterns.isUserId(userId)) {
@@ -190,7 +191,7 @@ object CommandParser {
                     }
                 }
                 Command.MARKDOWN.command               -> {
-                    return if (messageParts.size == 2) {
+                    if (messageParts.size == 2) {
                         when {
                             "on".equals(messageParts[1], true)  -> ParsedCommand.SetMarkdown(true)
                             "off".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(false)
@@ -201,7 +202,7 @@ object CommandParser {
                     }
                 }
                 Command.CLEAR_SCALAR_TOKEN.command     -> {
-                    return if (messageParts.size == 1) {
+                    if (messageParts.size == 1) {
                         ParsedCommand.ClearScalarToken
                     } else {
                         ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN)
@@ -210,11 +211,11 @@ object CommandParser {
                 Command.SPOILER.command                -> {
                     val message = textMessage.substring(Command.SPOILER.command.length).trim()
 
-                    return ParsedCommand.SendSpoiler(message)
+                    ParsedCommand.SendSpoiler(message)
                 }
                 else                                   -> {
                     // Unknown command
-                    return ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
+                    ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
                 }
             }
         }