From c141b26212317c1c10146a074979285ccfab1a3c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 14 Apr 2021 17:38:51 +0200 Subject: [PATCH] Login UX flow: set avatar and display name after account creation --- .../im/vector/app/core/di/FragmentModule.kt | 6 + .../app/features/home/AvatarRenderer.kt | 18 ++ .../features/login2/AbstractLoginFragment2.kt | 18 +- .../app/features/login2/LoginAction2.kt | 4 + .../app/features/login2/LoginActivity2.kt | 24 ++- .../login2/LoginFragment2SigninPassword.kt | 23 +-- .../app/features/login2/LoginViewEvents2.kt | 2 + .../app/features/login2/LoginViewModel2.kt | 6 + .../login2/created/AccountCreatedAction.kt | 25 +++ .../login2/created/AccountCreatedFragment.kt | 160 ++++++++++++++++++ .../created/AccountCreatedViewEvents.kt | 27 +++ .../login2/created/AccountCreatedViewModel.kt | 105 ++++++++++++ .../login2/created/AccountCreatedViewState.kt | 29 ++++ .../layout/fragment_login_account_created.xml | 124 ++++++++++++++ .../src/main/res/values/strings_login_v2.xml | 6 + 15 files changed, 555 insertions(+), 22 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt create mode 100644 vector/src/main/res/layout/fragment_login_account_created.xml diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 943667c5d6..aef61f3bc0 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -76,6 +76,7 @@ import im.vector.app.features.login2.LoginFragment2SigninPassword import im.vector.app.features.login2.LoginFragment2SigninUsername import im.vector.app.features.login2.LoginFragment2SignupPassword import im.vector.app.features.login2.LoginFragment2SignupUsername +import im.vector.app.features.login2.created.AccountCreatedFragment import im.vector.app.features.login2.LoginFragmentToAny2 import im.vector.app.features.login2.LoginGenericTextInputFormFragment2 import im.vector.app.features.login2.LoginResetPasswordFragment2 @@ -286,6 +287,11 @@ interface FragmentModule { @FragmentKey(LoginFragment2SigninUsername::class) fun bindLoginFragment2SigninUsername(fragment: LoginFragment2SigninUsername): Fragment + @Binds + @IntoMap + @FragmentKey(AccountCreatedFragment::class) + fun bindAccountCreatedFragment(fragment: AccountCreatedFragment): Fragment + @Binds @IntoMap @FragmentKey(LoginFragment2SignupUsername::class) diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 23ca5eee9c..65bc5e1200 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -41,6 +41,7 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.ColorFilterTransformation +import org.matrix.android.sdk.api.auth.login.LoginProfileInfo import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.util.MatrixItem @@ -113,6 +114,23 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active .into(imageView) } + @UiThread + fun render(profileInfo: LoginProfileInfo, imageView: ImageView) { + // Create a Fake MatrixItem, for the placeholder + val matrixItem = MatrixItem.UserItem( + // Need an id starting with @ + id = profileInfo.matrixId, + displayName = profileInfo.displayName + ) + + val placeholder = getPlaceholderDrawable(matrixItem) + GlideApp.with(imageView) + .load(profileInfo.fullAvatarUrl) + .apply(RequestOptions.circleCropTransform()) + .placeholder(placeholder) + .into(imageView) + } + @UiThread fun render(glideRequests: GlideRequests, matrixItem: MatrixItem, diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt index 5175f32f05..314867ef8b 100644 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure /** * Parent Fragment for all the login/registration screens */ -abstract class AbstractLoginFragment2 : VectorBaseFragment(), OnBackPressed { +abstract class AbstractLoginFragment2 : VectorBaseFragment(), OnBackPressed { protected val loginViewModel: LoginViewModel2 by activityViewModel() @@ -147,11 +147,19 @@ abstract class AbstractLoginFragment2 : VectorBaseFragment( } } - final override fun invalidate() = withState(loginViewModel) { state -> - // True when email is sent with success to the homeserver - isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not() + final override fun invalidate() { + withState(loginViewModel) { state -> + // True when email is sent with success to the homeserver + isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not() - updateWithState(state) + updateWithState(state) + } + + invalidateMore() + } + + protected open fun invalidateMore() { + // No op by default } open fun updateWithState(state: LoginViewState2) { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt index 8f3e88abbb..63c163fd6e 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt @@ -41,6 +41,7 @@ sealed class LoginAction2 : VectorViewModelAction { // Username to Login or Register, depending on the signMode data class SetUserName(val username: String) : LoginAction2() + // Password to Login or Register, depending on the signMode data class SetUserPassword(val password: String) : LoginAction2() @@ -82,4 +83,7 @@ sealed class LoginAction2 : VectorViewModelAction { data class PostViewEvent(val viewEvent: LoginViewEvents2) : LoginAction2() data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction2() + + // Account customization is over + object Finish : LoginAction2() } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt index 149abb69da..dd96c14fec 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt @@ -46,6 +46,7 @@ import im.vector.app.features.login.LoginWaitForEmailFragmentArgument import im.vector.app.features.login.isSupported import im.vector.app.features.login.terms.LoginTermsFragmentArgument import im.vector.app.features.login.terms.toLocalizedLoginTerms +import im.vector.app.features.login2.created.AccountCreatedFragment import im.vector.app.features.login2.terms.LoginTermsFragment2 import im.vector.app.features.pin.UnlockedActivity @@ -245,14 +246,26 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC is LoginViewEvents2.OnLoginModeNotSupported -> onLoginModeNotSupported(event.supportedTypes) is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) + is LoginViewEvents2.Finish -> terminate(true) }.exhaustive } private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) { - // TODO Propose to set avatar and display name + if (event.newAccount) { + // Propose to set avatar and display name + // Back on this Fragment will finish the Activity + addFragmentToBackstack(R.id.loginFragmentContainer, + AccountCreatedFragment::class.java, + option = commonOption) + } else { + terminate(false) + } + } + + private fun terminate(newAccount: Boolean) { val intent = HomeActivity.newIntent( this, - accountCreation = event.newAccount + accountCreation = newAccount ) startActivity(intent) finish() @@ -260,7 +273,12 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC private fun updateWithState(LoginViewState2: LoginViewState2) { // Loading - views.loginLoading.isVisible = LoginViewState2.isLoading + setIsLoading(LoginViewState2.isLoading) + } + + // Hack for AccountCreatedFragment + fun setIsLoading(isLoading: Boolean) { + views.loginLoading.isVisible = isLoading } private fun onWebLoginError(onWebLoginError: LoginViewEvents2.OnWebLoginError) { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragment2SigninPassword.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragment2SigninPassword.kt index 9a644613e1..9517685a9e 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragment2SigninPassword.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragment2SigninPassword.kt @@ -23,16 +23,14 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import com.bumptech.glide.Glide -import com.bumptech.glide.request.RequestOptions import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.showPassword import im.vector.app.databinding.FragmentLogin2SigninPasswordBinding +import im.vector.app.features.home.AvatarRenderer import io.reactivex.rxkotlin.subscribeBy +import org.matrix.android.sdk.api.auth.login.LoginProfileInfo import org.matrix.android.sdk.api.failure.isInvalidPassword import javax.inject.Inject @@ -42,7 +40,9 @@ import javax.inject.Inject * - the user is asked for password to sign in to a homeserver. * - He also can reset his password */ -class LoginFragment2SigninPassword @Inject constructor() : AbstractSSOLoginFragment2() { +class LoginFragment2SigninPassword @Inject constructor( + private val avatarRenderer: AvatarRenderer +) : AbstractSSOLoginFragment2() { private var passwordShown = false @@ -106,15 +106,10 @@ class LoginFragment2SigninPassword @Inject constructor() : AbstractSSOLoginFragm state.loginProfileInfo?.displayName?.takeIf { it.isNotBlank() } ?: state.userIdentifier() ) - if (state.loginProfileInfo != null) { - views.loginUserIcon.isVisible = true - Glide.with(requireContext()) - .load(state.loginProfileInfo.fullAvatarUrl) - .apply(RequestOptions.circleCropTransform()) - .into(views.loginUserIcon) - } else { - views.loginUserIcon.isVisible = false - } + avatarRenderer.render( + profileInfo = state.loginProfileInfo ?: LoginProfileInfo(state.userIdentifier(), null, null), + imageView = views.loginUserIcon + ) } private fun setupSubmitButton() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt index 45e093e1c7..91cb6cac58 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt @@ -56,4 +56,6 @@ sealed class LoginViewEvents2 : VectorViewEvents { data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents2() data class OnSessionCreated(val newAccount: Boolean): LoginViewEvents2() + + object Finish : LoginViewEvents2() } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index daa9631041..b09fc4ac8a 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -145,9 +145,15 @@ class LoginViewModel2 @AssistedInject constructor( is LoginAction2.UserAcceptCertificate -> handleUserAcceptCertificate(action) LoginAction2.ClearHomeServerHistory -> handleClearHomeServerHistory() is LoginAction2.PostViewEvent -> _viewEvents.post(action.viewEvent) + is LoginAction2.Finish -> handleFinish() }.exhaustive } + private fun handleFinish() { + // Just post a view Event + _viewEvents.post(LoginViewEvents2.Finish) + } + private fun handleChooseAServerForSignin() { // Just post a view Event _viewEvents.post(LoginViewEvents2.OpenServerSelection) diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt new file mode 100644 index 0000000000..f108bfa886 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 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.login2.created + +import android.net.Uri +import im.vector.app.core.platform.VectorViewModelAction + +sealed class AccountCreatedAction : VectorViewModelAction { + data class SetDisplayName(val displayName: String) : AccountCreatedAction() + data class SetAvatar(val avatarUri: Uri, val filename: String) : AccountCreatedAction() +} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt new file mode 100644 index 0000000000..4b7f43f9a1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2021 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.login2.created + +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.intent.getFilenameFromUri +import im.vector.app.core.resources.ColorProvider +import im.vector.app.databinding.DialogBaseEditTextBinding +import im.vector.app.databinding.FragmentLoginAccountCreatedBinding +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import im.vector.app.features.login2.AbstractLoginFragment2 +import im.vector.app.features.login2.LoginAction2 +import im.vector.app.features.login2.LoginActivity2 +import im.vector.app.features.login2.LoginViewState2 +import org.matrix.android.sdk.api.util.MatrixItem +import java.util.UUID +import javax.inject.Inject + +/** + * In this screen: + * - the account has been created and we propose the user to set an avatar and a display name + */ +class AccountCreatedFragment @Inject constructor( + val accountCreatedViewModelFactory: AccountCreatedViewModel.Factory, + private val avatarRenderer: AvatarRenderer, + private val matrixItemColorProvider: MatrixItemColorProvider, + colorProvider: ColorProvider +) : AbstractLoginFragment2(), + GalleryOrCameraDialogHelper.Listener { + + private val viewModel: AccountCreatedViewModel by fragmentViewModel() + + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginAccountCreatedBinding { + return FragmentLoginAccountCreatedBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupClickListener() + setupSubmitButton() + observeViewEvents() + } + + private fun observeViewEvents() { + viewModel.observeViewEvents { + when (it) { + is AccountCreatedViewEvents.Failure -> displayErrorDialog(it.throwable) + } + } + } + + private fun setupClickListener() { + views.loginAccountCreatedMessage.setOnClickListener { + // Update display name + displayDialog() + } + views.loginAccountCreatedAvatar.setOnClickListener { + galleryOrCameraDialogHelper.show() + } + } + + private fun displayDialog() = withState(viewModel) { state -> + val inflater = requireActivity().layoutInflater + val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) + val views = DialogBaseEditTextBinding.bind(layout) + views.editText.setText(state.currentUser()?.getBestName().orEmpty()) + + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.settings_display_name) + .setView(layout) + .setPositiveButton(R.string.ok) { _, _ -> + val newName = views.editText.text.toString() + viewModel.handle(AccountCreatedAction.SetDisplayName(newName)) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + override fun onImageReady(uri: Uri?) { + uri ?: return + viewModel.handle(AccountCreatedAction.SetAvatar( + avatarUri = uri, + filename = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()) + ) + } + + private fun setupSubmitButton() { + views.loginAccountCreatedLater.setOnClickListener { terminate() } + views.loginAccountCreatedDone.setOnClickListener { terminate() } + } + + private fun terminate() { + loginViewModel.handle(LoginAction2.Finish) + } + + override fun invalidateMore() = withState(viewModel) { state -> + // Ugly hack... + (activity as? LoginActivity2)?.setIsLoading(state.isLoading) + + views.loginAccountCreatedSubtitle.text = getString(R.string.login_account_created_subtitle, state.userId) + + val user = state.currentUser() + if (user != null) { + avatarRenderer.render(user, views.loginAccountCreatedAvatar) + views.loginAccountCreatedMemberName.text = user.getBestName() + } else { + // Should not happen + views.loginAccountCreatedMemberName.text = state.userId + } + + // User color + views.loginAccountCreatedMemberName + .setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(state.userId))) + + views.loginAccountCreatedLater.isVisible = state.hasBeenModified.not() + views.loginAccountCreatedDone.isVisible = state.hasBeenModified + } + + override fun updateWithState(state: LoginViewState2) { + // No op + } + + override fun resetViewModel() { + // No op + } + + override fun onBackPressed(toolbarButton: Boolean): Boolean { + // Just start the next Activity + terminate() + return false + } +} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt new file mode 100644 index 0000000000..4677e1abd5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2021 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.login2.created + +import im.vector.app.core.platform.VectorViewEvents + +/** + * Transient events for Account Created + */ +sealed class AccountCreatedViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : AccountCreatedViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt new file mode 100644 index 0000000000..d1684a9867 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021 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.login2.created + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap + +class AccountCreatedViewModel @AssistedInject constructor( + @Assisted initialState: AccountCreatedViewState, + private val session: Session +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory { + fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: AccountCreatedViewState): AccountCreatedViewModel? { + val fragment: AccountCreatedFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.accountCreatedViewModelFactory.create(state) + } + } + + init { + setState { + copy( + userId = session.myUserId + ) + } + observeUser() + } + + private fun observeUser() { + session.rx() + .liveUser(session.myUserId) + .unwrap() + .map { it.toMatrixItem() } + .execute { + copy(currentUser = it) + } + } + + override fun handle(action: AccountCreatedAction) { + when (action) { + is AccountCreatedAction.SetAvatar -> handleSetAvatar(action) + is AccountCreatedAction.SetDisplayName -> handleSetDisplayName(action) + } + } + + private fun handleSetAvatar(action: AccountCreatedAction.SetAvatar) { + setState { copy(isLoading = true) } + viewModelScope.launch { + val result = runCatching { session.updateAvatar(session.myUserId, action.avatarUri, action.filename) } + .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } + setState { + copy( + isLoading = false, + hasBeenModified = hasBeenModified || result.isSuccess + ) + } + } + } + + private fun handleSetDisplayName(action: AccountCreatedAction.SetDisplayName) { + setState { copy(isLoading = true) } + viewModelScope.launch { + val result = runCatching { session.setDisplayName(session.myUserId, action.displayName) } + .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } + setState { + copy( + isLoading = false, + hasBeenModified = hasBeenModified || result.isSuccess + ) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt new file mode 100644 index 0000000000..80211b3da2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 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.login2.created + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.api.util.MatrixItem + +data class AccountCreatedViewState( + val userId: String = "", + val isLoading: Boolean = false, + val currentUser: Async = Uninitialized, + val hasBeenModified: Boolean = false +) : MvRxState diff --git a/vector/src/main/res/layout/fragment_login_account_created.xml b/vector/src/main/res/layout/fragment_login_account_created.xml new file mode 100644 index 0000000000..8885dd3a5c --- /dev/null +++ b/vector/src/main/res/layout/fragment_login_account_created.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values/strings_login_v2.xml b/vector/src/main/res/values/strings_login_v2.xml index 61c71f2b54..a32912cc00 100644 --- a/vector/src/main/res/values/strings_login_v2.xml +++ b/vector/src/main/res/values/strings_login_v2.xml @@ -27,5 +27,11 @@ We just sent an email to %1$s. Click on the link it contains to continue the account creation. + Congratulations! + You account %s has been successfully created. + To complete your profile, you can set a profile image and/or a display name. This can also be done later from the settings. + This is how your messages will appear: + Hello Matrix world! + Click on the image and on your name to configure them. \ No newline at end of file