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

Commit 9709f9a6 authored by Andre Le's avatar Andre Le
Browse files

ComposeClock: Apply custom AM/PM styling to the new clock

Add the support of custom AM/PM styling to the new composable clock. The
old Clock.java support normal, gone, and small style. However, only
normal and gone styles are in use today, so we will only support these 2
types in the new clock.

Future callers of this clock should use the view model to define which
style to use.

Bug: 390204943
Flag: com.android.systemui.clock_modernization
Test: ClockViewModelTest
Change-Id: I41cd8cd5a3c037e31214ed21df30b0366954a0de
parent 11c0b809
Loading
Loading
Loading
Loading
+58 −3
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.clock.domain.interactor.clockInteractor
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
@@ -137,12 +138,12 @@ class ClockViewModelTest : SysuiTestCase() {
            )
            runCurrent()

            assertThat(underTest.clockText).isEqualTo("08:12")
            assertThat(underTest.clockText).isEqualTo("8:12")
            assertThat(underTest.contentDescriptionText).isEqualTo("08:12")
        }

    @Test
    fun clockTextAndDescription_updatesWhenLocaleChanged() =
    fun clockTextAndDescription_updatesWhenLocaleChanged_traditionalChinese() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            underTest.activateIn(testScope)
@@ -157,10 +158,64 @@ class ClockViewModelTest : SysuiTestCase() {
            )
            runCurrent()

            assertThat(underTest.clockText).isEqualTo("下午11:12")
            assertThat(underTest.clockText).isEqualTo("11:12\u202F下午")
            assertThat(underTest.contentDescriptionText).isEqualTo("下午11:12")
        }

    @Test
    fun clockTextAndDescription_updatesWhenLocaleChanged_burmese() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            underTest.activateIn(testScope)

            assertThat(underTest.clockText).isEqualTo("11:12\u202FPM")
            assertThat(underTest.contentDescriptionText).isEqualTo("11:12\u202FPM")

            Locale.setDefault(Locale.Builder().setLanguage("my").build())
            broadcastDispatcher.sendIntentToMatchingReceiversOnly(
                context,
                Intent(Intent.ACTION_LOCALE_CHANGED),
            )
            runCurrent()

            assertThat(underTest.clockText).isEqualTo("၁၁:၁၂\u202Fညနေ")
            assertThat(underTest.contentDescriptionText).isEqualTo("ညနေ ၁၁:၁၂")
        }

    @Test
    fun clockTextAndDescription_amPmStyleGone() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(false)
            val viewModel =
                ClockViewModel(
                    clockInteractor = clockInteractor,
                    dateFormatUtil = dateFormatUtil,
                    amPmStyle = AmPmStyle.Gone,
                )
            viewModel.activateIn(testScope)

            assertThat(viewModel.clockText).isEqualTo("11:12")
            assertThat(viewModel.contentDescriptionText).isEqualTo("11:12\u202FPM")
        }

    @Test
    fun clockTextAndDescription_amPmStyleShown() =
        kosmos.runTest {
            fakeSystemClock.setCurrentTimeMillis(CURRENT_TIME_MILLIS)
            whenever(dateFormatUtil.is24HourFormat).thenReturn(false)
            val viewModel =
                ClockViewModel(
                    clockInteractor = clockInteractor,
                    dateFormatUtil = dateFormatUtil,
                    amPmStyle = AmPmStyle.Shown,
                )
            viewModel.activateIn(testScope)

            assertThat(viewModel.clockText).isEqualTo("11:12\u202FPM")
            assertThat(viewModel.contentDescriptionText).isEqualTo("11:12\u202FPM")
        }

    companion object {
        private const val CURRENT_TIME_MILLIS = 16641673939408L
    }
+7 −2
Original line number Diff line number Diff line
@@ -21,13 +21,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import com.android.systemui.clock.ui.viewmodel.AmPmStyle
import com.android.systemui.clock.ui.viewmodel.ClockViewModel
import com.android.systemui.lifecycle.rememberViewModel

/** Composable for the clock UI that is shown on the top left of the status bar and the shade. */
@Composable
fun Clock(viewModelFactory: ClockViewModel.Factory, modifier: Modifier = Modifier) {
    val clockViewModel = rememberViewModel("Clock-viewModel") { viewModelFactory.create() }
fun Clock(
    viewModelFactory: ClockViewModel.Factory,
    modifier: Modifier = Modifier,
    amPmStyle: AmPmStyle = AmPmStyle.Gone,
) {
    val clockViewModel = rememberViewModel("Clock-viewModel") { viewModelFactory.create(amPmStyle) }
    Text(
        text = clockViewModel.clockText,
        modifier = modifier.semantics { contentDescription = clockViewModel.contentDescriptionText },
+40 −19
Original line number Diff line number Diff line
@@ -23,8 +23,10 @@ import com.android.systemui.clock.domain.interactor.ClockInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.util.time.DateFormatUtil
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,23 +36,31 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapLatest

/** AM/PM styling for the clock UI */
enum class AmPmStyle {
    Shown,
    Gone,
}

/** Models UI state for the clock. */
@OptIn(ExperimentalCoroutinesApi::class)
class ClockViewModel
@AssistedInject
constructor(clockInteractor: ClockInteractor, private val dateFormatUtil: DateFormatUtil) :
    ExclusiveActivatable() {
constructor(
    clockInteractor: ClockInteractor,
    private val dateFormatUtil: DateFormatUtil,
    @Assisted private val amPmStyle: AmPmStyle,
) : ExclusiveActivatable() {
    private val hydrator = Hydrator("ClockViewModel.hydrator")
    private lateinit var dateTimePatternGenerator: DateTimePatternGenerator

    private val formatString: Flow<String> =
        clockInteractor.onTimezoneOrLocaleChanged.mapLatest { getFormatString() }
    // For content description, we use `DateTimePatternGenerator` to generate the best time format
    // for all the locales. For clock time, since we want to utilize removing AM/PM marker for
    // `AmPmStyle.Gone`, we will just use `SimpleDateFormat` instead.
    private lateinit var dateTimePatternGenerator: DateTimePatternGenerator

    private val contentDescriptionFormat: Flow<SimpleDateFormat> =
        formatString.mapLatest { format ->
            // We already provide the locale value in `DateTimePatternGenerator`, so it is okay to
            // not provide the locale in `SimpleDateFormat` here.
            @Suppress("SimpleDateFormat") SimpleDateFormat(format)
    private val contentDescriptionFormat: Flow<DateFormat> =
        clockInteractor.onTimezoneOrLocaleChanged.mapLatest {
            getSimpleDateFormat(getContentDescriptionFormatString())
        }

    private val _contentDescriptionText: Flow<String> =
@@ -68,7 +78,9 @@ constructor(clockInteractor: ClockInteractor, private val dateFormatUtil: DateFo
        )

    private val clockTextFormat: Flow<SimpleDateFormat> =
        formatString.mapLatest { format -> getClockTextFormat(format) }
        clockInteractor.onTimezoneOrLocaleChanged.mapLatest {
            getSimpleDateFormat(getClockTextFormatString())
        }

    private val _clockText: Flow<String> =
        combine(clockTextFormat, clockInteractor.currentTime) { clockTextFormat, time ->
@@ -92,10 +104,10 @@ constructor(clockInteractor: ClockInteractor, private val dateFormatUtil: DateFo

    @AssistedFactory
    interface Factory {
        fun create(): ClockViewModel
        fun create(amPmStyle: AmPmStyle): ClockViewModel
    }

    private fun getFormatString(): String {
    private fun getContentDescriptionFormatString(): String {
        dateTimePatternGenerator = DateTimePatternGenerator.getInstance(Locale.getDefault())

        // TODO(b/390204943): use different value depending on if the system want to show seconds.
@@ -104,11 +116,20 @@ constructor(clockInteractor: ClockInteractor, private val dateFormatUtil: DateFo
        return dateTimePatternGenerator.getBestPattern(formatSkeleton)
    }

    private fun getClockTextFormat(format: String): SimpleDateFormat {
        // TODO(b/390204943): handle AM/PM style
        // We already provide the locale value in `DateTimePatternGenerator` above, so it is okay
        // to not provide the locale in `SimpleDateFormat` here.
        @Suppress("SimpleDateFormat")
        return SimpleDateFormat(format)
    private fun getClockTextFormatString(): String {
        // TODO(b/390204943): use different value depending on if the system want to show seconds.
        return if (dateFormatUtil.is24HourFormat) {
            "H:mm"
        } else if (amPmStyle == AmPmStyle.Shown) {
            // Note that we always put the AM/PM marker at the end of the string, and this could be
            // wrong for certain languages.
            "h:mm\u202Fa"
        } else {
            "h:mm"
        }
    }

    private fun getSimpleDateFormat(formatString: String): SimpleDateFormat {
        return SimpleDateFormat(formatString, Locale.getDefault())
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -22,5 +22,9 @@ import com.android.systemui.util.time.dateFormatUtil

val Kosmos.clockViewModel: ClockViewModel by
    Kosmos.Fixture {
        ClockViewModel(clockInteractor = clockInteractor, dateFormatUtil = dateFormatUtil)
        ClockViewModel(
            clockInteractor = clockInteractor,
            dateFormatUtil = dateFormatUtil,
            amPmStyle = AmPmStyle.Shown,
        )
    }