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

Commit f0e91f6e authored by Andre Le's avatar Andre Le Committed by Android (Google) Code Review
Browse files

Merge "ComposeClock: Apply custom AM/PM styling to the new clock" into main

parents 1acbca71 9709f9a6
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,
        )
    }