Merge pull request #6903 from vector-im/feature/ons/device_manager_current_session

[Device Manager] Render current session (PSG-668)
This commit is contained in:
Onuray Sahin 2022-08-26 16:22:46 +03:00 committed by GitHub
commit dc99c1122d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 369 additions and 4 deletions

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

@ -0,0 +1 @@
[Device Manager] Current Session Section

View File

@ -23,10 +23,23 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.dialogs.ManuallyVerifyDialog
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.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.
@ -35,6 +48,8 @@ import im.vector.app.databinding.FragmentSettingsDevicesBinding
class VectorSettingsDevicesFragment :
VectorBaseFragment<FragmentSettingsDevicesBinding>() {
private val viewModel: DevicesViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding {
return FragmentSettingsDevicesBinding.inflate(inflater, container, false)
}
@ -52,7 +67,45 @@ class VectorSettingsDevicesFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initLearnMoreButtons()
initWaitingView()
observerViewEvents()
}
private fun observerViewEvents() {
viewModel.observeViewEvents {
when (it) {
is DevicesViewEvents.Loading -> showLoading(it.message)
is DevicesViewEvents.Failure -> showFailure(it.throwable)
is DevicesViewEvents.RequestReAuth -> Unit // TODO. Next PR
is DevicesViewEvents.PromptRenameDevice -> Unit // TODO. Next PR
is DevicesViewEvents.ShowVerifyDevice -> {
VerificationBottomSheet.withArgs(
roomId = null,
otherUserId = it.userId,
transactionId = it.transactionId
).show(childFragmentManager, "REQPOP")
}
is DevicesViewEvents.SelfVerification -> {
VerificationBottomSheet.forSelfVerification(it.session)
.show(childFragmentManager, "REQPOP")
}
is DevicesViewEvents.ShowManuallyVerify -> {
ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {
viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
}
}
is DevicesViewEvents.PromptResetSecrets -> {
navigator.open4SSetup(requireContext(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
}
}
}
}
private fun initWaitingView() {
views.waitingView.waitingStatusText.setText(R.string.please_wait)
views.waitingView.waitingStatusText.isVisible = true
}
override fun onDestroyView() {
@ -61,12 +114,48 @@ class VectorSettingsDevicesFragment :
}
private fun initLearnMoreButtons() {
views.devicesListHeaderSectionOther.onLearnMoreClickListener = {
views.deviceListHeaderSectionOther.onLearnMoreClickListener = {
Toast.makeText(context, "Learn more other", Toast.LENGTH_LONG).show()
}
}
private fun cleanUpLearnMoreButtonsListeners() {
views.devicesListHeaderSectionOther.onLearnMoreClickListener = null
views.deviceListHeaderSectionOther.onLearnMoreClickListener = null
}
override fun invalidate() = withState(viewModel) { state ->
val currentDeviceInfo = state.devices()
?.firstOrNull {
it.deviceInfo.deviceId == state.myDeviceId
}
if (state.devices is Success && currentDeviceInfo != null) {
renderCurrentDevice(state)
} else {
hideCurrentSessionView()
}
handleRequestStatus(state.request)
}
private fun hideCurrentSessionView() {
views.deviceListHeaderSectionCurrent.isVisible = false
views.deviceListCurrentSession.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 handleRequestStatus(unIgnoreRequest: Async<Unit>) {
views.waitingView.root.isVisible = when (unIgnoreRequest) {
is Loading -> true
else -> false
}
}
}

View File

@ -0,0 +1,83 @@
/*
* 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 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.themes.ThemeUtils
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
class CurrentSessionView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val views: ViewCurrentSessionBinding
init {
inflate(context, R.layout.view_current_session, this)
views = ViewCurrentSessionBinding.bind(this)
}
fun update(accountCrossSigningIsTrusted: Boolean, legacyMode: Boolean) {
renderDeviceType()
renderVerificationStatus(accountCrossSigningIsTrusted, legacyMode)
}
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) {
renderCrossSigningVerified()
} else {
renderCrossSigningUnverified()
}
}
private fun renderCrossSigningVerified() {
views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified)
views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified)
views.currentSessionVerifySessionButton.isVisible = false
}
private fun renderCrossSigningUnverified() {
views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified)
views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError))
views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified)
views.currentSessionVerifySessionButton.isVisible = true
}
// TODO. We don't have this info yet. Update later accordingly.
private fun renderDeviceType() {
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)
}
}

View File

@ -22,6 +22,7 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.use
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.setTextWithColoredPart
import im.vector.app.databinding.ViewDevicesListHeaderBinding
@ -58,12 +59,18 @@ class DevicesListHeaderView @JvmOverloads constructor(
private fun setDescription(typedArray: TypedArray) {
val description = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderDescription)
if (description.isNullOrEmpty()) {
binding.devicesListHeaderDescription.isVisible = false
return
}
val learnMore = context.getString(R.string.action_learn_more)
val stringBuilder = StringBuilder()
stringBuilder.append(description)
stringBuilder.append(" ")
stringBuilder.append(learnMore)
binding.devicesListHeaderDescription.isVisible = true
binding.devicesListHeaderDescription.setTextWithColoredPart(
fullText = stringBuilder.toString(),
coloredPart = learnMore,

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="?vctr_content_quinary" />
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="?vctr_system" />
</shape>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="22dp"
android:height="19dp"
android:viewportWidth="22"
android:viewportHeight="19"
tools:ignore="UnusedResources">
<path
android:pathData="M3,15.5C2.45,15.5 1.979,15.304 1.588,14.913C1.196,14.521 1,14.05 1,13.5V2.5C1,1.95 1.196,1.479 1.588,1.087C1.979,0.696 2.45,0.5 3,0.5H19C19.55,0.5 20.021,0.696 20.413,1.087C20.804,1.479 21,1.95 21,2.5V13.5C21,14.05 20.804,14.521 20.413,14.913C20.021,15.304 19.55,15.5 19,15.5H3ZM3,13.5H19V2.5H3V13.5ZM1,18.5C0.717,18.5 0.479,18.404 0.288,18.212C0.096,18.021 0,17.783 0,17.5C0,17.217 0.096,16.979 0.288,16.788C0.479,16.596 0.717,16.5 1,16.5H21C21.283,16.5 21.521,16.596 21.712,16.788C21.904,16.979 22,17.217 22,17.5C22,17.783 21.904,18.021 21.712,18.212C21.521,18.404 21.283,18.5 21,18.5H1ZM3,13.5V2.5V13.5Z"
android:fillColor="#737D8C"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="22dp"
android:viewportWidth="14"
android:viewportHeight="22">
<path
android:pathData="M12,0.01L2,0C0.9,0 0,0.9 0,2V20C0,21.1 0.9,22 2,22H12C13.1,22 14,21.1 14,20V2C14,0.9 13.1,0.01 12,0.01ZM12,18H2V4H12V18Z"
android:fillColor="#737D8C"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="20dp"
android:height="17dp"
android:viewportWidth="20"
android:viewportHeight="17"
tools:ignore="UnusedResources">
<path
android:pathData="M18,16.5H2C1.45,16.5 0.979,16.304 0.588,15.913C0.196,15.521 0,15.05 0,14.5V2.5C0,1.95 0.196,1.479 0.588,1.088C0.979,0.696 1.45,0.5 2,0.5H18C18.55,0.5 19.021,0.696 19.413,1.088C19.804,1.479 20,1.95 20,2.5V14.5C20,15.05 19.804,15.521 19.413,15.913C19.021,16.304 18.55,16.5 18,16.5ZM2,4.5V14.5H18V4.5H2Z"
android:fillColor="#737D8C"/>
</vector>

View File

@ -5,13 +5,37 @@
android:layout_height="match_parent">
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
android:id="@+id/devices_list_header_section_other"
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" />
<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/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_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/deviceListCurrentSession" />
<include
android:id="@+id/waiting_view"
layout="@layout/merge_overlay_waiting_view" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,98 @@
<?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:background="@drawable/bg_current_session"
android:paddingBottom="16dp">
<ImageView
android:id="@+id/currentSessionDeviceTypeImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:contentDescription="@string/a11y_device_manager_device_type_mobile"
android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@drawable/bg_device_type"
tools:src="@drawable/ic_device_type_mobile" />
<TextView
android:id="@+id/currentSessionDeviceTypeTextView"
style="@style/TextAppearance.Vector.Headline.Medium"
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" />
<LinearLayout
android:id="@+id/currentSessionVerificationStatusContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentSessionDeviceTypeTextView">
<im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/currentSessionVerificationStatusImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:importantForAccessibility="no"
tools:src="@drawable/ic_shield_trusted" />
<TextView
android:id="@+id/currentSessionVerificationStatusTextView"
style="@style/TextAppearance.Vector.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
tools:text="@string/device_manager_verification_status_verified"
tools:textColor="?colorPrimary" />
</LinearLayout>
<TextView
android:id="@+id/currentSessionVerificationStatusDetailTextView"
style="@style/TextAppearance.Vector.Caption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="12dp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentSessionVerificationStatusContainer"
tools:text="@string/device_manager_verification_status_detail_verified" />
<Button
android:id="@+id/currentSessionVerifySessionButton"
android:layout_width="0dp"
android:layout_height="52dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="16dp"
android:text="@string/device_manager_verify_session"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentSessionVerificationStatusDetailTextView" />
<Button
android:id="@+id/currentSessionViewDetailsButton"
style="@style/Widget.Vector.Button.Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="8dp"
android:text="@string/device_manager_view_details"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/currentSessionVerifySessionButton" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3209,4 +3209,16 @@
<!-- 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="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>
<string name="device_manager_verification_status_detail_unverified">Verify your current session for enhanced secure messaging.</string>
<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>
</resources>