Skip to content

Commit

Permalink
Merge pull request #6946 from vector-im/feature/ons/device_manager_ot…
Browse files Browse the repository at this point in the history
…her_session_list

[Device Manager] Render other sessions (PSG-668)
  • Loading branch information
onurays authored Aug 31, 2022
2 parents 0950e41 + 357a859 commit 6341cf9
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 85 deletions.
1 change: 1 addition & 0 deletions changelog.d/6945.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Device Manager] Other Sessions Section
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
Expand All @@ -79,12 +80,13 @@ data class DevicesViewState(
// TODO Replace by isLoading boolean
val request: Async<Unit> = Uninitialized,
val hasAccountCrossSigning: Boolean = false,
val accountCrossSigningIsTrusted: Boolean = false
val accountCrossSigningIsTrusted: Boolean = false,
) : MavericksState

data class DeviceFullInfo(
val deviceInfo: DeviceInfo,
val cryptoDeviceInfo: CryptoDeviceInfo?
val cryptoDeviceInfo: CryptoDeviceInfo?,
val trustLevelForShield: RoomEncryptionTrustLevel,
)

class DevicesViewModel @AssistedInject constructor(
Expand All @@ -108,11 +110,13 @@ class DevicesViewModel @AssistedInject constructor(
private val refreshSource = PublishDataSource<Unit>()

init {
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()

setState {
copy(
hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(),
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(),
hasAccountCrossSigning = hasAccountCrossSigning,
accountCrossSigningIsTrusted = accountCrossSigningIsTrusted,
myDeviceId = session.sessionParams.deviceId ?: ""
)
}
Expand All @@ -125,7 +129,13 @@ class DevicesViewModel @AssistedInject constructor(
.sortedByDescending { it.lastSeenTs }
.map { deviceInfo ->
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
DeviceFullInfo(deviceInfo, cryptoDeviceInfo)
val trustLevelForShield = computeTrustLevelForShield(
currentSessionCrossTrusted = accountCrossSigningIsTrusted,
legacyMode = !hasAccountCrossSigning,
deviceTrustLevel = cryptoDeviceInfo?.trustLevel,
isCurrentDevice = deviceInfo.deviceId == session.sessionParams.deviceId
)
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield)
}
}
.distinctUntilChanged()
Expand Down Expand Up @@ -243,6 +253,20 @@ class DevicesViewModel @AssistedInject constructor(
}
}

private fun computeTrustLevelForShield(
currentSessionCrossTrusted: Boolean,
legacyMode: Boolean,
deviceTrustLevel: DeviceTrustLevel?,
isCurrentDevice: Boolean,
): RoomEncryptionTrustLevel {
return TrustUtils.shieldForTrust(
currentDevice = isCurrentDevice,
trustMSK = currentSessionCrossTrusted,
legacyMode = legacyMode,
deviceTrustLevel = deviceTrustLevel
)
}

private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
val txID = session.cryptoService()
.verificationService()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSettingsDevicesBinding
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.settings.devices.DeviceFullInfo
import im.vector.app.features.settings.devices.DevicesAction
import im.vector.app.features.settings.devices.DevicesViewEvents
import im.vector.app.features.settings.devices.DevicesViewModel
import im.vector.app.features.settings.devices.DevicesViewState

/**
* Display the list of the user's devices and sessions.
Expand Down Expand Up @@ -114,42 +114,61 @@ class VectorSettingsDevicesFragment :
}

private fun initLearnMoreButtons() {
views.deviceListHeaderSectionOther.onLearnMoreClickListener = {
views.deviceListHeaderOtherSessions.onLearnMoreClickListener = {
Toast.makeText(context, "Learn more other", Toast.LENGTH_LONG).show()
}
}

private fun cleanUpLearnMoreButtonsListeners() {
views.deviceListHeaderSectionOther.onLearnMoreClickListener = null
views.deviceListHeaderOtherSessions.onLearnMoreClickListener = null
}

override fun invalidate() = withState(viewModel) { state ->
val currentDeviceInfo = state.devices()
?.firstOrNull {
it.deviceInfo.deviceId == state.myDeviceId
}
if (state.devices is Success) {
val devices = state.devices()
val currentDeviceInfo = devices?.firstOrNull {
it.deviceInfo.deviceId == state.myDeviceId
}
val otherDevices = devices?.filter { it.deviceInfo.deviceId != state.myDeviceId }

if (state.devices is Success && currentDeviceInfo != null) {
renderCurrentDevice(state)
renderCurrentDevice(currentDeviceInfo)
renderOtherSessionsView(otherDevices)
} else {
hideCurrentSessionView()
hideOtherSessionsView()
}

handleRequestStatus(state.request)
}

private fun hideCurrentSessionView() {
views.deviceListHeaderSectionCurrent.isVisible = false
views.deviceListCurrentSession.isVisible = false
private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?) {
if (otherDevices.isNullOrEmpty()) {
hideOtherSessionsView()
} else {
views.deviceListHeaderOtherSessions.isVisible = true
views.deviceListOtherSessions.isVisible = true
views.deviceListOtherSessions.render(otherDevices)
}
}

private fun hideOtherSessionsView() {
views.deviceListHeaderOtherSessions.isVisible = false
views.deviceListOtherSessions.isVisible = false
}

private fun renderCurrentDevice(state: DevicesViewState) {
views.deviceListHeaderSectionCurrent.isVisible = true
views.deviceListCurrentSession.isVisible = true
views.deviceListCurrentSession.update(
accountCrossSigningIsTrusted = state.accountCrossSigningIsTrusted,
legacyMode = !state.hasAccountCrossSigning
)
private fun renderCurrentDevice(currentDeviceInfo: DeviceFullInfo?) {
currentDeviceInfo?.let {
views.deviceListHeaderCurrentSession.isVisible = true
views.deviceListCurrentSession.isVisible = true
views.deviceListCurrentSession.render(it)
} ?: run {
hideCurrentSessionView()
}
}

private fun hideCurrentSessionView() {
views.deviceListHeaderCurrentSession.isVisible = false
views.deviceListCurrentSession.isVisible = false
}

private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.databinding.ViewCurrentSessionBinding
import im.vector.app.features.settings.devices.TrustUtils
import im.vector.app.features.settings.devices.DeviceFullInfo
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel

class CurrentSessionView @JvmOverloads constructor(
context: Context,
Expand All @@ -39,21 +39,14 @@ class CurrentSessionView @JvmOverloads constructor(
views = ViewCurrentSessionBinding.bind(this)
}

fun update(accountCrossSigningIsTrusted: Boolean, legacyMode: Boolean) {
renderDeviceType()
renderVerificationStatus(accountCrossSigningIsTrusted, legacyMode)
fun render(currentDeviceInfo: DeviceFullInfo) {
renderDeviceInfo(currentDeviceInfo.deviceInfo.displayName.orEmpty())
renderVerificationStatus(currentDeviceInfo.trustLevelForShield)
}

private fun renderVerificationStatus(accountCrossSigningIsTrusted: Boolean, legacyMode: Boolean) {
val deviceTrustLevel = DeviceTrustLevel(crossSigningVerified = accountCrossSigningIsTrusted, locallyVerified = true)
val shield = TrustUtils.shieldForTrust(
currentDevice = true,
trustMSK = accountCrossSigningIsTrusted,
legacyMode = legacyMode,
deviceTrustLevel = deviceTrustLevel
)
views.currentSessionVerificationStatusImageView.render(shield)
if (deviceTrustLevel.crossSigningVerified) {
private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) {
views.currentSessionVerificationStatusImageView.render(trustLevelForShield)
if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
renderCrossSigningVerified()
} else {
renderCrossSigningUnverified()
Expand All @@ -75,9 +68,9 @@ class CurrentSessionView @JvmOverloads constructor(
}

// TODO. We don't have this info yet. Update later accordingly.
private fun renderDeviceType() {
private fun renderDeviceInfo(sessionName: String) {
views.currentSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
views.currentSessionDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile)
views.currentSessionDeviceTypeTextView.text = context.getString(R.string.device_manager_device_type_android)
views.currentSessionNameTextView.text = sessionName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 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.settings.devices.v2.list

enum class DeviceType {
MOBILE,
WEB,
DESKTOP,
UNKNOWN,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2022 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.settings.devices.v2.list

import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.views.ShieldImageView
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel

@EpoxyModelClass
abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.layout.item_other_session) {

@EpoxyAttribute
var deviceType: DeviceType = DeviceType.UNKNOWN

@EpoxyAttribute
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null

@EpoxyAttribute
var sessionName: String? = null

@EpoxyAttribute
var sessionDescription: String? = null

@EpoxyAttribute
lateinit var stringProvider: StringProvider

override fun bind(holder: Holder) {
super.bind(holder)
when (deviceType) {
DeviceType.MOBILE -> {
holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_mobile)
}
DeviceType.WEB -> {
holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_web)
holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_web)
}
DeviceType.DESKTOP -> {
holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_desktop)
holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_desktop)
}
DeviceType.UNKNOWN -> {
holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_unknown)
holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_unknown)
}
}
holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel)
holder.otherSessionNameTextView.text = sessionName
holder.otherSessionDescriptionTextView.text = sessionDescription
}

class Holder : VectorEpoxyHolder() {
val otherSessionDeviceTypeImageView by bind<ImageView>(R.id.otherSessionDeviceTypeImageView)
val otherSessionVerificationStatusImageView by bind<ShieldImageView>(R.id.otherSessionVerificationStatusImageView)
val otherSessionNameTextView by bind<TextView>(R.id.otherSessionNameTextView)
val otherSessionDescriptionTextView by bind<TextView>(R.id.otherSessionDescriptionTextView)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 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.settings.devices.v2.list

import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.devices.DeviceFullInfo
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import javax.inject.Inject

class OtherSessionsController @Inject constructor(
private val stringProvider: StringProvider,
private val dateFormatter: VectorDateFormatter,
) : TypedEpoxyController<List<DeviceFullInfo>>() {

override fun buildModels(data: List<DeviceFullInfo>?) {
val host = this

if (data.isNullOrEmpty()) {
noResultItem {
id("empty")
text(host.stringProvider.getString(R.string.no_result_placeholder))
}
} else {
data.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER).forEach { device ->
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
val description = if (device.trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
} else {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
}

otherSessionItem {
id(device.deviceInfo.deviceId)
deviceType(DeviceType.UNKNOWN) // TODO. We don't have this info yet. Update accordingly.
roomEncryptionTrustLevel(device.trustLevelForShield)
sessionName(device.deviceInfo.displayName)
sessionDescription(description)
stringProvider(this@OtherSessionsController.stringProvider)
}
}
}
}
}
Loading

0 comments on commit 6341cf9

Please sign in to comment.