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

Commit 870d8a49 authored by Aaron Liu's avatar Aaron Liu
Browse files

Add clock section

Test: show large clock and small clock
Bug: 316211368
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Flag: ACONFIG com.android.systemui.migrate_clocks_to_blueprint
DEVELOPMENT

Change-Id: Iaf68f655270b3a285f5041851944c11ae458380f
parent ceb31668
Loading
Loading
Loading
Loading
+62 −27
Original line number Diff line number Diff line
@@ -16,79 +16,114 @@

package com.android.systemui.keyguard.ui.composable.section

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.res.R
import javax.inject.Inject

class ClockSection
@Inject
constructor(
    private val viewModel: KeyguardClockViewModel,
    private val clockInteractor: KeyguardClockInteractor,
) {

    @Composable
    fun SceneScope.SmallClock(
        onTopChanged: (top: Float?) -> Unit,
        modifier: Modifier = Modifier,
    ) {
        if (viewModel.useLargeClock) {
        val clockSize by viewModel.clockSize.collectAsState()
        val currentClock by viewModel.currentClock.collectAsState()
        viewModel.clock = currentClock

        if (clockSize != KeyguardClockSwitch.SMALL) {
            onTopChanged(null)
            return
        }

        if (currentClock?.smallClock?.view == null) {
            return
        }

        val view = LocalView.current

        DisposableEffect(view) {
            clockInteractor.clockEventController.registerListeners(view)

            onDispose { clockInteractor.clockEventController.unregisterListeners() }
        }

        MovableElement(
            key = ClockElementKey,
            modifier = modifier,
        ) {
            content {
                Box(
                AndroidView(
                    factory = { checkNotNull(currentClock).smallClock.view },
                    modifier =
                        Modifier.fillMaxWidth()
                            .background(Color.Magenta)
                            .onTopPlacementChanged(onTopChanged)
                ) {
                    Text(
                        text = "TODO(b/316211368): Small clock",
                        color = Color.White,
                        modifier = Modifier.align(Alignment.Center),
                        Modifier.padding(
                                horizontal =
                                    dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
                            )
                            .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
                            .onTopPlacementChanged(onTopChanged),
                )
                }
            }
        }
    }

    @Composable
    fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
        if (!viewModel.useLargeClock) {
        val clockSize by viewModel.clockSize.collectAsState()
        val currentClock by viewModel.currentClock.collectAsState()
        viewModel.clock = currentClock

        if (clockSize != KeyguardClockSwitch.LARGE) {
            return
        }

        if (currentClock?.largeClock?.view == null) {
            return
        }

        val view = LocalView.current

        DisposableEffect(view) {
            clockInteractor.clockEventController.registerListeners(view)

            onDispose { clockInteractor.clockEventController.unregisterListeners() }
        }

        MovableElement(
            key = ClockElementKey,
            modifier = modifier,
        ) {
            content {
                Box(
                    modifier = Modifier.fillMaxWidth().background(Color.Blue),
                ) {
                    Text(
                        text = "TODO(b/316211368): Large clock",
                        color = Color.White,
                        modifier = Modifier.align(Alignment.Center),
                AndroidView(
                    factory = { checkNotNull(currentClock).largeClock.view },
                    modifier =
                        Modifier.fillMaxWidth()
                            .padding(top = { viewModel.getLargeClockTopMargin(view.context) })
                )
            }
        }
    }
}
}

private val ClockElementKey = ElementKey("Clock")
+20 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.customization.R
import com.android.systemui.dagger.qualifiers.Background
@@ -325,6 +326,10 @@ constructor(
                    }
                }

                if (visible) {
                    refreshTime()
                }

                smallTimeListener?.update(shouldTimeListenerRun)
                largeTimeListener?.update(shouldTimeListenerRun)
            }
@@ -346,6 +351,19 @@ constructor(
                weatherData = data
                clock?.run { events.onWeatherDataChanged(data) }
            }

            override fun onTimeChanged() {
                refreshTime()
            }

            private fun refreshTime() {
                if (!migrateClocksToBlueprint()) {
                    return
                }

                clock?.smallClock?.events?.onTimeTick()
                clock?.largeClock?.events?.onTimeTick()
            }
        }

    private val zenModeCallback = object : ZenModeController.Callback {
@@ -558,7 +576,8 @@ constructor(
            isRunning = true
            when (clockFace.config.tickRate) {
                ClockTickRate.PER_MINUTE -> {
                    /* Handled by KeyguardClockSwitchController */
                    // Handled by KeyguardClockSwitchController and
                    // by KeyguardUpdateMonitorCallback#onTimeChanged.
                }
                ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
                ClockTickRate.PER_FRAME -> {
+46 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.keyguard.ui.viewmodel

import android.content.Context
import androidx.constraintlayout.helper.widget.Layer
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
@@ -25,6 +26,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -36,9 +40,10 @@ import kotlinx.coroutines.flow.stateIn
class KeyguardClockViewModel
@Inject
constructor(
    val keyguardInteractor: KeyguardInteractor,
    val keyguardClockInteractor: KeyguardClockInteractor,
    keyguardInteractor: KeyguardInteractor,
    private val keyguardClockInteractor: KeyguardClockInteractor,
    @Application private val applicationScope: CoroutineScope,
    private val splitShadeStateController: SplitShadeStateController,
) {
    var burnInLayer: Layer? = null
    val useLargeClock: Boolean
@@ -85,4 +90,43 @@ constructor(
            started = SharingStarted.WhileSubscribed(),
            initialValue = false
        )

    // Needs to use a non application context to get display cutout.
    fun getSmallClockTopMargin(context: Context) =
        if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
            context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
        } else {
            context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
                Utils.getStatusBarHeaderHeightKeyguard(context)
        }

    fun getLargeClockTopMargin(context: Context): Int {
        var largeClockTopMargin =
            context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
                context.resources.getDimensionPixelSize(
                    com.android.systemui.customization.R.dimen.small_clock_padding_top
                ) +
                context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
        largeClockTopMargin += getDimen(context, DATE_WEATHER_VIEW_HEIGHT)
        largeClockTopMargin += getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
        if (!useLargeClock) {
            largeClockTopMargin -=
                context.resources.getDimensionPixelSize(
                    com.android.systemui.customization.R.dimen.small_clock_height
                )
        }

        return largeClockTopMargin
    }

    private fun getDimen(context: Context, name: String): Int {
        val res = context.packageManager.getResourcesForApplication(context.packageName)
        val id = res.getIdentifier(name, "dimen", context.packageName)
        return res.getDimensionPixelSize(id)
    }

    companion object {
        private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
        private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -66,6 +67,8 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
    @Mock private lateinit var largeClock: ClockFaceController
    @Mock private lateinit var clockFaceConfig: ClockFaceConfig
    @Mock private lateinit var eventController: ClockEventController
    @Mock private lateinit var splitShadeStateController: SplitShadeStateController

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
@@ -92,6 +95,7 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
                keyguardInteractor,
                keyguardClockInteractor,
                scope.backgroundScope,
                splitShadeStateController,
            )
    }

+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.policy.splitShadeStateController

val Kosmos.keyguardClockViewModel by
    Kosmos.Fixture {
@@ -27,5 +28,6 @@ val Kosmos.keyguardClockViewModel by
            keyguardInteractor = keyguardInteractor,
            keyguardClockInteractor = keyguardClockInteractor,
            applicationScope = applicationCoroutineScope,
            splitShadeStateController = splitShadeStateController,
        )
    }