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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="@integer/rtl_mirror_flip"
android:endColor="#3372C7DA"
android:startColor="#33BBE7CF" />
</shape>

View File

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

View File

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

View File

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

View File

@ -38,6 +38,11 @@ class DebugFeaturesStateFactory @Inject constructor(
label = "FTUE Splash - I already have an account",
factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled,
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.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator
import im.vector.app.BuildConfig
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.features.VectorFeatures
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingFlow
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 java.net.UnknownHostException
import javax.inject.Inject
private const val CAROUSEL_ROTATION_DELAY_MS = 5000L
private const val CAROUSEL_TRANSITION_TIME_MS = 500L
class FtueAuthSplashCarouselFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val vectorFeatures: VectorFeatures,
@ -52,7 +62,8 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
}
private fun setupViews() {
views.splashCarousel.adapter = carouselController.adapter
val carouselAdapter = carouselController.adapter
views.splashCarousel.adapter = carouselAdapter
TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach()
carouselController.setData(SplashCarouselState())
@ -69,6 +80,25 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
"Branch: ${BuildConfig.GIT_BRANCH_NAME}"
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() {