Select devices with basic UI for tests

This commit is contained in:
Maxime NATUREL 2022-10-19 11:40:43 +02:00
parent ab2e91ae80
commit 5b1bf8a68e
8 changed files with 93 additions and 30 deletions

View File

@ -30,4 +30,5 @@ data class DeviceFullInfo(
val isCurrentDevice: Boolean, val isCurrentDevice: Boolean,
val deviceExtendedInfo: DeviceExtendedInfo, val deviceExtendedInfo: DeviceExtendedInfo,
val matrixClientInfo: MatrixClientInfoContent?, val matrixClientInfo: MatrixClientInfoContent?,
val isSelected: Boolean = false,
) )

View File

@ -17,10 +17,12 @@
package im.vector.app.features.settings.devices.v2.list package im.vector.app.features.settings.devices.v2.list
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View
import android.view.View.OnLongClickListener import android.view.View.OnLongClickListener
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R import im.vector.app.R
@ -57,6 +59,9 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
@EpoxyAttribute @EpoxyAttribute
lateinit var stringProvider: StringProvider lateinit var stringProvider: StringProvider
@EpoxyAttribute
var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null var clickListener: ClickListener? = null
@ -81,6 +86,9 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
holder.otherSessionDescriptionTextView.setTextColor(it) holder.otherSessionDescriptionTextView.setTextColor(it)
} }
holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null) holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null)
// TODO set drawable with correct color and corners
val color = if (selected) R.color.alert_default_error_background else android.R.color.transparent
holder.otherSessionItemBackgroundView.setBackgroundColor(ContextCompat.getColor(holder.view.context, color))
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
@ -88,5 +96,6 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
val otherSessionVerificationStatusImageView by bind<ShieldImageView>(R.id.otherSessionVerificationStatusImageView) val otherSessionVerificationStatusImageView by bind<ShieldImageView>(R.id.otherSessionVerificationStatusImageView)
val otherSessionNameTextView by bind<TextView>(R.id.otherSessionNameTextView) val otherSessionNameTextView by bind<TextView>(R.id.otherSessionNameTextView)
val otherSessionDescriptionTextView by bind<TextView>(R.id.otherSessionDescriptionTextView) val otherSessionDescriptionTextView by bind<TextView>(R.id.otherSessionDescriptionTextView)
val otherSessionItemBackgroundView by bind<View>(R.id.otherSessionItemBackground)
} }
} }

View File

@ -73,6 +73,7 @@ class OtherSessionsController @Inject constructor(
sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionDrawable(descriptionDrawable)
sessionDescriptionColor(descriptionColor) sessionDescriptionColor(descriptionColor)
stringProvider(this@OtherSessionsController.stringProvider) stringProvider(this@OtherSessionsController.stringProvider)
selected(device.isSelected)
clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } }
onLongClickListener(View.OnLongClickListener { onLongClickListener(View.OnLongClickListener {
device.deviceInfo.deviceId?.let { host.callback?.onItemLongClicked(it) } device.deviceInfo.deviceId?.let { host.callback?.onItemLongClicked(it) }

View File

@ -21,6 +21,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
sealed class OtherSessionsAction : VectorViewModelAction { sealed class OtherSessionsAction : VectorViewModelAction {
data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction() data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction()
object EnableSelectMode : OtherSessionsAction() data class EnableSelectMode(val deviceId: String?) : OtherSessionsAction()
object DisableSelectMode : OtherSessionsAction() object DisableSelectMode : OtherSessionsAction()
data class ToggleSelectionForDevice(val deviceId: String) : OtherSessionsAction()
} }

View File

@ -89,8 +89,8 @@ class OtherSessionsFragment :
} }
} }
private fun enableSelectMode(isEnabled: Boolean) { private fun enableSelectMode(isEnabled: Boolean, deviceId: String? = null) {
val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode val action = if (isEnabled) OtherSessionsAction.EnableSelectMode(deviceId) else OtherSessionsAction.DisableSelectMode
viewModel.handle(action) viewModel.handle(action)
} }
@ -153,23 +153,25 @@ class OtherSessionsFragment :
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
updateToolbar(state.isSelectModeEnabled)
if (state.devices is Success) { if (state.devices is Success) {
renderDevices(state.devices(), state.currentFilter) val devices = state.devices().orEmpty()
renderDevices(devices, state.currentFilter)
updateToolbar(devices, state.isSelectModeEnabled)
} }
} }
private fun updateToolbar(isSelectModeEnabled: Boolean) { private fun updateToolbar(devices: List<DeviceFullInfo>, isSelectModeEnabled: Boolean) {
invalidateOptionsMenu() invalidateOptionsMenu()
val title = if (isSelectModeEnabled) { val title = if (isSelectModeEnabled) {
stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, 0, 0) val selection = devices.count { it.isSelected }
stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, selection, selection)
} else { } else {
getString(args.titleResourceId) getString(args.titleResourceId)
} }
toolbar?.title = title toolbar?.title = title
} }
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.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS
@ -222,7 +224,7 @@ class OtherSessionsFragment :
} }
} }
if (devices.isNullOrEmpty()) { if (devices.isEmpty()) {
views.deviceListOtherSessions.isVisible = false views.deviceListOtherSessions.isVisible = false
views.otherSessionsNotFoundLayout.isVisible = true views.otherSessionsNotFoundLayout.isVisible = true
} else { } else {
@ -254,15 +256,19 @@ class OtherSessionsFragment :
override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state -> override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state ->
if (!state.isSelectModeEnabled) { if (!state.isSelectModeEnabled) {
enableSelectMode(true) enableSelectMode(true, deviceId)
} }
} }
override fun onOtherSessionClicked(deviceId: String) { override fun onOtherSessionClicked(deviceId: String) = withState(viewModel) { state ->
viewNavigator.navigateToSessionOverview( if (state.isSelectModeEnabled) {
context = requireActivity(), viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(deviceId))
deviceId = deviceId } else {
) viewNavigator.navigateToSessionOverview(
context = requireActivity(),
deviceId = deviceId
)
}
} }
override fun onViewAllOtherSessionsClicked() { override fun onViewAllOtherSessionsClicked() {

View File

@ -17,6 +17,7 @@
package im.vector.app.features.settings.devices.v2.othersessions package im.vector.app.features.settings.devices.v2.othersessions
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -69,7 +70,8 @@ class OtherSessionsViewModel @AssistedInject constructor(
when (action) { when (action) {
is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) is OtherSessionsAction.FilterDevices -> handleFilterDevices(action)
OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode() OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode()
OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode() is OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode(action.deviceId)
is OtherSessionsAction.ToggleSelectionForDevice -> handleToggleSelectionForDevice(action.deviceId)
} }
} }
@ -83,10 +85,37 @@ class OtherSessionsViewModel @AssistedInject constructor(
} }
private fun handleDisableSelectMode() { private fun handleDisableSelectMode() {
// TODO deselect all selected sessions
setState { copy(isSelectModeEnabled = false) } setState { copy(isSelectModeEnabled = false) }
} }
private fun handleEnableSelectMode() { private fun handleEnableSelectMode(deviceId: String?) {
setState { copy(isSelectModeEnabled = true) } toggleSelectionForDevice(deviceId, true)
}
private fun handleToggleSelectionForDevice(deviceId: String) = withState { state ->
toggleSelectionForDevice(deviceId, state.isSelectModeEnabled)
}
private fun toggleSelectionForDevice(deviceId: String?, enableSelectMode: Boolean) = withState { state ->
val updatedDevices = if (state.devices is Success) {
val devices = state.devices.invoke().toMutableList()
val indexToUpdate = devices.indexOfFirst { it.deviceInfo.deviceId == deviceId }
if (indexToUpdate >= 0) {
val currentInfo = devices[indexToUpdate]
val updatedInfo = currentInfo.copy(isSelected = !currentInfo.isSelected)
devices[indexToUpdate] = updatedInfo
}
Success(devices)
} else {
state.devices
}
setState {
copy(
devices = updatedDevices,
isSelectModeEnabled = enableSelectMode
)
}
} }
} }

View File

@ -5,30 +5,44 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:foreground="?selectableItemBackground" android:foreground="?selectableItemBackground"
android:paddingTop="16dp"> android:paddingTop="8dp"
android:paddingHorizontal="8dp">
<View
android:id="@+id/otherSessionItemBackground"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@id/otherSessionVerificationStatusImageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/otherSessionDeviceTypeImageView" android:id="@+id/otherSessionDeviceTypeImageView"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="11dp"
android:background="@drawable/bg_device_type" android:background="@drawable/bg_device_type"
android:contentDescription="@string/a11y_device_manager_device_type_mobile" android:contentDescription="@string/a11y_device_manager_device_type_mobile"
android:padding="8dp" android:padding="8dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="@+id/otherSessionItemBackground"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="@+id/otherSessionItemBackground"
app:layout_constraintTop_toTopOf="@+id/otherSessionItemBackground"
tools:src="@drawable/ic_device_type_mobile" /> tools:src="@drawable/ic_device_type_mobile" />
<im.vector.app.core.ui.views.ShieldImageView <im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/otherSessionVerificationStatusImageView" android:id="@+id/otherSessionVerificationStatusImageView"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginStart="24dp" android:layout_marginStart="26dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:background="@drawable/circle_with_border" android:background="@drawable/circle_with_border"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:padding="6dp" android:padding="6dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="@+id/otherSessionDeviceTypeImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="@id/otherSessionDeviceTypeImageView"
tools:src="@drawable/ic_shield_trusted" /> tools:src="@drawable/ic_shield_trusted" />
<TextView <TextView
@ -37,21 +51,23 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:ellipsize="end" android:ellipsize="end"
android:lines="1" android:lines="1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/otherSessionDeviceTypeImageView" app:layout_constraintStart_toEndOf="@id/otherSessionDeviceTypeImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="@id/otherSessionDeviceTypeImageView"
tools:text="Element Mobile: Android" /> tools:text="Element Mobile: Android" />
<TextView <TextView
android:id="@+id/otherSessionDescriptionTextView" android:id="@+id/otherSessionDescriptionTextView"
style="@style/TextAppearance.Vector.Body.DevicesManagement" style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:drawablePadding="8dp" android:drawablePadding="8dp"
app:layout_constraintBottom_toBottomOf="@id/otherSessionDeviceTypeImageView"
app:layout_constraintEnd_toEndOf="@id/otherSessionNameTextView"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView" app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionNameTextView" app:layout_constraintTop_toBottomOf="@id/otherSessionNameTextView"
tools:text="@string/device_manager_verification_status_verified" /> tools:text="@string/device_manager_verification_status_verified" />
@ -59,10 +75,10 @@
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="16dp" android:layout_marginTop="8dp"
android:background="?vctr_content_quinary" android:background="?vctr_content_quinary"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView" app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
app:layout_constraintTop_toBottomOf="@id/otherSessionDescriptionTextView" /> app:layout_constraintTop_toBottomOf="@+id/otherSessionItemBackground" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,7 +9,6 @@
android:id="@+id/otherSessionsRecyclerView" android:id="@+id/otherSessionsRecyclerView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -21,6 +20,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="0dp" android:padding="0dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="@id/otherSessionsRecyclerView" app:layout_constraintStart_toStartOf="@id/otherSessionsRecyclerView"
app:layout_constraintTop_toBottomOf="@id/otherSessionsRecyclerView" app:layout_constraintTop_toBottomOf="@id/otherSessionsRecyclerView"
tools:text="@string/device_manager_other_sessions_view_all" /> tools:text="@string/device_manager_other_sessions_view_all" />