Merge pull request #4740 from vector-im/feature/adm/automatic-splash-carousel-transitions

Automatic carousel rotation
This commit is contained in:
Adam Brown 2022-01-11 11:14:41 +00:00 committed by GitHub
commit d485e69062
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 1 deletions

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient <gradient
android:angle="@integer/rtl_mirror_flip"
android:endColor="#3372C7DA" android:endColor="#3372C7DA"
android:startColor="#33BBE7CF" /> android:startColor="#33BBE7CF" />
</shape> </shape>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient <gradient
android:angle="@integer/rtl_mirror_flip"
android:endColor="#33B972DA" android:endColor="#33B972DA"
android:startColor="#3372C7DA" /> android:startColor="#3372C7DA" />
</shape> </shape>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient <gradient
android:angle="@integer/rtl_mirror_flip"
android:endColor="#330DBD8B" android:endColor="#330DBD8B"
android:startColor="#33B972DA" /> android:startColor="#33B972DA" />
</shape> </shape>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient <gradient
android:angle="@integer/rtl_mirror_flip"
android:endColor="#33BBE7CF" android:endColor="#33BBE7CF"
android:startColor="#330DBD8B" /> android:startColor="#330DBD8B" />
</shape> </shape>

View File

@ -38,6 +38,11 @@ class DebugFeaturesStateFactory @Inject constructor(
label = "FTUE Splash - I already have an account", label = "FTUE Splash - I already have an account",
factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled, factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled,
key = DebugFeatureKeys.alreadyHaveAnAccount key = DebugFeatureKeys.alreadyHaveAnAccount
),
createBooleanFeature(
label = "FTUE Splash - Carousel",
factory = VectorFeatures::isSplashCarouselEnabled,
key = DebugFeatureKeys.splashCarousel
) )
)) ))
} }

View File

@ -0,0 +1,26 @@
/*
* 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.core.extensions
fun Int.incrementByOneAndWrap(max: Int, min: Int = 0): Int {
val incrementedValue = this + 1
return if (incrementedValue > max) {
min
} else {
incrementedValue
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.core.extensions
import android.animation.Animator
import android.animation.TimeInterpolator
import android.animation.ValueAnimator
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.viewpager2.widget.ViewPager2
fun ViewPager2.setCurrentItem(
item: Int,
duration: Long,
interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(),
pagePxWidth: Int = width,
) {
val pxToDrag: Int = pagePxWidth * (item - currentItem)
val animator = ValueAnimator.ofInt(0, pxToDrag)
var previousValue = 0
val isRtl = this.layoutDirection == View.LAYOUT_DIRECTION_RTL
animator.addUpdateListener { valueAnimator ->
val currentValue = valueAnimator.animatedValue as Int
val currentPxToDrag = (currentValue - previousValue).toFloat()
kotlin.runCatching {
when {
isRtl -> fakeDragBy(currentPxToDrag)
else -> fakeDragBy(-currentPxToDrag)
}
previousValue = currentValue
}.onFailure { animator.cancel() }
}
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
isUserInputEnabled = false
beginFakeDrag()
}
override fun onAnimationEnd(animation: Animator?) {
isUserInputEnabled = true
endFakeDrag()
}
override fun onAnimationCancel(animation: Animator?) = Unit
override fun onAnimationRepeat(animation: Animator?) = Unit
})
animator.interpolator = interpolator
animator.duration = duration
animator.start()
}

View File

@ -22,20 +22,30 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.incrementByOneAndWrap
import im.vector.app.core.extensions.setCurrentItem
import im.vector.app.databinding.FragmentFtueSplashCarouselBinding import im.vector.app.databinding.FragmentFtueSplashCarouselBinding
import im.vector.app.features.VectorFeatures import im.vector.app.features.VectorFeatures
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingFlow
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
private const val CAROUSEL_ROTATION_DELAY_MS = 5000L
private const val CAROUSEL_TRANSITION_TIME_MS = 500L
class FtueAuthSplashCarouselFragment @Inject constructor( class FtueAuthSplashCarouselFragment @Inject constructor(
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val vectorFeatures: VectorFeatures, private val vectorFeatures: VectorFeatures,
@ -52,7 +62,8 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
} }
private fun setupViews() { private fun setupViews() {
views.splashCarousel.adapter = carouselController.adapter val carouselAdapter = carouselController.adapter
views.splashCarousel.adapter = carouselAdapter
TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach() TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach()
carouselController.setData(SplashCarouselState()) carouselController.setData(SplashCarouselState())
@ -69,6 +80,25 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
"Branch: ${BuildConfig.GIT_BRANCH_NAME}" "Branch: ${BuildConfig.GIT_BRANCH_NAME}"
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
} }
views.splashCarousel.apply {
var scheduledTransition: Job? = null
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
scheduledTransition?.cancel()
scheduledTransition = scheduleCarouselTransition()
}
})
scheduledTransition = scheduleCarouselTransition()
}
}
private fun ViewPager2.scheduleCarouselTransition(): Job {
val itemCount = adapter?.itemCount ?: throw IllegalStateException("An adapter must be set")
return lifecycleScope.launch {
delay(CAROUSEL_ROTATION_DELAY_MS)
setCurrentItem(currentItem.incrementByOneAndWrap(max = itemCount - 1), duration = CAROUSEL_TRANSITION_TIME_MS)
}
} }
private fun getStarted() { private fun getStarted() {