From 7952e205b9abcb49358425e00e0f3aac9e564975 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Oct 2020 18:42:34 +0200 Subject: [PATCH] Implement "Jump to read receipt" and "Mention" actions on the room member profile screen --- CHANGES.md | 1 + .../sdk/api/session/room/read/ReadService.kt | 8 ++++++ .../session/room/read/DefaultReadService.kt | 10 ++++++++ .../im/vector/app/core/di/VectorComponent.kt | 3 +++ .../vector/app/core/utils/TemporaryStore.kt | 17 +++++++------ .../home/room/detail/RoomDetailAction.kt | 2 ++ .../home/room/detail/RoomDetailFragment.kt | 14 ++++++++++- .../room/detail/RoomDetailPendingAction.kt | 22 ++++++++++++++++ .../detail/RoomDetailPendingActionStore.kt | 25 +++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 6 +++++ .../RoomMemberProfileFragment.kt | 11 +++++--- .../members/RoomMemberListFragment.kt | 11 +++++++- 12 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt diff --git a/CHANGES.md b/CHANGES.md index ec853d96ef..05d189ed12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Improvements 🙌: - Add a menu item in the timeline as a shortcut to invite user (#2171) - Drawer: move settings access and add sign out action (#2171) - Filter room member (and banned users) by name (#2184) + - Implement "Jump to read receipt" and "Mention" actions on the room member profile screen Bugfix 🐛: - Improve support for image/audio/video/file selection with intent changes (#1376) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt index 3aa9d60e6a..8f76295fe7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt @@ -63,6 +63,14 @@ interface ReadService { */ fun getMyReadReceiptLive(): LiveData> + /** + * Get the eventId where the read receipt for the provided user is + * @param otherUserId the userId param to look for + * + * @return the eventId where the read receipt for the provided user is attached, or null if not found + */ + fun getUserReadReceipt(otherUserId: String): String? + /** * Returns a live list of read receipts for a given event * @param eventId: the event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index a5520972b0..162ad7d895 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -107,6 +107,16 @@ internal class DefaultReadService @AssistedInject constructor( } } + override fun getUserReadReceipt(otherUserId: String): String? { + var eventId: String? = null + monarchy.doWithRealm { + eventId = ReadReceiptEntity.where(it, roomId = roomId, userId = otherUserId) + .findFirst() + ?.eventId + } + return eventId + } + override fun getEventReadReceiptsLive(eventId: String): LiveData> { val liveRealmData = monarchy.findAllMappedWithChanges( { ReadReceiptsSummaryEntity.where(it, eventId) }, diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index 4ba3d6ba13..28f3a52efa 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -36,6 +36,7 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.HomeRoomListDataSource +import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.VectorHtmlCompressor @@ -114,6 +115,8 @@ interface VectorComponent { fun selectedGroupStore(): SelectedGroupDataSource + fun roomDetailPendingActionStore(): RoomDetailPendingActionStore + fun activeSessionObservableStore(): ActiveSessionDataSource fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler diff --git a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt index 32b995c004..63f80341eb 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt @@ -23,6 +23,7 @@ const val THREE_MINUTES = 3 * 60_000L /** * Store an object T for a specific period of time + * @param delay delay to keep the data, in millis */ open class TemporaryStore(private val delay: Long = THREE_MINUTES) { @@ -30,14 +31,16 @@ open class TemporaryStore(private val delay: Long = THREE_MINUTES) { var data: T? = null set(value) { - field = value timer?.cancel() - timer = Timer().also { - it.schedule(object : TimerTask() { - override fun run() { - field = null - } - }, delay) + field = value + if (value != null) { + timer = Timer().also { + it.schedule(object : TimerTask() { + override fun run() { + field = null + } + }, delay) + } } } } 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 5f851b8da7..03809d3c75 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 @@ -88,4 +88,6 @@ sealed class RoomDetailAction : VectorViewModelAction { data class EnsureNativeWidgetAllowed(val widget: Widget, val userJustAccepted: Boolean, val grantedEvents: RoomDetailViewEvents) : RoomDetailAction() + + data class JumpToReadReceipt(val userId: 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 66e49cf060..eadea299e6 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 @@ -216,7 +216,8 @@ class RoomDetailFragment @Inject constructor( private val notificationUtils: NotificationUtils, private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, private val matrixItemColorProvider: MatrixItemColorProvider, - private val imageContentRenderer: ImageContentRenderer + private val imageContentRenderer: ImageContentRenderer, + private val roomDetailPendingActionStore: RoomDetailPendingActionStore ) : VectorBaseFragment(), TimelineEventController.Callback, @@ -878,6 +879,17 @@ class RoomDetailFragment @Inject constructor( override fun onResume() { super.onResume() notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId) + roomDetailPendingActionStore.data?.let { handlePendingAction(it) } + roomDetailPendingActionStore.data = null + } + + private fun handlePendingAction(roomDetailPendingAction: RoomDetailPendingAction) { + when (roomDetailPendingAction) { + is RoomDetailPendingAction.JumpToReadReceipt -> + roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) + is RoomDetailPendingAction.MentionUser -> + insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) + }.exhaustive } override fun onPause() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt new file mode 100644 index 0000000000..394d46ef8d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt @@ -0,0 +1,22 @@ +/* + * 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.home.room.detail + +sealed class RoomDetailPendingAction { + data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction() + data class MentionUser(val userId: String) : RoomDetailPendingAction() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt new file mode 100644 index 0000000000..9ffbb83a47 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt @@ -0,0 +1,25 @@ +/* + * 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.home.room.detail + +import im.vector.app.core.utils.TemporaryStore +import javax.inject.Inject +import javax.inject.Singleton + +// Store to keep a pending action from sub screen of a room detail +@Singleton +class RoomDetailPendingActionStore @Inject constructor() : TemporaryStore(10_000) 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 ebc265d935..009f8ec802 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 @@ -274,9 +274,15 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) is RoomDetailAction.CancelSend -> handleCancel(action) + is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) }.exhaustive } + private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) { + room.getUserReadReceipt(action.userId) + ?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) } + } + private fun handleSendSticker(action: RoomDetailAction.SendSticker) { room.sendEvent(EventType.STICKER, action.stickerContent.toContent()) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 169cba09eb..2f5b2d5387 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -43,6 +43,8 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.RoomDetailPendingAction +import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import kotlinx.android.parcel.Parcelize @@ -61,7 +63,8 @@ data class RoomMemberProfileArgs( class RoomMemberProfileFragment @Inject constructor( val viewModelFactory: RoomMemberProfileViewModel.Factory, private val roomMemberProfileController: RoomMemberProfileController, - private val avatarRenderer: AvatarRenderer + private val avatarRenderer: AvatarRenderer, + private val roomDetailPendingActionStore: RoomDetailPendingActionStore ) : VectorBaseFragment(), RoomMemberProfileController.Callback { private val fragmentArgs: RoomMemberProfileArgs by args() @@ -276,11 +279,13 @@ class RoomMemberProfileFragment @Inject constructor( } override fun onJumpToReadReceiptClicked() { - vectorBaseActivity.notImplemented("Jump to read receipts") + roomDetailPendingActionStore.data = RoomDetailPendingAction.JumpToReadReceipt(fragmentArgs.userId) + vectorBaseActivity.finish() } override fun onMentionClicked() { - vectorBaseActivity.notImplemented("Mention") + roomDetailPendingActionStore.data = RoomDetailPendingAction.MentionUser(fragmentArgs.userId) + vectorBaseActivity.finish() } private fun handleShareRoomMemberProfile(permalink: String) { 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 67577e866e..d6d56b8cb9 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 @@ -36,6 +36,7 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.roomprofile.RoomProfileArgs import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import javax.inject.Inject @@ -43,7 +44,8 @@ import javax.inject.Inject class RoomMemberListFragment @Inject constructor( val viewModelFactory: RoomMemberListViewModel.Factory, private val roomMemberListController: RoomMemberListController, - private val avatarRenderer: AvatarRenderer + private val avatarRenderer: AvatarRenderer, + private val roomDetailPendingActionStore: RoomDetailPendingActionStore ) : VectorBaseFragment(), RoomMemberListController.Callback { private val viewModel: RoomMemberListViewModel by fragmentViewModel() @@ -96,6 +98,13 @@ class RoomMemberListFragment @Inject constructor( }) } + override fun onResume() { + super.onResume() + if (roomDetailPendingActionStore.data != null) { + vectorBaseActivity.finish() + } + } + override fun onDestroyView() { recyclerView.cleanup() super.onDestroyView()