mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Calling signout multi sessions use case in other sessions screen
This commit is contained in:
parent
bb262f0c41
commit
1bda54323a
@ -20,6 +20,12 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
|
||||
sealed class OtherSessionsAction : VectorViewModelAction {
|
||||
// ReAuth
|
||||
object SsoAuthDone : OtherSessionsAction()
|
||||
data class PasswordAuthDone(val password: String) : OtherSessionsAction()
|
||||
object ReAuthCancelled : OtherSessionsAction()
|
||||
|
||||
// Others
|
||||
data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction()
|
||||
data class EnableSelectMode(val deviceId: String?) : OtherSessionsAction()
|
||||
object DisableSelectMode : OtherSessionsAction()
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.app.features.settings.devices.v2.othersessions
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@ -32,6 +33,7 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.extensions.setTextColor
|
||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
|
||||
@ -40,6 +42,7 @@ 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.auth.ReAuthActivity
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
@ -47,6 +50,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.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -158,8 +162,9 @@ class OtherSessionsFragment :
|
||||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is OtherSessionsViewEvents.Loading -> showLoading(it.message)
|
||||
is OtherSessionsViewEvents.Failure -> showFailure(it.throwable)
|
||||
is OtherSessionsViewEvents.SignoutError -> showFailure(it.error)
|
||||
is OtherSessionsViewEvents.RequestReAuth -> askForReAuthentication(it)
|
||||
OtherSessionsViewEvents.SignoutSuccess -> enableSelectMode(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,6 +196,7 @@ class OtherSessionsFragment :
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
updateLoading(state.isLoading)
|
||||
if (state.devices is Success) {
|
||||
val devices = state.devices.invoke()
|
||||
renderDevices(devices, state.currentFilter)
|
||||
@ -198,6 +204,14 @@ class OtherSessionsFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLoading(isLoading: Boolean) {
|
||||
if (isLoading) {
|
||||
showLoading(null)
|
||||
} else {
|
||||
dismissLoadingDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateToolbar(devices: List<DeviceFullInfo>, isSelectModeEnabled: Boolean) {
|
||||
invalidateOptionsMenu()
|
||||
val title = if (isSelectModeEnabled) {
|
||||
@ -312,4 +326,37 @@ class OtherSessionsFragment :
|
||||
override fun onViewAllOtherSessionsClicked() {
|
||||
// NOOP. We don't have this button in this screen
|
||||
}
|
||||
|
||||
private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||
when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) {
|
||||
LoginFlowTypes.SSO -> {
|
||||
viewModel.handle(OtherSessionsAction.SsoAuthDone)
|
||||
}
|
||||
LoginFlowTypes.PASSWORD -> {
|
||||
val password = activityResult.data?.extras?.getString(ReAuthActivity.RESULT_VALUE) ?: ""
|
||||
viewModel.handle(OtherSessionsAction.PasswordAuthDone(password))
|
||||
}
|
||||
else -> {
|
||||
viewModel.handle(OtherSessionsAction.ReAuthCancelled)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewModel.handle(OtherSessionsAction.ReAuthCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the re auth activity to get credentials.
|
||||
*/
|
||||
private fun askForReAuthentication(reAuthReq: OtherSessionsViewEvents.RequestReAuth) {
|
||||
ReAuthActivity.newIntent(
|
||||
requireContext(),
|
||||
reAuthReq.registrationFlowResponse,
|
||||
reAuthReq.lastErrorCode,
|
||||
getString(R.string.devices_delete_dialog_title)
|
||||
).let { intent ->
|
||||
reAuthActivityResultLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,14 @@
|
||||
package im.vector.app.features.settings.devices.v2.othersessions
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
|
||||
sealed class OtherSessionsViewEvents : VectorViewEvents {
|
||||
data class Loading(val message: CharSequence? = null) : OtherSessionsViewEvents()
|
||||
data class Failure(val throwable: Throwable) : OtherSessionsViewEvents()
|
||||
data class RequestReAuth(
|
||||
val registrationFlowResponse: RegistrationFlowResponse,
|
||||
val lastErrorCode: String?
|
||||
) : OtherSessionsViewEvents()
|
||||
|
||||
object SignoutSuccess : OtherSessionsViewEvents()
|
||||
data class SignoutError(val error: Throwable) : OtherSessionsViewEvents()
|
||||
}
|
||||
|
@ -21,19 +21,38 @@ import com.airbnb.mvrx.Success
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
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.resources.StringProvider
|
||||
import im.vector.app.features.auth.PendingAuthHandler
|
||||
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.VectorSessionsListViewModel
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
|
||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult
|
||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||
import timber.log.Timber
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
class OtherSessionsViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: OtherSessionsViewState,
|
||||
activeSessionHolder: ActiveSessionHolder,
|
||||
private val stringProvider: StringProvider,
|
||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
||||
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
|
||||
private val pendingAuthHandler: PendingAuthHandler,
|
||||
refreshDevicesUseCase: RefreshDevicesUseCase
|
||||
) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(
|
||||
initialState, activeSessionHolder, refreshDevicesUseCase
|
||||
@ -68,6 +87,9 @@ class OtherSessionsViewModel @AssistedInject constructor(
|
||||
// TODO update unit tests
|
||||
override fun handle(action: OtherSessionsAction) {
|
||||
when (action) {
|
||||
is OtherSessionsAction.PasswordAuthDone -> handlePasswordAuthDone(action)
|
||||
OtherSessionsAction.ReAuthCancelled -> handleReAuthCancelled()
|
||||
OtherSessionsAction.SsoAuthDone -> handleSsoAuthDone()
|
||||
is OtherSessionsAction.FilterDevices -> handleFilterDevices(action)
|
||||
OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode()
|
||||
is OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode(action.deviceId)
|
||||
@ -145,7 +167,80 @@ class OtherSessionsViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMultiSignout() {
|
||||
// TODO call multi signout use case with all or only selected devices depending on the ViewState
|
||||
private fun handleMultiSignout() = withState { state ->
|
||||
viewModelScope.launch {
|
||||
setLoading(true)
|
||||
val deviceIds = getDeviceIdsToSignout(state)
|
||||
if (deviceIds.isEmpty()) {
|
||||
return@launch
|
||||
}
|
||||
val signoutResult = signout(deviceIds)
|
||||
setLoading(false)
|
||||
|
||||
if (signoutResult.isSuccess) {
|
||||
onSignoutSuccess()
|
||||
} else {
|
||||
when (val failure = signoutResult.exceptionOrNull()) {
|
||||
null -> onSignoutSuccess()
|
||||
else -> onSignoutFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeviceIdsToSignout(state: OtherSessionsViewState): List<String> {
|
||||
return if (state.isSelectModeEnabled) {
|
||||
state.devices()?.filter { it.isSelected }.orEmpty()
|
||||
} else {
|
||||
state.devices().orEmpty()
|
||||
}.mapNotNull { it.deviceInfo.deviceId }
|
||||
}
|
||||
|
||||
private suspend fun signout(deviceIds: List<String>) = signoutSessionsUseCase.execute(deviceIds, object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) {
|
||||
is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result)
|
||||
is SignoutSessionResult.Completed -> Unit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun onReAuthNeeded(reAuthNeeded: SignoutSessionResult.ReAuthNeeded) {
|
||||
Timber.d("onReAuthNeeded")
|
||||
pendingAuthHandler.pendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session)
|
||||
pendingAuthHandler.uiaContinuation = reAuthNeeded.uiaContinuation
|
||||
_viewEvents.post(OtherSessionsViewEvents.RequestReAuth(reAuthNeeded.flowResponse, reAuthNeeded.errCode))
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
setState { copy(isLoading = isLoading) }
|
||||
}
|
||||
|
||||
private fun onSignoutSuccess() {
|
||||
Timber.d("signout success")
|
||||
refreshDeviceList()
|
||||
_viewEvents.post(OtherSessionsViewEvents.SignoutSuccess)
|
||||
}
|
||||
|
||||
private fun onSignoutFailure(failure: Throwable) {
|
||||
Timber.e("signout failure", failure)
|
||||
val failureMessage = if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
|
||||
stringProvider.getString(R.string.authentication_error)
|
||||
} else {
|
||||
stringProvider.getString(R.string.matrix_error)
|
||||
}
|
||||
_viewEvents.post(OtherSessionsViewEvents.SignoutError(Exception(failureMessage)))
|
||||
}
|
||||
|
||||
private fun handleSsoAuthDone() {
|
||||
pendingAuthHandler.ssoAuthDone()
|
||||
}
|
||||
|
||||
private fun handlePasswordAuthDone(action: OtherSessionsAction.PasswordAuthDone) {
|
||||
pendingAuthHandler.passwordAuthDone(action.password)
|
||||
}
|
||||
|
||||
private fun handleReAuthCancelled() {
|
||||
pendingAuthHandler.reAuthCancelled()
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ data class OtherSessionsViewState(
|
||||
val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
val excludeCurrentDevice: Boolean = false,
|
||||
val isSelectModeEnabled: Boolean = false,
|
||||
val isLoading: Boolean = false,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice)
|
||||
|
@ -21,6 +21,9 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.util.awaitCallback
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use case to signout a single session.
|
||||
*/
|
||||
class SignoutSessionUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
) {
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.signout
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.util.awaitCallback
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use case to signout several sessions.
|
||||
*/
|
||||
class SignoutSessionsUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
) {
|
||||
|
||||
// TODO add unit tests
|
||||
suspend fun execute(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor): Result<Unit> {
|
||||
return deleteDevices(deviceIds, userInteractiveAuthInterceptor)
|
||||
}
|
||||
|
||||
private suspend fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = runCatching {
|
||||
awaitCallback { matrixCallback ->
|
||||
activeSessionHolder.getActiveSession()
|
||||
.cryptoService()
|
||||
.deleteDevices(deviceIds, userInteractiveAuthInterceptor, matrixCallback)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user