From 85417eb24c2dff8039ac3fb5c9a8227ebb7b29b3 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Fri, 2 Oct 2020 21:12:19 +0200
Subject: [PATCH 01/17] Add SearchView to filter members.

Author: Onuray (I squashed 2 commits)
---
 CHANGES.md                                    |  1 +
 .../members/RoomMemberListAction.kt           |  1 +
 .../members/RoomMemberListController.kt       |  4 ++-
 .../members/RoomMemberListFragment.kt         | 17 +++++++++++++
 .../members/RoomMemberListViewModel.kt        | 25 +++++++++++++++++++
 .../members/RoomMemberListViewState.kt        |  1 +
 .../layout/fragment_room_setting_generic.xml  | 14 ++++++++++-
 7 files changed, 61 insertions(+), 2 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 4a158086a2..f5d6fca073 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,7 @@ Improvements 🙌:
  - Small optimisation of scrolling experience in timeline (#2114)
  - Allow user to reset cross signing if he has no way to recover (#2052)
  - Create home shortcut for any room (#1525)
+ - Filter room member by name (#2184)
 
 Bugfix 🐛:
  - Improve support for image/audio/video/file selection with intent changes (#1376)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListAction.kt
index c61dcb98f5..342a2e8585 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListAction.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListAction.kt
@@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
 
 sealed class RoomMemberListAction : VectorViewModelAction {
     data class RevokeThreePidInvite(val stateKey: String) : RoomMemberListAction()
+    data class FilterMemberList(val searchTerm: String) : RoomMemberListAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
index 59b1bca26f..f0c7e146c6 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
@@ -53,7 +53,9 @@ class RoomMemberListController @Inject constructor(
     }
 
     override fun buildModels(data: RoomMemberListViewState?) {
-        val roomMembersByPowerLevel = data?.roomMemberSummaries?.invoke() ?: return
+        data ?: return
+
+        val roomMembersByPowerLevel = data.filteredRoomMemberSummaries ?: data.roomMemberSummaries.invoke() ?: return
         val threePidInvites = data.threePidInvites().orEmpty()
         var threePidInvitesDone = threePidInvites.isEmpty()
 
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
index 46d0f35b95..e12cce9484 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
@@ -21,6 +21,8 @@ import android.view.Menu
 import android.view.MenuItem
 import android.view.View
 import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.isVisible
 import com.airbnb.mvrx.args
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
@@ -72,12 +74,27 @@ class RoomMemberListFragment @Inject constructor(
         super.onViewCreated(view, savedInstanceState)
         roomMemberListController.callback = this
         setupToolbar(roomSettingsToolbar)
+        setupSearchView()
         recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
         viewModel.selectSubscribe(this, RoomMemberListViewState::actionsPermissions) {
             invalidateOptionsMenu()
         }
     }
 
+    private fun setupSearchView() {
+        searchView.isVisible = true
+        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+            override fun onQueryTextSubmit(query: String): Boolean {
+                return true
+            }
+
+            override fun onQueryTextChange(newText: String): Boolean {
+                viewModel.handle(RoomMemberListAction.FilterMemberList(newText))
+                return true
+            }
+        })
+    }
+
     override fun onDestroyView() {
         recyclerView.cleanup()
         super.onDestroyView()
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
index fedf1729ad..c45dbb1093 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
@@ -188,6 +188,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
     override fun handle(action: RoomMemberListAction) {
         when (action) {
             is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action)
+            is RoomMemberListAction.FilterMemberList     -> handleFilterMemberList(action)
         }.exhaustive
     }
 
@@ -201,4 +202,28 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
             )
         }
     }
+
+    private fun handleFilterMemberList(action: RoomMemberListAction.FilterMemberList) = withState { state ->
+        if (action.searchTerm.isBlank()) {
+            setState { copy(filteredRoomMemberSummaries = null) }
+            return@withState
+        }
+        val roomMemberSummaries = state.roomMemberSummaries.invoke()
+        roomMemberSummaries
+                ?.mapNotNull { (powerLevelCategory, roomMemberList) ->
+                    roomMemberList
+                            .filter { it.displayName?.contains(action.searchTerm).orFalse() || it.userId.contains(action.searchTerm) }
+                            .takeIf { it.isNotEmpty() }
+                            ?.let { filteredMemberList ->
+                                Pair(powerLevelCategory, filteredMemberList)
+                            }
+                }
+                ?.also { filteredRoomMemberSummaries ->
+                    setState {
+                        copy(
+                                filteredRoomMemberSummaries = filteredRoomMemberSummaries
+                        )
+                    }
+                }
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
index 4d21aa103f..01fd290e78 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
@@ -31,6 +31,7 @@ data class RoomMemberListViewState(
         val roomId: String,
         val roomSummary: Async<RoomSummary> = Uninitialized,
         val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
+        val filteredRoomMemberSummaries: RoomMemberSummaries? = null,
         val threePidInvites: Async<List<Event>> = Uninitialized,
         val trustLevelMap: Async<Map<String, RoomEncryptionTrustLevel?>> = Uninitialized,
         val actionsPermissions: ActionPermissions = ActionPermissions()
diff --git a/vector/src/main/res/layout/fragment_room_setting_generic.xml b/vector/src/main/res/layout/fragment_room_setting_generic.xml
index aa86ee342b..cdbf678b2b 100644
--- a/vector/src/main/res/layout/fragment_room_setting_generic.xml
+++ b/vector/src/main/res/layout/fragment_room_setting_generic.xml
@@ -53,6 +53,18 @@
 
     </androidx.appcompat.widget.Toolbar>
 
+    <androidx.appcompat.widget.SearchView
+        android:id="@+id/searchView"
+        style="@style/VectorSearchView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:visibility="gone"
+        tools:visibility="visible"
+        app:queryHint="@string/search_hint"
+        app:layout_constraintTop_toBottomOf="@id/roomSettingsToolbar"
+        android:background="@null" />
+
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/recyclerView"
         android:layout_width="0dp"
@@ -61,7 +73,7 @@
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar"
+        app:layout_constraintTop_toBottomOf="@+id/searchView"
         tools:listitem="@layout/item_profile_action" />
 
     <include layout="@layout/merge_overlay_waiting_view" />

From 78ed184f60381c529b287bea20f3a33975ccb661 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 21:40:10 +0200
Subject: [PATCH 02/17] Filter in the controller, to keep a live members list

---
 .../members/RoomMemberListController.kt       | 24 +++++++-----
 .../members/RoomMemberListViewModel.kt        | 26 +++----------
 .../members/RoomMemberListViewState.kt        |  2 +-
 .../members/RoomMemberSummaryFilter.kt        | 37 +++++++++++++++++++
 4 files changed, 57 insertions(+), 32 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt

diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
index f0c7e146c6..0d4b732da9 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
@@ -17,12 +17,6 @@
 package im.vector.app.features.roomprofile.members
 
 import com.airbnb.epoxy.TypedEpoxyController
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
-import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
-import org.matrix.android.sdk.api.util.MatrixItem
-import org.matrix.android.sdk.api.util.toMatrixItem
 import im.vector.app.R
 import im.vector.app.core.epoxy.dividerItem
 import im.vector.app.core.epoxy.profiles.buildProfileSection
@@ -31,11 +25,18 @@ import im.vector.app.core.extensions.join
 import im.vector.app.core.resources.ColorProvider
 import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
+import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
 
 class RoomMemberListController @Inject constructor(
         private val avatarRenderer: AvatarRenderer,
         private val stringProvider: StringProvider,
+        private val roomMemberSummaryFilter: RoomMemberSummaryFilter,
         colorProvider: ColorProvider
 ) : TypedEpoxyController<RoomMemberListViewState>() {
 
@@ -55,17 +56,20 @@ class RoomMemberListController @Inject constructor(
     override fun buildModels(data: RoomMemberListViewState?) {
         data ?: return
 
-        val roomMembersByPowerLevel = data.filteredRoomMemberSummaries ?: data.roomMemberSummaries.invoke() ?: return
+        roomMemberSummaryFilter.filter = data.filter
+
+        val roomMembersByPowerLevel = data.roomMemberSummaries.invoke() ?: return
         val threePidInvites = data.threePidInvites().orEmpty()
         var threePidInvitesDone = threePidInvites.isEmpty()
 
         for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) {
-            if (roomMemberList.isEmpty()) {
+            val filteredRoomMemberList = roomMemberList.filter { roomMemberSummaryFilter.test(it) }
+            if (filteredRoomMemberList.isEmpty()) {
                 continue
             }
 
             if (powerLevelCategory == RoomMemberListCategories.USER && !threePidInvitesDone) {
-                // If there is not regular invite, display threepid invite before the regular user
+                // If there is no regular invite, display threepid invite before the regular user
                 buildProfileSection(
                         stringProvider.getString(RoomMemberListCategories.INVITE.titleRes)
                 )
@@ -77,7 +81,7 @@ class RoomMemberListController @Inject constructor(
             buildProfileSection(
                     stringProvider.getString(powerLevelCategory.titleRes)
             )
-            roomMemberList.join(
+            filteredRoomMemberList.join(
                     each = { _, roomMember ->
                         profileMatrixItem {
                             id(roomMember.userId)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
index c45dbb1093..60cb5867a7 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
@@ -203,27 +203,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
         }
     }
 
-    private fun handleFilterMemberList(action: RoomMemberListAction.FilterMemberList) = withState { state ->
-        if (action.searchTerm.isBlank()) {
-            setState { copy(filteredRoomMemberSummaries = null) }
-            return@withState
+    private fun handleFilterMemberList(action: RoomMemberListAction.FilterMemberList) = withState {
+        setState {
+            copy(
+                    filter = action.searchTerm
+            )
         }
-        val roomMemberSummaries = state.roomMemberSummaries.invoke()
-        roomMemberSummaries
-                ?.mapNotNull { (powerLevelCategory, roomMemberList) ->
-                    roomMemberList
-                            .filter { it.displayName?.contains(action.searchTerm).orFalse() || it.userId.contains(action.searchTerm) }
-                            .takeIf { it.isNotEmpty() }
-                            ?.let { filteredMemberList ->
-                                Pair(powerLevelCategory, filteredMemberList)
-                            }
-                }
-                ?.also { filteredRoomMemberSummaries ->
-                    setState {
-                        copy(
-                                filteredRoomMemberSummaries = filteredRoomMemberSummaries
-                        )
-                    }
-                }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
index 01fd290e78..787ae920e1 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
@@ -31,7 +31,7 @@ data class RoomMemberListViewState(
         val roomId: String,
         val roomSummary: Async<RoomSummary> = Uninitialized,
         val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
-        val filteredRoomMemberSummaries: RoomMemberSummaries? = null,
+        val filter: String = "",
         val threePidInvites: Async<List<Event>> = Uninitialized,
         val trustLevelMap: Async<Map<String, RoomEncryptionTrustLevel?>> = Uninitialized,
         val actionsPermissions: ActionPermissions = ActionPermissions()
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
new file mode 100644
index 0000000000..813679fef3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 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.app.features.roomprofile.members
+
+import io.reactivex.functions.Predicate
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import javax.inject.Inject
+
+class RoomMemberSummaryFilter @Inject constructor() : Predicate<RoomMemberSummary> {
+    var filter: String = ""
+
+    override fun test(roomMemberSummary: RoomMemberSummary): Boolean {
+        if (filter.isBlank()) {
+            // No filter
+            return true
+        }
+
+        return roomMemberSummary.displayName?.contains(filter, ignoreCase = true).orFalse()
+                // We should maybe exclude the domain from the userId
+                || roomMemberSummary.userId.contains(filter, ignoreCase = true)
+    }
+}

From 9df3de2afb33fcf3e8adcd75157e24d3faa98e6c Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 21:50:01 +0200
Subject: [PATCH 03/17] Rename method

---
 .../features/roomprofile/members/RoomMemberListController.kt  | 4 ++--
 .../features/roomprofile/members/RoomMemberListFragment.kt    | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
index 0d4b732da9..ee6b9a71bf 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
@@ -42,7 +42,7 @@ class RoomMemberListController @Inject constructor(
 
     interface Callback {
         fun onRoomMemberClicked(roomMember: RoomMemberSummary)
-        fun onThreePidInvites(event: Event)
+        fun onThreePidInviteClicked(event: Event)
     }
 
     private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
@@ -134,7 +134,7 @@ class RoomMemberListController @Inject constructor(
                                             avatarRenderer(avatarRenderer)
                                             editable(data.actionsPermissions.canRevokeThreePidInvite)
                                             clickListener { _ ->
-                                                callback?.onThreePidInvites(event)
+                                                callback?.onThreePidInviteClicked(event)
                                             }
                                         }
                                     }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
index e12cce9484..5367788466 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
@@ -109,7 +109,7 @@ class RoomMemberListFragment @Inject constructor(
         navigator.openRoomMemberProfile(roomMember.userId, roomId = roomProfileArgs.roomId, context = requireActivity())
     }
 
-    override fun onThreePidInvites(event: Event) {
+    override fun onThreePidInviteClicked(event: Event) {
         // Display a dialog to revoke invite if power level is high enough
         val content = event.content.toModel<RoomThirdPartyInviteContent>() ?: return
         val stateKey = event.stateKey ?: return

From f5580212ea51e1d961ab023aa3253ca8439211c2 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 22:16:13 +0200
Subject: [PATCH 04/17] Also filter threePids

---
 .../roomprofile/members/RoomMemberListController.kt  | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
index ee6b9a71bf..71ac7fcec4 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
@@ -59,7 +59,14 @@ class RoomMemberListController @Inject constructor(
         roomMemberSummaryFilter.filter = data.filter
 
         val roomMembersByPowerLevel = data.roomMemberSummaries.invoke() ?: return
-        val threePidInvites = data.threePidInvites().orEmpty()
+        val threePidInvites = data.threePidInvites()
+                ?.filter { event ->
+                    event.content.toModel<RoomThirdPartyInviteContent>()
+                            ?.takeIf {
+                                data.filter.isEmpty() || it.displayName.contains(data.filter, ignoreCase = true)
+                            } != null
+                }
+                .orEmpty()
         var threePidInvitesDone = threePidInvites.isEmpty()
 
         for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) {
@@ -100,12 +107,13 @@ class RoomMemberListController @Inject constructor(
                         }
                     }
             )
-            if (powerLevelCategory == RoomMemberListCategories.INVITE) {
+            if (powerLevelCategory == RoomMemberListCategories.INVITE && !threePidInvitesDone) {
                 // Display the threepid invite after the regular invite
                 dividerItem {
                     id("divider_threepidinvites")
                     color(dividerColor)
                 }
+
                 buildThreePidInvites(data)
                 threePidInvitesDone = true
             }

From 50f6a4732c40b89622e3ba28a194ef71f242124b Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 22:21:13 +0200
Subject: [PATCH 05/17] Change hint

---
 .../features/roomprofile/members/RoomMemberListFragment.kt  | 1 +
 .../src/main/res/layout/fragment_room_setting_generic.xml   | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
index 5367788466..e4d17b4da6 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
@@ -83,6 +83,7 @@ class RoomMemberListFragment @Inject constructor(
 
     private fun setupSearchView() {
         searchView.isVisible = true
+        searchView.queryHint = getString(R.string.search_members_hint)
         searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
             override fun onQueryTextSubmit(query: String): Boolean {
                 return true
diff --git a/vector/src/main/res/layout/fragment_room_setting_generic.xml b/vector/src/main/res/layout/fragment_room_setting_generic.xml
index cdbf678b2b..03e211a806 100644
--- a/vector/src/main/res/layout/fragment_room_setting_generic.xml
+++ b/vector/src/main/res/layout/fragment_room_setting_generic.xml
@@ -59,11 +59,11 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="16dp"
+        android:background="@null"
         android:visibility="gone"
-        tools:visibility="visible"
-        app:queryHint="@string/search_hint"
         app:layout_constraintTop_toBottomOf="@id/roomSettingsToolbar"
-        android:background="@null" />
+        tools:queryHint="@string/search_hint"
+        tools:visibility="visible" />
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/recyclerView"

From 6147a87e46717489daa2a050aa94a84d3238e8f3 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 23:06:59 +0200
Subject: [PATCH 06/17] Hide the SearchView when scrolling down

---
 .../members/RoomMemberListFragment.kt         |  2 +-
 .../layout/fragment_room_setting_generic.xml  | 52 +++++++++++++------
 2 files changed, 36 insertions(+), 18 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
index e4d17b4da6..67577e866e 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
@@ -82,7 +82,7 @@ class RoomMemberListFragment @Inject constructor(
     }
 
     private fun setupSearchView() {
-        searchView.isVisible = true
+        searchViewAppBarLayout.isVisible = true
         searchView.queryHint = getString(R.string.search_members_hint)
         searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
             override fun onQueryTextSubmit(query: String): Boolean {
diff --git a/vector/src/main/res/layout/fragment_room_setting_generic.xml b/vector/src/main/res/layout/fragment_room_setting_generic.xml
index 03e211a806..4ab6d2c7df 100644
--- a/vector/src/main/res/layout/fragment_room_setting_generic.xml
+++ b/vector/src/main/res/layout/fragment_room_setting_generic.xml
@@ -53,28 +53,46 @@
 
     </androidx.appcompat.widget.Toolbar>
 
-    <androidx.appcompat.widget.SearchView
-        android:id="@+id/searchView"
-        style="@style/VectorSearchView"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
-        android:background="@null"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@id/roomSettingsToolbar"
-        tools:queryHint="@string/search_hint"
-        tools:visibility="visible" />
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/recyclerView"
+    <androidx.coordinatorlayout.widget.CoordinatorLayout
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:overScrollMode="always"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/searchView"
-        tools:listitem="@layout/item_profile_action" />
+        app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recyclerView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:overScrollMode="always"
+            app:layout_behavior="@string/appbar_scrolling_view_behavior"
+            tools:listitem="@layout/item_profile_action" />
+
+        <com.google.android.material.appbar.AppBarLayout
+            android:id="@+id/searchViewAppBarLayout"
+            style="@style/VectorAppBarLayoutStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:elevation="4dp"
+            android:visibility="gone"
+            tools:visibility="visible">
+
+            <androidx.appcompat.widget.SearchView
+                android:id="@+id/searchView"
+                style="@style/VectorSearchView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp"
+                android:background="@null"
+                android:minHeight="0dp"
+                app:layout_scrollFlags="scroll|enterAlways|snap"
+                tools:queryHint="@string/search_hint" />
+
+        </com.google.android.material.appbar.AppBarLayout>
+
+    </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
     <include layout="@layout/merge_overlay_waiting_view" />
 

From c0842d4da70e56eeb1fa20ec6d6661a09e0c6497 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 23:20:28 +0200
Subject: [PATCH 07/17] Also apply the filter to banned user screen

---
 CHANGES.md                                    |  2 +-
 .../banned/RoomBannedListMemberAction.kt      |  1 +
 .../banned/RoomBannedListMemberViewModel.kt   | 10 ++++
 .../banned/RoomBannedMemberListController.kt  | 59 ++++++++++---------
 .../banned/RoomBannedMemberListFragment.kt    | 18 ++++++
 .../banned/RoomBannedMemberListViewState.kt   |  1 +
 vector/src/main/res/values/strings.xml        |  1 +
 7 files changed, 64 insertions(+), 28 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index f5d6fca073..9103acaa60 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,7 +11,7 @@ Improvements 🙌:
  - Small optimisation of scrolling experience in timeline (#2114)
  - Allow user to reset cross signing if he has no way to recover (#2052)
  - Create home shortcut for any room (#1525)
- - Filter room member by name (#2184)
+ - Filter room member (and banned users) by name (#2184)
 
 Bugfix 🐛:
  - Improve support for image/audio/video/file selection with intent changes (#1376)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt
index 9af554e883..67cacf4541 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt
@@ -22,4 +22,5 @@ import im.vector.app.core.platform.VectorViewModelAction
 sealed class RoomBannedListMemberAction : VectorViewModelAction {
     data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
     data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
+    data class Filter(val filter: String) : RoomBannedListMemberAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt
index b65b3de807..d1acd80c25 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback
 import org.matrix.android.sdk.rx.rx
 import org.matrix.android.sdk.rx.unwrap
 import im.vector.app.R
+import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
@@ -90,6 +91,15 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia
         when (action) {
             is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
             is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary)
+            is RoomBannedListMemberAction.Filter    -> handleFilter(action)
+        }.exhaustive
+    }
+
+    private fun handleFilter(action: RoomBannedListMemberAction.Filter) {
+        setState {
+            copy(
+                    filter = action.filter
+            )
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListController.kt
index a65a30441d..2a0c787a7a 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListController.kt
@@ -17,8 +17,6 @@
 package im.vector.app.features.roomprofile.banned
 
 import com.airbnb.epoxy.TypedEpoxyController
-import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
-import org.matrix.android.sdk.api.util.toMatrixItem
 import im.vector.app.R
 import im.vector.app.core.epoxy.dividerItem
 import im.vector.app.core.epoxy.profiles.buildProfileSection
@@ -28,11 +26,15 @@ import im.vector.app.core.resources.ColorProvider
 import im.vector.app.core.resources.StringProvider
 import im.vector.app.core.ui.list.genericFooterItem
 import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.roomprofile.members.RoomMemberSummaryFilter
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
 
 class RoomBannedMemberListController @Inject constructor(
         private val avatarRenderer: AvatarRenderer,
         private val stringProvider: StringProvider,
+        private val roomMemberSummaryFilter: RoomMemberSummaryFilter,
         colorProvider: ColorProvider
 ) : TypedEpoxyController<RoomBannedMemberListViewState>() {
 
@@ -63,34 +65,37 @@ class RoomBannedMemberListController @Inject constructor(
         } else {
             buildProfileSection(quantityString)
 
-            bannedList.join(
-                    each = { _, roomMember ->
-                        val actionInProgress = data.onGoingModerationAction.contains(roomMember.userId)
-                        profileMatrixItemWithProgress {
-                            id(roomMember.userId)
-                            matrixItem(roomMember.toMatrixItem())
-                            avatarRenderer(avatarRenderer)
-                            apply {
-                                if (actionInProgress) {
-                                    inProgress(true)
-                                    editable(false)
-                                } else {
-                                    inProgress(false)
-                                    editable(true)
-                                    clickListener { _ ->
-                                        callback?.onUnbanClicked(roomMember)
+            roomMemberSummaryFilter.filter = data.filter
+            bannedList
+                    .filter { roomMemberSummaryFilter.test(it) }
+                    .join(
+                            each = { _, roomMember ->
+                                val actionInProgress = data.onGoingModerationAction.contains(roomMember.userId)
+                                profileMatrixItemWithProgress {
+                                    id(roomMember.userId)
+                                    matrixItem(roomMember.toMatrixItem())
+                                    avatarRenderer(avatarRenderer)
+                                    apply {
+                                        if (actionInProgress) {
+                                            inProgress(true)
+                                            editable(false)
+                                        } else {
+                                            inProgress(false)
+                                            editable(true)
+                                            clickListener { _ ->
+                                                callback?.onUnbanClicked(roomMember)
+                                            }
+                                        }
                                     }
                                 }
+                            },
+                            between = { _, roomMemberBefore ->
+                                dividerItem {
+                                    id("divider_${roomMemberBefore.userId}")
+                                    color(dividerColor)
+                                }
                             }
-                        }
-                    },
-                    between = { _, roomMemberBefore ->
-                        dividerItem {
-                            id("divider_${roomMemberBefore.userId}")
-                            color(dividerColor)
-                        }
-                    }
-            )
+                    )
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt
index 480deea6af..84eccccfae 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt
@@ -19,6 +19,8 @@ package im.vector.app.features.roomprofile.banned
 import android.os.Bundle
 import android.view.View
 import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.isVisible
 import com.airbnb.mvrx.args
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
@@ -53,6 +55,7 @@ class RoomBannedMemberListFragment @Inject constructor(
         super.onViewCreated(view, savedInstanceState)
         roomMemberListController.callback = this
         setupToolbar(roomSettingsToolbar)
+        setupSearchView()
         recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
 
         viewModel.observeViewEvents {
@@ -84,6 +87,21 @@ class RoomBannedMemberListFragment @Inject constructor(
         super.onDestroyView()
     }
 
+    private fun setupSearchView() {
+        searchViewAppBarLayout.isVisible = true
+        searchView.queryHint = getString(R.string.search_banned_user_hint)
+        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+            override fun onQueryTextSubmit(query: String): Boolean {
+                return true
+            }
+
+            override fun onQueryTextChange(newText: String): Boolean {
+                viewModel.handle(RoomBannedListMemberAction.Filter(newText))
+                return true
+            }
+        })
+    }
+
     override fun invalidate() = withState(viewModel) { viewState ->
         roomMemberListController.setData(viewState)
         renderRoomSummary(viewState)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt
index ee8e534b1f..6d8bb2f42a 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt
@@ -27,6 +27,7 @@ data class RoomBannedMemberListViewState(
         val roomId: String,
         val roomSummary: Async<RoomSummary> = Uninitialized,
         val bannedMemberSummaries: Async<List<RoomMemberSummary>> = Uninitialized,
+        val filter: String = "",
         val onGoingModerationAction: List<String> = emptyList(),
         val canUserBan: Boolean = false
 ) : MvRxState {
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 9310af7577..9099c276e8 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -623,6 +623,7 @@
     <!-- Search -->
     <string name="search_hint">Search</string>
     <string name="search_members_hint">Filter room members</string>
+    <string name="search_banned_user_hint">Filter banned users</string>
     <string name="search_no_results">No results</string>
     <string name="tab_title_search_rooms">ROOMS</string>
     <string name="tab_title_search_messages">MESSAGES</string>

From 8170f523e091ef4ca366e4e787a79fb9fa467a12 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 2 Oct 2020 23:29:48 +0200
Subject: [PATCH 08/17] Small improvement

---
 .../im/vector/app/features/home/room/list/RoomListNameFilter.kt | 2 +-
 .../app/features/roomprofile/members/RoomMemberListViewModel.kt | 2 +-
 .../app/features/roomprofile/members/RoomMemberSummaryFilter.kt | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt
index 4c4a9755d2..6e787b8b95 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt
@@ -25,7 +25,7 @@ class RoomListNameFilter @Inject constructor() : Predicate<RoomSummary> {
     var filter: String = ""
 
     override fun test(roomSummary: RoomSummary): Boolean {
-        if (filter.isBlank()) {
+        if (filter.isEmpty()) {
             // No filter
             return true
         }
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
index 60cb5867a7..9e402c675b 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
@@ -203,7 +203,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
         }
     }
 
-    private fun handleFilterMemberList(action: RoomMemberListAction.FilterMemberList) = withState {
+    private fun handleFilterMemberList(action: RoomMemberListAction.FilterMemberList) {
         setState {
             copy(
                     filter = action.searchTerm
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
index 813679fef3..e2cc3f7b99 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
@@ -25,7 +25,7 @@ class RoomMemberSummaryFilter @Inject constructor() : Predicate<RoomMemberSummar
     var filter: String = ""
 
     override fun test(roomMemberSummary: RoomMemberSummary): Boolean {
-        if (filter.isBlank()) {
+        if (filter.isEmpty()) {
             // No filter
             return true
         }

From 1b3a5097c10cd431840b2c7a40970ae32b399fef Mon Sep 17 00:00:00 2001
From: Cadence Ember <cloudrac3r@vivaldi.net>
Date: Sun, 4 Oct 2020 12:43:26 +1300
Subject: [PATCH 09/17] Replace "him" with "them" in report interface

All genders can spam a chatroom :)
---
 vector/src/main/res/values/strings.xml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 9310af7577..c025054f98 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1851,11 +1851,11 @@
     <string name="block_user">"IGNORE USER"</string>
 
     <string name="content_reported_title">"Content reported"</string>
-    <string name="content_reported_content">"This content was reported.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages"</string>
+    <string name="content_reported_content">"This content was reported.\n\nIf you don't want to see any more content from this user, you can ignore them to hide their messages."</string>
     <string name="content_reported_as_spam_title">"Reported as spam"</string>
-    <string name="content_reported_as_spam_content">"This content was reported as spam.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages"</string>
+    <string name="content_reported_as_spam_content">"This content was reported as spam.\n\nIf you don't want to see any more content from this user, you can ignore them to hide their messages."</string>
     <string name="content_reported_as_inappropriate_title">"Reported as inappropriate"</string>
-    <string name="content_reported_as_inappropriate_content">"This content was reported as inappropriate.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages"</string>
+    <string name="content_reported_as_inappropriate_content">"This content was reported as inappropriate.\n\nIf you don't want to see any more content from this user, you can ignore them to hide their messages."</string>
 
     <string name="permissions_rationale_msg_keys_backup_export">Element needs permission to save your E2E keys on disk.\n\nPlease allow access on the next pop-up to be able to export your keys manually.</string>
 

From b2030930739b839b8d5dc483d05e95537b7f2ba0 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Sun, 4 Oct 2020 11:17:57 +0200
Subject: [PATCH 10/17] Use an extra container for the margin to be taken into
 account by the layout_scrollFlags

---
 .../layout/fragment_room_setting_generic.xml  | 25 ++++++++++++-------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/res/layout/fragment_room_setting_generic.xml b/vector/src/main/res/layout/fragment_room_setting_generic.xml
index 4ab6d2c7df..07744436ea 100644
--- a/vector/src/main/res/layout/fragment_room_setting_generic.xml
+++ b/vector/src/main/res/layout/fragment_room_setting_generic.xml
@@ -78,17 +78,24 @@
             android:visibility="gone"
             tools:visibility="visible">
 
-            <androidx.appcompat.widget.SearchView
-                android:id="@+id/searchView"
-                style="@style/VectorSearchView"
+            <!-- Use an extra container for the margin to be taken into account by the layout_scrollFlags -->
+            <FrameLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="4dp"
-                android:layout_marginBottom="4dp"
-                android:background="@null"
-                android:minHeight="0dp"
-                app:layout_scrollFlags="scroll|enterAlways|snap"
-                tools:queryHint="@string/search_hint" />
+                app:layout_scrollFlags="scroll|enterAlways|snap">
+
+                <androidx.appcompat.widget.SearchView
+                    android:id="@+id/searchView"
+                    style="@style/VectorSearchView"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="4dp"
+                    android:layout_marginBottom="4dp"
+                    android:background="@null"
+                    android:minHeight="0dp"
+                    tools:queryHint="@string/search_hint" />
+
+            </FrameLayout>
 
         </com.google.android.material.appbar.AppBarLayout>
 

From aa0520d47dba1c2b0d50b946aef4784a6317d75f Mon Sep 17 00:00:00 2001
From: ganfra
 <francoisg@matrix.orgfrancoisg@matrix.orgfrancoisg@matrix.orgfrancoisg@matrix.org>
Date: Tue, 22 Sep 2020 18:56:05 +0200
Subject: [PATCH 11/17] Start reworking draft (simplify)

---
 .../java/org/matrix/android/sdk/rx/RxRoom.kt  |   7 +-
 .../sdk/api/session/room/send/DraftService.kt |  11 +-
 .../session/room/draft/DefaultDraftService.kt |   7 +-
 .../session/room/draft/DraftRepository.kt     | 111 ++++++------------
 .../home/room/detail/RoomDetailViewModel.kt   |  61 ++--------
 5 files changed, 65 insertions(+), 132 deletions(-)

diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
index 0717a10d03..2bb6c0ff69 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
@@ -101,8 +101,11 @@ class RxRoom(private val room: Room) {
         return room.getEventReadReceiptsLive(eventId).asObservable()
     }
 
-    fun liveDrafts(): Observable<List<UserDraft>> {
-        return room.getDraftsLive().asObservable()
+    fun liveDraft(): Observable<Optional<UserDraft>> {
+        return room.getDraftLive().asObservable()
+                .startWithCallable {
+                    room.getDraft().toOptional()
+                }
     }
 
     fun liveNotificationState(): Observable<RoomNotificationState> {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt
index dc91d5177f..6ef73d6486 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt
@@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.util.Cancelable
+import org.matrix.android.sdk.api.util.Optional
 
 interface DraftService {
 
@@ -34,8 +35,12 @@ interface DraftService {
     fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
 
     /**
-     * Return the current drafts if any, as a live data
-     * The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
+     * Return the current draft or null
      */
-    fun getDraftsLive(): LiveData<List<UserDraft>>
+    fun getDraft(): UserDraft?
+
+    /**
+     * Return the current draft if any, as a live data
+     */
+    fun getDraftLive(): LiveData<Optional<UserDraft>>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt
index dafa7df0eb..0426ee13e1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.room.send.DraftService
 import org.matrix.android.sdk.api.session.room.send.UserDraft
 import org.matrix.android.sdk.api.util.Cancelable
+import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.launchToCallback
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
@@ -55,7 +56,11 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private
         }
     }
 
-    override fun getDraftsLive(): LiveData<List<UserDraft>> {
+    override fun getDraft(): UserDraft? {
+        return draftRepository.getDraft(roomId)
+    }
+
+    override fun getDraftLive(): LiveData<Optional<UserDraft>> {
         return draftRepository.getDraftsLive(roomId)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
index bc50b2d990..9262eb2f49 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
@@ -20,35 +20,61 @@ package org.matrix.android.sdk.internal.session.room.draft
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import io.realm.kotlin.createObject
 import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.session.room.send.UserDraft
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.DraftMapper
-import org.matrix.android.sdk.internal.database.model.DraftEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.UserDraftsEntity
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.util.awaitTransaction
-import io.realm.Realm
-import io.realm.kotlin.createObject
 import timber.log.Timber
 import javax.inject.Inject
 
-class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
+internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
 
     suspend fun saveDraft(roomId: String, userDraft: UserDraft) {
         monarchy.awaitTransaction {
-            saveDraft(it, userDraft, roomId)
+            saveDraftInDb(it, userDraft, roomId)
         }
     }
 
     suspend fun deleteDraft(roomId: String) {
         monarchy.awaitTransaction {
-            deleteDraft(it, roomId)
+            deleteDraftFromDb(it, roomId)
         }
     }
 
-    private fun deleteDraft(realm: Realm, roomId: String) {
+    fun getDraft(roomId: String): UserDraft? {
+        return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
+            UserDraftsEntity.where(realm, roomId).findFirst()
+                    ?.userDrafts
+                    ?.firstOrNull()
+                    ?.let {
+                        DraftMapper.map(it)
+                    }
+        }
+    }
+
+    fun getDraftsLive(roomId: String): LiveData<Optional<UserDraft>> {
+        val liveData = monarchy.findAllMappedWithChanges(
+                { UserDraftsEntity.where(it, roomId) },
+                {
+                    it.userDrafts.map { draft ->
+                        DraftMapper.map(draft)
+                    }
+                }
+        )
+        return Transformations.map(liveData) {
+            it.firstOrNull()?.firstOrNull().toOptional()
+        }
+    }
+
+    private fun deleteDraftFromDb(realm: Realm, roomId: String) {
         UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity ->
             if (userDraftsEntity.userDrafts.isNotEmpty()) {
                 userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1)
@@ -56,7 +82,7 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
         }
     }
 
-    private fun saveDraft(realm: Realm, draft: UserDraft, roomId: String) {
+    private fun saveDraftInDb(realm: Realm, draft: UserDraft, roomId: String) {
         val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
                 ?: realm.createObject(roomId)
 
@@ -68,62 +94,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
         userDraftsEntity.let { userDraftEntity ->
             // Save only valid draft
             if (draft.isValid()) {
-                // Add a new draft or update the current one?
+                // Replace the current draft
                 val newDraft = DraftMapper.map(draft)
-
-                // Is it an update of the top draft?
-                val topDraft = userDraftEntity.userDrafts.lastOrNull()
-
-                if (topDraft == null) {
-                    Timber.d("Draft: create a new draft ${privacySafe(draft)}")
-                    userDraftEntity.userDrafts.add(newDraft)
-                } else if (topDraft.draftMode == DraftEntity.MODE_EDIT) {
-                    // top draft is an edit
-                    if (newDraft.draftMode == DraftEntity.MODE_EDIT) {
-                        if (topDraft.linkedEventId == newDraft.linkedEventId) {
-                            // Update the top draft
-                            Timber.d("Draft: update the top edit draft ${privacySafe(draft)}")
-                            topDraft.content = newDraft.content
-                        } else {
-                            // Check a previously EDIT draft with the same id
-                            val existingEditDraftOfSameEvent = userDraftEntity.userDrafts.find {
-                                it.draftMode == DraftEntity.MODE_EDIT && it.linkedEventId == newDraft.linkedEventId
-                            }
-
-                            if (existingEditDraftOfSameEvent != null) {
-                                // Ignore the new text, restore what was typed before, by putting the draft to the top
-                                Timber.d("Draft: restore a previously edit draft ${privacySafe(draft)}")
-                                userDraftEntity.userDrafts.remove(existingEditDraftOfSameEvent)
-                                userDraftEntity.userDrafts.add(existingEditDraftOfSameEvent)
-                            } else {
-                                Timber.d("Draft: add a new edit draft ${privacySafe(draft)}")
-                                userDraftEntity.userDrafts.add(newDraft)
-                            }
-                        }
-                    } else {
-                        // Add a new regular draft to the top
-                        Timber.d("Draft: add a new draft ${privacySafe(draft)}")
-                        userDraftEntity.userDrafts.add(newDraft)
-                    }
-                } else {
-                    // Top draft is not an edit
-                    if (newDraft.draftMode == DraftEntity.MODE_EDIT) {
-                        Timber.d("Draft: create a new edit draft ${privacySafe(draft)}")
-                        userDraftEntity.userDrafts.add(newDraft)
-                    } else {
-                        // Update the top draft
-                        Timber.d("Draft: update the top draft ${privacySafe(draft)}")
-                        topDraft.draftMode = newDraft.draftMode
-                        topDraft.content = newDraft.content
-                        topDraft.linkedEventId = newDraft.linkedEventId
-                    }
-                }
+                Timber.d("Draft: create a new draft ${privacySafe(draft)}")
+                userDraftEntity.userDrafts.clear()
+                userDraftEntity.userDrafts.add(newDraft)
             } else {
                 // There is no draft to save, so the composer was clear
                 Timber.d("Draft: delete a draft")
-
                 val topDraft = userDraftEntity.userDrafts.lastOrNull()
-
                 if (topDraft == null) {
                     Timber.d("Draft: nothing to do")
                 } else {
@@ -135,20 +114,6 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
         }
     }
 
-    fun getDraftsLive(roomId: String): LiveData<List<UserDraft>> {
-        val liveData = monarchy.findAllMappedWithChanges(
-                { UserDraftsEntity.where(it, roomId) },
-                {
-                    it.userDrafts.map { draft ->
-                        DraftMapper.map(draft)
-                    }
-                }
-        )
-        return Transformations.map(liveData) {
-            it.firstOrNull().orEmpty()
-        }
-    }
-
     private fun privacySafe(o: Any): Any {
         if (BuildConfig.LOG_PRIVATE_DATA) {
             return o
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 1b5e928843..42ce8567cd 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -680,7 +680,7 @@ class RoomDetailViewModel @AssistedInject constructor(
                         }
                     }.exhaustive
                 }
-                is SendMode.EDIT    -> {
+                is SendMode.EDIT -> {
                     // is original event a reply?
                     val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
                             ?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
@@ -706,7 +706,7 @@ class RoomDetailViewModel @AssistedInject constructor(
                     _viewEvents.post(RoomDetailViewEvents.MessageSent)
                     popDraft()
                 }
-                is SendMode.QUOTE   -> {
+                is SendMode.QUOTE -> {
                     val messageContent: MessageContent? =
                             state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
                                     ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
@@ -729,7 +729,7 @@ class RoomDetailViewModel @AssistedInject constructor(
                     _viewEvents.post(RoomDetailViewEvents.MessageSent)
                     popDraft()
                 }
-                is SendMode.REPLY   -> {
+                is SendMode.REPLY -> {
                     state.sendMode.timelineEvent.let {
                         room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
                         _viewEvents.post(RoomDetailViewEvents.MessageSent)
@@ -741,6 +741,9 @@ class RoomDetailViewModel @AssistedInject constructor(
     }
 
     private fun popDraft() {
+        setState {
+            copy(sendMode = SendMode.REGULAR(""))
+        }
         room.deleteDraft(NoOpMatrixCallback())
     }
 
@@ -915,73 +918,25 @@ class RoomDetailViewModel @AssistedInject constructor(
     }
 
     private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
-        saveCurrentDraft(action.text)
-
         room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
-            setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) }
-            timelineEvent.root.eventId?.let {
-                room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback())
-            }
+            setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
         }
     }
 
     private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
-        saveCurrentDraft(action.text)
-
         room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
             setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) }
-            withState { state ->
-                // Save a new draft and keep the previously entered text, if it was not an edit
-                timelineEvent.root.eventId?.let {
-                    if (state.sendMode is SendMode.EDIT) {
-                        room.saveDraft(UserDraft.QUOTE(it, ""), NoOpMatrixCallback())
-                    } else {
-                        room.saveDraft(UserDraft.QUOTE(it, action.text), NoOpMatrixCallback())
-                    }
-                }
-            }
         }
     }
 
     private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
-        saveCurrentDraft(action.text)
-
         room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
             setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) }
-            withState { state ->
-                // Save a new draft and keep the previously entered text, if it was not an edit
-                timelineEvent.root.eventId?.let {
-                    if (state.sendMode is SendMode.EDIT) {
-                        room.saveDraft(UserDraft.REPLY(it, ""), NoOpMatrixCallback())
-                    } else {
-                        room.saveDraft(UserDraft.REPLY(it, action.text), NoOpMatrixCallback())
-                    }
-                }
-            }
-        }
-    }
-
-    private fun saveCurrentDraft(draft: String) {
-        // Save the draft with the current text if any
-        withState {
-            if (draft.isNotBlank()) {
-                when (it.sendMode) {
-                    is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(draft), NoOpMatrixCallback())
-                    is SendMode.REPLY   -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
-                    is SendMode.QUOTE   -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
-                    is SendMode.EDIT    -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
-                }
-            }
         }
     }
 
     private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState {
-        if (it.sendMode is SendMode.EDIT) {
-            room.deleteDraft(NoOpMatrixCallback())
-        } else {
-            // Save a new draft and keep the previously entered text
-            room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback())
-        }
+        room.deleteDraft(NoOpMatrixCallback())
         setState { copy(sendMode = SendMode.REGULAR(action.text)) }
     }
 

From 91b81af5a8d74a3b114eb99c6cc12d1be5908cb7 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Wed, 23 Sep 2020 13:32:07 +0200
Subject: [PATCH 12/17] Draft: handle sharing so it doesn't destroy the
 previous draft

---
 .../session/room/draft/DraftRepository.kt     |   6 +-
 .../home/room/detail/RoomDetailAction.kt      |   2 +-
 .../home/room/detail/RoomDetailFragment.kt    |   9 +-
 .../home/room/detail/RoomDetailViewModel.kt   | 184 +++++++++---------
 .../home/room/detail/RoomDetailViewState.kt   |   9 +-
 5 files changed, 106 insertions(+), 104 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
index 9262eb2f49..7ca5e3c3ce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
@@ -75,11 +75,7 @@ internal class DraftRepository @Inject constructor(@SessionDatabase private val
     }
 
     private fun deleteDraftFromDb(realm: Realm, roomId: String) {
-        UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity ->
-            if (userDraftsEntity.userDrafts.isNotEmpty()) {
-                userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1)
-            }
-        }
+        UserDraftsEntity.where(realm, roomId).findFirst()?.userDrafts?.clear()
     }
 
     private fun saveDraftInDb(realm: Realm, draft: UserDraft, roomId: String) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
index 4bdf2e7e57..5f851b8da7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
@@ -51,7 +51,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
     data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction()
     data class EnterQuoteMode(val eventId: String, val text: String) : RoomDetailAction()
     data class EnterReplyMode(val eventId: String, val text: String) : RoomDetailAction()
-    data class ExitSpecialMode(val text: String) : RoomDetailAction()
+    data class EnterRegularMode(val text: String, val fromSharing: Boolean) : RoomDetailAction()
 
     data class ResendMessage(val eventId: String) : RoomDetailAction()
     data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 51aeda2aab..2fc2fe94a5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -485,8 +485,7 @@ class RoomDetailFragment @Inject constructor(
         if (savedInstanceState == null) {
             when (val sharedData = roomDetailArgs.sharedData) {
                 is SharedData.Text        -> {
-                    // Save a draft to set the shared text to the composer
-                    roomDetailViewModel.handle(RoomDetailAction.SaveDraft(sharedData.text))
+                    roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
                 }
                 is SharedData.Attachments -> {
                     // open share edition
@@ -1014,7 +1013,7 @@ class RoomDetailFragment @Inject constructor(
             }
 
             override fun onCloseRelatedMessage() {
-                roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString()))
+                roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false))
             }
 
             override fun onRichContentSelected(contentUri: Uri): Boolean {
@@ -1147,12 +1146,8 @@ class RoomDetailFragment @Inject constructor(
 
     private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
         when (sendMessageResult) {
-            is RoomDetailViewEvents.MessageSent                -> {
-                updateComposerText("")
-            }
             is RoomDetailViewEvents.SlashCommandHandled        -> {
                 sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
-                updateComposerText("")
             }
             is RoomDetailViewEvents.SlashCommandError          -> {
                 displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 42ce8567cd..985604a49c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor(
         getUnreadState()
         observeSyncState()
         observeEventDisplayedActions()
-        observeDrafts()
+        getDraftIfAny()
         observeUnreadState()
         observeMyRoomMember()
         observeActiveRoomWidgets()
@@ -226,52 +226,52 @@ class RoomDetailViewModel @AssistedInject constructor(
 
     override fun handle(action: RoomDetailAction) {
         when (action) {
-            is RoomDetailAction.UserIsTyping                     -> handleUserIsTyping(action)
-            is RoomDetailAction.SaveDraft                        -> handleSaveDraft(action)
-            is RoomDetailAction.SendMessage                      -> handleSendMessage(action)
-            is RoomDetailAction.SendMedia                        -> handleSendMedia(action)
-            is RoomDetailAction.SendSticker                      -> handleSendSticker(action)
-            is RoomDetailAction.TimelineEventTurnsVisible        -> handleEventVisible(action)
-            is RoomDetailAction.TimelineEventTurnsInvisible      -> handleEventInvisible(action)
-            is RoomDetailAction.LoadMoreTimelineEvents           -> handleLoadMore(action)
-            is RoomDetailAction.SendReaction                     -> handleSendReaction(action)
-            is RoomDetailAction.AcceptInvite                     -> handleAcceptInvite()
-            is RoomDetailAction.RejectInvite                     -> handleRejectInvite()
-            is RoomDetailAction.RedactAction                     -> handleRedactEvent(action)
-            is RoomDetailAction.UndoReaction                     -> handleUndoReact(action)
-            is RoomDetailAction.UpdateQuickReactAction           -> handleUpdateQuickReaction(action)
-            is RoomDetailAction.ExitSpecialMode                  -> handleExitSpecialMode(action)
-            is RoomDetailAction.EnterEditMode                    -> handleEditAction(action)
-            is RoomDetailAction.EnterQuoteMode                   -> handleQuoteAction(action)
-            is RoomDetailAction.EnterReplyMode                   -> handleReplyAction(action)
-            is RoomDetailAction.DownloadOrOpen                   -> handleOpenOrDownloadFile(action)
-            is RoomDetailAction.NavigateToEvent                  -> handleNavigateToEvent(action)
-            is RoomDetailAction.HandleTombstoneEvent             -> handleTombstoneEvent(action)
-            is RoomDetailAction.ResendMessage                    -> handleResendEvent(action)
-            is RoomDetailAction.RemoveFailedEcho                 -> handleRemove(action)
-            is RoomDetailAction.ClearSendQueue                   -> handleClearSendQueue()
-            is RoomDetailAction.ResendAll                        -> handleResendAll()
-            is RoomDetailAction.MarkAllAsRead                    -> handleMarkAllAsRead()
-            is RoomDetailAction.ReportContent                    -> handleReportContent(action)
-            is RoomDetailAction.IgnoreUser                       -> handleIgnoreUser(action)
+            is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
+            is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
+            is RoomDetailAction.SendMessage -> handleSendMessage(action)
+            is RoomDetailAction.SendMedia -> handleSendMedia(action)
+            is RoomDetailAction.SendSticker -> handleSendSticker(action)
+            is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
+            is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
+            is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
+            is RoomDetailAction.SendReaction -> handleSendReaction(action)
+            is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
+            is RoomDetailAction.RejectInvite -> handleRejectInvite()
+            is RoomDetailAction.RedactAction -> handleRedactEvent(action)
+            is RoomDetailAction.UndoReaction -> handleUndoReact(action)
+            is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
+            is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
+            is RoomDetailAction.EnterEditMode -> handleEditAction(action)
+            is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
+            is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
+            is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
+            is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
+            is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
+            is RoomDetailAction.ResendMessage -> handleResendEvent(action)
+            is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
+            is RoomDetailAction.ClearSendQueue -> handleClearSendQueue()
+            is RoomDetailAction.ResendAll -> handleResendAll()
+            is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
+            is RoomDetailAction.ReportContent -> handleReportContent(action)
+            is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
             is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
-            is RoomDetailAction.ExitTrackingUnreadMessagesState  -> stopTrackingUnreadMessages()
-            is RoomDetailAction.ReplyToOptions                   -> handleReplyToOptions(action)
-            is RoomDetailAction.AcceptVerificationRequest        -> handleAcceptVerification(action)
-            is RoomDetailAction.DeclineVerificationRequest       -> handleDeclineVerification(action)
-            is RoomDetailAction.RequestVerification              -> handleRequestVerification(action)
-            is RoomDetailAction.ResumeVerification               -> handleResumeRequestVerification(action)
-            is RoomDetailAction.ReRequestKeys                    -> handleReRequestKeys(action)
-            is RoomDetailAction.TapOnFailedToDecrypt             -> handleTapOnFailedToDecrypt(action)
-            is RoomDetailAction.SelectStickerAttachment          -> handleSelectStickerAttachment()
-            is RoomDetailAction.OpenIntegrationManager           -> handleOpenIntegrationManager()
-            is RoomDetailAction.StartCall                        -> handleStartCall(action)
-            is RoomDetailAction.EndCall                          -> handleEndCall()
-            is RoomDetailAction.ManageIntegrations               -> handleManageIntegrations()
-            is RoomDetailAction.AddJitsiWidget                   -> handleAddJitsiConference(action)
-            is RoomDetailAction.RemoveWidget                     -> handleDeleteWidget(action.widgetId)
-            is RoomDetailAction.EnsureNativeWidgetAllowed        -> handleCheckWidgetAllowed(action)
-            is RoomDetailAction.CancelSend                       -> handleCancel(action)
+            is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
+            is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
+            is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
+            is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
+            is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
+            is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
+            is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
+            is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
+            is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
+            is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
+            is RoomDetailAction.StartCall -> handleStartCall(action)
+            is RoomDetailAction.EndCall -> handleEndCall()
+            is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
+            is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
+            is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
+            is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
+            is RoomDetailAction.CancelSend -> handleCancel(action)
         }.exhaustive
     }
 
@@ -451,45 +451,48 @@ class RoomDetailViewModel @AssistedInject constructor(
      */
     private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) {
         withState {
-            when (it.sendMode) {
-                is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
-                is SendMode.REPLY   -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
-                is SendMode.QUOTE   -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
-                is SendMode.EDIT    -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
-            }.exhaustive
+            when {
+                it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
+                    room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
+                }
+                it.sendMode is SendMode.REPLY                               -> {
+                    room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+                }
+                it.sendMode is SendMode.QUOTE                               -> {
+                    room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+                }
+                it.sendMode is SendMode.EDIT                                -> {
+                    room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+                }
+            }
         }
     }
 
-    private fun observeDrafts() {
-        room.rx().liveDrafts()
-                .subscribe {
-                    Timber.d("Draft update --> SetState")
-                    setState {
-                        val draft = it.lastOrNull() ?: UserDraft.REGULAR("")
-                        copy(
-                                // Create a sendMode from a draft and retrieve the TimelineEvent
-                                sendMode = when (draft) {
-                                    is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
-                                    is UserDraft.QUOTE   -> {
-                                        room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
-                                            SendMode.QUOTE(timelineEvent, draft.text)
-                                        }
-                                    }
-                                    is UserDraft.REPLY   -> {
-                                        room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
-                                            SendMode.REPLY(timelineEvent, draft.text)
-                                        }
-                                    }
-                                    is UserDraft.EDIT    -> {
-                                        room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
-                                            SendMode.EDIT(timelineEvent, draft.text)
-                                        }
-                                    }
-                                } ?: SendMode.REGULAR("")
-                        )
-                    }
-                }
-                .disposeOnClear()
+    private fun getDraftIfAny() {
+        val currentDraft = room.getDraft() ?: return
+        setState {
+            copy(
+                    // Create a sendMode from a draft and retrieve the TimelineEvent
+                    sendMode = when (currentDraft) {
+                        is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false)
+                        is UserDraft.QUOTE   -> {
+                            room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
+                                SendMode.QUOTE(timelineEvent, currentDraft.text)
+                            }
+                        }
+                        is UserDraft.REPLY   -> {
+                            room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
+                                SendMode.REPLY(timelineEvent, currentDraft.text)
+                            }
+                        }
+                        is UserDraft.EDIT    -> {
+                            room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
+                                SendMode.EDIT(timelineEvent, currentDraft.text)
+                            }
+                        }
+                    } ?: SendMode.REGULAR("", fromSharing = false)
+            )
+        }
     }
 
     private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) {
@@ -740,11 +743,15 @@ class RoomDetailViewModel @AssistedInject constructor(
         }
     }
 
-    private fun popDraft() {
-        setState {
-            copy(sendMode = SendMode.REGULAR(""))
+    private fun popDraft() = withState {
+        if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) {
+            // If we were sharing, we want to get back our last value from draft
+            getDraftIfAny()
+        } else {
+            // Otherwise we clear the composer and remove the draft from db
+            setState { copy(sendMode = SendMode.REGULAR("", false)) }
+            room.deleteDraft(NoOpMatrixCallback())
         }
-        room.deleteDraft(NoOpMatrixCallback())
     }
 
     private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
@@ -935,9 +942,8 @@ class RoomDetailViewModel @AssistedInject constructor(
         }
     }
 
-    private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState {
-        room.deleteDraft(NoOpMatrixCallback())
-        setState { copy(sendMode = SendMode.REGULAR(action.text)) }
+    private fun handleEnterRegularMode(action: RoomDetailAction.EnterRegularMode) = setState {
+        copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing))
     }
 
     private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
index 36acc35148..c443d61252 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
@@ -37,7 +37,12 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget
  * Depending on the state the bottom toolbar will change (icons/preview/actions...)
  */
 sealed class SendMode(open val text: String) {
-    data class REGULAR(override val text: String) : SendMode(text)
+    data class REGULAR(
+            override val text: String,
+            val fromSharing: Boolean,
+            // This is necessary for forcing refresh on selectSubscribe
+            private val ts: Long = System.currentTimeMillis()
+    ) : SendMode(text)
     data class QUOTE(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
     data class EDIT(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
     data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
@@ -58,7 +63,7 @@ data class RoomDetailViewState(
         val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
         val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
         val typingMessage: String? = null,
-        val sendMode: SendMode = SendMode.REGULAR(""),
+        val sendMode: SendMode = SendMode.REGULAR("", false),
         val tombstoneEvent: Event? = null,
         val tombstoneEventHandling: Async<String> = Uninitialized,
         val syncState: SyncState = SyncState.Idle,

From dfbe1188f016eea2b041e004fad24beef7c127fe Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Wed, 23 Sep 2020 13:43:34 +0200
Subject: [PATCH 13/17] Draft: use session realm provider when possible

---
 .../sdk/internal/session/room/draft/DraftRepository.kt      | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
index 7ca5e3c3ce..5cc6c990d9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.session.room.send.UserDraft
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.internal.database.RealmSessionProvider
 import org.matrix.android.sdk.internal.database.mapper.DraftMapper
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.UserDraftsEntity
@@ -35,7 +36,8 @@ import org.matrix.android.sdk.internal.util.awaitTransaction
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
+internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
+                                                   private val realmSessionProvider: RealmSessionProvider) {
 
     suspend fun saveDraft(roomId: String, userDraft: UserDraft) {
         monarchy.awaitTransaction {
@@ -50,7 +52,7 @@ internal class DraftRepository @Inject constructor(@SessionDatabase private val
     }
 
     fun getDraft(roomId: String): UserDraft? {
-        return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
+        return realmSessionProvider.withRealm { realm ->
             UserDraftsEntity.where(realm, roomId).findFirst()
                     ?.userDrafts
                     ?.firstOrNull()

From 211a8dc27277878649efdee539e3faf0954126fe Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Wed, 23 Sep 2020 13:43:42 +0200
Subject: [PATCH 14/17] Draft: fix icon tint

---
 vector/src/main/res/layout/item_room.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml
index c844f10ed2..814ef645d7 100644
--- a/vector/src/main/res/layout/item_room.xml
+++ b/vector/src/main/res/layout/item_room.xml
@@ -105,6 +105,7 @@
         android:layout_marginEnd="4dp"
         android:src="@drawable/ic_edit"
         android:visibility="gone"
+        android:tint="?riotx_text_primary"
         app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
         app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView"
         app:layout_constraintStart_toEndOf="@+id/roomNameView"

From 3b8a0f8671048a20cab6d8b27195c001269ce2a7 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Wed, 23 Sep 2020 14:03:08 +0200
Subject: [PATCH 15/17] Tint swip reply icon

---
 .../room/detail/RoomMessageTouchHelperCallback.kt  | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt
index ef13865374..41f386c606 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt
@@ -25,12 +25,15 @@ import android.view.MotionEvent
 import android.view.View
 import androidx.annotation.DrawableRes
 import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE
 import androidx.recyclerview.widget.RecyclerView
 import com.airbnb.epoxy.EpoxyModel
 import com.airbnb.epoxy.EpoxyTouchHelperCallback
 import com.airbnb.epoxy.EpoxyViewHolder
+import im.vector.app.R
+import im.vector.app.features.themes.ThemeUtils
 import timber.log.Timber
 import kotlin.math.abs
 import kotlin.math.min
@@ -52,7 +55,16 @@ class RoomMessageTouchHelperCallback(private val context: Context,
     private var replyButtonProgress: Float = 0F
     private var lastReplyButtonAnimationTime: Long = 0
 
-    private var imageDrawable: Drawable = ContextCompat.getDrawable(context, actionIcon)!!
+    private val imageDrawable: Drawable = DrawableCompat.wrap(
+            ContextCompat.getDrawable(context, actionIcon)!!
+    )
+
+    init {
+        DrawableCompat.setTint(
+                imageDrawable,
+                ThemeUtils.getColor(context, R.attr.riotx_text_primary)
+        )
+    }
 
     private val triggerDistance = convertToPx(100)
     private val minShowDistance = convertToPx(20)

From bcc64fb276195ae03b7e452446e9211cc387baa0 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Wed, 23 Sep 2020 14:03:39 +0200
Subject: [PATCH 16/17] Update CHANGES

---
 CHANGES.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGES.md b/CHANGES.md
index 4a158086a2..a3bf5d4f9c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,7 @@ Improvements 🙌:
 Bugfix 🐛:
  - Improve support for image/audio/video/file selection with intent changes (#1376)
  - Fix Splash layout on small screens
+ - Simplifies draft management and should fix bunch of draft issues (#952, #683)
 
 Translations 🗣:
  -

From 038e6613c08f73ce60c3c35df286aa565484c9d5 Mon Sep 17 00:00:00 2001
From: ganfra
 <francoisg@matrix.orgfrancoisg@matrix.orgfrancoisg@matrix.orgfrancoisg@matrix.org>
Date: Mon, 28 Sep 2020 14:05:24 +0200
Subject: [PATCH 17/17] Draft: update state when saving draft to avoid loosing
 current text

---
 .../home/room/detail/RoomDetailViewModel.kt   | 32 ++++++++++---------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 985604a49c..ce48d8ff89 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -449,21 +449,23 @@ class RoomDetailViewModel @AssistedInject constructor(
     /**
      * Convert a send mode to a draft and save the draft
      */
-    private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) {
-        withState {
-            when {
-                it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
-                    room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
-                }
-                it.sendMode is SendMode.REPLY                               -> {
-                    room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
-                }
-                it.sendMode is SendMode.QUOTE                               -> {
-                    room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
-                }
-                it.sendMode is SendMode.EDIT                                -> {
-                    room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
-                }
+    private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
+        when {
+            it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
+                setState { copy(sendMode = it.sendMode.copy(action.draft)) }
+                room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
+            }
+            it.sendMode is SendMode.REPLY                               -> {
+                setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
+                room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+            }
+            it.sendMode is SendMode.QUOTE                               -> {
+                setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
+                room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+            }
+            it.sendMode is SendMode.EDIT                                -> {
+                setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
+                room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
             }
         }
     }