List devices.

This commit is contained in:
Onuray Sahin 2022-09-08 13:47:07 +03:00
parent 643f99b8e0
commit ab4ebc7f11
8 changed files with 81 additions and 9 deletions

View File

@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized 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( data class DevicesViewState(
val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(), val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
@ -26,4 +28,17 @@ data class DevicesViewState(
val unverifiedSessionsCount: Int = 0, val unverifiedSessionsCount: Int = 0,
val inactiveSessionsCount: Int = 0, val inactiveSessionsCount: Int = 0,
val isLoading: Boolean = false, val isLoading: Boolean = false,
) : MavericksState val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
) : MavericksState {
fun List<DeviceFullInfo>?.filteredDevices(): List<DeviceFullInfo>? {
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
}
}
}
}

View File

@ -37,10 +37,11 @@ import im.vector.app.core.resources.DrawableProvider
import im.vector.app.databinding.FragmentSettingsDevicesBinding import im.vector.app.databinding.FragmentSettingsDevicesBinding
import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.crypto.verification.VerificationBottomSheet 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.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.SecurityRecommendationViewState
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -198,7 +199,7 @@ class VectorSettingsDevicesFragment :
} else { } else {
views.deviceListHeaderOtherSessions.isVisible = true views.deviceListHeaderOtherSessions.isVisible = true
views.deviceListOtherSessions.isVisible = true views.deviceListOtherSessions.isVisible = true
views.deviceListOtherSessions.render(otherDevices) views.deviceListOtherSessions.render(otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER), otherDevices.size)
} }
} }

View File

@ -50,7 +50,7 @@ class OtherSessionsController @Inject constructor(
text(host.stringProvider.getString(R.string.no_result_placeholder)) text(host.stringProvider.getString(R.string.no_result_placeholder))
} }
} else { } 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 dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind) val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
val description = if (device.isInactive) { val description = if (device.isInactive) {

View File

@ -55,9 +55,9 @@ class OtherSessionsView @JvmOverloads constructor(
} }
} }
fun render(devices: List<DeviceFullInfo>) { fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int) {
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true) 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) otherSessionsController.setData(devices)
} }

View File

@ -54,7 +54,12 @@ class SessionsListHeaderView @JvmOverloads constructor(
private fun setTitle(typedArray: TypedArray) { private fun setTitle(typedArray: TypedArray) {
val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle) 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) { private fun setDescription(typedArray: TypedArray) {

View File

@ -21,16 +21,25 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import 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 dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentOtherSessionsBinding import im.vector.app.databinding.FragmentOtherSessionsBinding
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.DevicesViewModel
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
@AndroidEntryPoint @AndroidEntryPoint
class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener { class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener {
private val viewModel: DevicesViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false) return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
} }
@ -54,4 +63,25 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG).show() 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<DeviceFullInfo>?, currentFilter: DeviceManagerFilterType) {
views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
if (devices.isNullOrEmpty()) {
// TODO. Render empty state
} else {
views.deviceListOtherSessions.render(devices, devices.size)
}
}
} }

View File

@ -46,4 +46,25 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
android:id="@+id/deviceListHeaderOtherSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription="@string/settings_sessions_other_description"
app:devicesListHeaderTitle=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"/>
<im.vector.app.features.settings.devices.v2.list.OtherSessionsView
android:id="@+id/deviceListOtherSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -23,10 +23,10 @@
style="@style/TextAppearance.Vector.Body.DevicesManagement" style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
android:layout_marginTop="18.5dp" android:layout_marginTop="18.5dp"
android:layout_marginEnd="40dp"
app:layout_constraintEnd_toEndOf="parent" 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" 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 dont recognize or use anymore. Learn More." /> tools:text="For best security, verify your sessions and sign out from any session that you dont recognize or use anymore. Learn More." />
</merge> </merge>