Verify from RoomMember Profile

This commit is contained in:
Valere 2020-01-24 19:15:23 +01:00
parent a758efc018
commit d60351bcb7
14 changed files with 198 additions and 165 deletions

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.crypto.sas package im.vector.matrix.android.api.session.crypto.sas
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
/** /**
@ -51,7 +52,7 @@ interface VerificationService {
/** /**
* Request a key verification from another user using toDevice events. * Request a key verification from another user using toDevice events.
*/ */
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String): PendingVerificationRequest fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String, localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String)

View File

@ -715,7 +715,7 @@ internal class DefaultVerificationService @Inject constructor(
} }
} }
override fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String) override fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String, localId: String?)
: PendingVerificationRequest { : PendingVerificationRequest {
Timber.i("## SAS Requesting verification to user: $userId in room $roomId") Timber.i("## SAS Requesting verification to user: $userId in room $roomId")
@ -737,7 +737,7 @@ internal class DefaultVerificationService @Inject constructor(
} }
} }
val localID = LocalEcho.createLocalEchoId() val localID = localId ?: LocalEcho.createLocalEchoId()
val verificationRequest = PendingVerificationRequest( val verificationRequest = PendingVerificationRequest(
ageLocalTs = System.currentTimeMillis(), ageLocalTs = System.currentTimeMillis(),

View File

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.VersioningState
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
@ -36,6 +37,7 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.query.process import im.vector.matrix.android.internal.query.process
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
@ -76,17 +78,23 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
override fun getExistingDirectRoomWithUser(otherUserId: String): Room? { override fun getExistingDirectRoomWithUser(otherUserId: String): Room? {
Realm.getInstance(monarchy.realmConfiguration).use { realm -> Realm.getInstance(monarchy.realmConfiguration).use { realm ->
val roomId = RoomSummaryEntity.where(realm) val candidates = RoomSummaryEntity.where(realm)
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
.findAll()?.let { dms -> .findAll()?.filter { dm ->
dms.firstOrNull { dm.otherMemberIds.contains(otherUserId)
it.otherMemberIds.contains(otherUserId) && dm.membership == Membership.JOIN
}?.map {
it.roomId
} }
} ?: return null
?.roomId ?: return null candidates.forEach { roomId ->
if (RoomMemberHelper(realm, roomId).getActiveRoomMemberIds().any { it == otherUserId }) {
return RoomEntity.where(realm, roomId).findFirst()?.let { roomFactory.create(roomId) } return RoomEntity.where(realm, roomId).findFirst()?.let { roomFactory.create(roomId) }
} }
} }
return null
}
}
override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
return monarchy return monarchy

View File

@ -40,7 +40,6 @@ import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMet
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestViewModel
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_verification.* import kotlinx.android.synthetic.main.bottom_sheet_verification.*
@ -57,8 +56,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
val roomId: String? = null val roomId: String? = null
) : Parcelable ) : Parcelable
@Inject
lateinit var verificationRequestViewModelFactory: VerificationRequestViewModel.Factory
@Inject @Inject
lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory
@Inject @Inject
@ -133,7 +130,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
it.otherUserMxItem?.id ?: "", it.otherUserMxItem?.id ?: "",
// If it was outgoing it.transaction id would be null, but the pending request // If it was outgoing it.transaction id would be null, but the pending request
// would be updated (from localID to txId) // would be updated (from localID to txId)
it.pendingRequest?.transactionId ?: it.transactionId)) it.pendingRequest.invoke()?.transactionId ?: it.transactionId))
}) })
} }
VerificationTxState.Verified, VerificationTxState.Verified,
@ -153,36 +150,36 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
// At this point there is no transaction for this request // At this point there is no transaction for this request
// Transaction has not yet started // Transaction has not yet started
if (it.pendingRequest?.cancelConclusion != null) { if (it.pendingRequest.invoke()?.cancelConclusion != null) {
// The request has been declined, we should dismiss // The request has been declined, we should dismiss
dismiss() dismiss()
} }
// If it's an outgoing // If it's an outgoing
if (it.pendingRequest == null || !it.pendingRequest.isIncoming) { if (it.pendingRequest.invoke() == null || it.pendingRequest.invoke()?.isIncoming == false) {
Timber.v("## SAS show bottom sheet for outgoing request") Timber.v("## SAS show bottom sheet for outgoing request")
if (it.pendingRequest?.isReady == true) { if (it.pendingRequest.invoke()?.isReady == true) {
Timber.v("## SAS show bottom sheet for outgoing and ready request") Timber.v("## SAS show bottom sheet for outgoing and ready request")
// Show choose method fragment with waiting // Show choose method fragment with waiting
showFragment(VerificationChooseMethodFragment::class, Bundle().apply { showFragment(VerificationChooseMethodFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id
?: "", it.pendingRequest.transactionId)) ?: "", it.pendingRequest.invoke()?.transactionId))
}) })
} else { } else {
// Stay on the start fragment // Stay on the start fragment
showFragment(VerificationRequestFragment::class, Bundle().apply { showFragment(VerificationRequestFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs( putParcelable(MvRx.KEY_ARG, VerificationArgs(
it.otherUserMxItem?.id ?: "", it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId, it.pendingRequest.invoke()?.transactionId,
it.roomId)) it.roomId))
}) })
} }
} else if (it.pendingRequest.isIncoming) { } else if (it.pendingRequest.invoke()?.isIncoming == true) {
Timber.v("## SAS show bottom sheet for Incoming request") Timber.v("## SAS show bottom sheet for Incoming request")
// For incoming we can switch to choose method because ready is being sent or already sent // For incoming we can switch to choose method because ready is being sent or already sent
showFragment(VerificationChooseMethodFragment::class, Bundle().apply { showFragment(VerificationChooseMethodFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id putParcelable(MvRx.KEY_ARG, VerificationArgs(it.otherUserMxItem?.id
?: "", it.pendingRequest.transactionId)) ?: "", it.pendingRequest.invoke()?.transactionId))
}) })
} }
super.invalidate() super.invalidate()
@ -209,7 +206,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet { fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet {
return VerificationBottomSheet().apply { return VerificationBottomSheet().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( putParcelable(MvRx.KEY_ARG, VerificationArgs(
otherUserId = otherUserId, otherUserId = otherUserId,
roomId = roomId, roomId = roomId,
verificationId = transactionId verificationId = transactionId

View File

@ -18,21 +18,27 @@ package im.vector.riotx.features.crypto.verification
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.QRVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.QRVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
@ -43,7 +49,8 @@ import im.vector.riotx.core.utils.LiveEvent
data class VerificationBottomSheetViewState( data class VerificationBottomSheetViewState(
val otherUserMxItem: MatrixItem? = null, val otherUserMxItem: MatrixItem? = null,
val roomId: String? = null, val roomId: String? = null,
val pendingRequest: PendingVerificationRequest? = null, val pendingRequest: Async<PendingVerificationRequest> = Uninitialized,
val pendingLocalId: String? = null,
val transactionState: VerificationTxState? = null, val transactionState: VerificationTxState? = null,
val transactionId: String? = null, val transactionId: String? = null,
val cancelCode: CancelCode? = null val cancelCode: CancelCode? = null
@ -93,7 +100,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
otherUserMxItem = userItem?.toMatrixItem(), otherUserMxItem = userItem?.toMatrixItem(),
transactionState = sasTx?.state, transactionState = sasTx?.state,
transactionId = args.verificationId, transactionId = args.verificationId,
pendingRequest = pr, pendingRequest = if (pr != null) Success(pr) else Uninitialized,
roomId = args.roomId) roomId = args.roomId)
) )
} }
@ -106,9 +113,40 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
when (action) { when (action) {
is VerificationAction.RequestVerificationByDM -> { is VerificationAction.RequestVerificationByDM -> {
if (roomId == null) return@withState if (roomId == null) {
val localID = LocalEcho.createLocalEchoId()
setState { setState {
copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId)) copy(
pendingLocalId = localID,
pendingRequest = Loading()
)
}
val roomParams = CreateRoomParams().apply {
invitedUserIds = listOf(otherUserId).toMutableList()
setDirectMessage()
}
session.createRoom(roomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) {
setState {
copy(
roomId = data,
pendingRequest = Success(
session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, data, pendingLocalId)
)
)
}
}
override fun onFailure(failure: Throwable) {
setState {
copy(pendingRequest = Fail(failure))
}
}
})
} else {
setState {
copy(pendingRequest = Success(session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId)))
}
} }
} }
is VerificationAction.StartSASVerification -> { is VerificationAction.StartSASVerification -> {
@ -154,7 +192,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
} }
override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
if (tx.transactionId == (state.pendingRequest?.transactionId ?: state.transactionId)) { if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request // A SAS tx has been started following this request
setState { setState {
copy( copy(
@ -171,9 +209,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
if (pr.localID == state.pendingRequest?.localID || state.pendingRequest?.transactionId == pr.transactionId) { if (pr.localID == state.pendingLocalId
|| pr.localID == state.pendingRequest.invoke()?.localID
|| state.pendingRequest.invoke()?.transactionId == pr.transactionId) {
setState { setState {
copy(pendingRequest = pr) copy(pendingRequest = Success(pr))
} }
} }
} }

View File

@ -72,7 +72,7 @@ class VerificationChooseMethodFragment @Inject constructor(
override fun doVerifyBySas() = withState(sharedViewModel) { override fun doVerifyBySas() = withState(sharedViewModel) {
sharedViewModel.handle(VerificationAction.StartSASVerification( sharedViewModel.handle(VerificationAction.StartSASVerification(
it.otherUserMxItem?.id ?: "", it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId ?: "")) it.pendingRequest.invoke()?.transactionId ?: ""))
} }
override fun openCamera() { override fun openCamera() {
@ -115,7 +115,7 @@ class VerificationChooseMethodFragment @Inject constructor(
private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) { private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) {
sharedViewModel.handle(VerificationAction.RemoteQrCodeScanned( sharedViewModel.handle(VerificationAction.RemoteQrCodeScanned(
it.otherUserMxItem?.id ?: "", it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId ?: "", it.pendingRequest.invoke()?.transactionId ?: "",
remoteQrCode remoteQrCode
)) ))
} }

View File

@ -19,11 +19,14 @@ package im.vector.riotx.features.crypto.verification.request
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.core.utils.colorizeMatchingText
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
@ -36,17 +39,18 @@ class VerificationRequestController @Inject constructor(
var listener: Listener? = null var listener: Listener? = null
private var viewState: VerificationRequestViewState? = null private var viewState: VerificationBottomSheetViewState? = null
fun update(viewState: VerificationRequestViewState) { fun update(viewState: VerificationBottomSheetViewState) {
this.viewState = viewState this.viewState = viewState
requestModelBuild() requestModelBuild()
} }
override fun buildModels() { override fun buildModels() {
val state = viewState ?: return val state = viewState ?: return
val matrixItem = viewState?.otherUserMxItem ?: return
val styledText = state.matrixItem.let { val styledText = matrixItem.let {
stringProvider.getString(R.string.verification_request_notice, it.id) stringProvider.getString(R.string.verification_request_notice, it.id)
.toSpannable() .toSpannable()
.colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)) .colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color))
@ -61,14 +65,8 @@ class VerificationRequestController @Inject constructor(
id("sep") id("sep")
} }
when (state.started) { when (val pr = state.pendingRequest) {
is Loading -> { is Uninitialized -> {
bottomSheetVerificationWaitingItem {
id("waiting")
title(stringProvider.getString(R.string.verification_request_waiting_for, state.matrixItem.getBestName()))
}
}
else -> {
bottomSheetVerificationActionItem { bottomSheetVerificationActionItem {
id("start") id("start")
title(stringProvider.getString(R.string.start_verification)) title(stringProvider.getString(R.string.start_verification))
@ -79,6 +77,20 @@ class VerificationRequestController @Inject constructor(
listener { listener?.onClickOnVerificationStart() } listener { listener?.onClickOnVerificationStart() }
} }
} }
is Loading -> {
bottomSheetVerificationWaitingItem {
id("waiting")
title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName()))
}
}
is Success -> {
if (!pr.invoke().isReady) {
bottomSheetVerificationWaitingItem {
id("waiting")
title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName()))
}
}
}
} }
} }

View File

@ -30,22 +30,19 @@ import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject import javax.inject.Inject
class VerificationRequestFragment @Inject constructor( class VerificationRequestFragment @Inject constructor(
val verificationRequestViewModelFactory: VerificationRequestViewModel.Factory,
val controller: VerificationRequestController val controller: VerificationRequestController
) : VectorBaseFragment(), VerificationRequestController.Listener { ) : VectorBaseFragment(), VerificationRequestController.Listener {
private val viewModel by fragmentViewModel(VerificationRequestViewModel::class) private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupRecyclerView() setupRecyclerView()
} }
override fun onDestroyView() { override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup() bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null controller.listener = null
@ -61,7 +58,9 @@ class VerificationRequestFragment @Inject constructor(
controller.update(state) controller.update(state)
} }
override fun onClickOnVerificationStart() = withState(viewModel) { state -> override fun onClickOnVerificationStart(): Unit = withState(viewModel) { state ->
sharedViewModel.handle(VerificationAction.RequestVerificationByDM(state.matrixItem.id, state.roomId)) state.otherUserMxItem?.id?.let { otherUserId ->
viewModel.handle(VerificationAction.RequestVerificationByDM(otherUserId, state.roomId))
}
} }
} }

View File

@ -1,103 +0,0 @@
/*
* Copyright 2019 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.riotx.features.crypto.verification.request
import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
data class VerificationRequestViewState(
val roomId: String? = null,
val matrixItem: MatrixItem,
val started: Async<Boolean> = Success(false)
) : MvRxState
class VerificationRequestViewModel @AssistedInject constructor(
@Assisted initialState: VerificationRequestViewState,
private val session: Session
) : VectorViewModel<VerificationRequestViewState, EmptyAction>(initialState), VerificationService.VerificationListener {
@AssistedInject.Factory
interface Factory {
fun create(initialState: VerificationRequestViewState): VerificationRequestViewModel
}
init {
session.getSasVerificationService().addListener(this)
}
override fun onCleared() {
session.getSasVerificationService().removeListener(this)
super.onCleared()
}
companion object : MvRxViewModelFactory<VerificationRequestViewModel, VerificationRequestViewState> {
override fun create(viewModelContext: ViewModelContext, state: VerificationRequestViewState): VerificationRequestViewModel? {
val fragment: VerificationRequestFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.verificationRequestViewModelFactory.create(state)
}
override fun initialState(viewModelContext: ViewModelContext): VerificationRequestViewState? {
val args = viewModelContext.args<VerificationBottomSheet.VerificationArgs>()
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
val pr = session.getSasVerificationService()
.getExistingVerificationRequest(args.otherUserId, args.verificationId)
return session.getUser(args.otherUserId)?.let {
VerificationRequestViewState(
started = Success(false).takeIf { pr == null }
?: Success(true).takeIf { pr?.isReady == true }
?: Loading(),
matrixItem = it.toMatrixItem()
)
}
}
}
override fun handle(action: EmptyAction) {}
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: VerificationTransaction) {}
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
verificationRequestUpdated(pr)
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
if (pr.otherUserId == state.matrixItem.id) {
if (pr.isReady) {
setState {
copy(started = Success(true))
}
} else {
setState {
copy(started = Loading())
}
}
}
}
}

View File

@ -1027,6 +1027,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onAvatarClicked(informationData: MessageInformationData) { override fun onAvatarClicked(informationData: MessageInformationData) {
//roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.senderId))
openRoomMemberProfile(informationData.senderId) openRoomMemberProfile(informationData.senderId)
} }

View File

@ -23,4 +23,5 @@ sealed class RoomMemberProfileAction : VectorViewModelAction {
object RetryFetchingInfo: RoomMemberProfileAction() object RetryFetchingInfo: RoomMemberProfileAction()
object IgnoreUser: RoomMemberProfileAction() object IgnoreUser: RoomMemberProfileAction()
data class VerifyUser(val userId: String? = null, val roomId: String? = null): RoomMemberProfileAction()
} }

View File

@ -37,7 +37,9 @@ class RoomMemberProfileController @Inject constructor(
interface Callback { interface Callback {
fun onIgnoreClicked() fun onIgnoreClicked()
fun onLearnMoreClicked() fun onTapVerify()
fun onShowDeviceList()
fun onShowDeviceListNoCrossSigning()
fun onJumpToReadReceiptClicked() fun onJumpToReadReceiptClicked()
fun onMentionClicked() fun onMentionClicked()
} }
@ -90,7 +92,7 @@ class RoomMemberProfileController @Inject constructor(
editable = true, editable = true,
icon = icon, icon = icon,
divider = false, divider = false,
action = { callback?.onLearnMoreClicked() } action = { callback?.onShowDeviceList() }
) )
} else { } else {
//Not trusted, propose to verify //Not trusted, propose to verify
@ -102,7 +104,7 @@ class RoomMemberProfileController @Inject constructor(
editable = true, editable = true,
icon = R.drawable.ic_shield_black, icon = R.drawable.ic_shield_black,
divider = false, divider = false,
action = { callback?.onLearnMoreClicked() } action = { callback?.onTapVerify() }
) )
} }
@ -120,7 +122,7 @@ class RoomMemberProfileController @Inject constructor(
editable = false, editable = false,
divider = false, divider = false,
subtitle = stringProvider.getString(R.string.room_profile_encrypted_subtitle), subtitle = stringProvider.getString(R.string.room_profile_encrypted_subtitle),
action = { callback?.onLearnMoreClicked() } action = { callback?.onShowDeviceListNoCrossSigning() }
) )
} }
} else { } else {

View File

@ -20,16 +20,23 @@ package im.vector.riotx.features.roommemberprofile
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View import android.view.View
import com.airbnb.mvrx.* import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.animations.AppBarStateChangeListener import im.vector.riotx.core.animations.AppBarStateChangeListener
import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.fragment_matrix_profile.*
@ -83,6 +90,21 @@ class RoomMemberProfileFragment @Inject constructor(
} }
} }
.disposeOnDestroyView() .disposeOnDestroyView()
viewModel.actionResultLiveData.observeEvent(this) { async ->
when (async) {
is Success -> {
when (val action = async.invoke()) {
is RoomMemberProfileAction.VerifyUser -> {
VerificationBottomSheet
.withArgs(roomId = null, otherUserId = action.userId!!)
.show(parentFragmentManager, "VERIF")
}
}
}
}
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -126,8 +148,39 @@ class RoomMemberProfileFragment @Inject constructor(
viewModel.handle(RoomMemberProfileAction.IgnoreUser) viewModel.handle(RoomMemberProfileAction.IgnoreUser)
} }
override fun onLearnMoreClicked() { override fun onTapVerify() {
vectorBaseActivity.notImplemented("Learn more") viewModel.handle(RoomMemberProfileAction.VerifyUser())
// if (state.isRoomEncrypted) {
// if( !state.isMine && state.userMXCrossSigningInfo?.isTrusted == false) {
// // we want to verify
// // TODO do not use current room, find or create DM
// VerificationBottomSheet.withArgs(
// state.roomId,
// state.userId
// ).show(parentFragmentManager, "REQ")
// }
// }
}
// override fun onTapVerify() = withState(viewModel) { state ->
// if (state.isRoomEncrypted) {
// if( !state.isMine && state.userMXCrossSigningInfo?.isTrusted == false) {
// // we want to verify
// // TODO do not use current room, find or create DM
// VerificationBottomSheet.withArgs(
// state.roomId,
// state.userId
// ).show(parentFragmentManager, "REQ")
// }
// }
// }
override fun onShowDeviceList() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onShowDeviceListNoCrossSigning() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
override fun onJumpToReadReceiptClicked() { override fun onJumpToReadReceiptClicked() {

View File

@ -17,9 +17,13 @@
package im.vector.riotx.features.roommemberprofile package im.vector.riotx.features.roommemberprofile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
@ -46,6 +50,7 @@ import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DataSource import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.core.utils.PublishDataSource
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
@ -86,6 +91,10 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>() private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>()
val viewEvents: DataSource<RoomMemberProfileViewEvents> = _viewEvents val viewEvents: DataSource<RoomMemberProfileViewEvents> = _viewEvents
private val _actionResultLiveData = MutableLiveData<LiveEvent<Async<RoomMemberProfileAction>>>()
val actionResultLiveData: LiveData<LiveEvent<Async<RoomMemberProfileAction>>>
get() = _actionResultLiveData
private val room = if (initialState.roomId != null) { private val room = if (initialState.roomId != null) {
session.getRoom(initialState.roomId) session.getRoom(initialState.roomId)
} else { } else {
@ -134,11 +143,25 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
override fun handle(action: RoomMemberProfileAction) { override fun handle(action: RoomMemberProfileAction) {
when (action) { when (action) {
RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo() is RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo()
is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction() is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction()
is RoomMemberProfileAction.VerifyUser -> prepareVerification(action)
} }
} }
private fun prepareVerification(action: RoomMemberProfileAction.VerifyUser) = withState { state ->
// Sanity
if (state.isRoomEncrypted) {
if (!state.isMine && state.userMXCrossSigningInfo?.isTrusted == false) {
// ok, let's find or create the DM room
_actionResultLiveData.postValue(
LiveEvent(Success(action.copy(userId = state.userId)))
)
}
}
}
private fun observeRoomMemberSummary(room: Room) { private fun observeRoomMemberSummary(room: Room) {
val queryParams = roomMemberQueryParams { val queryParams = roomMemberQueryParams {
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
@ -163,7 +186,6 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
.execute { .execute {
copy(userMatrixItem = it) copy(userMatrixItem = it)
} }
} }
private fun observeRoomSummaryAndPowerLevels(room: Room) { private fun observeRoomSummaryAndPowerLevels(room: Room) {