diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt index 3fc061daa4..5ca3e71a06 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt @@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2 import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType +import org.matrix.android.sdk.api.extensions.orFalse data class DevicesViewState( val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(), @@ -26,4 +28,17 @@ data class DevicesViewState( val unverifiedSessionsCount: Int = 0, val inactiveSessionsCount: Int = 0, val isLoading: Boolean = false, -) : MavericksState + val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS, +) : MavericksState { + + fun List?.filteredDevices(): List? { + return this?.filter { + when (currentFilter) { + DeviceManagerFilterType.ALL_SESSIONS -> true + DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse() + DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse() + DeviceManagerFilterType.INACTIVE -> it.isInactive + } + } + } +} 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 684f9cdae9..33c0a7ca74 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 @@ -37,10 +37,11 @@ import im.vector.app.core.resources.DrawableProvider import im.vector.app.databinding.FragmentSettingsDevicesBinding import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.VerificationBottomSheet +import im.vector.app.features.settings.devices.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.SecurityRecommendationViewState import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState -import im.vector.app.features.settings.devices.v2.list.OtherSessionsView import javax.inject.Inject /** @@ -198,7 +199,7 @@ class VectorSettingsDevicesFragment : } else { views.deviceListHeaderOtherSessions.isVisible = true views.deviceListOtherSessions.isVisible = true - views.deviceListOtherSessions.render(otherDevices) + views.deviceListOtherSessions.render(otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER), otherDevices.size) } } 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 468b19c45a..06f3373f61 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 @@ -50,7 +50,7 @@ class OtherSessionsController @Inject constructor( text(host.stringProvider.getString(R.string.no_result_placeholder)) } } else { - data.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER).forEach { device -> + 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) { 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 37a2db2a96..c6eccc94b6 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 @@ -55,9 +55,9 @@ class OtherSessionsView @JvmOverloads constructor( } } - fun render(devices: List) { + fun render(devices: List, totalNumberOfDevices: Int) { views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true) - views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, devices.size) + views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices) otherSessionsController.setData(devices) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt index 547ed93f24..2b93c3447e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt @@ -54,7 +54,12 @@ class SessionsListHeaderView @JvmOverloads constructor( private fun setTitle(typedArray: TypedArray) { val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle) - binding.sessionsListHeaderTitle.text = title + if (title.isNullOrEmpty()) { + binding.sessionsListHeaderTitle.isVisible = false + } else { + binding.sessionsListHeaderTitle.isVisible = true + binding.sessionsListHeaderTitle.text = title + } } private fun setDescription(typedArray: TypedArray) { 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 43d3005f16..8b014c4ba8 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 @@ -21,16 +21,25 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.core.view.isVisible +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint 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.databinding.FragmentOtherSessionsBinding +import im.vector.app.features.settings.devices.v2.DeviceFullInfo +import im.vector.app.features.settings.devices.v2.DevicesViewModel import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet +import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType @AndroidEntryPoint class OtherSessionsFragment : VectorBaseFragment(), VectorBaseBottomSheetDialogFragment.ResultListener { + private val viewModel: DevicesViewModel by fragmentViewModel() + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding { return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false) } @@ -54,4 +63,25 @@ class OtherSessionsFragment : VectorBaseFragment() Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG).show() } } + + override fun invalidate() = withState(viewModel) { state -> + if (state.devices is Success) { + with(state) { + val devices = state.devices() + ?.filter { it.deviceInfo.deviceId != state.currentSessionCrossSigningInfo.deviceId } + ?.filteredDevices() + renderDevices(devices, state.currentFilter) + } + } + } + + private fun renderDevices(devices: List?, currentFilter: DeviceManagerFilterType) { + views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS + + if (devices.isNullOrEmpty()) { + // TODO. Render empty state + } else { + views.deviceListOtherSessions.render(devices, devices.size) + } + } } diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml index e0450fb5e5..8b504ca903 100644 --- a/vector/src/main/res/layout/fragment_other_sessions.xml +++ b/vector/src/main/res/layout/fragment_other_sessions.xml @@ -46,4 +46,25 @@ + + + + + + diff --git a/vector/src/main/res/layout/view_sessions_list_header.xml b/vector/src/main/res/layout/view_sessions_list_header.xml index d690ee4c87..6139ff4815 100644 --- a/vector/src/main/res/layout/view_sessions_list_header.xml +++ b/vector/src/main/res/layout/view_sessions_list_header.xml @@ -23,10 +23,10 @@ style="@style/TextAppearance.Vector.Body.DevicesManagement" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/layout_horizontal_margin" android:layout_marginTop="18.5dp" - android:layout_marginEnd="40dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/sessions_list_header_title" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title" tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />