From b532112f584a0cbeebf2b943bb9a3812739bb643 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 18 Oct 2022 15:14:02 +0200 Subject: [PATCH 01/15] Adding changelog entry --- changelog.d/7396.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7396.feature diff --git a/changelog.d/7396.feature b/changelog.d/7396.feature new file mode 100644 index 0000000000..8ce14eb3d3 --- /dev/null +++ b/changelog.d/7396.feature @@ -0,0 +1 @@ +Multi selection in sessions list From 2e155b1acc316df93a71cfa9794b92cde5494696 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 18 Oct 2022 16:24:04 +0200 Subject: [PATCH 02/15] Toggling of selectMode using menu i OtherSessionsFragment --- .../src/main/res/values/strings.xml | 7 ++ .../v2/othersessions/OtherSessionsAction.kt | 2 + .../v2/othersessions/OtherSessionsFragment.kt | 65 ++++++++++++++++++- .../othersessions/OtherSessionsViewModel.kt | 11 ++++ .../othersessions/OtherSessionsViewState.kt | 1 + .../src/main/res/menu/menu_other_sessions.xml | 21 ++++++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/res/menu/menu_other_sessions.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d5223a0638..7c74162859 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3328,6 +3328,13 @@ No unverified sessions found. No inactive sessions found. Clear Filter + Select sessions + Select all + Deselect all + + %1$d selected + %1$d selected + Sign out of this session Session details Application, device, and activity information. diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt index 7164ecc866..2d184ffeaa 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt @@ -21,4 +21,6 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType sealed class OtherSessionsAction : VectorViewModelAction { data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction() + object EnableSelectMode : OtherSessionsAction() + object DisableSelectMode : OtherSessionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 610776e22e..804344e00e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -18,8 +18,12 @@ package im.vector.app.features.settings.devices.v2.othersessions import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.activity.addCallback import androidx.annotation.StringRes import androidx.core.view.isVisible import com.airbnb.mvrx.Success @@ -31,7 +35,9 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.platform.VectorMenuProvider import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentOtherSessionsBinding import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet @@ -46,19 +52,65 @@ import javax.inject.Inject class OtherSessionsFragment : VectorBaseFragment(), VectorBaseBottomSheetDialogFragment.ResultListener, - OtherSessionsView.Callback { + OtherSessionsView.Callback, + VectorMenuProvider { private val viewModel: OtherSessionsViewModel by fragmentViewModel() private val args: OtherSessionsArgs by args() @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var stringProvider: StringProvider + @Inject lateinit var viewNavigator: OtherSessionsViewNavigator override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding { return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false) } + override fun getMenuRes() = R.menu.menu_other_sessions + + override fun handlePrepareMenu(menu: Menu) { + withState(viewModel) { state -> + val isSelectModeEnabled = state.isSelectModeEnabled + menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled + menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled + menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled + } + } + + override fun handleMenuItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.otherSessionsSelect -> { + enableSelectMode(true) + true + } + else -> false + } + } + + // TODO call enableSelectMode(true) on long press of an item when disabled + private fun enableSelectMode(isEnabled: Boolean) { + val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode + viewModel.handle(action) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(owner = this) { + handleBackPress(this) + } + } + + private fun handleBackPress(onBackPressedCallback: OnBackPressedCallback) = withState(viewModel) { state -> + if (state.isSelectModeEnabled) { + enableSelectMode(false) + } else { + onBackPressedCallback.isEnabled = false + activity?.onBackPressedDispatcher?.onBackPressed() + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar(views.otherSessionsToolbar).setTitle(args.titleResourceId).allowBack() @@ -102,11 +154,22 @@ class OtherSessionsFragment : } override fun invalidate() = withState(viewModel) { state -> + updateToolbar(state.isSelectModeEnabled) if (state.devices is Success) { renderDevices(state.devices(), state.currentFilter) } } + private fun updateToolbar(isSelectModeEnabled: Boolean) { + invalidateOptionsMenu() + val title = if (isSelectModeEnabled) { + stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, 0, 0) + } else { + getString(args.titleResourceId) + } + toolbar?.title = title + } + private fun renderDevices(devices: List?, currentFilter: DeviceManagerFilterType) { views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index e52953e2b6..21291fc082 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -64,9 +64,12 @@ class OtherSessionsViewModel @AssistedInject constructor( } } + // TODO update unit tests override fun handle(action: OtherSessionsAction) { when (action) { is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) + OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode() + OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode() } } @@ -78,4 +81,12 @@ class OtherSessionsViewModel @AssistedInject constructor( } observeDevices(action.filterType) } + + private fun handleDisableSelectMode() { + setState { copy(isSelectModeEnabled = false) } + } + + private fun handleEnableSelectMode() { + setState { copy(isSelectModeEnabled = true) } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt index 5256a9b27a..0db3c8cd0e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt @@ -26,6 +26,7 @@ data class OtherSessionsViewState( val devices: Async> = Uninitialized, val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS, val excludeCurrentDevice: Boolean = false, + val isSelectModeEnabled: Boolean = false, ) : MavericksState { constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice) diff --git a/vector/src/main/res/menu/menu_other_sessions.xml b/vector/src/main/res/menu/menu_other_sessions.xml new file mode 100644 index 0000000000..0edab99127 --- /dev/null +++ b/vector/src/main/res/menu/menu_other_sessions.xml @@ -0,0 +1,21 @@ + + + + + + + + + From ab2e91ae80fd33c12eef06e0ae9a88acf2b346e2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 18 Oct 2022 17:06:39 +0200 Subject: [PATCH 03/15] Enable selectMode when long pressing on list item --- .../settings/devices/v2/VectorSettingsDevicesFragment.kt | 4 ++++ .../features/settings/devices/v2/list/OtherSessionItem.kt | 5 +++++ .../settings/devices/v2/list/OtherSessionsController.kt | 6 ++++++ .../features/settings/devices/v2/list/OtherSessionsView.kt | 5 +++++ .../devices/v2/othersessions/OtherSessionsFragment.kt | 7 ++++++- 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index c507699e0b..1c348af4f9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -331,6 +331,10 @@ class VectorSettingsDevicesFragment : views.waitingView.root.isVisible = isLoading } + override fun onOtherSessionLongClicked(deviceId: String) { + // do nothing + } + override fun onOtherSessionClicked(deviceId: String) { navigateToSessionOverview(deviceId) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index f83f069a9f..6397a42f7f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.devices.v2.list import android.graphics.drawable.Drawable +import android.view.View.OnLongClickListener import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt @@ -59,11 +60,15 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var onLongClickListener: OnLongClickListener? = null + private val setDeviceTypeIconUseCase = SetDeviceTypeIconUseCase() override fun bind(holder: Holder) { super.bind(holder) holder.view.onClick(clickListener) + holder.view.setOnLongClickListener(onLongClickListener) if (clickListener == null) { holder.view.isClickable = false } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index 59e7e1888e..d7166d02d2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.devices.v2.list +import android.view.View import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R import im.vector.app.core.date.DateFormatKind @@ -38,6 +39,7 @@ class OtherSessionsController @Inject constructor( var callback: Callback? = null interface Callback { + fun onItemLongClicked(deviceId: String) fun onItemClicked(deviceId: String) } @@ -72,6 +74,10 @@ class OtherSessionsController @Inject constructor( sessionDescriptionColor(descriptionColor) stringProvider(this@OtherSessionsController.stringProvider) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } + onLongClickListener(View.OnLongClickListener { + device.deviceInfo.deviceId?.let { host.callback?.onItemLongClicked(it) } + true + }) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt index 6f6956c885..a4f8bb64db 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt @@ -40,6 +40,7 @@ class OtherSessionsView @JvmOverloads constructor( ) : ConstraintLayout(context, attrs, defStyleAttr), OtherSessionsController.Callback { interface Callback { + fun onOtherSessionLongClicked(deviceId: String) fun onOtherSessionClicked(deviceId: String) fun onViewAllOtherSessionsClicked() } @@ -107,4 +108,8 @@ class OtherSessionsView @JvmOverloads constructor( override fun onItemClicked(deviceId: String) { callback?.onOtherSessionClicked(deviceId) } + + override fun onItemLongClicked(deviceId: String) { + callback?.onOtherSessionLongClicked(deviceId) + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 804344e00e..77aac4d91d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -89,7 +89,6 @@ class OtherSessionsFragment : } } - // TODO call enableSelectMode(true) on long press of an item when disabled private fun enableSelectMode(isEnabled: Boolean) { val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode viewModel.handle(action) @@ -253,6 +252,12 @@ class OtherSessionsFragment : SessionLearnMoreBottomSheet.show(childFragmentManager, args) } + override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state -> + if (!state.isSelectModeEnabled) { + enableSelectMode(true) + } + } + override fun onOtherSessionClicked(deviceId: String) { viewNavigator.navigateToSessionOverview( context = requireActivity(), From 5b1bf8a68eca5a6a3442e22bb5e171d1928dcf60 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 11:40:43 +0200 Subject: [PATCH 04/15] Select devices with basic UI for tests --- .../settings/devices/v2/DeviceFullInfo.kt | 1 + .../devices/v2/list/OtherSessionItem.kt | 9 +++++ .../v2/list/OtherSessionsController.kt | 1 + .../v2/othersessions/OtherSessionsAction.kt | 3 +- .../v2/othersessions/OtherSessionsFragment.kt | 34 ++++++++++------- .../othersessions/OtherSessionsViewModel.kt | 35 +++++++++++++++-- .../main/res/layout/item_other_session.xml | 38 +++++++++++++------ .../main/res/layout/view_other_sessions.xml | 2 +- 8 files changed, 93 insertions(+), 30 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt index a47ea7e917..4864c41394 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt @@ -30,4 +30,5 @@ data class DeviceFullInfo( val isCurrentDevice: Boolean, val deviceExtendedInfo: DeviceExtendedInfo, val matrixClientInfo: MatrixClientInfoContent?, + val isSelected: Boolean = false, ) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index 6397a42f7f..888305fb4e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -17,10 +17,12 @@ package im.vector.app.features.settings.devices.v2.list import android.graphics.drawable.Drawable +import android.view.View import android.view.View.OnLongClickListener import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -57,6 +59,9 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la @EpoxyAttribute lateinit var stringProvider: StringProvider + @EpoxyAttribute + var selected: Boolean = false + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null @@ -81,6 +86,9 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la holder.otherSessionDescriptionTextView.setTextColor(it) } 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() { @@ -88,5 +96,6 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la val otherSessionVerificationStatusImageView by bind(R.id.otherSessionVerificationStatusImageView) val otherSessionNameTextView by bind(R.id.otherSessionNameTextView) val otherSessionDescriptionTextView by bind(R.id.otherSessionDescriptionTextView) + val otherSessionItemBackgroundView by bind(R.id.otherSessionItemBackground) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index d7166d02d2..b50abaaf7d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -73,6 +73,7 @@ class OtherSessionsController @Inject constructor( sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionColor(descriptionColor) stringProvider(this@OtherSessionsController.stringProvider) + selected(device.isSelected) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } onLongClickListener(View.OnLongClickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemLongClicked(it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt index 2d184ffeaa..becac467ec 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt @@ -21,6 +21,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType sealed class OtherSessionsAction : VectorViewModelAction { data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction() - object EnableSelectMode : OtherSessionsAction() + data class EnableSelectMode(val deviceId: String?) : OtherSessionsAction() object DisableSelectMode : OtherSessionsAction() + data class ToggleSelectionForDevice(val deviceId: String) : OtherSessionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 77aac4d91d..1a0d63f04a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -89,8 +89,8 @@ class OtherSessionsFragment : } } - private fun enableSelectMode(isEnabled: Boolean) { - val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode + private fun enableSelectMode(isEnabled: Boolean, deviceId: String? = null) { + val action = if (isEnabled) OtherSessionsAction.EnableSelectMode(deviceId) else OtherSessionsAction.DisableSelectMode viewModel.handle(action) } @@ -153,23 +153,25 @@ class OtherSessionsFragment : } override fun invalidate() = withState(viewModel) { state -> - updateToolbar(state.isSelectModeEnabled) 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, isSelectModeEnabled: Boolean) { invalidateOptionsMenu() 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 { getString(args.titleResourceId) } toolbar?.title = title } - private fun renderDevices(devices: List?, currentFilter: DeviceManagerFilterType) { + private fun renderDevices(devices: List, currentFilter: DeviceManagerFilterType) { views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.otherSessionsSecurityRecommendationView.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.otherSessionsNotFoundLayout.isVisible = true } else { @@ -254,15 +256,19 @@ class OtherSessionsFragment : override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state -> if (!state.isSelectModeEnabled) { - enableSelectMode(true) + enableSelectMode(true, deviceId) } } - override fun onOtherSessionClicked(deviceId: String) { - viewNavigator.navigateToSessionOverview( - context = requireActivity(), - deviceId = deviceId - ) + override fun onOtherSessionClicked(deviceId: String) = withState(viewModel) { state -> + if (state.isSelectModeEnabled) { + viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(deviceId)) + } else { + viewNavigator.navigateToSessionOverview( + context = requireActivity(), + deviceId = deviceId + ) + } } override fun onViewAllOtherSessionsClicked() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 21291fc082..4a00f0ab2b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.devices.v2.othersessions import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.Success import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -69,7 +70,8 @@ class OtherSessionsViewModel @AssistedInject constructor( when (action) { is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) 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() { + // TODO deselect all selected sessions setState { copy(isSelectModeEnabled = false) } } - private fun handleEnableSelectMode() { - setState { copy(isSelectModeEnabled = true) } + private fun handleEnableSelectMode(deviceId: String?) { + 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 + ) + } } } diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml index 2f93c2be5d..29f6fafcbc 100644 --- a/vector/src/main/res/layout/item_other_session.xml +++ b/vector/src/main/res/layout/item_other_session.xml @@ -5,30 +5,44 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:foreground="?selectableItemBackground" - android:paddingTop="16dp"> + android:paddingTop="8dp" + android:paddingHorizontal="8dp"> + + @@ -59,10 +75,10 @@ + app:layout_constraintTop_toBottomOf="@+id/otherSessionItemBackground" /> diff --git a/vector/src/main/res/layout/view_other_sessions.xml b/vector/src/main/res/layout/view_other_sessions.xml index aacbbe8ffe..2d02870174 100644 --- a/vector/src/main/res/layout/view_other_sessions.xml +++ b/vector/src/main/res/layout/view_other_sessions.xml @@ -9,7 +9,6 @@ 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" @@ -21,6 +20,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="0dp" + android:layout_marginStart="16dp" app:layout_constraintStart_toStartOf="@id/otherSessionsRecyclerView" app:layout_constraintTop_toBottomOf="@id/otherSessionsRecyclerView" tools:text="@string/device_manager_other_sessions_view_all" /> From 2fc2665ff31c994c334522638d0d1b036b4852e2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 12:51:59 +0200 Subject: [PATCH 05/15] Deselect all sessions when leaving select mode --- .../v2/othersessions/OtherSessionsFragment.kt | 2 +- .../othersessions/OtherSessionsViewModel.kt | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 1a0d63f04a..73e528b358 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -154,7 +154,7 @@ class OtherSessionsFragment : override fun invalidate() = withState(viewModel) { state -> if (state.devices is Success) { - val devices = state.devices().orEmpty() + val devices = state.devices.invoke() renderDevices(devices, state.currentFilter) updateToolbar(devices, state.isSelectModeEnabled) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 4a00f0ab2b..4ad2ab96e8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -85,16 +85,15 @@ class OtherSessionsViewModel @AssistedInject constructor( } private fun handleDisableSelectMode() { - // TODO deselect all selected sessions - setState { copy(isSelectModeEnabled = false) } + setSelectionForAllDevices(isSelected = false, enableSelectMode = false) } private fun handleEnableSelectMode(deviceId: String?) { - toggleSelectionForDevice(deviceId, true) + toggleSelectionForDevice(deviceId, enableSelectMode = true) } private fun handleToggleSelectionForDevice(deviceId: String) = withState { state -> - toggleSelectionForDevice(deviceId, state.isSelectModeEnabled) + toggleSelectionForDevice(deviceId, enableSelectMode = state.isSelectModeEnabled) } private fun toggleSelectionForDevice(deviceId: String?, enableSelectMode: Boolean) = withState { state -> @@ -118,4 +117,20 @@ class OtherSessionsViewModel @AssistedInject constructor( ) } } + + private fun setSelectionForAllDevices(isSelected: Boolean, enableSelectMode: Boolean) = withState { state -> + val updatedDevices = if (state.devices is Success) { + val updatedDevices = state.devices.invoke().map { it.copy(isSelected = isSelected) } + Success(updatedDevices) + } else { + state.devices + } + + setState { + copy( + devices = updatedDevices, + isSelectModeEnabled = enableSelectMode + ) + } + } } From a703b8ae1015e0335a6444dc1f5a9af770dbaee3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 14:16:31 +0200 Subject: [PATCH 06/15] Select all/Deselect all actions --- .../devices/v2/othersessions/OtherSessionsAction.kt | 2 ++ .../devices/v2/othersessions/OtherSessionsFragment.kt | 8 ++++++++ .../devices/v2/othersessions/OtherSessionsViewModel.kt | 10 ++++++++++ 3 files changed, 20 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt index becac467ec..1978708ebf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt @@ -24,4 +24,6 @@ sealed class OtherSessionsAction : VectorViewModelAction { data class EnableSelectMode(val deviceId: String?) : OtherSessionsAction() object DisableSelectMode : OtherSessionsAction() data class ToggleSelectionForDevice(val deviceId: String) : OtherSessionsAction() + object SelectAll : OtherSessionsAction() + object DeselectAll : OtherSessionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 73e528b358..958266631e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -85,6 +85,14 @@ class OtherSessionsFragment : enableSelectMode(true) true } + R.id.otherSessionsSelectAll -> { + viewModel.handle(OtherSessionsAction.SelectAll) + true + } + R.id.otherSessionsDeselectAll -> { + viewModel.handle(OtherSessionsAction.DeselectAll) + true + } else -> false } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 4ad2ab96e8..e4c1b98288 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -72,6 +72,8 @@ class OtherSessionsViewModel @AssistedInject constructor( OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode() is OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode(action.deviceId) is OtherSessionsAction.ToggleSelectionForDevice -> handleToggleSelectionForDevice(action.deviceId) + OtherSessionsAction.DeselectAll -> handleDeselectAll() + OtherSessionsAction.SelectAll -> handleSelectAll() } } @@ -118,6 +120,14 @@ class OtherSessionsViewModel @AssistedInject constructor( } } + private fun handleSelectAll() = withState { state -> + setSelectionForAllDevices(isSelected = true, enableSelectMode = state.isSelectModeEnabled) + } + + private fun handleDeselectAll() = withState { state -> + setSelectionForAllDevices(isSelected = false, enableSelectMode = state.isSelectModeEnabled) + } + private fun setSelectionForAllDevices(isSelected: Boolean, enableSelectMode: Boolean) = withState { state -> val updatedDevices = if (state.devices is Success) { val updatedDevices = state.devices.invoke().map { it.copy(isSelected = isSelected) } From 3390d7fde493101d1c0b2662210f666de467facc Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 15:32:09 +0200 Subject: [PATCH 07/15] Handling correct UI for selected session --- .../devices/v2/list/OtherSessionItem.kt | 24 ++++++++++++++----- .../v2/list/OtherSessionsController.kt | 2 ++ .../src/main/res/drawable/bg_device_type.xml | 18 +++++++++----- .../main/res/drawable/bg_other_session.xml | 9 +++++++ .../main/res/layout/item_other_session.xml | 1 + 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/res/drawable/bg_other_session.xml diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index 888305fb4e..de1cd33d35 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -22,7 +22,6 @@ import android.view.View.OnLongClickListener import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt -import androidx.core.content.ContextCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -30,6 +29,8 @@ import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider 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 @@ -59,6 +60,12 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la @EpoxyAttribute lateinit var stringProvider: StringProvider + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + @EpoxyAttribute var selected: Boolean = false @@ -74,11 +81,18 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la super.bind(holder) holder.view.onClick(clickListener) holder.view.setOnLongClickListener(onLongClickListener) - if (clickListener == null) { + if (clickListener == null && onLongClickListener == null) { holder.view.isClickable = false } - setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider) + holder.otherSessionDeviceTypeImageView.isSelected = selected + if (selected) { + val drawableColor = colorProvider.getColorFromAttribute(android.R.attr.colorBackground) + val drawable = drawableProvider.getDrawable(R.drawable.ic_check_on, drawableColor) + holder.otherSessionDeviceTypeImageView.setImageDrawable(drawable) + } else { + setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider) + } holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel) holder.otherSessionNameTextView.text = sessionName holder.otherSessionDescriptionTextView.text = sessionDescription @@ -86,9 +100,7 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la holder.otherSessionDescriptionTextView.setTextColor(it) } 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)) + holder.otherSessionItemBackgroundView.isSelected = selected } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index b50abaaf7d..9193479b74 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -73,6 +73,8 @@ class OtherSessionsController @Inject constructor( sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionColor(descriptionColor) stringProvider(this@OtherSessionsController.stringProvider) + colorProvider(this@OtherSessionsController.colorProvider) + drawableProvider(this@OtherSessionsController.drawableProvider) selected(device.isSelected) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } onLongClickListener(View.OnLongClickListener { diff --git a/vector/src/main/res/drawable/bg_device_type.xml b/vector/src/main/res/drawable/bg_device_type.xml index 88a90ccbe6..dc85b723a8 100644 --- a/vector/src/main/res/drawable/bg_device_type.xml +++ b/vector/src/main/res/drawable/bg_device_type.xml @@ -1,7 +1,13 @@ - - - - - + + + + + + + + + + + + diff --git a/vector/src/main/res/drawable/bg_other_session.xml b/vector/src/main/res/drawable/bg_other_session.xml new file mode 100644 index 0000000000..a0c988b8a2 --- /dev/null +++ b/vector/src/main/res/drawable/bg_other_session.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml index 29f6fafcbc..908a0e0073 100644 --- a/vector/src/main/res/layout/item_other_session.xml +++ b/vector/src/main/res/layout/item_other_session.xml @@ -12,6 +12,7 @@ android:id="@+id/otherSessionItemBackground" android:layout_width="0dp" android:layout_height="0dp" + android:background="@drawable/bg_other_session" app:layout_constraintBottom_toBottomOf="@id/otherSessionVerificationStatusImageView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From 4a8289c6cc7d667324ebaea495c7270408069559 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 16:19:57 +0200 Subject: [PATCH 08/15] Adding first unit test on OtherSessionsViewModel for init of ViewModel --- .../othersessions/OtherSessionsViewModel.kt | 1 - .../OtherSessionsViewModelTest.kt | 114 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index e4c1b98288..2cd0c6af66 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -65,7 +65,6 @@ class OtherSessionsViewModel @AssistedInject constructor( } } - // TODO update unit tests override fun handle(action: OtherSessionsAction) { when (action) { is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt new file mode 100644 index 0000000000..c1018003a5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -0,0 +1,114 @@ +/* + * 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.os.SystemClock +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.features.settings.devices.v2.DeviceFullInfo +import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase +import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase +import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeVerificationService +import im.vector.app.test.test +import im.vector.app.test.testDispatcher +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verifyAll +import kotlinx.coroutines.flow.flowOf +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +private const val A_TITLE_RES_ID = 1 + +class OtherSessionsViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + + private val defaultArgs = OtherSessionsArgs( + titleResourceId = A_TITLE_RES_ID, + defaultFilter = DeviceManagerFilterType.ALL_SESSIONS, + excludeCurrentDevice = false, + ) + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + private val fakeGetDeviceFullInfoListUseCase = mockk() + private val fakeRefreshDevicesUseCaseUseCase = mockk() + + private fun createViewModel(args: OtherSessionsArgs = defaultArgs) = OtherSessionsViewModel( + initialState = OtherSessionsViewState(args), + activeSessionHolder = fakeActiveSessionHolder.instance, + getDeviceFullInfoListUseCase = fakeGetDeviceFullInfoListUseCase, + refreshDevicesUseCase = fakeRefreshDevicesUseCaseUseCase, + ) + + @Before + fun setup() { + // Needed for internal usage of Flow.throttleFirst() inside the ViewModel + mockkStatic(SystemClock::class) + every { SystemClock.elapsedRealtime() } returns 1234 + + givenVerificationService() + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given the viewModel has been initialized then viewState is updated with devices list`() { + // Given + val devices = mockk>() + givenGetDeviceFullInfoListReturns(devices) + val expectedState = OtherSessionsViewState( + devices = Success(devices), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + + // Then + viewModel.test() + .assertLatestState { state -> state == expectedState } + .finish() + verifyAll { fakeGetDeviceFullInfoListUseCase.execute(defaultArgs.defaultFilter, defaultArgs.excludeCurrentDevice) } + } + + private fun givenGetDeviceFullInfoListReturns(devices: List) { + every { fakeGetDeviceFullInfoListUseCase.execute(any(), any()) } returns flowOf(devices) + } + + private fun givenVerificationService(): FakeVerificationService { + val fakeVerificationService = fakeActiveSessionHolder + .fakeSession + .fakeCryptoService + .fakeVerificationService + fakeVerificationService.givenAddListenerSucceeds() + fakeVerificationService.givenRemoveListenerSucceeds() + return fakeVerificationService + } +} From 3bba9dea25fb8adada71205bb96098a4b52bfaa4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 16:30:40 +0200 Subject: [PATCH 09/15] Adding unit test for filter action --- .../OtherSessionsViewModelTest.kt | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index c1018003a5..41c54f9255 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -80,7 +80,7 @@ class OtherSessionsViewModelTest { fun `given the viewModel has been initialized then viewState is updated with devices list`() { // Given val devices = mockk>() - givenGetDeviceFullInfoListReturns(devices) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( devices = Success(devices), currentFilter = defaultArgs.defaultFilter, @@ -98,8 +98,41 @@ class OtherSessionsViewModelTest { verifyAll { fakeGetDeviceFullInfoListUseCase.execute(defaultArgs.defaultFilter, defaultArgs.excludeCurrentDevice) } } - private fun givenGetDeviceFullInfoListReturns(devices: List) { - every { fakeGetDeviceFullInfoListUseCase.execute(any(), any()) } returns flowOf(devices) + @Test + fun `given filter devices action when handling the action then viewState is updated with filter option and devices are filtered`() { + // Given + val filterType = DeviceManagerFilterType.UNVERIFIED + val devices = mockk>() + val filteredDevices = mockk>() + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + givenGetDeviceFullInfoListReturns(filterType = filterType, filteredDevices) + val expectedState = OtherSessionsViewState( + devices = Success(filteredDevices), + currentFilter = filterType, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.FilterDevices(filterType)) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + verifyAll { + fakeGetDeviceFullInfoListUseCase.execute(defaultArgs.defaultFilter, defaultArgs.excludeCurrentDevice) + fakeGetDeviceFullInfoListUseCase.execute(filterType, defaultArgs.excludeCurrentDevice) + } + } + + private fun givenGetDeviceFullInfoListReturns( + filterType: DeviceManagerFilterType, + devices: List, + ) { + every { fakeGetDeviceFullInfoListUseCase.execute(filterType, any()) } returns flowOf(devices) } private fun givenVerificationService(): FakeVerificationService { From 2e99d45c829965e9790da710df7a1898704393d4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 17:22:19 +0200 Subject: [PATCH 10/15] Adding unit test about select mode --- .../OtherSessionsViewModelTest.kt | 161 +++++++++++++++++- 1 file changed, 153 insertions(+), 8 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index 41c54f9255..fa61918689 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -22,7 +22,9 @@ import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase +import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType +import im.vector.app.features.settings.devices.v2.list.DeviceType import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeVerificationService import im.vector.app.test.test @@ -37,8 +39,11 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel private const val A_TITLE_RES_ID = 1 +private const val A_DEVICE_ID = "device-id" class OtherSessionsViewModelTest { @@ -71,6 +76,16 @@ class OtherSessionsViewModelTest { givenVerificationService() } + private fun givenVerificationService(): FakeVerificationService { + val fakeVerificationService = fakeActiveSessionHolder + .fakeSession + .fakeCryptoService + .fakeVerificationService + fakeVerificationService.givenAddListenerSucceeds() + fakeVerificationService.givenRemoveListenerSucceeds() + return fakeVerificationService + } + @After fun tearDown() { unmockkAll() @@ -128,6 +143,129 @@ class OtherSessionsViewModelTest { } } + @Test + fun `given enable select mode action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val devices: List = listOf(deviceFullInfo) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo.copy(isSelected = true))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = true, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.EnableSelectMode(A_DEVICE_ID)) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given disable select mode action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo1.copy(isSelected = false), deviceFullInfo2.copy(isSelected = false))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.DisableSelectMode) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given toggle selection for device action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val devices: List = listOf(deviceFullInfo) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo.copy(isSelected = true))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(A_DEVICE_ID)) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given select all action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo1.copy(isSelected = true), deviceFullInfo2.copy(isSelected = true))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.SelectAll) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given deselect all action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo1.copy(isSelected = false), deviceFullInfo2.copy(isSelected = false))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.DeselectAll) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + private fun givenGetDeviceFullInfoListReturns( filterType: DeviceManagerFilterType, devices: List, @@ -135,13 +273,20 @@ class OtherSessionsViewModelTest { every { fakeGetDeviceFullInfoListUseCase.execute(filterType, any()) } returns flowOf(devices) } - private fun givenVerificationService(): FakeVerificationService { - val fakeVerificationService = fakeActiveSessionHolder - .fakeSession - .fakeCryptoService - .fakeVerificationService - fakeVerificationService.givenAddListenerSucceeds() - fakeVerificationService.givenRemoveListenerSucceeds() - return fakeVerificationService + private fun givenDeviceFullInfo(deviceId: String, isSelected: Boolean): DeviceFullInfo { + return DeviceFullInfo( + deviceInfo = DeviceInfo( + deviceId = deviceId, + ), + cryptoDeviceInfo = null, + roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted, + isInactive = true, + isCurrentDevice = true, + deviceExtendedInfo = DeviceExtendedInfo( + deviceType = DeviceType.MOBILE, + ), + matrixClientInfo = null, + isSelected = isSelected, + ) } } From b7f9419bd4ad21254dbdd3859f74d17e9e37e1b4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 17:52:55 +0200 Subject: [PATCH 11/15] Fix usage of @+id in xml file --- vector/src/main/res/layout/item_other_session.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml index 908a0e0073..f514cea56b 100644 --- a/vector/src/main/res/layout/item_other_session.xml +++ b/vector/src/main/res/layout/item_other_session.xml @@ -5,8 +5,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:foreground="?selectableItemBackground" - android:paddingTop="8dp" - android:paddingHorizontal="8dp"> + android:paddingHorizontal="8dp" + android:paddingTop="8dp"> @@ -80,6 +80,6 @@ android:background="?vctr_content_quinary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView" - app:layout_constraintTop_toBottomOf="@+id/otherSessionItemBackground" /> + app:layout_constraintTop_toBottomOf="@id/otherSessionItemBackground" /> From 600f65025661e81e834345fde69983a612b7a244 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 20 Oct 2022 16:48:02 +0200 Subject: [PATCH 12/15] Fixing visibility of the select session action when empty list --- .../settings/devices/v2/othersessions/OtherSessionsFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 958266631e..d166a38d54 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -46,6 +46,7 @@ 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.more.SessionLearnMoreBottomSheet import im.vector.app.features.themes.ThemeUtils +import org.matrix.android.sdk.api.extensions.orFalse import javax.inject.Inject @AndroidEntryPoint @@ -75,7 +76,7 @@ class OtherSessionsFragment : val isSelectModeEnabled = state.isSelectModeEnabled menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled - menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled + menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled && state.devices()?.isNotEmpty().orFalse() } } From 3e1c110343096686dfe35ad02613f763f2a5dc45 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 25 Oct 2022 17:21:55 +0200 Subject: [PATCH 13/15] Updating some new string keys to make them more generic --- .../ui-strings/src/main/res/values/strings.xml | 15 +++++++++------ .../v2/othersessions/OtherSessionsFragment.kt | 2 +- vector/src/main/res/menu/menu_other_sessions.xml | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7c74162859..897c2853d8 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1,6 +1,13 @@ + + + %1$d selected + %1$d selected + + + %s\'s invitation Your invitation %1$s created the room @@ -407,6 +414,8 @@ Learn more Next Got it + Select all + Deselect all Copied to clipboard @@ -3329,12 +3338,6 @@ No inactive sessions found. Clear Filter Select sessions - Select all - Deselect all - - %1$d selected - %1$d selected - Sign out of this session Session details Application, device, and activity information. diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index d166a38d54..4f1c8353f5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -173,7 +173,7 @@ class OtherSessionsFragment : invalidateOptionsMenu() val title = if (isSelectModeEnabled) { val selection = devices.count { it.isSelected } - stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, selection, selection) + stringProvider.getQuantityString(R.plurals.x_selected, selection, selection) } else { getString(args.titleResourceId) } diff --git a/vector/src/main/res/menu/menu_other_sessions.xml b/vector/src/main/res/menu/menu_other_sessions.xml index 0edab99127..8339286fe7 100644 --- a/vector/src/main/res/menu/menu_other_sessions.xml +++ b/vector/src/main/res/menu/menu_other_sessions.xml @@ -11,11 +11,11 @@ From db17d02f36ad02b759d728827e801ea4a15702de Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 25 Oct 2022 17:25:12 +0200 Subject: [PATCH 14/15] Using host variable to make the code nicer --- .../settings/devices/v2/list/OtherSessionsController.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index 9193479b74..8d70552101 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -72,9 +72,9 @@ class OtherSessionsController @Inject constructor( sessionDescription(description) sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionColor(descriptionColor) - stringProvider(this@OtherSessionsController.stringProvider) - colorProvider(this@OtherSessionsController.colorProvider) - drawableProvider(this@OtherSessionsController.drawableProvider) + stringProvider(host.stringProvider) + colorProvider(host.colorProvider) + drawableProvider(host.drawableProvider) selected(device.isSelected) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } onLongClickListener(View.OnLongClickListener { From e765575cf6cde04c426723503c710af0c8940a64 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 25 Oct 2022 17:31:46 +0200 Subject: [PATCH 15/15] Renaming and creating a fixture method for DeviceFullInfo mocks --- .../OtherSessionsViewModelTest.kt | 38 +++++------------- .../test/fixtures/DeviceFullInfoFixture.kt | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+), 29 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index fa61918689..e7b8eeee9b 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -22,11 +22,10 @@ import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase -import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType -import im.vector.app.features.settings.devices.v2.list.DeviceType import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeVerificationService +import im.vector.app.test.fixtures.aDeviceFullInfo import im.vector.app.test.test import im.vector.app.test.testDispatcher import io.mockk.every @@ -39,8 +38,6 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel private const val A_TITLE_RES_ID = 1 private const val A_DEVICE_ID = "device-id" @@ -146,7 +143,7 @@ class OtherSessionsViewModelTest { @Test fun `given enable select mode action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) val devices: List = listOf(deviceFullInfo) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -170,8 +167,8 @@ class OtherSessionsViewModelTest { @Test fun `given disable select mode action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) - val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo1 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -195,7 +192,7 @@ class OtherSessionsViewModelTest { @Test fun `given toggle selection for device action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) val devices: List = listOf(deviceFullInfo) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -219,8 +216,8 @@ class OtherSessionsViewModelTest { @Test fun `given select all action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) - val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo1 = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -244,8 +241,8 @@ class OtherSessionsViewModelTest { @Test fun `given deselect all action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) - val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo1 = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -272,21 +269,4 @@ class OtherSessionsViewModelTest { ) { every { fakeGetDeviceFullInfoListUseCase.execute(filterType, any()) } returns flowOf(devices) } - - private fun givenDeviceFullInfo(deviceId: String, isSelected: Boolean): DeviceFullInfo { - return DeviceFullInfo( - deviceInfo = DeviceInfo( - deviceId = deviceId, - ), - cryptoDeviceInfo = null, - roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted, - isInactive = true, - isCurrentDevice = true, - deviceExtendedInfo = DeviceExtendedInfo( - deviceType = DeviceType.MOBILE, - ), - matrixClientInfo = null, - isSelected = isSelected, - ) - } } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt new file mode 100644 index 0000000000..d5f987b5c6 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt @@ -0,0 +1,40 @@ +/* + * 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.test.fixtures + +import im.vector.app.features.settings.devices.v2.DeviceFullInfo +import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo +import im.vector.app.features.settings.devices.v2.list.DeviceType +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel + +fun aDeviceFullInfo(deviceId: String, isSelected: Boolean): DeviceFullInfo { + return DeviceFullInfo( + deviceInfo = DeviceInfo( + deviceId = deviceId, + ), + cryptoDeviceInfo = null, + roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted, + isInactive = true, + isCurrentDevice = true, + deviceExtendedInfo = DeviceExtendedInfo( + deviceType = DeviceType.MOBILE, + ), + matrixClientInfo = null, + isSelected = isSelected, + ) +}