Render security recommendation sessions.

This commit is contained in:
Onuray Sahin 2022-09-19 15:37:13 +03:00
parent 740b69d48c
commit 7db222af0c
9 changed files with 78 additions and 16 deletions

View file

@ -3246,6 +3246,7 @@
<string name="device_manager_other_sessions_description_verified">Verified · Last activity %1$s</string>
<!-- Examples: Unverified · Last activity Yesterday at 6PM, Unverified · Last activity Aug 31 at 5:47PM -->
<string name="device_manager_other_sessions_description_unverified">Unverified · Last activity %1$s</string>
<string name="device_manager_other_sessions_description_unverified_current_session">Unverified · Your current session</string>
<!-- Example: Inactive for 90+ days (Dec 25, 2021) -->
<plurals name="device_manager_other_sessions_description_inactive">
<item quantity="one">Inactive for %1$d+ day (%2$s)</item>

View file

@ -25,4 +25,5 @@ data class DeviceFullInfo(
val cryptoDeviceInfo: CryptoDeviceInfo?,
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel,
val isInactive: Boolean,
val isCurrentDevice: Boolean,
)

View file

@ -41,6 +41,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import javax.inject.Inject
@ -84,6 +85,7 @@ class VectorSettingsDevicesFragment :
initLearnMoreButtons()
initWaitingView()
initOtherSessionsView()
initSecurityRecommendationsView()
observeViewEvents()
}
@ -126,6 +128,29 @@ class VectorSettingsDevicesFragment :
views.deviceListOtherSessions.callback = this
}
private fun initSecurityRecommendationsView() {
views.deviceListUnverifiedSessionsRecommendation.callback = object : SecurityRecommendationView.Callback {
override fun onViewAllClicked() {
viewNavigator.navigateToOtherSessions(
requireActivity(),
R.string.device_manager_header_section_security_recommendations_title,
DeviceManagerFilterType.UNVERIFIED,
includeCurrentSession = true
)
}
}
views.deviceListInactiveSessionsRecommendation.callback = object : SecurityRecommendationView.Callback {
override fun onViewAllClicked() {
viewNavigator.navigateToOtherSessions(
requireActivity(),
R.string.device_manager_header_section_security_recommendations_title,
DeviceManagerFilterType.INACTIVE,
includeCurrentSession = true
)
}
}
}
override fun onDestroyView() {
cleanUpLearnMoreButtonsListeners()
super.onDestroyView()

View file

@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2.list
import android.graphics.drawable.Drawable
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
@ -45,6 +46,10 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
@EpoxyAttribute
var sessionDescription: String? = null
@EpoxyAttribute
@ColorInt
var sessionDescriptionColor: Int? = null
@EpoxyAttribute
var sessionDescriptionDrawable: Drawable? = null
@ -82,6 +87,9 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel)
holder.otherSessionNameTextView.text = sessionName
holder.otherSessionDescriptionTextView.text = sessionDescription
sessionDescriptionColor?.let {
holder.otherSessionDescriptionTextView.setTextColor(it)
}
holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null)
}

View file

@ -53,20 +53,14 @@ class OtherSessionsController @Inject constructor(
data.forEach { device ->
val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
val description = if (device.isInactive) {
stringProvider.getQuantityString(
R.plurals.device_manager_other_sessions_description_inactive,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
formattedLastActivityDate
)
} else if (device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
val description = calculateDescription(device, formattedLastActivityDate)
val descriptionColor = if (device.isCurrentDevice) {
host.colorProvider.getColorFromAttribute(R.attr.colorError)
} else {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
}
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
val descriptionDrawable = if (device.isInactive) drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) else null
val drawableColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
val descriptionDrawable = if (device.isInactive) host.drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) else null
otherSessionItem {
id(device.deviceInfo.deviceId)
@ -75,10 +69,28 @@ class OtherSessionsController @Inject constructor(
sessionName(device.deviceInfo.displayName)
sessionDescription(description)
sessionDescriptionDrawable(descriptionDrawable)
sessionDescriptionColor(descriptionColor)
stringProvider(this@OtherSessionsController.stringProvider)
clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } }
}
}
}
}
private fun calculateDescription(device: DeviceFullInfo, formattedLastActivityDate: String): String {
return if (device.isInactive) {
stringProvider.getQuantityString(
R.plurals.device_manager_other_sessions_description_inactive,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
formattedLastActivityDate
)
} else if (device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
} else if (device.isCurrentDevice) {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified_current_session)
} else {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
}
}
}

View file

@ -31,7 +31,12 @@ class SecurityRecommendationView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
interface Callback {
fun onViewAllClicked()
}
private val views: ViewSecurityRecommendationBinding
var callback: Callback? = null
init {
inflate(context, R.layout.view_security_recommendation, this)
@ -47,6 +52,10 @@ class SecurityRecommendationView @JvmOverloads constructor(
setDescription(it)
setImage(it)
}
views.recommendationViewAllButton.setOnClickListener {
callback?.onViewAllClicked()
}
}
private fun setTitle(typedArray: TypedArray) {

View file

@ -30,7 +30,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import kotlinx.coroutines.Job
class OtherSessionsViewModel @AssistedInject constructor(
@Assisted initialState: OtherSessionsViewState,
@Assisted private val initialState: OtherSessionsViewState,
activeSessionHolder: ActiveSessionHolder,
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
refreshDevicesUseCase: RefreshDevicesUseCase
@ -55,7 +55,7 @@ class OtherSessionsViewModel @AssistedInject constructor(
observeDevicesJob?.cancel()
observeDevicesJob = getDeviceFullInfoListUseCase.execute(
filterType = currentFilter,
excludeCurrentDevice = true
excludeCurrentDevice = !initialState.includeCurrentSession
)
.execute { async ->
copy(

View file

@ -25,4 +25,8 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
data class OtherSessionsViewState(
val devices: Async<List<DeviceFullInfo>> = Uninitialized,
val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
) : MavericksState
val includeCurrentSession: Boolean = false,
) : MavericksState {
constructor(args: OtherSessionsArgs) : this(includeCurrentSession = args.includeCurrentSession)
}

View file

@ -48,11 +48,13 @@ class GetDeviceFullInfoUseCase @Inject constructor(
val fullInfo = if (info != null && cryptoInfo != null) {
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoInfo.deviceId
DeviceFullInfo(
deviceInfo = info,
cryptoDeviceInfo = cryptoInfo,
roomEncryptionTrustLevel = roomEncryptionTrustLevel,
isInactive = isInactive
isInactive = isInactive,
isCurrentDevice = isCurrentDevice,
)
} else {
null