Implement "Jump to read receipt" and "Mention" actions on the room member profile screen

This commit is contained in:
Benoit Marty 2020-10-05 18:42:34 +02:00 committed by Benoit Marty
parent 1fd24e746c
commit 7952e205b9
12 changed files with 118 additions and 12 deletions

View File

@ -15,6 +15,7 @@ Improvements 🙌:
- Add a menu item in the timeline as a shortcut to invite user (#2171) - Add a menu item in the timeline as a shortcut to invite user (#2171)
- Drawer: move settings access and add sign out action (#2171) - Drawer: move settings access and add sign out action (#2171)
- Filter room member (and banned users) by name (#2184) - Filter room member (and banned users) by name (#2184)
- Implement "Jump to read receipt" and "Mention" actions on the room member profile screen
Bugfix 🐛: Bugfix 🐛:
- Improve support for image/audio/video/file selection with intent changes (#1376) - Improve support for image/audio/video/file selection with intent changes (#1376)

View File

@ -63,6 +63,14 @@ interface ReadService {
*/ */
fun getMyReadReceiptLive(): LiveData<Optional<String>> fun getMyReadReceiptLive(): LiveData<Optional<String>>
/**
* 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 * Returns a live list of read receipts for a given event
* @param eventId: the event * @param eventId: the event

View File

@ -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<List<ReadReceipt>> { override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
val liveRealmData = monarchy.findAllMappedWithChanges( val liveRealmData = monarchy.findAllMappedWithChanges(
{ ReadReceiptsSummaryEntity.where(it, eventId) }, { ReadReceiptsSummaryEntity.where(it, eventId) },

View File

@ -36,6 +36,7 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan
import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.grouplist.SelectedGroupDataSource
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.HomeRoomListDataSource 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.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.html.VectorHtmlCompressor
@ -114,6 +115,8 @@ interface VectorComponent {
fun selectedGroupStore(): SelectedGroupDataSource fun selectedGroupStore(): SelectedGroupDataSource
fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
fun activeSessionObservableStore(): ActiveSessionDataSource fun activeSessionObservableStore(): ActiveSessionDataSource
fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler

View File

@ -23,6 +23,7 @@ const val THREE_MINUTES = 3 * 60_000L
/** /**
* Store an object T for a specific period of time * Store an object T for a specific period of time
* @param delay delay to keep the data, in millis
*/ */
open class TemporaryStore<T>(private val delay: Long = THREE_MINUTES) { open class TemporaryStore<T>(private val delay: Long = THREE_MINUTES) {
@ -30,14 +31,16 @@ open class TemporaryStore<T>(private val delay: Long = THREE_MINUTES) {
var data: T? = null var data: T? = null
set(value) { set(value) {
field = value
timer?.cancel() timer?.cancel()
timer = Timer().also { field = value
it.schedule(object : TimerTask() { if (value != null) {
override fun run() { timer = Timer().also {
field = null it.schedule(object : TimerTask() {
} override fun run() {
}, delay) field = null
}
}, delay)
}
} }
} }
} }

View File

@ -88,4 +88,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class EnsureNativeWidgetAllowed(val widget: Widget, data class EnsureNativeWidgetAllowed(val widget: Widget,
val userJustAccepted: Boolean, val userJustAccepted: Boolean,
val grantedEvents: RoomDetailViewEvents) : RoomDetailAction() val grantedEvents: RoomDetailViewEvents) : RoomDetailAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailAction()
} }

View File

@ -216,7 +216,8 @@ class RoomDetailFragment @Inject constructor(
private val notificationUtils: NotificationUtils, private val notificationUtils: NotificationUtils,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
private val matrixItemColorProvider: MatrixItemColorProvider, private val matrixItemColorProvider: MatrixItemColorProvider,
private val imageContentRenderer: ImageContentRenderer private val imageContentRenderer: ImageContentRenderer,
private val roomDetailPendingActionStore: RoomDetailPendingActionStore
) : ) :
VectorBaseFragment(), VectorBaseFragment(),
TimelineEventController.Callback, TimelineEventController.Callback,
@ -878,6 +879,17 @@ class RoomDetailFragment @Inject constructor(
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId) 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() { override fun onPause() {

View File

@ -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()
}

View File

@ -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<RoomDetailPendingAction>(10_000)

View File

@ -274,9 +274,15 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action) is RoomDetailAction.CancelSend -> handleCancel(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
}.exhaustive }.exhaustive
} }
private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
room.getUserReadReceipt(action.userId)
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
}
private fun handleSendSticker(action: RoomDetailAction.SendSticker) { private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
room.sendEvent(EventType.STICKER, action.stickerContent.toContent()) room.sendEvent(EventType.STICKER, action.stickerContent.toContent())
} }

View File

@ -43,6 +43,8 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.home.AvatarRenderer 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.devices.DeviceListBottomSheet
import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@ -61,7 +63,8 @@ data class RoomMemberProfileArgs(
class RoomMemberProfileFragment @Inject constructor( class RoomMemberProfileFragment @Inject constructor(
val viewModelFactory: RoomMemberProfileViewModel.Factory, val viewModelFactory: RoomMemberProfileViewModel.Factory,
private val roomMemberProfileController: RoomMemberProfileController, private val roomMemberProfileController: RoomMemberProfileController,
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer,
private val roomDetailPendingActionStore: RoomDetailPendingActionStore
) : VectorBaseFragment(), RoomMemberProfileController.Callback { ) : VectorBaseFragment(), RoomMemberProfileController.Callback {
private val fragmentArgs: RoomMemberProfileArgs by args() private val fragmentArgs: RoomMemberProfileArgs by args()
@ -276,11 +279,13 @@ class RoomMemberProfileFragment @Inject constructor(
} }
override fun onJumpToReadReceiptClicked() { override fun onJumpToReadReceiptClicked() {
vectorBaseActivity.notImplemented("Jump to read receipts") roomDetailPendingActionStore.data = RoomDetailPendingAction.JumpToReadReceipt(fragmentArgs.userId)
vectorBaseActivity.finish()
} }
override fun onMentionClicked() { override fun onMentionClicked() {
vectorBaseActivity.notImplemented("Mention") roomDetailPendingActionStore.data = RoomDetailPendingAction.MentionUser(fragmentArgs.userId)
vectorBaseActivity.finish()
} }
private fun handleShareRoomMemberProfile(permalink: String) { private fun handleShareRoomMemberProfile(permalink: String) {

View File

@ -36,6 +36,7 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs
import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import javax.inject.Inject import javax.inject.Inject
@ -43,7 +44,8 @@ import javax.inject.Inject
class RoomMemberListFragment @Inject constructor( class RoomMemberListFragment @Inject constructor(
val viewModelFactory: RoomMemberListViewModel.Factory, val viewModelFactory: RoomMemberListViewModel.Factory,
private val roomMemberListController: RoomMemberListController, private val roomMemberListController: RoomMemberListController,
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer,
private val roomDetailPendingActionStore: RoomDetailPendingActionStore
) : VectorBaseFragment(), RoomMemberListController.Callback { ) : VectorBaseFragment(), RoomMemberListController.Callback {
private val viewModel: RoomMemberListViewModel by fragmentViewModel() 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() { override fun onDestroyView() {
recyclerView.cleanup() recyclerView.cleanup()
super.onDestroyView() super.onDestroyView()