Update device list according to the filter type.

This commit is contained in:
Onuray Sahin 2022-09-08 18:28:17 +03:00
parent ab4ebc7f11
commit 41ca662dcc
10 changed files with 283 additions and 9 deletions

View File

@ -3295,5 +3295,14 @@
</plurals> </plurals>
<string name="device_manager_other_sessions_title">Other sessions</string> <string name="device_manager_other_sessions_title">Other sessions</string>
<string name="a11y_device_manager_filter">Filter</string> <string name="a11y_device_manager_filter">Filter</string>
<string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
<string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you dont recognize or use anymore.</string>
<string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
<string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you dont recognize or use anymore.</string>
<string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
<plurals name="device_manager_other_sessions_recommendation_description_inactive">
<item quantity="one">Consider signing out from old sessions (%1$d day or more) you dont use anymore.</item>
<item quantity="other">Consider signing out from old sessions (%1$d days or more) you dont use anymore.</item>
</plurals>
</resources> </resources>

View File

@ -141,6 +141,7 @@
<!-- Shield colors --> <!-- Shield colors -->
<color name="shield_color_trust">#0DBD8B</color> <color name="shield_color_trust">#0DBD8B</color>
<color name="shield_color_trust_background">#0F0DBD8B</color>
<color name="shield_color_black">#17191C</color> <color name="shield_color_black">#17191C</color>
<color name="shield_color_warning">#FF4B55</color> <color name="shield_color_warning">#FF4B55</color>
<color name="shield_color_warning_background">#0FFF4B55</color> <color name="shield_color_warning_background">#0FFF4B55</color>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="OtherSessionsSecurityRecommendationView">
<attr name="otherSessionsRecommendationTitle" format="string" />
<attr name="otherSessionsRecommendationDescription" format="string" />
<attr name="otherSessionsRecommendationImageResource" format="reference" />
<attr name="otherSessionsRecommendationImageBackgroundTint" format="color" />
</declare-styleable>
</resources>

View File

@ -17,8 +17,10 @@
package im.vector.app.features.settings.devices.v2 package im.vector.app.features.settings.devices.v2
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
sealed class DevicesAction : VectorViewModelAction { sealed class DevicesAction : VectorViewModelAction {
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction() data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
data class FilterDevices(val filterType: DeviceManagerFilterType) : DevicesAction()
} }

View File

@ -144,9 +144,19 @@ class DevicesViewModel @AssistedInject constructor(
override fun handle(action: DevicesAction) { override fun handle(action: DevicesAction) {
when (action) { when (action) {
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction() is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
is DevicesAction.FilterDevices -> handleFilterDevices(action)
} }
} }
private fun handleFilterDevices(action: DevicesAction.FilterDevices) {
setState {
copy(
currentFilter = action.filterType
)
}
queryRefreshDevicesList()
}
private fun handleMarkAsManuallyVerifiedAction() { private fun handleMarkAsManuallyVerifiedAction() {
// TODO implement when needed // TODO implement when needed
} }

View File

@ -20,25 +20,31 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentOtherSessionsBinding import im.vector.app.databinding.FragmentOtherSessionsBinding
import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.DevicesAction
import im.vector.app.features.settings.devices.v2.DevicesViewModel import im.vector.app.features.settings.devices.v2.DevicesViewModel
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
import im.vector.app.features.themes.ThemeUtils
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener { class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener {
private val viewModel: DevicesViewModel by fragmentViewModel() private val viewModel: DevicesViewModel by fragmentViewModel()
@Inject lateinit var colorProvider: ColorProvider
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false) return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
@ -59,8 +65,8 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
} }
override fun onBottomSheetResult(resultCode: Int, data: Any?) { override fun onBottomSheetResult(resultCode: Int, data: Any?) {
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null && data is DeviceManagerFilterType) {
Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG).show() viewModel.handle(DevicesAction.FilterDevices(data))
} }
} }
@ -77,10 +83,51 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
private fun renderDevices(devices: List<DeviceFullInfo>?, currentFilter: DeviceManagerFilterType) { private fun renderDevices(devices: List<DeviceFullInfo>?, currentFilter: DeviceManagerFilterType) {
views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS
when (currentFilter) {
DeviceManagerFilterType.VERIFIED -> {
views.otherSessionsSecurityRecommendationView.render(
OtherSessionsSecurityRecommendationViewState(
title = getString(R.string.device_manager_other_sessions_recommendation_title_verified),
description = getString(R.string.device_manager_other_sessions_recommendation_description_verified),
imageResourceId = R.drawable.ic_shield_trusted_no_border,
imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_trust_background)
)
)
}
DeviceManagerFilterType.UNVERIFIED -> {
views.otherSessionsSecurityRecommendationView.render(
OtherSessionsSecurityRecommendationViewState(
title = getString(R.string.device_manager_other_sessions_recommendation_title_unverified),
description = getString(R.string.device_manager_other_sessions_recommendation_description_unverified),
imageResourceId = R.drawable.ic_shield_warning_no_border,
imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_warning_background)
)
)
}
DeviceManagerFilterType.INACTIVE -> {
views.otherSessionsSecurityRecommendationView.render(
OtherSessionsSecurityRecommendationViewState(
title = getString(R.string.device_manager_other_sessions_recommendation_title_inactive),
description = resources.getQuantityString(
R.plurals.device_manager_other_sessions_recommendation_description_inactive,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
),
imageResourceId = R.drawable.ic_inactive_sessions,
imageTintColorResourceId = ThemeUtils.getColor(requireContext(), R.attr.vctr_system)
)
)
}
DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ }
}
if (devices.isNullOrEmpty()) { if (devices.isNullOrEmpty()) {
// TODO. Render empty state views.deviceListOtherSessions.isVisible = false
} else { } else {
views.deviceListOtherSessions.isVisible = true
views.deviceListOtherSessions.render(devices, devices.size) views.deviceListOtherSessions.render(devices, devices.size)
} }
} }

View File

@ -0,0 +1,107 @@
/*
* 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.othersessions
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.use
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.setTextWithColoredPart
import im.vector.app.databinding.ViewOtherSessionSecurityRecommendationBinding
@AndroidEntryPoint
class OtherSessionsSecurityRecommendationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val views: ViewOtherSessionSecurityRecommendationBinding
var onLearnMoreClickListener: (() -> Unit)? = null
init {
inflate(context, R.layout.view_other_session_security_recommendation, this)
views = ViewOtherSessionSecurityRecommendationBinding.bind(this)
context.obtainStyledAttributes(
attrs,
R.styleable.OtherSessionsSecurityRecommendationView,
0,
0
).use {
setTitle(it)
setDescription(it)
setImage(it)
}
}
private fun setTitle(typedArray: TypedArray) {
val title = typedArray.getString(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationTitle)
setTitle(title)
}
private fun setTitle(title: String?) {
views.recommendationTitleTextView.text = title
}
private fun setDescription(typedArray: TypedArray) {
val description = typedArray.getString(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationDescription)
setDescription(description)
}
private fun setImage(typedArray: TypedArray) {
val imageResource = typedArray.getResourceId(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationImageResource, 0)
val backgroundTint = typedArray.getColor(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationImageBackgroundTint, 0)
setImageResource(imageResource)
setImageBackgroundTint(backgroundTint)
}
private fun setImageResource(resourceId: Int) {
views.recommendationShieldImageView.setImageResource(resourceId)
}
private fun setImageBackgroundTint(backgroundTintColor: Int) {
views.recommendationShieldImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor)
}
private fun setDescription(description: String?) {
val learnMore = context.getString(R.string.action_learn_more)
val stringBuilder = StringBuilder()
stringBuilder.append(description)
stringBuilder.append(" ")
stringBuilder.append(learnMore)
views.recommendationDescriptionTextView.setTextWithColoredPart(
fullText = stringBuilder.toString(),
coloredPart = learnMore,
underline = false
) {
onLearnMoreClickListener?.invoke()
}
}
fun render(viewState: OtherSessionsSecurityRecommendationViewState) {
setTitle(viewState.title)
setDescription(viewState.description)
setImageResource(viewState.imageResourceId)
setImageBackgroundTint(viewState.imageTintColorResourceId)
}
}

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.othersessions
data class OtherSessionsSecurityRecommendationViewState(
val title: String,
val description: String,
val imageResourceId: Int,
val imageTintColorResourceId: Int,
)

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -24,7 +25,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:layout_marginEnd="16dp"> android:padding="8dp"
android:layout_marginEnd="8dp">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -47,7 +49,6 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView <im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
android:id="@+id/deviceListHeaderOtherSessions" android:id="@+id/deviceListHeaderOtherSessions"
android:layout_width="0dp" android:layout_width="0dp"
@ -56,15 +57,31 @@
app:devicesListHeaderTitle="" app:devicesListHeaderTitle=""
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"/> app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsSecurityRecommendationView
android:id="@+id/otherSessionsSecurityRecommendationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions"
app:otherSessionsRecommendationDescription="@string/device_manager_other_sessions_recommendation_description_unverified"
app:otherSessionsRecommendationImageBackgroundTint="@color/shield_color_warning_background"
app:otherSessionsRecommendationImageResource="@drawable/ic_shield_warning_no_border"
app:otherSessionsRecommendationTitle="@string/device_manager_other_sessions_recommendation_title_unverified"
tools:visibility="visible" />
<im.vector.app.features.settings.devices.v2.list.OtherSessionsView <im.vector.app.features.settings.devices.v2.list.OtherSessionsView
android:id="@+id/deviceListOtherSessions" android:id="@+id/deviceListOtherSessions"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" /> app:layout_constraintTop_toBottomOf="@id/otherSessionsSecurityRecommendationView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<merge 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"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/recommendationShieldImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/bg_security_recommendation_shield"
android:importantForAccessibility="no"
android:padding="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:backgroundTint="@color/shield_color_warning_background"
tools:src="@drawable/ic_shield_warning_no_border" />
<TextView
android:id="@+id/recommendationTitleTextView"
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/recommendationShieldImageView"
app:layout_constraintTop_toTopOf="@id/recommendationShieldImageView"
app:layout_constraintBottom_toBottomOf="@id/recommendationShieldImageView"
tools:text="@string/device_manager_other_sessions_recommendation_title_unverified" />
<TextView
android:id="@+id/recommendationDescriptionTextView"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="40dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/recommendationTitleTextView"
app:layout_constraintTop_toBottomOf="@id/recommendationTitleTextView"
tools:text="@string/device_manager_other_sessions_recommendation_description_unverified" />
</merge>