mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
Merge pull request #2212 from vector-im/feature/bma/jump_to_read_receipt
Add "jump to read receipt" and "mention" actions from room member detail screen
This commit is contained in:
commit
50cf5b5322
15 changed files with 132 additions and 19 deletions
|
@ -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)
|
||||||
|
|
|
@ -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 userId the id of the user to look for
|
||||||
|
*
|
||||||
|
* @return the eventId where the read receipt for the provided user is attached, or null if not found
|
||||||
|
*/
|
||||||
|
fun getUserReadReceipt(userId: 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
|
||||||
|
|
|
@ -107,6 +107,16 @@ internal class DefaultReadService @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUserReadReceipt(userId: String): String? {
|
||||||
|
var eventId: String? = null
|
||||||
|
monarchy.doWithRealm {
|
||||||
|
eventId = ReadReceiptEntity.where(it, roomId = roomId, userId = userId)
|
||||||
|
.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) },
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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)
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,13 +172,16 @@ class RoomMemberProfileController @Inject constructor(
|
||||||
val membership = state.asyncMembership() ?: return
|
val membership = state.asyncMembership() ?: return
|
||||||
|
|
||||||
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
||||||
buildProfileAction(
|
|
||||||
id = "read_receipt",
|
if (state.hasReadReceipt) {
|
||||||
editable = false,
|
buildProfileAction(
|
||||||
title = stringProvider.getString(R.string.room_member_jump_to_read_receipt),
|
id = "read_receipt",
|
||||||
dividerColor = dividerColor,
|
editable = false,
|
||||||
action = { callback?.onJumpToReadReceiptClicked() }
|
title = stringProvider.getString(R.string.room_member_jump_to_read_receipt),
|
||||||
)
|
dividerColor = dividerColor,
|
||||||
|
action = { callback?.onJumpToReadReceiptClicked() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val ignoreActionTitle = state.buildIgnoreActionTitle()
|
val ignoreActionTitle = state.buildIgnoreActionTitle()
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -85,7 +85,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
isMine = session.myUserId == this.userId,
|
isMine = session.myUserId == this.userId,
|
||||||
userMatrixItem = room?.getRoomMember(initialState.userId)?.toMatrixItem()?.let { Success(it) } ?: Uninitialized
|
userMatrixItem = room?.getRoomMember(initialState.userId)?.toMatrixItem()?.let { Success(it) } ?: Uninitialized,
|
||||||
|
hasReadReceipt = room?.getUserReadReceipt(initialState.userId) != null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
observeIgnoredState()
|
observeIgnoredState()
|
||||||
|
|
|
@ -39,6 +39,7 @@ data class RoomMemberProfileViewState(
|
||||||
val allDevicesAreTrusted: Boolean = false,
|
val allDevicesAreTrusted: Boolean = false,
|
||||||
val allDevicesAreCrossSignedTrusted: Boolean = false,
|
val allDevicesAreCrossSignedTrusted: Boolean = false,
|
||||||
val asyncMembership: Async<Membership> = Uninitialized,
|
val asyncMembership: Async<Membership> = Uninitialized,
|
||||||
|
val hasReadReceipt: Boolean = false,
|
||||||
val actionPermissions: ActionPermissions = ActionPermissions()
|
val actionPermissions: ActionPermissions = ActionPermissions()
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.app.core.extensions.addFragment
|
||||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||||
import im.vector.app.core.platform.ToolbarConfigurable
|
import im.vector.app.core.platform.ToolbarConfigurable
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
||||||
import im.vector.app.features.room.RequireActiveMembershipViewEvents
|
import im.vector.app.features.room.RequireActiveMembershipViewEvents
|
||||||
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
||||||
import im.vector.app.features.room.RequireActiveMembershipViewState
|
import im.vector.app.features.room.RequireActiveMembershipViewState
|
||||||
|
@ -61,6 +62,9 @@ class RoomProfileActivity :
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var requireActiveMembershipViewModelFactory: RequireActiveMembershipViewModel.Factory
|
lateinit var requireActiveMembershipViewModelFactory: RequireActiveMembershipViewModel.Factory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore
|
||||||
|
|
||||||
override fun create(initialState: RequireActiveMembershipViewState): RequireActiveMembershipViewModel {
|
override fun create(initialState: RequireActiveMembershipViewState): RequireActiveMembershipViewModel {
|
||||||
return requireActiveMembershipViewModelFactory.create(initialState)
|
return requireActiveMembershipViewModelFactory.create(initialState)
|
||||||
}
|
}
|
||||||
|
@ -97,6 +101,13 @@ class RoomProfileActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (roomDetailPendingActionStore.data != null) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleRoomLeft(roomLeft: RequireActiveMembershipViewEvents.RoomLeft) {
|
private fun handleRoomLeft(roomLeft: RequireActiveMembershipViewEvents.RoomLeft) {
|
||||||
if (roomLeft.leftMessage != null) {
|
if (roomLeft.leftMessage != null) {
|
||||||
Toast.makeText(this, roomLeft.leftMessage, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, roomLeft.leftMessage, Toast.LENGTH_LONG).show()
|
||||||
|
|
Loading…
Reference in a new issue