SoftLogout: recovery with SSO
This commit is contained in:
parent
183d6b53bd
commit
4e74b545ad
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.api.session.signout
|
package im.vector.matrix.android.api.session.signout
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +32,12 @@ interface SignOutService {
|
|||||||
fun signInAgain(password: String,
|
fun signInAgain(password: String,
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the session with credentials received after SSO
|
||||||
|
*/
|
||||||
|
fun updateCredentials(credentials: Credentials,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out, and release the session, clear all the session data, including crypto data
|
* Sign out, and release the session, clear all the session data, including crypto data
|
||||||
* @param sigOutFromHomeserver true if the sign out request has to be done
|
* @param sigOutFromHomeserver true if the sign out request has to be done
|
||||||
|
@ -17,14 +17,21 @@
|
|||||||
package im.vector.matrix.android.internal.session.signout
|
package im.vector.matrix.android.internal.session.signout
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
||||||
private val signInAgainTask: SignInAgainTask,
|
private val signInAgainTask: SignInAgainTask,
|
||||||
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor) : SignOutService {
|
private val taskExecutor: TaskExecutor) : SignOutService {
|
||||||
|
|
||||||
override fun signInAgain(password: String,
|
override fun signInAgain(password: String,
|
||||||
@ -36,6 +43,13 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateCredentials(credentials: Credentials,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
sessionParamsStore.updateCredentials(credentials)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun signOut(sigOutFromHomeserver: Boolean,
|
override fun signOut(sigOutFromHomeserver: Boolean,
|
||||||
callback: MatrixCallback<Unit>): Cancelable {
|
callback: MatrixCallback<Unit>): Cancelable {
|
||||||
return signOutTask
|
return signOutTask
|
||||||
|
@ -55,4 +55,7 @@ sealed class LoginAction : VectorViewModelAction {
|
|||||||
object ResetSignMode : ResetAction()
|
object ResetSignMode : ResetAction()
|
||||||
object ResetLogin : ResetAction()
|
object ResetLogin : ResetAction()
|
||||||
object ResetResetPassword : ResetAction()
|
object ResetResetPassword : ResetAction()
|
||||||
|
|
||||||
|
// For the soft logout case
|
||||||
|
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,18 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||||||
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||||
is LoginAction.ResetAction -> handleResetAction(action)
|
is LoginAction.ResetAction -> handleResetAction(action)
|
||||||
|
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
signMode = SignMode.SignIn,
|
||||||
|
loginMode = LoginMode.Sso,
|
||||||
|
homeServerUrl = action.homeServerUrl,
|
||||||
|
deviceId = action.deviceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ data class LoginViewState(
|
|||||||
val resetPasswordEmail: String? = null,
|
val resetPasswordEmail: String? = null,
|
||||||
@PersistState
|
@PersistState
|
||||||
val homeServerUrl: String? = null,
|
val homeServerUrl: String? = null,
|
||||||
|
// For SSO session recovery
|
||||||
|
@PersistState
|
||||||
|
val deviceId: String? = null,
|
||||||
|
|
||||||
// Network result
|
// Network result
|
||||||
@PersistState
|
@PersistState
|
||||||
|
@ -30,10 +30,14 @@ import android.webkit.SslErrorHandler
|
|||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.utils.AssetReader
|
import im.vector.riotx.core.utils.AssetReader
|
||||||
|
import im.vector.riotx.features.signout.soft.SoftLogoutAction
|
||||||
|
import im.vector.riotx.features.signout.soft.SoftLogoutViewModel
|
||||||
import kotlinx.android.synthetic.main.fragment_login_web.*
|
import kotlinx.android.synthetic.main.fragment_login_web.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
@ -48,9 +52,12 @@ class LoginWebFragment @Inject constructor(
|
|||||||
private val errorFormatter: ErrorFormatter
|
private val errorFormatter: ErrorFormatter
|
||||||
) : AbstractLoginFragment() {
|
) : AbstractLoginFragment() {
|
||||||
|
|
||||||
|
private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_login_web
|
override fun getLayoutResId() = R.layout.fragment_login_web
|
||||||
|
|
||||||
private var isWebViewLoaded = false
|
private var isWebViewLoaded = false
|
||||||
|
private var isForSessionRecovery = false
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
@ -60,6 +67,9 @@ class LoginWebFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun updateWithState(state: LoginViewState) {
|
override fun updateWithState(state: LoginViewState) {
|
||||||
setupTitle(state)
|
setupTitle(state)
|
||||||
|
|
||||||
|
isForSessionRecovery = state.deviceId?.isNotBlank() == true
|
||||||
|
|
||||||
if (!isWebViewLoaded) {
|
if (!isWebViewLoaded) {
|
||||||
setupWebView(state)
|
setupWebView(state)
|
||||||
isWebViewLoaded = true
|
isWebViewLoaded = true
|
||||||
@ -110,13 +120,22 @@ class LoginWebFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun launchWebView(state: LoginViewState) {
|
private fun launchWebView(state: LoginViewState) {
|
||||||
if (state.signMode == SignMode.SignIn) {
|
val url = buildString {
|
||||||
loginWebWebView.loadUrl(state.homeServerUrl?.trim { it == '/' } + "/_matrix/static/client/login/")
|
append(state.homeServerUrl?.trim { it == '/' })
|
||||||
} else {
|
if (state.signMode == SignMode.SignIn) {
|
||||||
// MODE_REGISTER
|
append("/_matrix/static/client/login/")
|
||||||
loginWebWebView.loadUrl(state.homeServerUrl?.trim { it == '/' } + "/_matrix/static/client/register/")
|
state.deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||||
|
// But https://github.com/matrix-org/synapse/issues/5755
|
||||||
|
append("?device_id=$it")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// MODE_REGISTER
|
||||||
|
append("/_matrix/static/client/register/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loginWebWebView.loadUrl(url)
|
||||||
|
|
||||||
loginWebWebView.webViewClient = object : WebViewClient() {
|
loginWebWebView.webViewClient = object : WebViewClient() {
|
||||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler,
|
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler,
|
||||||
error: SslError) {
|
error: SslError) {
|
||||||
@ -212,10 +231,7 @@ class LoginWebFragment @Inject constructor(
|
|||||||
if (state.signMode == SignMode.SignIn) {
|
if (state.signMode == SignMode.SignIn) {
|
||||||
try {
|
try {
|
||||||
if (action == "onLogin") {
|
if (action == "onLogin") {
|
||||||
val credentials = javascriptResponse.credentials
|
javascriptResponse.credentials?.let { notifyViewModel(it) }
|
||||||
if (credentials != null) {
|
|
||||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## shouldOverrideUrlLoading() : failed")
|
Timber.e(e, "## shouldOverrideUrlLoading() : failed")
|
||||||
@ -224,10 +240,7 @@ class LoginWebFragment @Inject constructor(
|
|||||||
// MODE_REGISTER
|
// MODE_REGISTER
|
||||||
// check the required parameters
|
// check the required parameters
|
||||||
if (action == "onRegistered") {
|
if (action == "onRegistered") {
|
||||||
val credentials = javascriptResponse.credentials
|
javascriptResponse.credentials?.let { notifyViewModel(it) }
|
||||||
if (credentials != null) {
|
|
||||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,6 +252,14 @@ class LoginWebFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun notifyViewModel(credentials: Credentials) {
|
||||||
|
if (isForSessionRecovery) {
|
||||||
|
softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials))
|
||||||
|
} else {
|
||||||
|
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
loginViewModel.handle(LoginAction.ResetLogin)
|
loginViewModel.handle(LoginAction.ResetLogin)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.signout.soft
|
package im.vector.riotx.features.signout.soft
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class SoftLogoutAction : VectorViewModelAction {
|
sealed class SoftLogoutAction : VectorViewModelAction {
|
||||||
@ -24,5 +25,5 @@ sealed class SoftLogoutAction : VectorViewModelAction {
|
|||||||
object TogglePassword : SoftLogoutAction()
|
object TogglePassword : SoftLogoutAction()
|
||||||
|
|
||||||
data class SignInAgain(val password: String) : SoftLogoutAction()
|
data class SignInAgain(val password: String) : SoftLogoutAction()
|
||||||
// TODO Add reset pwd...
|
data class WebLoginSuccess(val credentials: Credentials) : SoftLogoutAction()
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import com.airbnb.mvrx.Success
|
|||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.loadingItem
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
|
import im.vector.riotx.core.extensions.toReducedUrl
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.login.LoginMode
|
import im.vector.riotx.features.login.LoginMode
|
||||||
import im.vector.riotx.features.signout.soft.epoxy.*
|
import im.vector.riotx.features.signout.soft.epoxy.*
|
||||||
@ -71,7 +72,7 @@ class SoftLogoutController @Inject constructor(
|
|||||||
loginTextItem {
|
loginTextItem {
|
||||||
id("signText1")
|
id("signText1")
|
||||||
text(stringProvider.getString(R.string.soft_logout_signin_notice,
|
text(stringProvider.getString(R.string.soft_logout_signin_notice,
|
||||||
state.homeServerUrl,
|
state.homeServerUrl.toReducedUrl(),
|
||||||
state.userDisplayName,
|
state.userDisplayName,
|
||||||
state.userId))
|
state.userId))
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
|||||||
import im.vector.riotx.features.MainActivity
|
import im.vector.riotx.features.MainActivity
|
||||||
import im.vector.riotx.features.MainActivityArgs
|
import im.vector.riotx.features.MainActivityArgs
|
||||||
import im.vector.riotx.features.login.AbstractLoginFragment
|
import im.vector.riotx.features.login.AbstractLoginFragment
|
||||||
|
import im.vector.riotx.features.login.LoginAction
|
||||||
|
import im.vector.riotx.features.login.LoginMode
|
||||||
import im.vector.riotx.features.login.LoginNavigation
|
import im.vector.riotx.features.login.LoginNavigation
|
||||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -56,6 +58,14 @@ class SoftLogoutFragment @Inject constructor(
|
|||||||
|
|
||||||
softLogoutViewModel.subscribe(this) { softLogoutViewState ->
|
softLogoutViewModel.subscribe(this) { softLogoutViewState ->
|
||||||
softLogoutController.update(softLogoutViewState)
|
softLogoutController.update(softLogoutViewState)
|
||||||
|
|
||||||
|
if (softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Sso) {
|
||||||
|
// Prepare the loginViewModel for a SSO recovery
|
||||||
|
loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
|
||||||
|
softLogoutViewState.homeServerUrl,
|
||||||
|
softLogoutViewState.deviceId
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +94,7 @@ class SoftLogoutFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun ssoSubmit() {
|
override fun ssoSubmit() {
|
||||||
// TODO loginSharedActionViewModel.post(LoginNavigation.Sso)
|
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
|
@ -54,8 +54,9 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
val activity: SoftLogoutActivity = (viewModelContext as ActivityViewModelContext).activity()
|
val activity: SoftLogoutActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||||
val userId = activity.session.myUserId
|
val userId = activity.session.myUserId
|
||||||
return SoftLogoutViewState(
|
return SoftLogoutViewState(
|
||||||
homeServerUrl = activity.session.sessionParams.homeServerConnectionConfig.homeServerUri.toString().toReducedUrl(),
|
homeServerUrl = activity.session.sessionParams.homeServerConnectionConfig.homeServerUri.toString(),
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
deviceId = activity.session.sessionParams.credentials.deviceId ?: "",
|
||||||
userDisplayName = activity.session.getUser(userId)?.displayName ?: userId,
|
userDisplayName = activity.session.getUser(userId)?.displayName ?: userId,
|
||||||
hasUnsavedKeys = activity.session.hasUnsavedKeys()
|
hasUnsavedKeys = activity.session.hasUnsavedKeys()
|
||||||
)
|
)
|
||||||
@ -139,14 +140,11 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Cleanup
|
|
||||||
// private val _viewEvents = PublishDataSource<LoginViewEvents>()
|
|
||||||
// val viewEvents: DataSource<LoginViewEvents> = _viewEvents
|
|
||||||
|
|
||||||
override fun handle(action: SoftLogoutAction) {
|
override fun handle(action: SoftLogoutAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is SoftLogoutAction.RetryLoginFlow -> getSupportedLoginFlow()
|
is SoftLogoutAction.RetryLoginFlow -> getSupportedLoginFlow()
|
||||||
is SoftLogoutAction.SignInAgain -> handleSignInAgain(action)
|
is SoftLogoutAction.SignInAgain -> handleSignInAgain(action)
|
||||||
|
is SoftLogoutAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||||
is SoftLogoutAction.PasswordChanged -> handlePasswordChange(action)
|
is SoftLogoutAction.PasswordChanged -> handlePasswordChange(action)
|
||||||
is SoftLogoutAction.TogglePassword -> handleTogglePassword()
|
is SoftLogoutAction.TogglePassword -> handleTogglePassword()
|
||||||
}
|
}
|
||||||
@ -171,6 +169,30 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleWebLoginSuccess(action: SoftLogoutAction.WebLoginSuccess) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
currentTask = session.updateCredentials(action.credentials,
|
||||||
|
object : MatrixCallback<Unit> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
onSessionRestored()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSignInAgain(action: SoftLogoutAction.SignInAgain) {
|
private fun handleSignInAgain(action: SoftLogoutAction.SignInAgain) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
@ -190,21 +212,25 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
activeSessionHolder.setActiveSession(session)
|
onSessionRestored()
|
||||||
// Start the sync
|
|
||||||
session.startSync(true)
|
|
||||||
|
|
||||||
// TODO Configure and start ? Check that the push still works...
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
asyncLoginAction = Success(Unit)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onSessionRestored() {
|
||||||
|
activeSessionHolder.setActiveSession(session)
|
||||||
|
// Start the sync
|
||||||
|
session.startSync(true)
|
||||||
|
|
||||||
|
// TODO Configure and start ? Check that the push still works...
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Success(Unit)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ data class SoftLogoutViewState(
|
|||||||
val asyncLoginAction: Async<Unit> = Uninitialized,
|
val asyncLoginAction: Async<Unit> = Uninitialized,
|
||||||
val homeServerUrl: String,
|
val homeServerUrl: String,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
|
val deviceId: String,
|
||||||
val userDisplayName: String,
|
val userDisplayName: String,
|
||||||
val hasUnsavedKeys: Boolean,
|
val hasUnsavedKeys: Boolean,
|
||||||
val passwordShown: Boolean = false,
|
val passwordShown: Boolean = false,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user