Introduce small clock carousel
The carousel will show small clocks when clock size set to Small
Otherwise, we show large clocks
Test: Manually tested that small clocks show on the carousel. See bug
Bug: 274927017
Change-Id: I4b859151ab370a8a4b81c859f3b020e1e8e33fbb
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
index 5323cf4..98c84f2 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -27,6 +27,7 @@
import com.android.customization.picker.clock.ui.view.ClockViewFactory
import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
import com.android.wallpaper.R
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
object ClockCarouselViewBinder {
@@ -40,6 +41,7 @@
lifecycleOwner: LifecycleOwner,
hideSmartspace: (Boolean) -> Unit,
) {
+ carouselView.setClockViewFactory(clockViewFactory)
val singleClockHostView =
singleClockView.requireViewById<FrameLayout>(R.id.single_clock_host_view)
lifecycleOwner.lifecycleScope.launch {
@@ -47,12 +49,11 @@
launch { viewModel.isCarouselVisible.collect { carouselView.isVisible = it } }
launch {
- viewModel.allClockIds.collect { allClockIds ->
+ combine(viewModel.selectedClockSize, viewModel.allClockIds, ::Pair).collect {
+ (size, allClockIds) ->
carouselView.setUpClockCarouselView(
+ clockSize = size,
clockIds = allClockIds,
- onGetClockController = { clockId ->
- clockViewFactory.getController(clockId)
- },
onClockSelected = { clockId ->
viewModel.setSelectedClock(clockId)
val hasCustomWeatherDataDisplay =
@@ -61,7 +62,6 @@
.largeClock
.config
.hasCustomWeatherDataDisplay
-
hideSmartspace(hasCustomWeatherDataDisplay)
},
)
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
index 46a2801..04ad81f 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
@@ -23,6 +23,10 @@
import android.widget.FrameLayout
import androidx.constraintlayout.helper.widget.Carousel
import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.doOnPreDraw
+import androidx.core.view.get
+import androidx.core.view.isNotEmpty
+import com.android.customization.picker.clock.shared.ClockSize
import com.android.systemui.plugins.ClockController
import com.android.wallpaper.R
import java.lang.Float.max
@@ -39,12 +43,15 @@
val carousel: Carousel
private val motionLayout: MotionLayout
private lateinit var adapter: ClockCarouselAdapter
- private var scalingUpClockController: ClockController? = null
- private var scalingDownClockController: ClockController? = null
- private var scalingUpClockView: View? = null
- private var scalingDownClockView: View? = null
- private var showingCardView: View? = null
- private var hidingCardView: View? = null
+ private lateinit var clockViewFactory: ClockViewFactory
+ private var toCenterClockController: ClockController? = null
+ private var offCenterClockController: ClockController? = null
+ private var toCenterClockView: View? = null
+ private var offCenterClockView: View? = null
+ private var toCenterClockHostView: ClockHostView? = null
+ private var offCenterClockHostView: ClockHostView? = null
+ private var toCenterCardView: View? = null
+ private var offCenterCardView: View? = null
init {
val clockCarousel = LayoutInflater.from(context).inflate(R.layout.clock_carousel, this)
@@ -52,12 +59,19 @@
motionLayout = clockCarousel.requireViewById(R.id.motion_container)
}
+ /**
+ * Make sure to set [clockViewFactory] before calling any functions from [ClockCarouselView].
+ */
+ fun setClockViewFactory(factory: ClockViewFactory) {
+ clockViewFactory = factory
+ }
+
fun setUpClockCarouselView(
+ clockSize: ClockSize,
clockIds: List<String>,
- onGetClockController: (clockId: String) -> ClockController,
onClockSelected: (clockId: String) -> Unit,
) {
- adapter = ClockCarouselAdapter(clockIds, onGetClockController, onClockSelected)
+ adapter = ClockCarouselAdapter(clockSize, clockIds, clockViewFactory, onClockSelected)
carousel.setAdapter(adapter)
carousel.refresh()
motionLayout.setTransitionListener(
@@ -68,70 +82,146 @@
startId: Int,
endId: Int
) {
- val scalingDownClockId = adapter.clockIds[carousel.currentIndex]
- val scalingUpIdx =
- if (endId == R.id.next) (carousel.currentIndex + 1) % adapter.count()
- else (carousel.currentIndex - 1 + adapter.count()) % adapter.count()
- val scalingUpClockId = adapter.clockIds[scalingUpIdx]
- scalingDownClockController = adapter.onGetClockController(scalingDownClockId)
- scalingUpClockController = adapter.onGetClockController(scalingUpClockId)
- scalingDownClockView = motionLayout?.findViewById(R.id.clock_scale_view_2)
- scalingUpClockView =
- motionLayout?.findViewById(
- if (endId == R.id.next) R.id.clock_scale_view_3
- else R.id.clock_scale_view_1
- )
- showingCardView = motionLayout?.findViewById(R.id.item_card_2)
- hidingCardView =
- motionLayout?.findViewById(
- if (endId == R.id.next) R.id.item_card_3 else R.id.item_card_1
- )
- setCardAnimationState(true)
+ if (motionLayout == null) {
+ return
+ }
+ when (clockSize) {
+ ClockSize.DYNAMIC -> prepareDynamicClockView(motionLayout, endId)
+ ClockSize.SMALL -> prepareSmallClockView(motionLayout, endId)
+ }
+ prepareCardView(motionLayout, endId)
+ setCarouselItemAnimationState(true)
}
override fun onTransitionChange(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
- progress: Float
+ progress: Float,
) {
- scalingDownClockController
- ?.largeClock
- ?.animations
- ?.onPickerCarouselSwiping(1 - progress)
- scalingUpClockController
- ?.largeClock
- ?.animations
- ?.onPickerCarouselSwiping(progress)
- val scalingUpScale = getScalingUpScale(progress)
- val scalingDownScale = getScalingDownScale(progress)
- scalingUpClockView?.scaleX = scalingUpScale
- scalingUpClockView?.scaleY = scalingUpScale
- scalingDownClockView?.scaleX = scalingDownScale
- scalingDownClockView?.scaleY = scalingDownScale
- showingCardView?.alpha = getShowingAlpha(progress)
- hidingCardView?.alpha = getHidingAlpha(progress)
+ when (clockSize) {
+ ClockSize.DYNAMIC -> onDynamicClockViewTransition(progress)
+ ClockSize.SMALL -> onSmallClockViewTransition(progress)
+ }
+ onCardViewTransition(progress)
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
- setCardAnimationState(currentId == R.id.start)
+ setCarouselItemAnimationState(currentId == R.id.start)
}
- private fun setCardAnimationState(isStart: Boolean) {
- scalingDownClockView?.scaleX = if (isStart) 1f else CLOCK_CAROUSEL_VIEW_SCALE
- scalingDownClockView?.scaleY = if (isStart) 1f else CLOCK_CAROUSEL_VIEW_SCALE
- scalingUpClockView?.scaleX = if (isStart) CLOCK_CAROUSEL_VIEW_SCALE else 1f
- scalingUpClockView?.scaleY = if (isStart) CLOCK_CAROUSEL_VIEW_SCALE else 1f
- scalingDownClockController
+ private fun prepareDynamicClockView(motionLayout: MotionLayout, endId: Int) {
+ val scalingDownClockId = adapter.clockIds[carousel.currentIndex]
+ val scalingUpIdx =
+ if (endId == R.id.next) (carousel.currentIndex + 1) % adapter.count()
+ else (carousel.currentIndex - 1 + adapter.count()) % adapter.count()
+ val scalingUpClockId = adapter.clockIds[scalingUpIdx]
+ offCenterClockController = clockViewFactory.getController(scalingDownClockId)
+ toCenterClockController = clockViewFactory.getController(scalingUpClockId)
+ offCenterClockView = motionLayout.findViewById(R.id.clock_scale_view_2)
+ toCenterClockView =
+ motionLayout.findViewById(
+ if (endId == R.id.next) R.id.clock_scale_view_3
+ else R.id.clock_scale_view_1
+ )
+ }
+
+ private fun prepareSmallClockView(motionLayout: MotionLayout, endId: Int) {
+ offCenterClockHostView = motionLayout.findViewById(R.id.clock_host_view_2)
+ toCenterClockHostView =
+ motionLayout.findViewById(
+ if (endId == R.id.next) R.id.clock_host_view_3
+ else R.id.clock_host_view_1
+ )
+ }
+
+ private fun prepareCardView(motionLayout: MotionLayout, endId: Int) {
+ offCenterCardView = motionLayout.findViewById(R.id.item_card_2)
+ toCenterCardView =
+ motionLayout.findViewById(
+ if (endId == R.id.next) R.id.item_card_3 else R.id.item_card_1
+ )
+ }
+
+ private fun onCardViewTransition(progress: Float) {
+ offCenterCardView?.alpha = getShowingAlpha(progress)
+ toCenterCardView?.alpha = getHidingAlpha(progress)
+ }
+
+ private fun onDynamicClockViewTransition(progress: Float) {
+ offCenterClockController
?.largeClock
?.animations
- ?.onPickerCarouselSwiping(if (isStart) 1f else 0f)
- scalingUpClockController
+ ?.onPickerCarouselSwiping(1 - progress)
+ toCenterClockController
?.largeClock
?.animations
- ?.onPickerCarouselSwiping(if (isStart) 0f else 1f)
- showingCardView?.alpha = if (isStart) 0f else 1f
- hidingCardView?.alpha = if (isStart) 1f else 0f
+ ?.onPickerCarouselSwiping(progress)
+ val scalingDownScale = getScalingDownScale(progress)
+ val scalingUpScale = getScalingUpScale(progress)
+ offCenterClockView?.scaleX = scalingDownScale
+ offCenterClockView?.scaleY = scalingDownScale
+ toCenterClockView?.scaleX = scalingUpScale
+ toCenterClockView?.scaleY = scalingUpScale
+ }
+
+ private fun onSmallClockViewTransition(progress: Float) {
+ val offCenterClockHostView = offCenterClockHostView ?: return
+ val toCenterClockHostView = toCenterClockHostView ?: return
+ val offCenterClockFrame =
+ if (offCenterClockHostView.isNotEmpty()) {
+ offCenterClockHostView[0]
+ } else {
+ null
+ }
+ ?: return
+ val toCenterClockFrame =
+ if (toCenterClockHostView.isNotEmpty()) {
+ toCenterClockHostView[0]
+ } else {
+ null
+ }
+ ?: return
+ offCenterClockHostView.doOnPreDraw {
+ it.pivotX = progress * it.width / 2
+ it.pivotY = progress * it.height / 2
+ }
+ toCenterClockHostView.doOnPreDraw {
+ it.pivotX = (1 - progress) * it.width / 2
+ it.pivotY = (1 - progress) * it.height / 2
+ }
+ offCenterClockFrame.translationX =
+ getTranslationDistance(
+ offCenterClockHostView.width,
+ offCenterClockFrame.width,
+ offCenterClockFrame.left,
+ ) * progress
+ offCenterClockFrame.translationY =
+ getTranslationDistance(
+ offCenterClockHostView.height,
+ offCenterClockFrame.height,
+ offCenterClockFrame.top,
+ ) * progress
+ toCenterClockFrame.translationX =
+ getTranslationDistance(
+ toCenterClockHostView.width,
+ toCenterClockFrame.width,
+ toCenterClockFrame.left,
+ ) * (1 - progress)
+ toCenterClockFrame.translationY =
+ getTranslationDistance(
+ toCenterClockHostView.height,
+ toCenterClockFrame.height,
+ toCenterClockFrame.top,
+ ) * (1 - progress)
+ }
+
+ private fun setCarouselItemAnimationState(isStart: Boolean) {
+ when (clockSize) {
+ ClockSize.DYNAMIC -> onDynamicClockViewTransition(if (isStart) 0f else 1f)
+ ClockSize.SMALL -> onSmallClockViewTransition(if (isStart) 0f else 1f)
+ }
+ onCardViewTransition(if (isStart) 0f else 1f)
}
override fun onTransitionTrigger(
@@ -154,9 +244,10 @@
}
}
- class ClockCarouselAdapter(
+ private class ClockCarouselAdapter(
+ val clockSize: ClockSize,
val clockIds: List<String>,
- val onGetClockController: (clockId: String) -> ClockController,
+ private val clockViewFactory: ClockViewFactory,
private val onClockSelected: (clockId: String) -> Unit
) : Carousel.Adapter {
@@ -175,29 +266,89 @@
val clockHostView =
getClockHostViewId(viewRoot.id)?.let { viewRoot.findViewById(it) as? ClockHostView }
?: return
+ val clockId = clockIds[index]
+
+ // Add the clock view to the cloc host view
clockHostView.removeAllViews()
- val clockView = onGetClockController(clockIds[index]).largeClock.view
+ val clockView =
+ when (clockSize) {
+ ClockSize.DYNAMIC -> clockViewFactory.getLargeView(clockId)
+ ClockSize.SMALL -> clockViewFactory.getSmallView(clockId)
+ }
// The clock view might still be attached to an existing parent. Detach before adding to
// another parent.
(clockView.parent as? ViewGroup)?.removeView(clockView)
clockHostView.addView(clockView)
- // initialize scaling state for all clocks
- if (!isMiddleView(viewRoot.id)) {
- cardView.alpha = 1f
- clockScaleView.scaleX = CLOCK_CAROUSEL_VIEW_SCALE
- clockScaleView.scaleY = CLOCK_CAROUSEL_VIEW_SCALE
- onGetClockController(clockIds[index])
- .largeClock
- .animations
- .onPickerCarouselSwiping(0F)
- } else {
- cardView.alpha = 0f
+
+ val isMiddleView = isMiddleView(viewRoot.id)
+ when (clockSize) {
+ ClockSize.DYNAMIC ->
+ initializeDynamicClockView(
+ isMiddleView,
+ clockScaleView,
+ clockId,
+ )
+ ClockSize.SMALL ->
+ initializeSmallClockView(
+ isMiddleView,
+ clockHostView,
+ clockView,
+ )
+ }
+ cardView.alpha = if (isMiddleView) 0f else 1f
+ }
+
+ private fun initializeDynamicClockView(
+ isMiddleView: Boolean,
+ clockScaleView: View,
+ clockId: String,
+ ) {
+ if (isMiddleView) {
clockScaleView.scaleX = 1f
clockScaleView.scaleY = 1f
- onGetClockController(clockIds[index])
+ clockViewFactory
+ .getController(clockId)
.largeClock
.animations
.onPickerCarouselSwiping(1F)
+ } else {
+ clockScaleView.scaleX = CLOCK_CAROUSEL_VIEW_SCALE
+ clockScaleView.scaleY = CLOCK_CAROUSEL_VIEW_SCALE
+ clockViewFactory
+ .getController(clockId)
+ .largeClock
+ .animations
+ .onPickerCarouselSwiping(0F)
+ }
+ }
+
+ private fun initializeSmallClockView(
+ isMiddleView: Boolean,
+ clockHostView: ClockHostView,
+ clockView: View,
+ ) {
+ clockHostView.doOnPreDraw {
+ if (isMiddleView) {
+ it.pivotX = 0F
+ it.pivotY = 0F
+ clockView.translationX = 0F
+ clockView.translationY = 0F
+ } else {
+ it.pivotX = it.width / 2F
+ it.pivotY = it.height / 2F
+ clockView.translationX =
+ getTranslationDistance(
+ clockHostView.width,
+ clockView.width,
+ clockView.left,
+ )
+ clockView.translationY =
+ getTranslationDistance(
+ clockHostView.height,
+ clockView.height,
+ clockView.top,
+ )
+ }
}
}
@@ -258,5 +409,13 @@
fun isMiddleView(rootViewId: Int): Boolean {
return rootViewId == R.id.item_view_2
}
+
+ private fun getTranslationDistance(
+ hostLength: Int,
+ frameLength: Int,
+ edgeDimen: Int,
+ ): Float {
+ return ((hostLength - frameLength) / 2 - edgeDimen).toFloat()
+ }
}
}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
index cfc05dc..1aa6206 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
@@ -70,21 +70,20 @@
FrameLayout.LayoutParams.WRAP_CONTENT,
resources.getDimensionPixelSize(R.dimen.small_clock_height)
)
- layoutParams.topMargin =
- getStatusBarHeight(resources) +
- resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+ layoutParams.topMargin = getSmallClockTopMargin()
+ layoutParams.marginStart = getSmallClockStartPadding()
smallClockFrame.layoutParams = layoutParams
-
- smallClockFrame.setPaddingRelative(
- resources.getDimensionPixelSize(R.dimen.clock_padding_start),
- 0,
- 0,
- 0
- )
smallClockFrame.clipChildren = false
return smallClockFrame
}
+ private fun getSmallClockTopMargin() =
+ getStatusBarHeight(appContext.resources) +
+ appContext.resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+
+ private fun getSmallClockStartPadding() =
+ appContext.resources.getDimensionPixelSize(R.dimen.clock_padding_start)
+
fun updateColorForAllClocks(@ColorInt seedColor: Int?) {
clockControllers.values.forEach { it.events.onSeedColorChanged(seedColor = seedColor) }
}
@@ -185,12 +184,9 @@
* and position the clock view
*/
private fun getSmallClockRegion(): Rect {
- val topMargin =
- getStatusBarHeight(resources) +
- resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
- val start = resources.getDimensionPixelSize(R.dimen.clock_padding_start)
+ val topMargin = getSmallClockTopMargin()
val targetHeight = resources.getDimensionPixelSize(R.dimen.small_clock_height)
- return Rect(start, topMargin, screenSize.x, topMargin + targetHeight)
+ return Rect(getSmallClockStartPadding(), topMargin, screenSize.x, topMargin + targetHeight)
}
companion object {
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index c11c333..781b48c 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -19,6 +19,7 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.shared.ClockSize
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
@@ -53,6 +54,8 @@
}
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
+ val selectedClockSize: Flow<ClockSize> = interactor.selectedClockSize
+
val seedColor: Flow<Int?> = interactor.seedColor
val isCarouselVisible: Flow<Boolean> = allClockIds.map { it.size > 1 }.distinctUntilChanged()