mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 19:05:56 +03:00
Code review fixes.
This commit is contained in:
parent
2763ebdd5a
commit
81cc8ab98b
19 changed files with 391 additions and 84 deletions
|
@ -1 +0,0 @@
|
|||
[Devices Management] Refactor some code to improve testability
|
|
@ -3265,6 +3265,32 @@
|
|||
<string name="device_manager_session_title">Session</string>
|
||||
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
||||
<string name="device_manager_session_last_activity">Last activity %1$s</string>
|
||||
<string name="device_manager_filter_bottom_sheet_title">Filter</string>
|
||||
<string name="device_manager_filter_option_all_sessions">All sessions</string>
|
||||
<string name="device_manager_filter_option_verified">Verified</string>
|
||||
<string name="device_manager_filter_option_verified_description">Ready for secure messaging</string>
|
||||
<string name="device_manager_filter_option_unverified">Unverified</string>
|
||||
<string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
|
||||
<string name="device_manager_filter_option_inactive">Inactive</string>
|
||||
<plurals name="device_manager_filter_option_inactive_description">
|
||||
<item quantity="one">Inactive for %1$d day or longer</item>
|
||||
<item quantity="other">Inactive for %1$d days or longer</item>
|
||||
</plurals>
|
||||
<string name="a11y_device_manager_filter">Filter</string>
|
||||
<string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
|
||||
<string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you don’t recognize or use anymore.</string>
|
||||
<string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
|
||||
<string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore.</string>
|
||||
<string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
|
||||
<plurals name="device_manager_other_sessions_recommendation_description_inactive">
|
||||
<item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
|
||||
<item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
|
||||
</plurals>
|
||||
<string name="device_manager_other_sessions_no_verified_sessions_found">No verified sessions found.</string>
|
||||
<string name="device_manager_other_sessions_no_unverified_sessions_found">No unverified sessions found.</string>
|
||||
<string name="device_manager_other_sessions_no_inactive_sessions_found">No inactive sessions found.</string>
|
||||
<string name="device_manager_other_sessions_clear_filter">Clear Filter</string>
|
||||
|
||||
<!-- Note to translators: %s will be replaces with selected space name -->
|
||||
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
|
||||
<!-- Note to translators: for RTL languages, Spaces will be at the bottom left. Please translate "bottom-left" instead of "bottom-right". Thanks!-->
|
||||
|
@ -3286,31 +3312,4 @@
|
|||
<string name="onboarding_new_app_layout_feedback_message">Tap top right to see the option to feedback.</string>
|
||||
<string name="onboarding_new_app_layout_button_try">Try it out</string>
|
||||
|
||||
<string name="device_manager_filter_bottom_sheet_title">Filter</string>
|
||||
<string name="device_manager_filter_option_all_sessions">All session</string>
|
||||
<string name="device_manager_filter_option_verified">Verified</string>
|
||||
<string name="device_manager_filter_option_verified_description">Ready for secure messaging</string>
|
||||
<string name="device_manager_filter_option_unverified">Unverified</string>
|
||||
<string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
|
||||
<string name="device_manager_filter_option_inactive">Inactive</string>
|
||||
<plurals name="device_manager_filter_option_inactive_description">
|
||||
<item quantity="one">Inactive for %1$d day or longer</item>
|
||||
<item quantity="other">Inactive for %1$d days or longer</item>
|
||||
</plurals>
|
||||
<string name="device_manager_other_sessions_title">Other sessions</string>
|
||||
<string name="a11y_device_manager_filter">Filter</string>
|
||||
<string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
|
||||
<string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you don’t recognize or use anymore.</string>
|
||||
<string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
|
||||
<string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore.</string>
|
||||
<string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
|
||||
<plurals name="device_manager_other_sessions_recommendation_description_inactive">
|
||||
<item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
|
||||
<item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
|
||||
</plurals>
|
||||
<string name="device_manager_other_sessions_no_verified_sessions_found">No verified sessions found.</string>
|
||||
<string name="device_manager_other_sessions_no_unverified_sessions_found">No unverified sessions found.</string>
|
||||
<string name="device_manager_other_sessions_no_inactive_sessions_found">No inactive sessions found.</string>
|
||||
<string name="device_manager_other_sessions_clear_filter">Clear Filter</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -88,6 +88,7 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie
|
|||
import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
|
||||
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
|
||||
import im.vector.app.features.settings.devices.DevicesViewModel
|
||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
|
||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
|
||||
import im.vector.app.features.settings.devtools.AccountDataViewModel
|
||||
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
||||
|
@ -641,4 +642,9 @@ interface MavericksViewModelModule {
|
|||
@IntoMap
|
||||
@MavericksViewModelKey(SessionOverviewViewModel::class)
|
||||
fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(OtherSessionsViewModel::class)
|
||||
fun otherSessionsViewModelFactory(factory: OtherSessionsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
|
|
@ -17,10 +17,8 @@
|
|||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
|
||||
sealed class DevicesAction : VectorViewModelAction {
|
||||
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
||||
data class FilterDevices(val filterType: DeviceManagerFilterType) : DevicesAction()
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
|||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.PublishDataSource
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.lib.core.utils.flow.throttleFirst
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -94,7 +95,10 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun observeDevices() {
|
||||
getDeviceFullInfoListUseCase.execute()
|
||||
getDeviceFullInfoListUseCase.execute(
|
||||
filterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
excludeCurrentDevice = false
|
||||
)
|
||||
.execute { async ->
|
||||
if (async is Success) {
|
||||
val deviceFullInfoList = async.invoke()
|
||||
|
@ -144,19 +148,9 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
override fun handle(action: DevicesAction) {
|
||||
when (action) {
|
||||
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
||||
is DevicesAction.FilterDevices -> handleFilterDevices(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFilterDevices(action: DevicesAction.FilterDevices) {
|
||||
setState {
|
||||
copy(
|
||||
currentFilter = action.filterType
|
||||
)
|
||||
}
|
||||
queryRefreshDevicesList()
|
||||
}
|
||||
|
||||
private fun handleMarkAsManuallyVerifiedAction() {
|
||||
// TODO implement when needed
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ 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(),
|
||||
|
@ -28,17 +26,4 @@ data class DevicesViewState(
|
|||
val unverifiedSessionsCount: Int = 0,
|
||||
val inactiveSessionsCount: Int = 0,
|
||||
val isLoading: Boolean = false,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) : MavericksState
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
|
||||
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
@ -32,16 +34,23 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
|||
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||
private val filterDevicesUseCase: FilterDevicesUseCase,
|
||||
) {
|
||||
|
||||
fun execute(): Flow<List<DeviceFullInfo>> {
|
||||
fun execute(filterType: DeviceManagerFilterType, excludeCurrentDevice: Boolean = false): Flow<List<DeviceFullInfo>> {
|
||||
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||
val deviceFullInfoFlow = combine(
|
||||
getCurrentSessionCrossSigningInfoUseCase.execute(),
|
||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||
session.flow().liveMyDevicesInfo()
|
||||
) { currentSessionCrossSigningInfo, cryptoList, infoList ->
|
||||
convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
|
||||
val deviceFullInfoList = convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
|
||||
val excludedDeviceIds = if (excludeCurrentDevice) {
|
||||
listOf(currentSessionCrossSigningInfo.deviceId)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
filterDevicesUseCase.execute(deviceFullInfoList, filterType, excludedDeviceIds)
|
||||
}
|
||||
|
||||
deviceFullInfoFlow.distinctUntilChanged()
|
||||
|
|
|
@ -50,7 +50,7 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
|
|||
}
|
||||
|
||||
private fun initFilterRadioGroup() {
|
||||
views.filterOptionInactiveRadioButtonDescription.text = resources.getQuantityString(
|
||||
views.filterOptionInactiveTextView.text = resources.getQuantityString(
|
||||
R.plurals.device_manager_filter_option_inactive_description,
|
||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||
|
@ -64,6 +64,16 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
|
|||
}
|
||||
views.filterOptionsRadioGroup.check(radioButtonId)
|
||||
|
||||
views.filterOptionVerifiedTextView.debouncedClicks {
|
||||
views.filterOptionsRadioGroup.check(R.id.filterOptionVerifiedRadioButton)
|
||||
}
|
||||
views.filterOptionUnverifiedTextView.debouncedClicks {
|
||||
views.filterOptionsRadioGroup.check(R.id.filterOptionUnverifiedRadioButton)
|
||||
}
|
||||
views.filterOptionInactiveTextView.debouncedClicks {
|
||||
views.filterOptionsRadioGroup.check(R.id.filterOptionInactiveRadioButton)
|
||||
}
|
||||
|
||||
views.filterOptionsRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
onFilterTypeChanged(checkedId)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.filter
|
||||
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import javax.inject.Inject
|
||||
|
||||
class FilterDevicesUseCase @Inject constructor() {
|
||||
|
||||
fun execute(
|
||||
devices: List<DeviceFullInfo>,
|
||||
filterType: DeviceManagerFilterType,
|
||||
excludedDeviceIds: List<String> = emptyList(),
|
||||
): List<DeviceFullInfo> {
|
||||
return devices
|
||||
.filter {
|
||||
when (filterType) {
|
||||
DeviceManagerFilterType.ALL_SESSIONS -> true
|
||||
DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse()
|
||||
DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse()
|
||||
DeviceManagerFilterType.INACTIVE -> it.isInactive
|
||||
}
|
||||
}
|
||||
.filter { it.deviceInfo.deviceId !in excludedDeviceIds }
|
||||
}
|
||||
}
|
|
@ -20,9 +20,12 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.databinding.ViewOtherSessionsBinding
|
||||
|
@ -44,18 +47,32 @@ class OtherSessionsView @JvmOverloads constructor(
|
|||
@Inject lateinit var otherSessionsController: OtherSessionsController
|
||||
|
||||
private val views: ViewOtherSessionsBinding
|
||||
private val recyclerViewDataObserver: RecyclerView.AdapterDataObserver
|
||||
private lateinit var recyclerViewDataObserver: RecyclerView.AdapterDataObserver
|
||||
private lateinit var stateRestorer: LayoutManagerStateRestorer
|
||||
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.view_other_sessions, this)
|
||||
views = ViewOtherSessionsBinding.bind(this)
|
||||
|
||||
otherSessionsController.callback = this
|
||||
configureOtherSessionsRecyclerView()
|
||||
|
||||
views.otherSessionsViewAllButton.setOnClickListener {
|
||||
callback?.onViewAllOtherSessionsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureOtherSessionsRecyclerView() {
|
||||
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = false)
|
||||
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
stateRestorer = LayoutManagerStateRestorer(layoutManager)
|
||||
views.otherSessionsRecyclerView.layoutManager = layoutManager
|
||||
layoutManager.recycleChildrenOnDetach = true
|
||||
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||
otherSessionsController.addModelBuildListener(modelBuildListener)
|
||||
|
||||
recyclerViewDataObserver = object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
|
@ -64,10 +81,11 @@ class OtherSessionsView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
otherSessionsController.adapter.registerAdapterDataObserver(recyclerViewDataObserver)
|
||||
|
||||
otherSessionsController.callback = this
|
||||
}
|
||||
|
||||
fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int, showViewAll: Boolean) {
|
||||
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
|
||||
if (showViewAll) {
|
||||
views.otherSessionsViewAllButton.isVisible = true
|
||||
views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices)
|
||||
|
@ -78,6 +96,8 @@ class OtherSessionsView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
otherSessionsController.removeModelBuildListener(modelBuildListener)
|
||||
modelBuildListener = null
|
||||
otherSessionsController.callback = null
|
||||
otherSessionsController.adapter.unregisterAdapterDataObserver(recyclerViewDataObserver)
|
||||
views.otherSessionsRecyclerView.cleanup()
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
|
||||
sealed class OtherSessionsAction : VectorViewModelAction {
|
||||
data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction()
|
||||
}
|
|
@ -32,9 +32,6 @@ import im.vector.app.core.platform.VectorBaseFragment
|
|||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.databinding.FragmentOtherSessionsBinding
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.DevicesAction
|
||||
import im.vector.app.features.settings.devices.v2.DevicesViewModel
|
||||
import im.vector.app.features.settings.devices.v2.VectorSettingsDevicesViewNavigator
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||
|
@ -48,9 +45,11 @@ class OtherSessionsFragment :
|
|||
VectorBaseBottomSheetDialogFragment.ResultListener,
|
||||
OtherSessionsView.Callback {
|
||||
|
||||
private val viewModel: DevicesViewModel by fragmentViewModel()
|
||||
private val viewModel: OtherSessionsViewModel by fragmentViewModel()
|
||||
|
||||
@Inject lateinit var colorProvider: ColorProvider
|
||||
@Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
|
||||
|
||||
@Inject lateinit var viewNavigator: OtherSessionsViewNavigator
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
|
||||
return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
|
||||
|
@ -59,9 +58,19 @@ class OtherSessionsFragment :
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupToolbar(views.otherSessionsToolbar).allowBack()
|
||||
observeViewEvents()
|
||||
initFilterView()
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is OtherSessionsViewEvents.Loading -> showLoading(it.message)
|
||||
is OtherSessionsViewEvents.Failure -> showFailure(it.throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initFilterView() {
|
||||
views.otherSessionsFilterFrameLayout.debouncedClicks {
|
||||
withState(viewModel) { state ->
|
||||
|
@ -72,7 +81,7 @@ class OtherSessionsFragment :
|
|||
}
|
||||
|
||||
views.otherSessionsClearFilterButton.debouncedClicks {
|
||||
viewModel.handle(DevicesAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
|
||||
viewModel.handle(OtherSessionsAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
|
||||
}
|
||||
|
||||
views.deviceListOtherSessions.callback = this
|
||||
|
@ -80,18 +89,13 @@ class OtherSessionsFragment :
|
|||
|
||||
override fun onBottomSheetResult(resultCode: Int, data: Any?) {
|
||||
if (resultCode == RESULT_OK && data != null && data is DeviceManagerFilterType) {
|
||||
viewModel.handle(DevicesAction.FilterDevices(data))
|
||||
viewModel.handle(OtherSessionsAction.FilterDevices(data))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
renderDevices(state.devices(), state.currentFilter)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.app.core.extensions.setTextWithColoredPart
|
|||
import im.vector.app.databinding.ViewOtherSessionSecurityRecommendationBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OtherSessionsSecurityRecommendationView @JvmOverloads constructor(
|
||||
class OtherSessionsSecurityRecommendationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
|
@ -84,13 +84,14 @@ class OtherSessionsSecurityRecommendationView @JvmOverloads constructor(
|
|||
|
||||
private fun setDescription(description: String?) {
|
||||
val learnMore = context.getString(R.string.action_learn_more)
|
||||
val stringBuilder = StringBuilder()
|
||||
stringBuilder.append(description)
|
||||
stringBuilder.append(" ")
|
||||
stringBuilder.append(learnMore)
|
||||
val formattedDescription = buildString {
|
||||
append(description)
|
||||
append(" ")
|
||||
append(learnMore)
|
||||
}
|
||||
|
||||
views.recommendationDescriptionTextView.setTextWithColoredPart(
|
||||
fullText = stringBuilder.toString(),
|
||||
fullText = formattedDescription,
|
||||
coloredPart = learnMore,
|
||||
underline = false
|
||||
) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class OtherSessionsViewEvents : VectorViewEvents {
|
||||
data class Loading(val message: CharSequence? = null) : OtherSessionsViewEvents()
|
||||
data class Failure(val throwable: Throwable) : OtherSessionsViewEvents()
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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 com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.PublishDataSource
|
||||
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.lib.core.utils.flow.throttleFirst
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class OtherSessionsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: OtherSessionsViewState,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||
private val refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||
) : VectorViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(initialState), VerificationService.Listener {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> {
|
||||
override fun create(initialState: OtherSessionsViewState): OtherSessionsViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private var observeDevicesJob: Job? = null
|
||||
|
||||
private val refreshSource = PublishDataSource<Unit>()
|
||||
private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
|
||||
|
||||
init {
|
||||
observeDevices(initialState.currentFilter)
|
||||
addVerificationListener()
|
||||
observeRefreshSource()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
removeVerificationListener()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
private fun observeDevices(currentFilter: DeviceManagerFilterType) {
|
||||
observeDevicesJob?.cancel()
|
||||
observeDevicesJob = getDeviceFullInfoListUseCase.execute(
|
||||
filterType = currentFilter,
|
||||
excludeCurrentDevice = true
|
||||
)
|
||||
.execute { async ->
|
||||
if (async is Success) {
|
||||
copy(
|
||||
devices = async,
|
||||
)
|
||||
} else {
|
||||
copy(
|
||||
devices = async
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addVerificationListener() {
|
||||
activeSessionHolder.getSafeActiveSession()
|
||||
?.cryptoService()
|
||||
?.verificationService()
|
||||
?.addListener(this)
|
||||
}
|
||||
|
||||
private fun removeVerificationListener() {
|
||||
activeSessionHolder.getSafeActiveSession()
|
||||
?.cryptoService()
|
||||
?.verificationService()
|
||||
?.removeListener(this)
|
||||
}
|
||||
|
||||
private fun observeRefreshSource() {
|
||||
refreshSource.stream()
|
||||
.throttleFirst(refreshThrottleDelayMs)
|
||||
.onEach { refreshDevicesUseCase.execute() }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.Verified) {
|
||||
queryRefreshDevicesList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryRefreshDevicesList() {
|
||||
refreshSource.post(Unit)
|
||||
}
|
||||
|
||||
override fun handle(action: OtherSessionsAction) {
|
||||
when (action) {
|
||||
is OtherSessionsAction.FilterDevices -> handleFilterDevices(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFilterDevices(action: OtherSessionsAction.FilterDevices) {
|
||||
setState {
|
||||
copy(
|
||||
currentFilter = action.filterType
|
||||
)
|
||||
}
|
||||
observeDevices(action.filterType)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.content.Context
|
||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class OtherSessionsViewNavigator @Inject constructor() {
|
||||
|
||||
fun navigateToSessionOverview(context: Context, deviceId: String) {
|
||||
context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
|
||||
data class OtherSessionsViewState(
|
||||
val devices: Async<List<DeviceFullInfo>> = Uninitialized,
|
||||
val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
) : MavericksState
|
|
@ -47,6 +47,7 @@
|
|||
android:text="@string/device_manager_filter_option_verified" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filterOptionVerifiedTextView"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -63,6 +64,7 @@
|
|||
android:text="@string/device_manager_filter_option_unverified" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filterOptionUnverifiedTextView"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -79,7 +81,7 @@
|
|||
android:text="@string/device_manager_filter_option_inactive" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filterOptionInactiveRadioButtonDescription"
|
||||
android:id="@+id/filterOptionInactiveTextView"
|
||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/ic_back_24dp"
|
||||
app:title="@string/device_manager_other_sessions_title">
|
||||
app:title="@string/settings_sessions_other_title">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/otherSessionsFilterFrameLayout"
|
||||
|
|
Loading…
Reference in a new issue