Merge pull request #2311 from vector-im/feature/bma/direct

Room member profile: Add action to create (or open) a DM (#2310)
This commit is contained in:
Benoit Marty 2020-10-29 16:21:26 +01:00 committed by GitHub
commit 239ead7ccb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 81 additions and 27 deletions

View file

@ -12,6 +12,7 @@ Improvements 🙌:
- Use Hardware keyboard enter to send message (use shift-enter for new line) (#1881, #1440) - Use Hardware keyboard enter to send message (use shift-enter for new line) (#1881, #1440)
- Edit and remove icons are now visible on image attachment preview screen (#2294) - Edit and remove icons are now visible on image attachment preview screen (#2294)
- Room profile: BigImageViewerActivity now only display the image. Use the room setting to change or delete the room Avatar - Room profile: BigImageViewerActivity now only display the image. Use the room setting to change or delete the room Avatar
- Room member profile: Add action to create (or open) a DM (#2310)
Bugfix 🐛: Bugfix 🐛:
- Messages encrypted with no way to decrypt after SDK update from 0.18 to 1.0.0 (#2252) - Messages encrypted with no way to decrypt after SDK update from 0.18 to 1.0.0 (#2252)

View file

@ -285,14 +285,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
fun createDM(alice: Session, bob: Session): String { fun createDM(alice: Session, bob: Session): String {
val roomId = mTestHelper.doSync<String> { val roomId = mTestHelper.doSync<String> {
alice.createRoom( alice.createDirectRoom(bob.myUserId, it)
CreateRoomParams().apply {
invitedUserIds.add(bob.myUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
},
it
)
} }
mTestHelper.waitWithLatch { latch -> mTestHelper.waitWithLatch { latch ->

View file

@ -35,6 +35,22 @@ interface RoomService {
fun createRoom(createRoomParams: CreateRoomParams, fun createRoom(createRoomParams: CreateRoomParams,
callback: MatrixCallback<String>): Cancelable callback: MatrixCallback<String>): Cancelable
/**
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
*/
fun createDirectRoom(otherUserId: String,
callback: MatrixCallback<String>): Cancelable {
return createRoom(
CreateRoomParams()
.apply {
invitedUserIds.add(otherUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
},
callback
)
}
/** /**
* Join a room by id * Join a room by id
* @param roomIdOrAlias the roomId or the room alias of the room to join * @param roomIdOrAlias the roomId or the room alias of the room to join
@ -113,5 +129,16 @@ interface RoomService {
*/ */
fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>>
fun getExistingDirectRoomWithUser(otherUserId: String): Room? /**
* Return the roomId of an existing DM with the other user, or null if such room does not exist
* A room is a DM if:
* - it is listed in the `m.direct` account data
* - the current user has joined the room
* - the other user is invited or has joined the room
* - it has exactly 2 members
* Note:
* - the returning room can be encrypted or not
* - the power level of the users are not taken into account. Normally in a DM, the 2 members are admins of the room
*/
fun getExistingDirectRoomWithUser(otherUserId: String): String?
} }

View file

@ -61,7 +61,7 @@ internal class DefaultRoomService @Inject constructor(
return roomGetter.getRoom(roomId) return roomGetter.getRoom(roomId)
} }
override fun getExistingDirectRoomWithUser(otherUserId: String): Room? { override fun getExistingDirectRoomWithUser(otherUserId: String): String? {
return roomGetter.getDirectRoomWith(otherUserId) return roomGetter.getDirectRoomWith(otherUserId)
} }

View file

@ -25,13 +25,12 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import javax.inject.Inject import javax.inject.Inject
internal interface RoomGetter { internal interface RoomGetter {
fun getRoom(roomId: String): Room? fun getRoom(roomId: String): Room?
fun getDirectRoomWith(otherUserId: String): Room? fun getDirectRoomWith(otherUserId: String): String?
} }
@SessionScope @SessionScope
@ -46,16 +45,14 @@ internal class DefaultRoomGetter @Inject constructor(
} }
} }
override fun getDirectRoomWith(otherUserId: String): Room? { override fun getDirectRoomWith(otherUserId: String): String? {
return realmSessionProvider.withRealm { realm -> return realmSessionProvider.withRealm { realm ->
RoomSummaryEntity.where(realm) RoomSummaryEntity.where(realm)
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.findAll() .findAll()
.filter { dm -> dm.otherMemberIds.contains(otherUserId) } .firstOrNull { dm -> dm.otherMemberIds.size == 1 && dm.otherMemberIds.first() == otherUserId }
.map { it.roomId } ?.roomId
.firstOrNull { roomId -> otherUserId in RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() }
?.let { roomId -> createRoom(realm, roomId) }
} }
} }

View file

@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
@ -233,7 +232,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
override fun handle(action: VerificationAction) = withState { state -> override fun handle(action: VerificationAction) = withState { state ->
val otherUserId = state.otherUserMxItem?.id ?: return@withState val otherUserId = state.otherUserMxItem?.id ?: return@withState
val roomId = state.roomId val roomId = state.roomId
?: session.getExistingDirectRoomWithUser(otherUserId)?.roomId ?: session.getExistingDirectRoomWithUser(otherUserId)
when (action) { when (action) {
is VerificationAction.RequestVerificationByDM -> { is VerificationAction.RequestVerificationByDM -> {
@ -245,14 +244,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
pendingRequest = Loading() pendingRequest = Loading()
) )
} }
val roomParams = CreateRoomParams() session.createDirectRoom(otherUserId, object : MatrixCallback<String> {
.apply {
invitedUserIds.add(otherUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
}
session.createRoom(roomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) { override fun onSuccess(data: String) {
setState { setState {
copy( copy(

View file

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

View file

@ -363,6 +363,7 @@ class RoomDetailFragment @Inject constructor(
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
}.exhaustive }.exhaustive
} }
@ -371,6 +372,10 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
navigator.openRoom(requireContext(), openRoom.roomId, null)
}
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) { private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
val tag = RoomWidgetPermissionBottomSheet::class.java.name val tag = RoomWidgetPermissionBottomSheet::class.java.name
val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet
@ -886,6 +891,8 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
is RoomDetailPendingAction.MentionUser -> is RoomDetailPendingAction.MentionUser ->
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm ->
roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
}.exhaustive }.exhaustive
} }

View file

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail package im.vector.app.features.home.room.detail
sealed class RoomDetailPendingAction { sealed class RoomDetailPendingAction {
data class OpenOrCreateDm(val userId: String) : RoomDetailPendingAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction() data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction()
data class MentionUser(val userId: String) : RoomDetailPendingAction() data class MentionUser(val userId: String) : RoomDetailPendingAction()
} }

View file

@ -38,6 +38,8 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class ShowInfoOkDialog(val message: String) : RoomDetailViewEvents() data class ShowInfoOkDialog(val message: String) : RoomDetailViewEvents()
data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents() data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents()
data class OpenRoom(val roomId: String) : RoomDetailViewEvents()
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents() data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents() data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()

View file

@ -273,10 +273,28 @@ 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.OpenOrCreateDm -> handleOpenOrCreateDm(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
}.exhaustive }.exhaustive
} }
private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) {
val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId)
if (existingDmRoomId == null) {
// First create a direct room
viewModelScope.launch(Dispatchers.IO) {
val roomId = awaitCallback<String> {
session.createDirectRoom(action.userId, it)
}
_viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId))
}
} else {
if (existingDmRoomId != initialState.roomId) {
_viewEvents.post(RoomDetailViewEvents.OpenRoom(existingDmRoomId))
}
}
}
private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) { private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
room.getUserReadReceipt(action.userId) room.getUserReadReceipt(action.userId)
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) } ?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }

View file

@ -45,6 +45,7 @@ class RoomMemberProfileController @Inject constructor(
fun onTapVerify() fun onTapVerify()
fun onShowDeviceList() fun onShowDeviceList()
fun onShowDeviceListNoCrossSigning() fun onShowDeviceListNoCrossSigning()
fun onOpenDmClicked()
fun onJumpToReadReceiptClicked() fun onJumpToReadReceiptClicked()
fun onMentionClicked() fun onMentionClicked()
fun onEditPowerLevel(currentRole: Role) fun onEditPowerLevel(currentRole: Role)
@ -173,6 +174,14 @@ class RoomMemberProfileController @Inject constructor(
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(
id = "direct",
editable = false,
title = stringProvider.getString(R.string.room_member_open_or_create_dm),
dividerColor = dividerColor,
action = { callback?.onOpenDmClicked() }
)
if (state.hasReadReceipt) { if (state.hasReadReceipt) {
buildProfileAction( buildProfileAction(
id = "read_receipt", id = "read_receipt",

View file

@ -278,6 +278,11 @@ class RoomMemberProfileFragment @Inject constructor(
DeviceListBottomSheet.newInstance(it.userId).show(parentFragmentManager, "DEV_LIST") DeviceListBottomSheet.newInstance(it.userId).show(parentFragmentManager, "DEV_LIST")
} }
override fun onOpenDmClicked() {
roomDetailPendingActionStore.data = RoomDetailPendingAction.OpenOrCreateDm(fragmentArgs.userId)
vectorBaseActivity.finish()
}
override fun onJumpToReadReceiptClicked() { override fun onJumpToReadReceiptClicked() {
roomDetailPendingActionStore.data = RoomDetailPendingAction.JumpToReadReceipt(fragmentArgs.userId) roomDetailPendingActionStore.data = RoomDetailPendingAction.JumpToReadReceipt(fragmentArgs.userId)
vectorBaseActivity.finish() vectorBaseActivity.finish()

View file

@ -2167,6 +2167,7 @@
<string name="room_member_power_level_default_in">Default in %1$s</string> <string name="room_member_power_level_default_in">Default in %1$s</string>
<string name="room_member_power_level_custom_in">Custom (%1$d) in %2$s</string> <string name="room_member_power_level_custom_in">Custom (%1$d) in %2$s</string>
<string name="room_member_open_or_create_dm">Direct message</string>
<string name="room_member_jump_to_read_receipt">Jump to read receipt</string> <string name="room_member_jump_to_read_receipt">Jump to read receipt</string>
<string name="rendering_event_error_type_of_event_not_handled">"Element does not handle events of type '%1$s'"</string> <string name="rendering_event_error_type_of_event_not_handled">"Element does not handle events of type '%1$s'"</string>