Login: move login code to the ViewModel

This commit is contained in:
Benoit Marty 2019-09-13 10:39:22 +02:00
parent 05b2092ffc
commit a47a3ead1f
5 changed files with 154 additions and 67 deletions

View file

@ -17,11 +17,8 @@
package im.vector.riotx.core.platform
import com.airbnb.mvrx.*
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.riotx.BuildConfig
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.disposables.Disposable
abstract class VectorViewModel<S : MvRxState>(initialState: S)
: BaseMvRxViewModel<S>(initialState, false) {

View file

@ -0,0 +1,24 @@
/*
* Copyright 2019 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.riotx.features.login
sealed class LoginActions {
data class UpdateHomeServer(val homeServerUrl: String) : LoginActions()
data class Login(val login: String, val password: String) : LoginActions()
}

View file

@ -20,26 +20,18 @@ import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.view.isVisible
import arrow.core.Try
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.airbnb.mvrx.*
import com.jakewharton.rxbinding3.view.focusChanges
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.extensions.setTextWithColoredPart
import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.openUrlInExternalBrowser
import im.vector.riotx.features.home.HomeActivity
import im.vector.riotx.features.homeserver.ServerUrlsRepository
import im.vector.riotx.features.notifications.PushRuleTriggerListener
import io.reactivex.Observable
import io.reactivex.functions.Function3
import io.reactivex.rxkotlin.subscribeBy
@ -55,9 +47,6 @@ class LoginFragment : VectorBaseFragment() {
private val viewModel: LoginViewModel by activityViewModel()
@Inject lateinit var authenticator: Authenticator
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener
private var passwordShown = false
@Inject lateinit var errorFormatter: ErrorFormatter
@ -75,13 +64,19 @@ class LoginFragment : VectorBaseFragment() {
setupNotice()
setupAuthButton()
setupPasswordReveal()
homeServerField.focusChanges()
.subscribe {
if (!it) {
// TODO Also when clicking on button?
viewModel.handle(LoginActions.UpdateHomeServer(homeServerField.text.toString()))
}
}
.disposeOnDestroy()
homeServerField.setText(ServerUrlsRepository.getDefaultHomeServerUrl(requireContext()))
// viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable ->
// Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
// .show()
// }
viewModel.handle(LoginActions.UpdateHomeServer(homeServerField.text.toString()))
}
private fun setupNotice() {
@ -93,42 +88,10 @@ class LoginFragment : VectorBaseFragment() {
}
private fun authenticate() {
passwordShown = false
renderPasswordField()
val login = loginField.text?.trim().toString()
val password = passwordField.text?.trim().toString()
buildHomeServerConnectionConfig().fold(
{ Toast.makeText(requireActivity(), "Authenticate failure: $it", Toast.LENGTH_LONG).show() },
{ authenticateWith(it, login, password) }
)
}
private fun authenticateWith(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String) {
progressBar.isVisible = true
touchArea.isVisible = true
authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
activeSessionHolder.setActiveSession(data)
data.configureAndStart(pushRuleTriggerListener)
goToHome()
}
override fun onFailure(failure: Throwable) {
progressBar.isVisible = false
touchArea.isVisible = false
Toast.makeText(requireActivity(), "Authenticate failure: $failure", Toast.LENGTH_LONG).show()
}
})
}
private fun buildHomeServerConnectionConfig(): Try<HomeServerConnectionConfig> {
return Try {
val homeServerUri = homeServerField.text?.trim().toString()
HomeServerConnectionConfig.Builder()
.withHomeServerUri(homeServerUri)
.build()
}
viewModel.handle(LoginActions.Login(login, password))
}
private fun setupAuthButton() {
@ -164,14 +127,25 @@ class LoginFragment : VectorBaseFragment() {
passwordReveal.setImageResource(if (passwordShown) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
}
private fun goToHome() {
val intent = HomeActivity.newIntent(requireActivity())
startActivity(intent)
requireActivity().finish()
}
override fun invalidate() = withState(viewModel) { state ->
when (state.asyncLoginAction) {
is Loading -> {
progressBar.isVisible = true
touchArea.isVisible = true
passwordShown = false
renderPasswordField()
}
is Fail -> {
progressBar.isVisible = false
touchArea.isVisible = false
Toast.makeText(requireActivity(), "Authenticate failure: ${state.asyncLoginAction.error}", Toast.LENGTH_LONG).show()
}
is Success -> {
val intent = HomeActivity.newIntent(requireActivity())
startActivity(intent)
requireActivity().finish()
}
}
}
}

View file

@ -16,14 +16,24 @@
package im.vector.riotx.features.login
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import arrow.core.Try
import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.notifications.PushRuleTriggerListener
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState) : VectorViewModel<LoginViewState>(initialState) {
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
val authenticator: Authenticator,
val activeSessionHolder: ActiveSessionHolder,
val pushRuleTriggerListener: PushRuleTriggerListener) : VectorViewModel<LoginViewState>(initialState) {
@AssistedInject.Factory
interface Factory {
@ -43,5 +53,87 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
var homeServerConnectionConfig: HomeServerConnectionConfig? = null
private var currentTask: Cancelable? = null
fun handle(action: LoginActions) {
when (action) {
is LoginActions.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginActions.Login -> handleLogin(action)
}
}
private fun handleLogin(action: LoginActions.Login) {
val homeServerConnectionConfigFinal = homeServerConnectionConfig
if (homeServerConnectionConfigFinal == null) {
setState {
copy(
asyncLoginAction = Fail(Throwable("Bad configuration"))
)
}
} else {
setState {
copy(
asyncLoginAction = Loading()
)
}
authenticator.authenticate(homeServerConnectionConfigFinal, action.login, action.password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
activeSessionHolder.setActiveSession(data)
data.configureAndStart(pushRuleTriggerListener)
setState {
copy(
asyncLoginAction = Success(Unit)
)
}
}
override fun onFailure(failure: Throwable) {
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
}
})
}
}
private fun handleUpdateHomeserver(action: LoginActions.UpdateHomeServer) {
Try {
val homeServerUri = action.homeServerUrl
homeServerConnectionConfig = HomeServerConnectionConfig.Builder()
.withHomeServerUri(homeServerUri)
.build()
}
// TODO Do request
/*
currentTask?.cancel()
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading()
)
}
// TODO currentTask =
*/
}
override fun onCleared() {
super.onCleared()
currentTask?.cancel()
}
}

View file

@ -21,7 +21,7 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
data class LoginViewState(
// Current pagination request
val asyncLoginAction: Async<Unit> = Uninitialized,
val asyncHomeServerLoginFlowRequest: Async<LoginFlowResult> = Uninitialized
) : MvRxState