From 85417eb24c2dff8039ac3fb5c9a8227ebb7b29b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty 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 = Uninitialized, val roomMemberSummaries: Async = Uninitialized, + val filteredRoomMemberSummaries: RoomMemberSummaries? = null, val threePidInvites: Async> = Uninitialized, val trustLevelMap: Async> = 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 @@ + + From 78ed184f60381c529b287bea20f3a33975ccb661 Mon Sep 17 00:00:00 2001 From: Benoit Marty 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() { @@ -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 = Uninitialized, val roomMemberSummaries: Async = Uninitialized, - val filteredRoomMemberSummaries: RoomMemberSummaries? = null, + val filter: String = "", val threePidInvites: Async> = Uninitialized, val trustLevelMap: Async> = 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 { + 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 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() ?: return val stateKey = event.stateKey ?: return From f5580212ea51e1d961ab023aa3253ca8439211c2 Mon Sep 17 00:00:00 2001 From: Benoit Marty 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() + ?.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 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" /> 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 @@ - - - + app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar"> + + + + + + + + + + From c0842d4da70e56eeb1fa20ec6d6661a09e0c6497 Mon Sep 17 00:00:00 2001 From: Benoit Marty 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() { @@ -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 = Uninitialized, val bannedMemberSummaries: Async> = Uninitialized, + val filter: String = "", val onGoingModerationAction: List = 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 Filter room members + Filter banned users No results ROOMS MESSAGES From 8170f523e091ef4ca366e4e787a79fb9fa467a12 Mon Sep 17 00:00:00 2001 From: Benoit Marty 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 { 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 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 @@ "IGNORE USER" "Content reported" - "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" + "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." "Reported as spam" - "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" + "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." "Reported as inappropriate" - "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" + "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." 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. From b2030930739b839b8d5dc483d05e95537b7f2ba0 Mon Sep 17 00:00:00 2001 From: Benoit Marty 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"> - + + app:layout_scrollFlags="scroll|enterAlways|snap"> + + + + From aa0520d47dba1c2b0d50b946aef4784a6317d75f Mon Sep 17 00:00:00 2001 From: ganfra 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> { - return room.getDraftsLive().asObservable() + fun liveDraft(): Observable> { + return room.getDraftLive().asObservable() + .startWithCallable { + room.getDraft().toOptional() + } } fun liveNotificationState(): Observable { 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): 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> + fun getDraft(): UserDraft? + + /** + * Return the current draft if any, as a live data + */ + fun getDraftLive(): LiveData> } 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> { + override fun getDraft(): UserDraft? { + return draftRepository.getDraft(roomId) + } + + override fun getDraftLive(): LiveData> { 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> { + 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> { - 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()?.relatesTo?.inReplyTo?.eventId ?: state.sendMode.timelineEvent.root.content.toModel()?.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 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 = Uninitialized, val activeRoomWidgets: Async> = Uninitialized, val typingMessage: String? = null, - val sendMode: SendMode = SendMode.REGULAR(""), + val sendMode: SendMode = SendMode.REGULAR("", false), val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, val syncState: SyncState = SyncState.Idle, From dfbe1188f016eea2b041e004fad24beef7c127fe Mon Sep 17 00:00:00 2001 From: ganfra 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 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 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 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 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()) } } }