Login UX flow: set avatar and display name after account creation
This commit is contained in:
parent
408a0fc010
commit
c141b26212
@ -76,6 +76,7 @@ import im.vector.app.features.login2.LoginFragment2SigninPassword
|
|||||||
import im.vector.app.features.login2.LoginFragment2SigninUsername
|
import im.vector.app.features.login2.LoginFragment2SigninUsername
|
||||||
import im.vector.app.features.login2.LoginFragment2SignupPassword
|
import im.vector.app.features.login2.LoginFragment2SignupPassword
|
||||||
import im.vector.app.features.login2.LoginFragment2SignupUsername
|
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.LoginFragmentToAny2
|
||||||
import im.vector.app.features.login2.LoginGenericTextInputFormFragment2
|
import im.vector.app.features.login2.LoginGenericTextInputFormFragment2
|
||||||
import im.vector.app.features.login2.LoginResetPasswordFragment2
|
import im.vector.app.features.login2.LoginResetPasswordFragment2
|
||||||
@ -286,6 +287,11 @@ interface FragmentModule {
|
|||||||
@FragmentKey(LoginFragment2SigninUsername::class)
|
@FragmentKey(LoginFragment2SigninUsername::class)
|
||||||
fun bindLoginFragment2SigninUsername(fragment: LoginFragment2SigninUsername): Fragment
|
fun bindLoginFragment2SigninUsername(fragment: LoginFragment2SigninUsername): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(AccountCreatedFragment::class)
|
||||||
|
fun bindAccountCreatedFragment(fragment: AccountCreatedFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(LoginFragment2SignupUsername::class)
|
@FragmentKey(LoginFragment2SignupUsername::class)
|
||||||
|
@ -41,6 +41,7 @@ import im.vector.app.core.utils.DimensionConverter
|
|||||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import jp.wasabeef.glide.transformations.ColorFilterTransformation
|
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.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
@ -113,6 +114,23 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||||||
.into(imageView)
|
.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
|
@UiThread
|
||||||
fun render(glideRequests: GlideRequests,
|
fun render(glideRequests: GlideRequests,
|
||||||
matrixItem: MatrixItem,
|
matrixItem: MatrixItem,
|
||||||
|
@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure
|
|||||||
/**
|
/**
|
||||||
* Parent Fragment for all the login/registration screens
|
* Parent Fragment for all the login/registration screens
|
||||||
*/
|
*/
|
||||||
abstract class AbstractLoginFragment2<VB: ViewBinding> : VectorBaseFragment<VB>(), OnBackPressed {
|
abstract class AbstractLoginFragment2<VB : ViewBinding> : VectorBaseFragment<VB>(), OnBackPressed {
|
||||||
|
|
||||||
protected val loginViewModel: LoginViewModel2 by activityViewModel()
|
protected val loginViewModel: LoginViewModel2 by activityViewModel()
|
||||||
|
|
||||||
@ -147,11 +147,19 @@ abstract class AbstractLoginFragment2<VB: ViewBinding> : VectorBaseFragment<VB>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun invalidate() = withState(loginViewModel) { state ->
|
final override fun invalidate() {
|
||||||
// True when email is sent with success to the homeserver
|
withState(loginViewModel) { state ->
|
||||||
isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not()
|
// 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) {
|
open fun updateWithState(state: LoginViewState2) {
|
||||||
|
@ -41,6 +41,7 @@ sealed class LoginAction2 : VectorViewModelAction {
|
|||||||
|
|
||||||
// Username to Login or Register, depending on the signMode
|
// Username to Login or Register, depending on the signMode
|
||||||
data class SetUserName(val username: String) : LoginAction2()
|
data class SetUserName(val username: String) : LoginAction2()
|
||||||
|
|
||||||
// Password to Login or Register, depending on the signMode
|
// Password to Login or Register, depending on the signMode
|
||||||
data class SetUserPassword(val password: String) : LoginAction2()
|
data class SetUserPassword(val password: String) : LoginAction2()
|
||||||
|
|
||||||
@ -82,4 +83,7 @@ sealed class LoginAction2 : VectorViewModelAction {
|
|||||||
data class PostViewEvent(val viewEvent: LoginViewEvents2) : LoginAction2()
|
data class PostViewEvent(val viewEvent: LoginViewEvents2) : LoginAction2()
|
||||||
|
|
||||||
data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction2()
|
data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction2()
|
||||||
|
|
||||||
|
// Account customization is over
|
||||||
|
object Finish : LoginAction2()
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ import im.vector.app.features.login.LoginWaitForEmailFragmentArgument
|
|||||||
import im.vector.app.features.login.isSupported
|
import im.vector.app.features.login.isSupported
|
||||||
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
||||||
import im.vector.app.features.login.terms.toLocalizedLoginTerms
|
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.login2.terms.LoginTermsFragment2
|
||||||
import im.vector.app.features.pin.UnlockedActivity
|
import im.vector.app.features.pin.UnlockedActivity
|
||||||
|
|
||||||
@ -245,14 +246,26 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
|
|||||||
is LoginViewEvents2.OnLoginModeNotSupported ->
|
is LoginViewEvents2.OnLoginModeNotSupported ->
|
||||||
onLoginModeNotSupported(event.supportedTypes)
|
onLoginModeNotSupported(event.supportedTypes)
|
||||||
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
|
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
|
||||||
|
is LoginViewEvents2.Finish -> terminate(true)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) {
|
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(
|
val intent = HomeActivity.newIntent(
|
||||||
this,
|
this,
|
||||||
accountCreation = event.newAccount
|
accountCreation = newAccount
|
||||||
)
|
)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
@ -260,7 +273,12 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
|
|||||||
|
|
||||||
private fun updateWithState(LoginViewState2: LoginViewState2) {
|
private fun updateWithState(LoginViewState2: LoginViewState2) {
|
||||||
// Loading
|
// 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) {
|
private fun onWebLoginError(onWebLoginError: LoginViewEvents2.OnWebLoginError) {
|
||||||
|
@ -23,16 +23,14 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import androidx.autofill.HintConstants
|
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 com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
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.core.extensions.showPassword
|
||||||
import im.vector.app.databinding.FragmentLogin2SigninPasswordBinding
|
import im.vector.app.databinding.FragmentLogin2SigninPasswordBinding
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import io.reactivex.rxkotlin.subscribeBy
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
|
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -42,7 +40,9 @@ import javax.inject.Inject
|
|||||||
* - the user is asked for password to sign in to a homeserver.
|
* - the user is asked for password to sign in to a homeserver.
|
||||||
* - He also can reset his password
|
* - He also can reset his password
|
||||||
*/
|
*/
|
||||||
class LoginFragment2SigninPassword @Inject constructor() : AbstractSSOLoginFragment2<FragmentLogin2SigninPasswordBinding>() {
|
class LoginFragment2SigninPassword @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer
|
||||||
|
) : AbstractSSOLoginFragment2<FragmentLogin2SigninPasswordBinding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
private var passwordShown = false
|
||||||
|
|
||||||
@ -106,15 +106,10 @@ class LoginFragment2SigninPassword @Inject constructor() : AbstractSSOLoginFragm
|
|||||||
state.loginProfileInfo?.displayName?.takeIf { it.isNotBlank() } ?: state.userIdentifier()
|
state.loginProfileInfo?.displayName?.takeIf { it.isNotBlank() } ?: state.userIdentifier()
|
||||||
)
|
)
|
||||||
|
|
||||||
if (state.loginProfileInfo != null) {
|
avatarRenderer.render(
|
||||||
views.loginUserIcon.isVisible = true
|
profileInfo = state.loginProfileInfo ?: LoginProfileInfo(state.userIdentifier(), null, null),
|
||||||
Glide.with(requireContext())
|
imageView = views.loginUserIcon
|
||||||
.load(state.loginProfileInfo.fullAvatarUrl)
|
)
|
||||||
.apply(RequestOptions.circleCropTransform())
|
|
||||||
.into(views.loginUserIcon)
|
|
||||||
} else {
|
|
||||||
views.loginUserIcon.isVisible = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSubmitButton() {
|
private fun setupSubmitButton() {
|
||||||
|
@ -56,4 +56,6 @@ sealed class LoginViewEvents2 : VectorViewEvents {
|
|||||||
data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents2()
|
data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents2()
|
||||||
|
|
||||||
data class OnSessionCreated(val newAccount: Boolean): LoginViewEvents2()
|
data class OnSessionCreated(val newAccount: Boolean): LoginViewEvents2()
|
||||||
|
|
||||||
|
object Finish : LoginViewEvents2()
|
||||||
}
|
}
|
||||||
|
@ -145,9 +145,15 @@ class LoginViewModel2 @AssistedInject constructor(
|
|||||||
is LoginAction2.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
is LoginAction2.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
||||||
LoginAction2.ClearHomeServerHistory -> handleClearHomeServerHistory()
|
LoginAction2.ClearHomeServerHistory -> handleClearHomeServerHistory()
|
||||||
is LoginAction2.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
is LoginAction2.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||||
|
is LoginAction2.Finish -> handleFinish()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleFinish() {
|
||||||
|
// Just post a view Event
|
||||||
|
_viewEvents.post(LoginViewEvents2.Finish)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleChooseAServerForSignin() {
|
private fun handleChooseAServerForSignin() {
|
||||||
// Just post a view Event
|
// Just post a view Event
|
||||||
_viewEvents.post(LoginViewEvents2.OpenServerSelection)
|
_viewEvents.post(LoginViewEvents2.OpenServerSelection)
|
||||||
|
@ -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()
|
||||||
|
}
|
@ -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<FragmentLoginAccountCreatedBinding>(),
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
@ -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<AccountCreatedViewState, AccountCreatedAction, AccountCreatedViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<AccountCreatedViewModel, AccountCreatedViewState> {
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<MatrixItem.UserItem> = Uninitialized,
|
||||||
|
val hasBeenModified: Boolean = false
|
||||||
|
) : MvRxState
|
124
vector/src/main/res/layout/fragment_login_account_created.xml
Normal file
124
vector/src/main/res/layout/fragment_login_account_created.xml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/login_fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?riotx_background">
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
|
||||||
|
|
||||||
|
<LinearLayout style="@style/LoginFormContainer">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
style="@style/LoginLogo"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/login_account_created_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginAccountCreatedSubtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Title.Small"
|
||||||
|
tools:text="@string/login_account_created_subtitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/login_account_created_notice"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||||
|
android:text="@string/login_account_created_notice_2"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Text" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/loginAccountCreatedMessage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_vertical_margin">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/loginAccountCreatedAvatar"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginAccountCreatedMemberName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_toEndOf="@+id/loginAccountCreatedAvatar"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="\@user:domain.org" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/loginAccountCreatedMemberName"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_toEndOf="@+id/loginAccountCreatedAvatar"
|
||||||
|
android:text="@string/login_account_created_message"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||||
|
android:text="@string/login_account_created_instruction"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Text" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/loginAccountCreatedLater"
|
||||||
|
style="@style/Style.Vector.Login.Button.Text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/later" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/loginAccountCreatedDone"
|
||||||
|
style="@style/Style.Vector.Login.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/done"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:layout_marginEnd="120dp"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
@ -27,5 +27,11 @@
|
|||||||
|
|
||||||
<string name="login_wait_for_email_notice_2">We just sent an email to %1$s.</string>
|
<string name="login_wait_for_email_notice_2">We just sent an email to %1$s.</string>
|
||||||
<string name="login_wait_for_email_help">Click on the link it contains to continue the account creation.</string>
|
<string name="login_wait_for_email_help">Click on the link it contains to continue the account creation.</string>
|
||||||
|
<string name="login_account_created_title">Congratulations!</string>
|
||||||
|
<string name="login_account_created_subtitle">You account %s has been successfully created.</string>
|
||||||
|
<string name="login_account_created_notice">To complete your profile, you can set a profile image and/or a display name. This can also be done later from the settings.</string>
|
||||||
|
<string name="login_account_created_notice_2">This is how your messages will appear:</string>
|
||||||
|
<string name="login_account_created_message">Hello Matrix world!</string>
|
||||||
|
<string name="login_account_created_instruction">Click on the image and on your name to configure them.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user