Update profile screen for xSigning

This commit is contained in:
Valere 2020-01-24 09:14:32 +01:00
parent e47791f290
commit bb5179140c
15 changed files with 237 additions and 25 deletions

View file

@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import io.reactivex.Observable
import io.reactivex.Single
@ -98,6 +99,10 @@ class RxSession(private val session: Session) {
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
session.getProfile(userId, it)
}
fun liveUserCryptoDevices(userId: String) : Observable<List<CryptoDeviceInfo>> {
return session.getLiveCryptoDeviceInfo(userId).asObservable()
}
}
fun Session.rx(): RxSession {

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.crypto
import android.content.Context
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
@ -117,6 +118,9 @@ interface CryptoService {
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
fun getCryptoDeviceInfo(userId: String) : List<CryptoDeviceInfo>
fun getLiveCryptoDeviceInfo(userId: String) : LiveData<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener)

View file

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto
import com.zhuinden.monarchy.Monarchy
import dagger.Binds
import dagger.Module
import dagger.Provides
@ -25,12 +26,64 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningServ
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUsers
import im.vector.matrix.android.internal.crypto.tasks.DefaultEncryptEventTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadSigningKeysTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.crypto.tasks.EncryptEventTask
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.SessionFilesDirectory
@ -203,5 +256,5 @@ internal abstract class CryptoModule {
: DeleteDeviceWithUserPasswordTask
@Binds
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService) : CrossSigningService
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
}

View file

@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.crypto
import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LiveData
import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
@ -154,6 +155,7 @@ internal class DefaultCryptoService @Inject constructor(
private val cryptoCoroutineScope: CoroutineScope
) : CryptoService {
init {
sasVerificationService.cryptoService = this
}
@ -397,6 +399,13 @@ internal class DefaultCryptoService @Inject constructor(
null
}
}
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
}
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList(userId)
}
/**
* Set the devices as known

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto.store
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.NewSessionListener
@ -195,6 +196,9 @@ internal interface IMXCryptoStore {
*/
fun getUserDevices(userId: String): Map<String, CryptoDeviceInfo>?
fun getUserDeviceList(userId: String): List<CryptoDeviceInfo>?
fun getLiveDeviceList(userId: String): LiveData<List<CryptoDeviceInfo>>
/**
* Store the crypto algorithm for a room.
*

View file

@ -16,6 +16,9 @@
package im.vector.matrix.android.internal.crypto.store.db
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
@ -96,6 +99,10 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
newSessionListeners.remove(listener)
}
private val monarchy = Monarchy.Builder()
.setRealmConfiguration(realmConfiguration)
.build()
/* ==========================================================================================
* Other data
* ========================================================================================== */
@ -343,6 +350,28 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
?.associateBy { it.deviceId }
}
override fun getUserDeviceList(userId: String): List<CryptoDeviceInfo>? {
return doRealmQueryAndCopy(realmConfiguration) {
it.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, userId)
.findFirst()
}
?.devices
?.map { CryptoMapper.mapToModel(it) }
}
override fun getLiveDeviceList(userId: String): LiveData<List<CryptoDeviceInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm -> realm.where<UserEntity>().equalTo(UserEntityFields.USER_ID, userId) },
{ entity ->
entity.devices.map { CryptoMapper.mapToModel(it) }
}
)
return Transformations.map(liveData) {
it.firstOrNull() ?: emptyList()
}
}
override fun storeRoomAlgorithm(roomId: String, algorithm: String) {
doRealmTransaction(realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).algorithm = algorithm

View file

@ -38,12 +38,18 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
var subtitle: String? = null
@EpoxyAttribute
var iconRes: Int = 0
@EpoxyAttribute
var editableRes: Int = R.drawable.ic_arrow_right
@EpoxyAttribute
var editable: Boolean = true
@EpoxyAttribute
var destructive: Boolean = false
@EpoxyAttribute
lateinit var listener: View.OnClickListener
var listener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
@ -63,6 +69,13 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
} else {
holder.icon.isVisible = false
}
if (editableRes != 0) {
holder.editable.setImageResource(editableRes)
holder.editable.isVisible = true
} else {
holder.editable.isVisible = false
}
}
class Holder : VectorEpoxyHolder() {

View file

@ -35,19 +35,27 @@ fun EpoxyController.buildProfileAction(
subtitle: String? = null,
editable: Boolean = true,
@DrawableRes icon: Int = 0,
@DrawableRes editableRes: Int? = null,
destructive: Boolean = false,
divider: Boolean = true,
action: () -> Unit
action: (() -> Unit)? = null
) {
profileActionItem {
iconRes(icon)
id("action_$id")
subtitle(subtitle)
editable(editable)
apply {
editableRes?.let { editableRes(editableRes) }
}
destructive(destructive)
title(title)
listener { _ ->
action()
apply {
action?.let {
listener { _ ->
it()
}
}
}
}

View file

@ -15,6 +15,7 @@
*/
package im.vector.riotx.core.ui.list
import android.view.Gravity
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
@ -41,12 +42,16 @@ abstract class GenericFooterItem : VectorEpoxyModel<GenericFooterItem.Holder>()
@EpoxyAttribute
var itemClickAction: GenericItem.Action? = null
@EpoxyAttribute
var centered: Boolean = true
override fun bind(holder: Holder) {
holder.text.setTextOrHide(text)
when (style) {
GenericItem.STYLE.BIG_TEXT -> holder.text.textSize = 18f
GenericItem.STYLE.NORMAL_TEXT -> holder.text.textSize = 14f
}
holder.text.gravity = if(centered) Gravity.CENTER_HORIZONTAL else Gravity.START
holder.view.setOnClickListener {
itemClickAction?.perform?.run()

View file

@ -1027,8 +1027,7 @@ class RoomDetailFragment @Inject constructor(
}
override fun onAvatarClicked(informationData: MessageInformationData) {
// DO NOT COMMIT openRoomMemberProfile(informationData.senderId)
roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.senderId))
openRoomMemberProfile(informationData.senderId)
}
private fun openRoomMemberProfile(userId: String) {

View file

@ -23,6 +23,7 @@ import im.vector.riotx.core.epoxy.profiles.buildProfileAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.list.genericFooterItem
import javax.inject.Inject
class RoomMemberProfileController @Inject constructor(
@ -70,20 +71,73 @@ class RoomMemberProfileController @Inject constructor(
private fun buildRoomMemberActions(state: RoomMemberProfileViewState) {
// Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
val learnMoreSubtitle = if (state.isRoomEncrypted) {
R.string.room_profile_encrypted_subtitle
if (state.isRoomEncrypted) {
if (state.userMXCrossSigningInfo != null) {
// Cross signing is enabled for this user
if (state.userMXCrossSigningInfo.isTrusted) {
//User is trusted
val icon = if (state.allDevicesAreTrusted.invoke() == true) R.drawable.ic_shield_trusted
else R.drawable.ic_shield_warning
val titleRes = if (state.allDevicesAreTrusted.invoke() == true) R.string.verification_profile_verified
else R.string.verification_profile_warning
buildProfileAction(
id = "learn_more",
title = stringProvider.getString(titleRes),
dividerColor = dividerColor,
editable = true,
icon = icon,
divider = false,
action = { callback?.onLearnMoreClicked() }
)
} else {
//Not trusted, propose to verify
if (!state.isMine) {
buildProfileAction(
id = "learn_more",
title = stringProvider.getString(R.string.verification_profile_verify),
dividerColor = dividerColor,
editable = true,
icon = R.drawable.ic_shield_black,
divider = false,
action = { callback?.onLearnMoreClicked() }
)
}
genericFooterItem {
id("verify_footer")
text(stringProvider.getString(R.string.room_profile_encrypted_subtitle))
centered(false)
}
}
} else {
buildProfileAction(
id = "learn_more",
title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
dividerColor = dividerColor,
editable = false,
divider = false,
subtitle = stringProvider.getString(R.string.room_profile_encrypted_subtitle),
action = { callback?.onLearnMoreClicked() }
)
}
} else {
R.string.room_profile_not_encrypted_subtitle
// buildProfileAction(
// id = "learn_more",
// title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
// dividerColor = dividerColor,
// editable = false,
// divider = false,
// subtitle = stringProvider.getString(R.string.room_profile_not_encrypted_subtitle)
// )
genericFooterItem {
id("verify_footer_not_encrypted")
text(stringProvider.getString(R.string.room_profile_not_encrypted_subtitle))
centered(false)
}
}
buildProfileAction(
id = "learn_more",
title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
dividerColor = dividerColor,
editable = false,
divider = false,
subtitle = stringProvider.getString(learnMoreSubtitle),
action = { callback?.onLearnMoreClicked() }
)
// More
if (!state.isMine) {

View file

@ -42,6 +42,7 @@ import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.R
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DataSource
@ -69,6 +70,17 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
val fragment: RoomMemberProfileFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.viewModelFactory.create(state)
}
override fun initialState(viewModelContext: ViewModelContext): RoomMemberProfileViewState? {
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
val args = viewModelContext.args<RoomMemberProfileArgs>()
return RoomMemberProfileViewState(
userId = args.userId,
roomId = args.roomId,
userMXCrossSigningInfo = session.getCrossSigningService().getUserCrossSigningKeys(args.userId)
)
}
}
private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>()
@ -97,6 +109,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
observeRoomMemberSummary(room)
observeRoomSummaryAndPowerLevels(room)
}
session.rx().liveUserCryptoDevices(initialState.userId)
.map {
it.fold(true, { prev, dev -> prev && dev.isVerified})
}
.execute {
copy(allDevicesAreTrusted = it)
}
}
}
@ -143,6 +163,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
.execute {
copy(userMatrixItem = it)
}
}
private fun observeRoomSummaryAndPowerLevels(room: Room) {

View file

@ -20,8 +20,10 @@ package im.vector.riotx.features.roommemberprofile
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
data class RoomMemberProfileViewState(
val userId: String,
@ -32,8 +34,10 @@ data class RoomMemberProfileViewState(
val isRoomEncrypted: Boolean = false,
val powerLevelsContent: Async<PowerLevelsContent> = Uninitialized,
val userPowerLevelString: Async<String> = Uninitialized,
val userMatrixItem: Async<MatrixItem> = Uninitialized
val userMatrixItem: Async<MatrixItem> = Uninitialized,
val userMXCrossSigningInfo: MXCrossSigningInfo? = null,
val allDevicesAreTrusted: Async<Boolean> = Uninitialized
) : MvRxState {
constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId)
//constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId)
}

View file

@ -20,7 +20,6 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:scaleType="center"
android:tint="?riotx_text_secondary"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -55,7 +55,7 @@
<string name="verification_request_waiting_for">Waiting for %s…</string>
<string name="verification_request_alert_description">For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person.</string>
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.</string>
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string>
<string name="room_profile_section_security">Security</string>
<string name="room_profile_section_security_learn_more">Learn more</string>
<string name="room_profile_section_more">More</string>
@ -119,4 +119,9 @@
<item quantity="other">%d active sessions</item>
</plurals>
<string name="verification_profile_verify">Verify</string>
<string name="verification_profile_verified">Verified</string>
<string name="verification_profile_warning">Warning</string>
</resources>