diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 1bfa871a42..3c4e9b23e8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -23,6 +23,7 @@ import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.Authenticator +import im.vector.matrix.android.api.auth.registration.RegistrationService import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.di.DaggerMatrixComponent import im.vector.matrix.android.internal.network.UserAgentHolder @@ -47,6 +48,7 @@ data class MatrixConfiguration( class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var authenticator: Authenticator + @Inject internal lateinit var registrationService: RegistrationService @Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var olmManager: OlmManager @@ -68,6 +70,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return authenticator } + fun registrationService(): RegistrationService { + return registrationService + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationResult.kt new file mode 100644 index 0000000000..ddc231f186 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationResult.kt @@ -0,0 +1,31 @@ +/* + * 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.matrix.android.api.auth.registration + +import im.vector.matrix.android.api.session.Session + +// Either a session or an object containing data about registration stages +sealed class RegistrationResult { + data class Success(val session: Session) : RegistrationResult() + data class FlowResponse(val flowResult: FlowResult) : RegistrationResult() +} + + +data class FlowResult( + val missingStages: List, + val completedStages: List +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationWizard.kt index 332c1ef781..7144ce389f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/RegistrationWizard.kt @@ -17,14 +17,15 @@ package im.vector.matrix.android.api.auth.registration import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable interface RegistrationWizard { - fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback): Cancelable + fun getRegistrationFlow(callback: MatrixCallback): Cancelable - fun performReCaptcha(response: String, callback: MatrixCallback): Cancelable + fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback): Cancelable + + fun performReCaptcha(response: String, callback: MatrixCallback): Cancelable // TODO Add other method here } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/Stage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/Stage.kt index 283a79348b..9f1883e4b1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/Stage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/registration/Stage.kt @@ -16,32 +16,29 @@ package im.vector.matrix.android.api.auth.registration -import im.vector.matrix.android.api.util.JsonDict - sealed class Stage(open val mandatory: Boolean) { - // m.login.password - data class Password(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory) - // m.login.recaptcha data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory) // m.login.oauth2 // m.login.email.identity - data class Email(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory) + data class Email(override val mandatory: Boolean) : Stage(mandatory) // m.login.msisdn - data class Msisdn(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory) + data class Msisdn(override val mandatory: Boolean) : Stage(mandatory) + // m.login.token + // m.login.dummy + object Dummy : Stage(false) // Undocumented yet: m.login.terms data class Terms(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory) - // TODO SSO - // For unknown stages - data class Other(override val mandatory: Boolean, val type: String, val params: JsonDict?) : Stage(mandatory) + data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory) } +//TODO class TermPolicies diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt index 8316589ad4..6e16393723 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt @@ -36,8 +36,8 @@ internal interface AuthAPI { * Register to the homeserver * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management */ - @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") - fun register(registrationParams: RegistrationParams): Call + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") + fun register(@Body registrationParams: RegistrationParams): Call /** * Get the supported login flow diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt index 31a85afbfb..e54073ac08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthModule.kt @@ -21,8 +21,10 @@ import dagger.Binds import dagger.Module import dagger.Provides import im.vector.matrix.android.api.auth.Authenticator +import im.vector.matrix.android.api.auth.registration.RegistrationService import im.vector.matrix.android.internal.auth.db.AuthRealmModule import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore +import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationService import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.di.AuthDatabase import io.realm.RealmConfiguration @@ -60,4 +62,7 @@ internal abstract class AuthModule { @Binds abstract fun bindAuthenticator(authenticator: DefaultAuthenticator): Authenticator + + @Binds + abstract fun bindRegistrationService(service: DefaultRegistrationService): RegistrationService } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt index 81196c7414..59c962c0ba 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.auth.data +// TODO Move to [InteractiveAuthenticationFlow] object LoginFlowTypes { const val PASSWORD = "m.login.password" const val OAUTH2 = "m.login.oauth2" @@ -25,4 +26,5 @@ object LoginFlowTypes { const val MSISDN = "m.login.msisdn" const val RECAPTCHA = "m.login.recaptcha" const val DUMMY = "m.login.dummy" + const val TERMS = "m.login.terms" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationService.kt index fadbfe39cd..68915fd990 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationService.kt @@ -26,14 +26,14 @@ import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import okhttp3.OkHttpClient +import javax.inject.Inject -// TODO Add @Inject -internal class DefaultRegistrationService(@Unauthenticated - private val okHttpClient: Lazy, - private val retrofitFactory: RetrofitFactory, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val sessionParamsStore: SessionParamsStore, - private val sessionManager: SessionManager) : RegistrationService { +internal class DefaultRegistrationService @Inject constructor(@Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sessionParamsStore: SessionParamsStore, + private val sessionManager: SessionManager) : RegistrationService { override fun getOrCreateRegistrationWizard(homeServerConnectionConfig: HomeServerConnectionConfig): RegistrationWizard { // TODO Persist the wizard? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt index f37f2b97be..1d234eaf8d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/DefaultRegistrationWizard.kt @@ -20,9 +20,9 @@ import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.NoOpCancellable import im.vector.matrix.android.internal.SessionManager @@ -47,10 +47,14 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig: private val authAPI = buildAuthAPI() private val registerTask = DefaultRegisterTask(authAPI) + override fun getRegistrationFlow(callback: MatrixCallback): Cancelable { + return performRegistrationRequest(RegistrationParams(), callback) + } + override fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, - callback: MatrixCallback): Cancelable { + callback: MatrixCallback): Cancelable { return performRegistrationRequest(RegistrationParams( username = userName, password = password, @@ -58,7 +62,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig: ), callback) } - override fun performReCaptcha(response: String, callback: MatrixCallback): Cancelable { + override fun performReCaptcha(response: String, callback: MatrixCallback): Cancelable { val safeSession = currentSession ?: run { callback.onFailure(IllegalStateException("developer error, call createAccount() method first")) return NoOpCancellable @@ -72,7 +76,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig: ), callback) } - private fun performRegistrationRequest(registrationParams: RegistrationParams, callback: MatrixCallback): Cancelable { + private fun performRegistrationRequest(registrationParams: RegistrationParams, callback: MatrixCallback): Cancelable { val job = GlobalScope.launch(coroutineDispatchers.main) { val result = runCatching { registerTask.execute(RegisterTask.Params(registrationParams)) @@ -83,13 +87,15 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig: sessionParamsStore.save(sessionParams) val session = sessionManager.getOrCreateSession(sessionParams) - callback.onSuccess(session) + callback.onSuccess(RegistrationResult.Success(session)) }, { if (it is Failure.RegistrationFlowError) { currentSession = it.registrationFlowResponse.session + callback.onSuccess(RegistrationResult.FlowResponse(it.registrationFlowResponse.toFlowResult())) + } else { + callback.onFailure(it) } - callback.onFailure(it) } ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt index 218251cfe5..aa9fae3362 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt @@ -18,8 +18,12 @@ package im.vector.matrix.android.internal.auth.registration import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.auth.registration.FlowResult +import im.vector.matrix.android.api.auth.registration.Stage +import im.vector.matrix.android.api.auth.registration.TermPolicies import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow +import im.vector.matrix.android.internal.auth.data.LoginFlowTypes @JsonClass(generateAdapter = true) data class RegistrationFlowResponse( @@ -51,3 +55,39 @@ data class RegistrationFlowResponse( @Json(name = "params") var params: JsonDict? = null ) + +/** + * Convert to something easier to exploit on client side + */ +fun RegistrationFlowResponse.toFlowResult(): FlowResult { + // Get all the returned stages + val allFlowTypes = mutableSetOf() + + val missingStage = mutableListOf() + val completedStage = mutableListOf() + + this.flows?.forEach { it.stages?.mapTo(allFlowTypes) { type -> type } } + + allFlowTypes.forEach { type -> + val isMandatory = flows?.all { type in it.stages ?: emptyList() } == true + + val stage = when (type) { + LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String) + ?: "") + LoginFlowTypes.DUMMY -> Stage.Dummy + LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, TermPolicies()) + LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory) + LoginFlowTypes.MSISDN -> Stage.Msisdn(isMandatory) + else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>)) + } + + if (type in completedStages ?: emptyList()) { + completedStage.add(stage) + } else { + missingStage.add(stage) + } + } + + return FlowResult(missingStage, completedStage) +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixComponent.kt index f7314fe6b4..97285bc75d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixComponent.kt @@ -23,6 +23,7 @@ import dagger.BindsInstance import dagger.Component import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.auth.Authenticator +import im.vector.matrix.android.api.auth.registration.RegistrationService import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.auth.AuthModule import im.vector.matrix.android.internal.auth.SessionParamsStore @@ -46,6 +47,8 @@ internal interface MatrixComponent { fun authenticator(): Authenticator + fun registrationService(): RegistrationService + fun context(): Context fun resources(): Resources diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index 17622020d0..9f0f83a41f 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -32,8 +32,8 @@ import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsB import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity -import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.riotx.features.home.room.list.RoomListModule +import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.link.LinkHandlerActivity import im.vector.riotx.features.login.LoginActivity @@ -47,7 +47,7 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.reactions.widget.ReactionButton import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity -import im.vector.riotx.features.settings.* +import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.share.IncomingShareActivity import im.vector.riotx.features.ui.UiStateRepository diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index d31955ce8e..2106ebf750 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -22,6 +22,7 @@ import dagger.BindsInstance import dagger.Component import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.auth.Authenticator +import im.vector.matrix.android.api.auth.registration.RegistrationService import im.vector.matrix.android.api.session.Session import im.vector.riotx.ActiveSessionDataSource import im.vector.riotx.EmojiCompatFontProvider @@ -99,6 +100,8 @@ interface VectorComponent { fun authenticator(): Authenticator + fun registrationService(): RegistrationService + fun bugReporter(): BugReporter fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt index e3df0eb635..3206c441e2 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt @@ -25,6 +25,7 @@ import dagger.Module import dagger.Provides import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.auth.Authenticator +import im.vector.matrix.android.api.auth.registration.RegistrationService import im.vector.matrix.android.api.session.Session import im.vector.riotx.features.navigation.DefaultNavigator import im.vector.riotx.features.navigation.Navigator @@ -67,6 +68,12 @@ abstract class VectorModule { fun providesAuthenticator(matrix: Matrix): Authenticator { return matrix.authenticator() } + + @Provides + @JvmStatic + fun providesRegistrationService(matrix: Matrix): RegistrationService { + return matrix.registrationService() + } } @Binds diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt index a03af1376d..be76013ca3 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginAction.kt @@ -28,6 +28,15 @@ sealed class LoginAction : VectorViewModelAction { data class InitWith(val loginConfig: LoginConfig) : LoginAction() data class ResetPassword(val email: String, val newPassword: String) : LoginAction() + // Register actions + open class RegisterAction : LoginAction() + + data class RegisterWith(val username: String, val password: String) : RegisterAction() + data class AddEmail(val email: String) : RegisterAction() + data class AddMsisdn(val msisdn: String) : RegisterAction() + data class ConfirmMsisdn(val code: String) : RegisterAction() + data class PerformCaptcha(val captcha: String /* TODO Add other params */) : RegisterAction() + // Reset actions open class ResetAction : LoginAction() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index 7bc713a4f2..f934ebf27f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -126,7 +126,7 @@ class LoginActivity : VectorBaseActivity() { private fun onSignModeSelected() { when (loginViewModel.signMode) { SignMode.Unknown -> error("Sign mode has to be set before calling this method") - SignMode.SignUp -> Unit // TODO addFragmentToBackstack(R.id.loginFragmentContainer, SignUpFragment::class.java) + SignMode.SignUp -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java) SignMode.SignIn -> { // It depends on the LoginMode withState(loginViewModel) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt index 4161673a52..f21ce74d1a 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt @@ -35,8 +35,11 @@ import kotlinx.android.synthetic.main.fragment_login.* import javax.inject.Inject /** - * In this screen, the user is asked for login and password to sign in to a homeserver. - * He also can reset his password + * In this screen, in signin mode: + * - the user is asked for login and password to sign in to a homeserver. + * - He also can reset his password + * In signup mode: + * - the user is asked for login and password */ class LoginFragment @Inject constructor( private val errorFormatter: ErrorFormatter @@ -53,38 +56,61 @@ class LoginFragment @Inject constructor( setupUi() setupSubmitButton() setupPasswordReveal() + setupButtons() } - private fun authenticate() { + @OnClick(R.id.loginSubmit) + fun submit() { val login = loginField.text?.trim().toString() val password = passwordField.text?.trim().toString() - loginViewModel.handle(LoginAction.Login(login, password)) + when (loginViewModel.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> loginViewModel.handle(LoginAction.RegisterWith(login, password)) + SignMode.SignIn -> loginViewModel.handle(LoginAction.Login(login, password)) + } } private fun setupUi() { + val resId = when (loginViewModel.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_to + SignMode.SignIn -> R.string.login_connect_to + } + when (loginViewModel.serverType) { ServerType.MatrixOrg -> { loginServerIcon.isVisible = true loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) - loginTitle.text = getString(R.string.login_connect_to, loginViewModel.getHomeServerUrlSimple()) + loginTitle.text = getString(resId, loginViewModel.getHomeServerUrlSimple()) loginNotice.text = getString(R.string.login_server_matrix_org_text) } ServerType.Modular -> { loginServerIcon.isVisible = true loginServerIcon.setImageResource(R.drawable.ic_logo_modular) // TODO - loginTitle.text = getString(R.string.login_connect_to, "TODO") + loginTitle.text = getString(resId, "TODO") loginNotice.text = loginViewModel.getHomeServerUrlSimple() } ServerType.Other -> { loginServerIcon.isVisible = false loginTitle.text = getString(R.string.login_server_other_title) - loginNotice.text = getString(R.string.login_connect_to, loginViewModel.getHomeServerUrlSimple()) + loginNotice.text = getString(resId, loginViewModel.getHomeServerUrlSimple()) } } } + private fun setupButtons() { + forgetPasswordButton.isVisible = loginViewModel.signMode == SignMode.SignIn + + loginSubmit.text = getString(when (loginViewModel.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_submit + SignMode.SignIn -> R.string.login_signin + }) + } + + private fun setupSubmitButton() { Observable .combineLatest( @@ -100,8 +126,6 @@ class LoginFragment @Inject constructor( loginSubmit.isEnabled = it } .disposeOnDestroyView() - - loginSubmit.setOnClickListener { authenticate() } } @OnClick(R.id.forgetPasswordButton) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt index c1023f3356..08872606bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt @@ -20,6 +20,12 @@ import android.os.Bundle import android.view.View import androidx.core.view.isVisible import butterknife.OnClick +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.auth.registration.FlowResult +import im.vector.matrix.android.api.auth.registration.RegistrationResult +import im.vector.matrix.android.api.auth.registration.Stage import im.vector.riotx.R import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.* import javax.inject.Inject @@ -63,7 +69,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr @OnClick(R.id.loginSignupSigninSignUp) fun signUp() { loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp)) - loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) } @OnClick(R.id.loginSignupSigninSignIn) @@ -75,4 +80,35 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr override fun resetViewModel() { loginViewModel.handle(LoginAction.ResetSignMode) } + + override fun invalidate() = withState(loginViewModel) { + when (it.asyncRegistration) { + is Success -> { + when (val res = it.asyncRegistration()) { + is RegistrationResult.Success -> + // Should not happen + Unit + is RegistrationResult.FlowResponse -> handleFlowResult(res.flowResult) + } + } + is Fail -> { + // TODO Registration disabled, etc + when (it.asyncRegistration.error) { + + } + } + } + } + + private fun handleFlowResult(flowResult: FlowResult) { + // Check that all flows are supported by the application + if (flowResult.missingStages.any { it is Stage.Other }) { + // Display a popup to propose use web fallback + // TODO + } else { + // Go on with registration flow + loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) + } + } + } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index abaa00283f..bfb6be6733 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -23,6 +23,10 @@ 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.auth.registration.FlowResult +import im.vector.matrix.android.api.auth.registration.RegistrationResult +import im.vector.matrix.android.api.auth.registration.RegistrationService +import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow @@ -36,6 +40,7 @@ import timber.log.Timber class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState, private val authenticator: Authenticator, + private val registrationService: RegistrationService, private val activeSessionHolder: ActiveSessionHolder, private val pushRuleTriggerListener: PushRuleTriggerListener, private val sessionListener: SessionListener) @@ -55,6 +60,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi } } + private var registrationWizard: RegistrationWizard? = null + var serverType: ServerType = ServerType.MatrixOrg private set var signMode: SignMode = SignMode.Unknown @@ -89,7 +96,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi LoginAction.ResetLogin -> { setState { copy( - asyncLoginAction = Uninitialized + asyncLoginAction = Uninitialized, + asyncRegistration = Uninitialized ) } } @@ -124,6 +132,10 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private fun handleUpdateSignMode(action: LoginAction.UpdateSignMode) { signMode = action.signMode + + if (signMode == SignMode.SignUp) { + startRegistrationFlow() + } } private fun handleUpdateServerType(action: LoginAction.UpdateServerType) { @@ -206,6 +218,53 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi } } + private fun startRegistrationFlow() { + val homeServerConnectionConfigFinal = homeServerConnectionConfig + + if (homeServerConnectionConfigFinal == null) { + setState { + copy( + asyncRegistration = Fail(Throwable("Bad configuration")) + ) + } + } else { + setState { + copy( + asyncRegistration = Loading() + ) + } + + registrationWizard = registrationService.getOrCreateRegistrationWizard(homeServerConnectionConfigFinal) + + currentTask = registrationWizard?.getRegistrationFlow(object : MatrixCallback { + override fun onSuccess(data: RegistrationResult) { + when (data) { + is RegistrationResult.Success -> onSessionCreated(data.session) + is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) + } + } + + override fun onFailure(failure: Throwable) { + // TODO Handled JobCancellationException + setState { + copy( + asyncRegistration = Fail(failure) + ) + } + } + }) + } + } + + private fun onFlowResponse(flowResult: FlowResult) { + setState { + copy( + asyncRegistration = Success(RegistrationResult.FlowResponse(flowResult)) + ) + } + } + + private fun onSessionCreated(session: Session) { activeSessionHolder.setActiveSession(session) session.configureAndStart(pushRuleTriggerListener, sessionListener) @@ -246,7 +305,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi // Do not retry if we already have flows for this config -> causes infinite focus loop if (newConfig?.homeServerUri?.toString() == homeServerConnectionConfig?.homeServerUri?.toString() - && state.asyncHomeServerLoginFlowRequest is Success) return@withState + && state.asyncHomeServerLoginFlowRequest is Success) return@withState currentTask?.cancel() currentTask = null diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt index 1f01e2348d..f42d46e22c 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt @@ -17,11 +17,13 @@ package im.vector.riotx.features.login import com.airbnb.mvrx.* +import im.vector.matrix.android.api.auth.registration.RegistrationResult data class LoginViewState( val asyncLoginAction: Async = Uninitialized, val asyncHomeServerLoginFlowRequest: Async = Uninitialized, - val asyncResetPassword: Async = Uninitialized + val asyncResetPassword: Async = Uninitialized, + val asyncRegistration: Async = Uninitialized ) : MvRxState { fun isLoading(): Boolean { @@ -29,10 +31,10 @@ data class LoginViewState( return asyncLoginAction is Loading || asyncHomeServerLoginFlowRequest is Loading || asyncResetPassword is Loading + || asyncRegistration is Loading } fun isUserLogged(): Boolean { - // TODO Add other async here return asyncLoginAction is Success } } diff --git a/vector/src/main/res/layout/fragment_login.xml b/vector/src/main/res/layout/fragment_login.xml index 990ccb3db3..1f9859f74b 100644 --- a/vector/src/main/res/layout/fragment_login.xml +++ b/vector/src/main/res/layout/fragment_login.xml @@ -58,7 +58,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="32dp" - android:hint="@string/auth_user_name_placeholder" + android:hint="@string/login_signup_username_hint" app:errorEnabled="true"> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index db6d9e673f..42667ce5fa 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -88,4 +88,10 @@ Send again Next + + Sign up to %1$s + Username + Password + Next +