Login screens: add some animation for Fragment transition (WIP)

This commit is contained in:
Benoit Marty 2019-11-28 20:36:29 +01:00
parent 1bec8c29b8
commit 500eb113b6
10 changed files with 108 additions and 41 deletions

View file

@ -16,10 +16,12 @@
package im.vector.riotx.features.login package im.vector.riotx.features.login
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.transition.TransitionInflater
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
@ -42,6 +44,14 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
// Due to async, we keep a boolean to avoid displaying twice the cancellation dialog // Due to async, we keep a boolean to avoid displaying twice the cancellation dialog
private var displayCancelDialog = true private var displayCancelDialog = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}
}
@CallSuper @CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -19,11 +19,15 @@ package im.vector.riotx.features.login
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.auth.registration.FlowResult import im.vector.matrix.android.api.auth.registration.FlowResult
@ -56,6 +60,27 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
injector.inject(this) injector.inject(this)
} }
private val enterAnim = R.anim.enter_fade_in
private val exitAnim = R.anim.exit_fade_out
private val popEnterAnim = R.anim.no_anim
private val popExitAnim = R.anim.exit_fade_out
private val topFragment: Fragment?
get() = supportFragmentManager.findFragmentById(R.id.loginFragmentContainer)
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
// Find the loginLogo on the current Fragment, this should not return null
(topFragment?.view as? ViewGroup)
// Find findViewById does not work, I do not know why
// findViewById<View?>(R.id.loginLogo)
?.children
?.first { it.id == R.id.loginLogo }
?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}
override fun getLayoutRes() = R.layout.activity_login override fun getLayoutRes() = R.layout.activity_login
override fun initUiAndData() { override fun initUiAndData() {
@ -96,31 +121,37 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val dummy = when (loginNavigation) { val dummy = when (loginNavigation) {
is LoginNavigation.OpenServerSelection -> is LoginNavigation.OpenServerSelection ->
addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginServerSelectionFragment::class.java,
option = { ft -> option = { ft ->
val view = findViewById<View?>(R.id.loginSplashLogo) findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
if (view != null) { findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
ft.addSharedElement(view, ViewCompat.getTransitionName(view) ?: "") findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
} // TODO Disabled because it provokes a flickering
//ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}) })
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone() is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected() is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnLoginFlowRetrieved -> is LoginNavigation.OnLoginFlowRetrieved ->
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginSignUpSignInSelectionFragment::class.java) LoginSignUpSignInSelectionFragment::class.java,
option = commonOption)
is LoginNavigation.OnWebLoginError -> onWebLoginError(loginNavigation) is LoginNavigation.OnWebLoginError -> onWebLoginError(loginNavigation)
is LoginNavigation.OnForgetPasswordClicked -> is LoginNavigation.OnForgetPasswordClicked ->
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginResetPasswordFragment::class.java) LoginResetPasswordFragment::class.java,
option = commonOption)
is LoginNavigation.OnResetPasswordSendThreePidDone -> { is LoginNavigation.OnResetPasswordSendThreePidDone -> {
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginResetPasswordMailConfirmationFragment::class.java) LoginResetPasswordMailConfirmationFragment::class.java,
option = commonOption)
} }
is LoginNavigation.OnResetPasswordMailConfirmationSuccess -> { is LoginNavigation.OnResetPasswordMailConfirmationSuccess -> {
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginResetPasswordSuccessFragment::class.java) LoginResetPasswordSuccessFragment::class.java,
option = commonOption)
} }
is LoginNavigation.OnResetPasswordMailConfirmationSuccessDone -> { is LoginNavigation.OnResetPasswordMailConfirmationSuccessDone -> {
// Go back to the login fragment // Go back to the login fragment
@ -130,12 +161,14 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWaitForEmailFragment::class.java, LoginWaitForEmailFragment::class.java,
LoginWaitForEmailFragmentArgument(loginNavigation.email), LoginWaitForEmailFragmentArgument(loginNavigation.email),
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
is LoginNavigation.OnSendMsisdnSuccess -> is LoginNavigation.OnSendMsisdnSuccess ->
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginGenericTextInputFormFragment::class.java, LoginGenericTextInputFormFragment::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn), LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn),
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
} }
} }
@ -156,7 +189,9 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
// This way it will be automatically popped in when starting the next registration stage // This way it will be automatically popped in when starting the next registration stage
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginFragment::class.java, LoginFragment::class.java,
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption
)
} }
} }
} }
@ -200,7 +235,9 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
when (state.serverType) { when (state.serverType) {
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
ServerType.Modular, ServerType.Modular,
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerUrlFormFragment::class.java) ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginServerUrlFormFragment::class.java,
option = commonOption)
} }
} }
@ -214,8 +251,13 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
// It depends on the LoginMode // It depends on the LoginMode
when (state.loginMode) { when (state.loginMode) {
LoginMode.Unknown -> error("Developer error") LoginMode.Unknown -> error("Developer error")
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java, tag = FRAGMENT_LOGIN_TAG) LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java) LoginFragment::class.java,
tag = FRAGMENT_LOGIN_TAG,
option = commonOption)
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWebFragment::class.java,
option = commonOption)
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes) LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
} }
} }
@ -227,7 +269,9 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
.setTitle(R.string.app_name) .setTitle(R.string.app_name)
.setMessage(getString(R.string.login_registration_not_supported)) .setMessage(getString(R.string.login_registration_not_supported))
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java) addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWebFragment::class.java,
option = commonOption)
} }
.setNegativeButton(R.string.no, null) .setNegativeButton(R.string.no, null)
.show() .show()
@ -238,7 +282,9 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
.setTitle(R.string.app_name) .setTitle(R.string.app_name)
.setMessage(getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) .setMessage(getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" }))
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java) addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWebFragment::class.java,
option = commonOption)
} }
.setNegativeButton(R.string.no, null) .setNegativeButton(R.string.no, null)
.show() .show()
@ -269,19 +315,23 @@ class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
is Stage.ReCaptcha -> addFragmentToBackstack(R.id.loginFragmentContainer, is Stage.ReCaptcha -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginCaptchaFragment::class.java, LoginCaptchaFragment::class.java,
LoginCaptchaFragmentArgument(stage.publicKey), LoginCaptchaFragmentArgument(stage.publicKey),
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
is Stage.Email -> addFragmentToBackstack(R.id.loginFragmentContainer, is Stage.Email -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginGenericTextInputFormFragment::class.java, LoginGenericTextInputFormFragment::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
is Stage.Msisdn -> addFragmentToBackstack(R.id.loginFragmentContainer, is Stage.Msisdn -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginGenericTextInputFormFragment::class.java, LoginGenericTextInputFormFragment::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
is Stage.Terms -> addFragmentToBackstack(R.id.loginFragmentContainer, is Stage.Terms -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginTermsFragment::class.java, LoginTermsFragment::class.java,
LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))), LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))),
tag = FRAGMENT_REGISTRATION_STAGE_TAG) tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
else -> Unit // Should not happen else -> Unit // Should not happen
} }
} }

View file

@ -16,11 +16,9 @@
package im.vector.riotx.features.login package im.vector.riotx.features.login
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.transition.TransitionInflater
import butterknife.OnClick import butterknife.OnClick
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
@ -39,14 +37,6 @@ class LoginServerSelectionFragment @Inject constructor(
override fun getLayoutResId() = R.layout.fragment_login_server_selection override fun getLayoutResId() = R.layout.fragment_login_server_selection
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/default_animation_duration" android:duration="@integer/default_animation_duration"
android:shareInterpolator="false"> android:startOffset="@integer/default_animation_offset">
<alpha <alpha
android:fromAlpha="0" android:fromAlpha="0"

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/default_animation_duration" android:duration="@integer/default_animation_half">
android:shareInterpolator="false">
<alpha <alpha
android:fromAlpha="1" android:fromAlpha="1"

View file

@ -9,7 +9,6 @@
<!-- Missing attributes are in the style --> <!-- Missing attributes are in the style -->
<ImageView <ImageView
style="@style/LoginLogo" style="@style/LoginLogo"
android:transitionName="loginLogoTransition"
tools:ignore="ContentDescription,MissingConstraints,UnusedAttribute" /> tools:ignore="ContentDescription,MissingConstraints,UnusedAttribute" />
<!-- Missing attributes are in the style --> <!-- Missing attributes are in the style -->
@ -25,6 +24,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/login_server_title" android:text="@string/login_server_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title" android:textAppearance="@style/TextAppearance.Vector.Login.Title"
android:transitionName="loginTitleTransition"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -148,7 +149,7 @@
<TextView <TextView
android:id="@+id/loginServerChoiceOtherTitle" android:id="@+id/loginServerChoiceOtherTitle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="start" android:gravity="start"
android:text="@string/login_server_other_title" android:text="@string/login_server_other_title"
@ -156,7 +157,6 @@
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/loginServerChoiceOtherText" app:layout_constraintBottom_toTopOf="@+id/loginServerChoiceOtherText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed" />
@ -183,6 +183,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:text="@string/login_continue" android:text="@string/login_continue"
android:transitionName="loginSubmitTransition"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View file

@ -37,7 +37,9 @@
android:layout_marginTop="48dp" android:layout_marginTop="48dp"
android:text="@string/login_splash_title" android:text="@string/login_splash_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title" android:textAppearance="@style/TextAppearance.Vector.Login.Title"
android:transitionName="loginTitleTransition"
app:layout_constraintBottom_toTopOf="@+id/loginSplashText1" app:layout_constraintBottom_toTopOf="@+id/loginSplashText1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginSplashLogo" /> app:layout_constraintTop_toBottomOf="@+id/loginSplashLogo" />
@ -123,6 +125,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="48dp" android:layout_marginTop="48dp"
android:text="@string/login_splash_submit" android:text="@string/login_splash_submit"
android:transitionName="loginSubmitTransition"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LoginLogo.v21" parent="LoginLogoBase">
<item name="android:transitionName">loginLogoTransition</item>
</style>
</resources>

View file

@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<integer name="default_animation_duration">500</integer> <integer name="default_animation_half">200</integer>
<integer name="default_animation_duration">400</integer>
<integer name="default_animation_offset">200</integer>
<integer name="rtl_mirror_flip">0</integer> <integer name="rtl_mirror_flip">0</integer>

View file

@ -22,7 +22,9 @@
<item name="layout_constraintStart_toStartOf">parent</item> <item name="layout_constraintStart_toStartOf">parent</item>
</style> </style>
<style name="LoginLogo" parent="LoginLogoBase"> <style name="LoginLogo.v21" parent="LoginLogoBase" />
<style name="LoginLogo" parent="LoginLogo.v21">
<item name="layout_constraintEnd_toEndOf">parent</item> <item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:layout_marginTop">32dp</item> <item name="android:layout_marginTop">32dp</item>
</style> </style>