Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1b5c91c authored by Sherry Zhou's avatar Sherry Zhou Committed by George Lin
Browse files

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
parent e0a0f51e
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.customization.picker.clock.ui.view.ClockCarouselView
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 @@ object ClockCarouselViewBinder {
        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 @@ object ClockCarouselViewBinder {
                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 @@ object ClockCarouselViewBinder {
                                        .largeClock
                                        .config
                                        .hasCustomWeatherDataDisplay

                                hideSmartspace(hasCustomWeatherDataDisplay)
                            },
                        )
+223 −64
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ import android.view.ViewGroup
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 @@ class ClockCarouselView(
    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 @@ class ClockCarouselView(
        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 @@ class ClockCarouselView(
                    startId: Int,
                    endId: Int
                ) {
                    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,
                ) {
                    when (clockSize) {
                        ClockSize.DYNAMIC -> onDynamicClockViewTransition(progress)
                        ClockSize.SMALL -> onSmallClockViewTransition(progress)
                    }
                    onCardViewTransition(progress)
                }

                override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
                    setCarouselItemAnimationState(currentId == R.id.start)
                }

                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]
                    scalingDownClockController = adapter.onGetClockController(scalingDownClockId)
                    scalingUpClockController = adapter.onGetClockController(scalingUpClockId)
                    scalingDownClockView = motionLayout?.findViewById(R.id.clock_scale_view_2)
                    scalingUpClockView =
                        motionLayout?.findViewById(
                    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
                        )
                    showingCardView = motionLayout?.findViewById(R.id.item_card_2)
                    hidingCardView =
                        motionLayout?.findViewById(
                }

                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
                        )
                    setCardAnimationState(true)
                }

                override fun onTransitionChange(
                    motionLayout: MotionLayout?,
                    startId: Int,
                    endId: Int,
                    progress: Float
                ) {
                    scalingDownClockController
                private fun onCardViewTransition(progress: Float) {
                    offCenterCardView?.alpha = getShowingAlpha(progress)
                    toCenterCardView?.alpha = getHidingAlpha(progress)
                }

                private fun onDynamicClockViewTransition(progress: Float) {
                    offCenterClockController
                        ?.largeClock
                        ?.animations
                        ?.onPickerCarouselSwiping(1 - progress)
                    scalingUpClockController
                    toCenterClockController
                        ?.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)
                    val scalingUpScale = getScalingUpScale(progress)
                    offCenterClockView?.scaleX = scalingDownScale
                    offCenterClockView?.scaleY = scalingDownScale
                    toCenterClockView?.scaleX = scalingUpScale
                    toCenterClockView?.scaleY = scalingUpScale
                }

                override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
                    setCardAnimationState(currentId == R.id.start)
                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 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
                        ?.largeClock
                        ?.animations
                        ?.onPickerCarouselSwiping(if (isStart) 1f else 0f)
                    scalingUpClockController
                        ?.largeClock
                        ?.animations
                        ?.onPickerCarouselSwiping(if (isStart) 0f else 1f)
                    showingCardView?.alpha = if (isStart) 0f else 1f
                    hidingCardView?.alpha = if (isStart) 1f else 0f
                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 ClockCarouselView(
        }
    }

    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 @@ class ClockCarouselView(
            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

            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
                clockViewFactory
                    .getController(clockId)
                    .largeClock
                    .animations
                    .onPickerCarouselSwiping(1F)
            } else {
                clockScaleView.scaleX = CLOCK_CAROUSEL_VIEW_SCALE
                clockScaleView.scaleY = CLOCK_CAROUSEL_VIEW_SCALE
                onGetClockController(clockIds[index])
                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 {
                cardView.alpha = 0f
                clockScaleView.scaleX = 1f
                clockScaleView.scaleY = 1f
                onGetClockController(clockIds[index])
                    .largeClock
                    .animations
                    .onPickerCarouselSwiping(1F)
                    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 @@ class ClockCarouselView(
        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()
        }
    }
}
+11 −15
Original line number Diff line number Diff line
@@ -70,21 +70,20 @@ class ClockViewFactory(
                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 @@ class ClockViewFactory(
     * 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 {
+3 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import androidx.lifecycle.ViewModel
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 @@ constructor(
            }
            .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()