Loading src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModel.kt +25 −3 Original line number Diff line number Diff line Loading @@ -18,17 +18,26 @@ package com.android.customization.picker.mode.ui.viewmodel import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.mode.domain.interactor.DarkModeInteractor import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch @ViewModelScoped class DarkModeViewModel @Inject constructor(private val interactor: DarkModeInteractor, private val logger: ThemesUserEventLogger) { constructor( private val colorUpdateViewModel: ColorUpdateViewModel, private val interactor: DarkModeInteractor, private val logger: ThemesUserEventLogger, ) { private val isDarkMode = interactor.isDarkMode val isEnabled = interactor.isEnabled Loading @@ -54,9 +63,22 @@ constructor(private val interactor: DarkModeInteractor, private val logger: Them combine(overridingIsDarkMode, isDarkMode, isEnabled) { override, current, isEnabled -> if (override != null && override != current && isEnabled) { { interactor.setIsDarkMode(override) coroutineScope { launch { interactor.setIsDarkMode(override) } // Dark mode change also invokes a color update. Suspend until both dark // mode and color are updated. combine( // Omit the first value which is emitted on subscribe. isDarkMode.drop(1).take(1), colorUpdateViewModel.systemColorsUpdatedNoReplay.take(1), ::Pair, ) .collect { (_, _) -> return@collect } logger.logDarkThemeApplied(override) } } } else null } Loading src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt +12 −9 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow Loading Loading @@ -169,7 +170,8 @@ constructor( null } else { { interactor.select(it) coroutineScope { launch { interactor.select(it) } // Suspend until first color update colorUpdateViewModel.systemColorsUpdatedNoReplay.take(1).collect { return@collect Loading @@ -183,6 +185,7 @@ constructor( } } } } fun resetPreview() { overridingColorOption.value = null Loading tests/robotests/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModelTest.kt +30 −7 Original line number Diff line number Diff line Loading @@ -16,20 +16,27 @@ package com.android.customization.picker.mode.ui.viewmodel import android.content.Context import androidx.test.platform.app.InstrumentationRegistry import com.android.customization.module.logging.TestThemesUserEventLogger import com.android.customization.picker.mode.data.repository.DarkModeRepository import com.android.customization.picker.mode.data.repository.DarkModeStateRepository import com.android.customization.picker.mode.domain.interactor.DarkModeInteractor import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel import com.android.wallpaper.testing.FakePowerManager import com.android.wallpaper.testing.FakeUiModeManager import com.android.wallpaper.testing.collectLastValue import com.google.common.truth.Truth.assertThat import dagger.hilt.android.internal.lifecycle.RetainedLifecycleImpl import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.Before Loading @@ -46,20 +53,26 @@ class DarkModeViewModelTest { @Inject lateinit var uiModeManager: FakeUiModeManager @Inject lateinit var powerManager: FakePowerManager @Inject lateinit var darkModeStateRepository: DarkModeStateRepository @Inject lateinit var darkModeRepository: DarkModeRepository @Inject lateinit var darkModeInteractor: DarkModeInteractor @Inject lateinit var logger: TestThemesUserEventLogger private lateinit var darkModeViewModel: DarkModeViewModel @Inject lateinit var testDispatcher: TestDispatcher @Inject lateinit var testScope: TestScope private lateinit var context: Context private lateinit var colorUpdateViewModel: ColorUpdateViewModel private lateinit var darkModeViewModel: DarkModeViewModel @Before fun setUp() { hiltRule.inject() Dispatchers.setMain(testDispatcher) darkModeViewModel = DarkModeViewModel(darkModeInteractor, logger) context = InstrumentationRegistry.getInstrumentation().targetContext colorUpdateViewModel = ColorUpdateViewModel(context, RetainedLifecycleImpl(), darkModeStateRepository) darkModeViewModel = DarkModeViewModel(colorUpdateViewModel, darkModeInteractor, logger) } @Test Loading Loading @@ -141,10 +154,9 @@ class DarkModeViewModelTest { uiModeManager.setNightModeActivated(false) darkModeRepository.refreshIsDarkMode() val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode) val onApply = collectLastValue(darkModeViewModel.onApply) getToggleDarkMode()?.invoke() onApply()?.invoke() applyDarkMode() assertThat(logger.useDarkTheme).isTrue() } Loading @@ -156,12 +168,23 @@ class DarkModeViewModelTest { uiModeManager.setNightModeActivated(false) darkModeRepository.refreshIsDarkMode() val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode) val onApply = collectLastValue(darkModeViewModel.onApply) getToggleDarkMode()?.invoke() onApply()?.invoke() applyDarkMode() assertThat(uiModeManager.getIsNightModeActivated()).isTrue() } } /** Simulates a user applying the previewing dark mode, and the apply completes. */ private fun TestScope.applyDarkMode() { val onApply = collectLastValue(darkModeViewModel.onApply)() testScope.launch { onApply?.invoke() } // Run coroutine launched in DarkModeViewModel#onApply runCurrent() // Simulate dark mode and color update config change colorUpdateViewModel.updateDarkModeAndColors() // Run coroutine launched in colorUpdateViewModel#updateColors runCurrent() } } Loading
src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModel.kt +25 −3 Original line number Diff line number Diff line Loading @@ -18,17 +18,26 @@ package com.android.customization.picker.mode.ui.viewmodel import com.android.customization.module.logging.ThemesUserEventLogger import com.android.customization.picker.mode.domain.interactor.DarkModeInteractor import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch @ViewModelScoped class DarkModeViewModel @Inject constructor(private val interactor: DarkModeInteractor, private val logger: ThemesUserEventLogger) { constructor( private val colorUpdateViewModel: ColorUpdateViewModel, private val interactor: DarkModeInteractor, private val logger: ThemesUserEventLogger, ) { private val isDarkMode = interactor.isDarkMode val isEnabled = interactor.isEnabled Loading @@ -54,9 +63,22 @@ constructor(private val interactor: DarkModeInteractor, private val logger: Them combine(overridingIsDarkMode, isDarkMode, isEnabled) { override, current, isEnabled -> if (override != null && override != current && isEnabled) { { interactor.setIsDarkMode(override) coroutineScope { launch { interactor.setIsDarkMode(override) } // Dark mode change also invokes a color update. Suspend until both dark // mode and color are updated. combine( // Omit the first value which is emitted on subscribe. isDarkMode.drop(1).take(1), colorUpdateViewModel.systemColorsUpdatedNoReplay.take(1), ::Pair, ) .collect { (_, _) -> return@collect } logger.logDarkThemeApplied(override) } } } else null } Loading
src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt +12 −9 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow Loading Loading @@ -169,7 +170,8 @@ constructor( null } else { { interactor.select(it) coroutineScope { launch { interactor.select(it) } // Suspend until first color update colorUpdateViewModel.systemColorsUpdatedNoReplay.take(1).collect { return@collect Loading @@ -183,6 +185,7 @@ constructor( } } } } fun resetPreview() { overridingColorOption.value = null Loading
tests/robotests/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModelTest.kt +30 −7 Original line number Diff line number Diff line Loading @@ -16,20 +16,27 @@ package com.android.customization.picker.mode.ui.viewmodel import android.content.Context import androidx.test.platform.app.InstrumentationRegistry import com.android.customization.module.logging.TestThemesUserEventLogger import com.android.customization.picker.mode.data.repository.DarkModeRepository import com.android.customization.picker.mode.data.repository.DarkModeStateRepository import com.android.customization.picker.mode.domain.interactor.DarkModeInteractor import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel import com.android.wallpaper.testing.FakePowerManager import com.android.wallpaper.testing.FakeUiModeManager import com.android.wallpaper.testing.collectLastValue import com.google.common.truth.Truth.assertThat import dagger.hilt.android.internal.lifecycle.RetainedLifecycleImpl import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.Before Loading @@ -46,20 +53,26 @@ class DarkModeViewModelTest { @Inject lateinit var uiModeManager: FakeUiModeManager @Inject lateinit var powerManager: FakePowerManager @Inject lateinit var darkModeStateRepository: DarkModeStateRepository @Inject lateinit var darkModeRepository: DarkModeRepository @Inject lateinit var darkModeInteractor: DarkModeInteractor @Inject lateinit var logger: TestThemesUserEventLogger private lateinit var darkModeViewModel: DarkModeViewModel @Inject lateinit var testDispatcher: TestDispatcher @Inject lateinit var testScope: TestScope private lateinit var context: Context private lateinit var colorUpdateViewModel: ColorUpdateViewModel private lateinit var darkModeViewModel: DarkModeViewModel @Before fun setUp() { hiltRule.inject() Dispatchers.setMain(testDispatcher) darkModeViewModel = DarkModeViewModel(darkModeInteractor, logger) context = InstrumentationRegistry.getInstrumentation().targetContext colorUpdateViewModel = ColorUpdateViewModel(context, RetainedLifecycleImpl(), darkModeStateRepository) darkModeViewModel = DarkModeViewModel(colorUpdateViewModel, darkModeInteractor, logger) } @Test Loading Loading @@ -141,10 +154,9 @@ class DarkModeViewModelTest { uiModeManager.setNightModeActivated(false) darkModeRepository.refreshIsDarkMode() val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode) val onApply = collectLastValue(darkModeViewModel.onApply) getToggleDarkMode()?.invoke() onApply()?.invoke() applyDarkMode() assertThat(logger.useDarkTheme).isTrue() } Loading @@ -156,12 +168,23 @@ class DarkModeViewModelTest { uiModeManager.setNightModeActivated(false) darkModeRepository.refreshIsDarkMode() val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode) val onApply = collectLastValue(darkModeViewModel.onApply) getToggleDarkMode()?.invoke() onApply()?.invoke() applyDarkMode() assertThat(uiModeManager.getIsNightModeActivated()).isTrue() } } /** Simulates a user applying the previewing dark mode, and the apply completes. */ private fun TestScope.applyDarkMode() { val onApply = collectLastValue(darkModeViewModel.onApply)() testScope.launch { onApply?.invoke() } // Run coroutine launched in DarkModeViewModel#onApply runCurrent() // Simulate dark mode and color update config change colorUpdateViewModel.updateDarkModeAndColors() // Run coroutine launched in colorUpdateViewModel#updateColors runCurrent() } }