Merge pull request #4740 from vector-im/feature/adm/automatic-splash-carousel-transitions
Automatic carousel rotation
This commit is contained in:
commit
d485e69062
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue