Merge pull request #6946 from vector-im/feature/ons/device_manager_other_session_list

[Device Manager] Render other sessions (PSG-668)
This commit is contained in:
Onuray Sahin 2022-08-31 17:01:20 +03:00 committed by GitHub
commit 6341cf92a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 507 additions and 81 deletions

1
changelog.d/6945.wip Normal file
View File

@ -0,0 +1 @@
[Device Manager] Other Sessions Section

View File

@ -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
@ -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(
@ -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 ?: ""
)
}
@ -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()
@ -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()

View File

@ -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.
@ -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 renderCurrentDevice(state: DevicesViewState) {
views.deviceListHeaderSectionCurrent.isVisible = true
views.deviceListCurrentSession.isVisible = true
views.deviceListCurrentSession.update(
accountCrossSigningIsTrusted = state.accountCrossSigningIsTrusted,
legacyMode = !state.hasAccountCrossSigning
)
private fun hideOtherSessionsView() {
views.deviceListHeaderOtherSessions.isVisible = false
views.deviceListOtherSessions.isVisible = false
}
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>) {

View File

@ -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,
@ -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()
@ -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
}
}

View File

@ -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,
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.databinding.ViewOtherSessionsBinding
import im.vector.app.features.settings.devices.DeviceFullInfo
import javax.inject.Inject
@AndroidEntryPoint
class OtherSessionsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
@Inject lateinit var otherSessionsController: OtherSessionsController
private val views: ViewOtherSessionsBinding
init {
inflate(context, R.layout.view_other_sessions, this)
views = ViewOtherSessionsBinding.bind(this)
}
fun render(devices: List<DeviceFullInfo>) {
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, devices.size)
otherSessionsController.setData(devices)
}
override fun onDetachedFromWindow() {
views.otherSessionsRecyclerView.cleanup()
super.onDetachedFromWindow()
}
}

View File

@ -0,0 +1,19 @@
/*
* 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
internal const val NUMBER_OF_OTHER_DEVICES_TO_RENDER = 5

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadius="0dp"
android:thicknessRatio="2"
android:useLevel="false">
<solid android:color="?android:colorBackground" />
<stroke
android:width="1dp"
android:color="?vctr_content_quinary" />
</shape>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="25dp"
android:viewportWidth="24"
android:viewportHeight="25">
<path
android:pathData="M12,23.5C18.075,23.5 23,18.575 23,12.5C23,6.425 18.075,1.5 12,1.5C5.925,1.5 1,6.425 1,12.5C1,18.575 5.925,23.5 12,23.5ZM12,19.26C12.76,19.26 13.375,18.645 13.375,17.885C13.375,17.126 12.76,16.51 12,16.51C11.241,16.51 10.625,17.126 10.625,17.885C10.625,18.645 11.241,19.26 12,19.26ZM10.09,10.428C10.09,9.37 10.949,8.518 12,8.518C13.048,8.518 13.909,9.38 13.909,10.428C13.909,10.913 13.702,11.087 12.884,11.652C12.521,11.902 12.025,12.25 11.638,12.76C11.223,13.306 10.968,13.987 10.968,14.853H13.031C13.031,14.429 13.144,14.187 13.281,14.007C13.445,13.79 13.683,13.606 14.056,13.349C14.095,13.321 14.137,13.293 14.18,13.264C14.856,12.804 15.972,12.045 15.972,10.428C15.972,8.241 14.187,6.456 12,6.456C9.816,6.456 8.027,8.225 8.027,10.428H10.09Z"
android:fillColor="#737D8C"
android:fillType="evenOdd"/>
</vector>

View File

@ -1,41 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
android:id="@+id/deviceListHeaderSectionCurrent"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription=""
app:devicesListHeaderTitle="@string/device_manager_header_section_current_session"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<im.vector.app.features.settings.devices.v2.list.CurrentSessionView
android:id="@+id/deviceListCurrentSession"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderSectionCurrent" />
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
android:id="@+id/deviceListHeaderCurrentSession"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription=""
app:devicesListHeaderTitle="@string/device_manager_header_section_current_session"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
android:id="@+id/deviceListHeaderSectionOther"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription="@string/settings_sessions_other_description"
app:devicesListHeaderTitle="@string/settings_sessions_other_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListCurrentSession" />
<im.vector.app.features.settings.devices.v2.list.CurrentSessionView
android:id="@+id/deviceListCurrentSession"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderCurrentSession" />
<include
android:id="@+id/waiting_view"
layout="@layout/merge_overlay_waiting_view" />
<View
android:id="@+id/deviceListDividerCurrentSession"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="24dp"
android:background="@drawable/divider_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListCurrentSession" />
</androidx.constraintlayout.widget.ConstraintLayout>
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
android:id="@+id/deviceListHeaderOtherSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription="@string/settings_sessions_other_description"
app:devicesListHeaderTitle="@string/settings_sessions_other_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" />
<im.vector.app.features.settings.devices.v2.list.OtherSessionsView
android:id="@+id/deviceListOtherSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" />
<include
android:id="@+id/waiting_view"
layout="@layout/merge_overlay_waiting_view"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp">
<ImageView
android:id="@+id/otherSessionDeviceTypeImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/bg_device_type"
android:contentDescription="@string/a11y_device_manager_device_type_mobile"
android:padding="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_device_type_mobile" />
<im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/otherSessionVerificationStatusImageView"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:background="@drawable/circle_with_border"
android:importantForAccessibility="no"
android:padding="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_shield_trusted" />
<TextView
android:id="@+id/otherSessionNameTextView"
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:lines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/otherSessionDeviceTypeImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="Element Mobile: Android" />
<TextView
android:id="@+id/otherSessionDescriptionTextView"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionNameTextView"
tools:text="@string/device_manager_verification_status_verified" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="16dp"
android:background="?vctr_content_quinary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionDescriptionTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -21,15 +21,15 @@
tools:src="@drawable/ic_device_type_mobile" />
<TextView
android:id="@+id/currentSessionDeviceTypeTextView"
style="@style/TextAppearance.Vector.Headline.Medium"
android:id="@+id/currentSessionNameTextView"
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentSessionDeviceTypeImageView"
tools:text="@string/device_manager_device_type_android" />
tools:text="Element Mobile: Android" />
<LinearLayout
android:id="@+id/currentSessionVerificationStatusContainer"
@ -40,7 +40,7 @@
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentSessionDeviceTypeTextView">
app:layout_constraintTop_toBottomOf="@id/currentSessionNameTextView">
<im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/currentSessionVerificationStatusImageView"
@ -61,7 +61,7 @@
<TextView
android:id="@+id/currentSessionVerificationStatusDetailTextView"
style="@style/TextAppearance.Vector.Caption"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/otherSessionsRecyclerView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_other_session" />
<Button
android:id="@+id/otherSessionsViewAllButton"
style="@style/Widget.Vector.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="0dp"
app:layout_constraintStart_toStartOf="@id/otherSessionsRecyclerView"
app:layout_constraintTop_toBottomOf="@id/otherSessionsRecyclerView"
tools:text="@string/device_manager_other_sessions_view_all" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3209,9 +3209,9 @@
<!-- Device Manager -->
<string name="device_manager_settings_active_sessions_show_all">Show All Sessions (V2, WIP)</string>
<string name="a11y_device_manager_device_type_mobile">Mobile</string>
<string name="a11y_device_manager_device_type_web" tools:ignore="UnusedResources">Web</string>
<string name="a11y_device_manager_device_type_desktop" tools:ignore="UnusedResources">Desktop</string>
<string name="device_manager_device_type_android">${app_name} Mobile: Android</string>
<string name="a11y_device_manager_device_type_web">Web</string>
<string name="a11y_device_manager_device_type_desktop">Desktop</string>
<string name="a11y_device_manager_device_type_unknown">Unknown device type</string>
<string name="device_manager_verification_status_verified">Verified session</string>
<string name="device_manager_verification_status_unverified">Unverified session</string>
<string name="device_manager_verification_status_detail_verified">Your current session is ready for secure messaging.</string>
@ -3219,5 +3219,8 @@
<string name="device_manager_verify_session">Verify Session</string>
<string name="device_manager_view_details">View Details</string>
<string name="device_manager_header_section_current_session">Current Session</string>
<string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
<string name="device_manager_other_sessions_description_verified">Verified · Last activity %1$s</string>
<string name="device_manager_other_sessions_description_unverified">Unverified · Last activity %1$s</string>
</resources>