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

Commit 86149709 authored by Catherine Liang's avatar Catherine Liang Committed by Android (Google) Code Review
Browse files

Merge "Delay color picker back navigation until apply is complete" into main

parents 90c74659 0d8f3bbe
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ViewModelScoped
import kotlin.coroutines.resume
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,6 +44,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine

/** Models UI state for a color picker experience. */
class ColorPickerViewModel2
@@ -57,6 +60,7 @@ constructor(
    val previewingColorOption = overridingColorOption.asStateFlow()

    private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null)
    private var onApplyContinuation: CancellableContinuation<Unit>? = null

    /** View-models for each color tab. */
    val colorTypeTabs: Flow<List<FloatingToolbarTabViewModel>> =
@@ -165,6 +169,10 @@ constructor(
                .toMap()
        }

    /**
     * This function suspends until onApplyComplete is called to accommodate for configuration
     * change updates, which are applied with a latency.
     */
    val onApply: Flow<(suspend () -> Unit)?> =
        previewingColorOption.map { previewingColorOption ->
            previewingColorOption?.let {
@@ -173,6 +181,12 @@ constructor(
                } else {
                    {
                        interactor.select(it)
                        // Suspend until onApplyComplete is called, e.g. on configuration change
                        suspendCancellableCoroutine { continuation: CancellableContinuation<Unit> ->
                            onApplyContinuation?.cancel()
                            onApplyContinuation = continuation
                            continuation.invokeOnCancellation { onApplyContinuation = null }
                        }
                        logger.logThemeColorApplied(
                            previewingColorOption.colorOption.sourceForLogging,
                            previewingColorOption.colorOption.styleForLogging,
@@ -187,6 +201,12 @@ constructor(
        overridingColorOption.value = null
    }

    /** Resumes the onApply function if apply is in progress, otherwise no-op */
    fun onApplyComplete() {
        onApplyContinuation?.resume(Unit)
        onApplyContinuation = null
    }

    /** The list of all available color options for the selected Color Type. */
    val colorOptions: Flow<List<OptionItemViewModel2<ColorOptionIconViewModel>>> =
        combine(allColorOptions, selectedColorTypeTabId) {
+11 −3
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
@@ -59,6 +60,8 @@ constructor(
    val shapeGridPickerViewModel =
        shapeGridPickerViewModelFactory.create(viewModelScope = viewModelScope)

    private var onApplyJob: Job? = null

    override val selectedOption = defaultCustomizationOptionsViewModel.selectedOption

    override fun handleBackPressed(): Boolean {
@@ -167,9 +170,14 @@ constructor(
            .map { onApply ->
                if (onApply != null) {
                    fun(onComplete: () -> Unit) {
                        // Prevent double apply
                        if (onApplyJob?.isActive != true) {
                            onApplyJob =
                                viewModelScope.launch {
                                    onApply()
                                    onComplete()
                                    onApplyJob = null
                                }
                        }
                    }
                } else {
+8 −3
Original line number Diff line number Diff line
@@ -158,14 +158,19 @@ constructor(
                            }
                        }

                        launch {
                            colorUpdateViewModel.systemColorsUpdated.collect {
                                viewModel.colorPickerViewModel2.onApplyComplete()
                            }
                        }

                        launch {
                            combine(
                                    viewModel.colorPickerViewModel2.previewingColorOption,
                                    viewModel.darkModeViewModel.overridingIsDarkMode,
                                    colorUpdateViewModel.systemColorsUpdated,
                                    ::Triple,
                                    ::Pair,
                                )
                                .collect { (colorModel, darkMode, _) ->
                                .collect { (colorModel, darkMode) ->
                                    val bundle =
                                        Bundle().apply {
                                            if (colorModel != null) {
+26 −3
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -95,6 +96,27 @@ class ColorPickerViewModel2Test {
        Dispatchers.resetMain()
    }

    @Test
    fun onApply_suspendsUntilOnApplyCompleteIsCalled() =
        testScope.runTest {
            val colorTypes = collectLastValue(underTest.colorTypeTabs)
            val colorOptions = collectLastValue(underTest.colorOptions)
            val onApply = collectLastValue(underTest.onApply)

            // Select "Wallpaper colors" tab
            colorTypes()?.get(0)?.onClick?.invoke()
            // Select a color option to preview
            selectColorOption(colorOptions, 1)
            // Apply the selected color option
            val job = testScope.launch { onApply()?.invoke() }

            assertThat(job.isActive).isTrue()

            underTest.onApplyComplete()

            assertThat(job.isActive).isFalse()
        }

    @Test
    fun onApply_wallpaperColor_shouldLogColor() =
        testScope.runTest {
@@ -203,7 +225,7 @@ class ColorPickerViewModel2Test {
            )
        }

    /** Simulates a user selecting the affordance at the given index, if that is clickable. */
    /** Simulates a user selecting the color option at the given index. */
    private fun TestScope.selectColorOption(
        colorOptions: () -> List<OptionItemViewModel2<ColorOptionIconViewModel>>?,
        index: Int,
@@ -217,10 +239,11 @@ class ColorPickerViewModel2Test {
        }
    }

    /** Simulates a user selecting the affordance at the given index, if that is clickable. */
    /** Simulates a user applying the color option at the given index, and the apply completes. */
    private suspend fun TestScope.applySelectedColorOption() {
        val onApply = collectLastValue(underTest.onApply)()
        onApply?.invoke()
        testScope.launch { onApply?.invoke() }
        underTest.onApplyComplete()
    }

    /**