Loading src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt +6 −3 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.customization.picker.settings.data.repository package com.android.customization.picker.settings.data.repository import android.app.UiModeManager import android.app.UiModeManager import android.app.UiModeManager.ContrastUtils import com.android.wallpaper.picker.di.modules.BackgroundDispatcher import com.android.wallpaper.picker.di.modules.BackgroundDispatcher import com.android.wallpaper.system.UiModeManagerWrapper import com.android.wallpaper.system.UiModeManagerWrapper import java.util.concurrent.Executor import java.util.concurrent.Executor Loading @@ -35,16 +36,18 @@ constructor( uiModeManager: UiModeManagerWrapper, uiModeManager: UiModeManagerWrapper, @BackgroundDispatcher bgDispatcher: CoroutineDispatcher, @BackgroundDispatcher bgDispatcher: CoroutineDispatcher, ) { ) { var contrast: Flow<Float> = callbackFlow { var contrast: Flow<Int> = callbackFlow { val executor: Executor = bgDispatcher.asExecutor() val executor: Executor = bgDispatcher.asExecutor() val listener = val listener = UiModeManager.ContrastChangeListener { contrast -> UiModeManager.ContrastChangeListener { contrast -> // Emit the new contrast value whenever it changes // Emit the new contrast value whenever it changes trySend(contrast) trySend(ContrastUtils.toContrastLevel(contrast)) } } // Emit the current contrast value immediately // Emit the current contrast value immediately uiModeManager.getContrast()?.let { currentContrast -> trySend(currentContrast) } uiModeManager.getContrast()?.let { currentContrast -> trySend(ContrastUtils.toContrastLevel(currentContrast)) } uiModeManager.addContrastChangeListener(executor, listener) uiModeManager.addContrastChangeListener(executor, listener) Loading src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -25,5 +25,5 @@ import kotlinx.coroutines.flow.Flow class ColorContrastSectionInteractor class ColorContrastSectionInteractor @Inject @Inject constructor(colorContrastSectionRepository: ColorContrastSectionRepository) { constructor(colorContrastSectionRepository: ColorContrastSectionRepository) { val contrast: Flow<Float> = colorContrastSectionRepository.contrast val contrast: Flow<Int> = colorContrastSectionRepository.contrast } } src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionViewModel.kt +20 −24 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.customization.picker.settings.ui.viewmodel package com.android.customization.picker.settings.ui.viewmodel import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor Loading @@ -28,62 +32,54 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map class ColorContrastSectionViewModel class ColorContrastSectionViewModel private constructor( private constructor(colorContrastSectionInteractor: ColorContrastSectionInteractor) : ViewModel() { colorContrastSectionInteractor: ColorContrastSectionInteractor, ) : ViewModel() { val summary: Flow<ColorContrastSectionDataViewModel> = val summary: Flow<ColorContrastSectionDataViewModel> = colorContrastSectionInteractor.contrast.map { contrastValue -> colorContrastSectionInteractor.contrast.map { contrastValue -> when (contrastValue) { when (contrastValue) { ContrastValue.STANDARD.value -> CONTRAST_LEVEL_STANDARD -> ColorContrastSectionDataViewModel( ColorContrastSectionDataViewModel( Text.Resource(R.string.color_contrast_default_title), Text.Resource(R.string.color_contrast_default_title), Icon.Resource( Icon.Resource( res = R.drawable.ic_contrast_standard, res = R.drawable.ic_contrast_standard, contentDescription = null, contentDescription = null, ), ) ) ) CONTRAST_LEVEL_MEDIUM -> ContrastValue.MEDIUM.value -> ColorContrastSectionDataViewModel( ColorContrastSectionDataViewModel( Text.Resource(R.string.color_contrast_medium_title), Text.Resource(R.string.color_contrast_medium_title), Icon.Resource( Icon.Resource( res = R.drawable.ic_contrast_medium, res = R.drawable.ic_contrast_medium, contentDescription = null, contentDescription = null, ), ) ) ) CONTRAST_LEVEL_HIGH -> ContrastValue.HIGH.value -> ColorContrastSectionDataViewModel( ColorContrastSectionDataViewModel( Text.Resource(R.string.color_contrast_high_title), Text.Resource(R.string.color_contrast_high_title), Icon.Resource( Icon.Resource(res = R.drawable.ic_contrast_high, contentDescription = null), res = R.drawable.ic_contrast_high, contentDescription = null, ) ) ) else -> { else -> { println("Invalid contrast value: $contrastValue") Log.e(TAG, "Invalid contrast value: $contrastValue") throw IllegalArgumentException("Invalid contrast value") throw IllegalArgumentException("Invalid contrast value: $contrastValue") } } } } } } enum class ContrastValue(val value: Float) { STANDARD(0f), MEDIUM(0.5f), HIGH(1f) } @Singleton @Singleton class Factory class Factory @Inject @Inject constructor( constructor(private val colorContrastSectionInteractor: ColorContrastSectionInteractor) : private val colorContrastSectionInteractor: ColorContrastSectionInteractor, ViewModelProvider.Factory { ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST") return ColorContrastSectionViewModel( return ColorContrastSectionViewModel( colorContrastSectionInteractor = colorContrastSectionInteractor, colorContrastSectionInteractor = colorContrastSectionInteractor ) ) as T as T } } } } companion object { private const val TAG = "ColorContrastSectionViewModel" } } } tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt +5 −2 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.customization.model.picker.settings.data.repository package com.android.customization.model.picker.settings.data.repository import android.app.UiModeManager.ContrastUtils import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository import com.android.wallpaper.testing.FakeUiModeManager import com.android.wallpaper.testing.FakeUiModeManager Loading Loading @@ -61,8 +62,10 @@ class ColorContrastSectionRepositoryTest { fun contrastFlowEmitsValues() = fun contrastFlowEmitsValues() = testScope.runTest { testScope.runTest { val nextContrastValues = listOf(0.5f, 0.7f, 0.8f) val nextContrastValues = listOf(0.5f, 0.7f, 0.8f) val expectedContrastValues = nextContrastValues.map { ContrastUtils.toContrastLevel(it) } // Set up a flow to collect all contrast values // Set up a flow to collect all contrast values val flowCollector = mutableListOf<Float>() val flowCollector = mutableListOf<Int>() // Start collecting values from the flow, using an unconfined dispatcher to start // Start collecting values from the flow, using an unconfined dispatcher to start // collecting from the flow right away (rather than explicitly calling `runCurrent`) // collecting from the flow right away (rather than explicitly calling `runCurrent`) // See https://developer.android.com/kotlin/flow/test#continuous-collection // See https://developer.android.com/kotlin/flow/test#continuous-collection Loading @@ -74,6 +77,6 @@ class ColorContrastSectionRepositoryTest { // Ignore the first contrast value from constructing the repository // Ignore the first contrast value from constructing the repository val collectedValues = flowCollector.drop(1) val collectedValues = flowCollector.drop(1) assertThat(collectedValues).containsExactlyElementsIn(nextContrastValues) assertThat(collectedValues).containsExactlyElementsIn(expectedContrastValues) } } } } tests/robotests/src/com/android/customization/model/picker/settings/domain/interactor/ColorContrastSectionInteractorTest.kt +5 −3 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.customization.model.picker.settings.domain.interactor package com.android.customization.model.picker.settings.domain.interactor import android.app.UiModeManager.ContrastUtils import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor import com.android.wallpaper.testing.FakeUiModeManager import com.android.wallpaper.testing.FakeUiModeManager Loading Loading @@ -47,11 +48,12 @@ class ColorContrastSectionInteractorTest { @Test @Test fun contrastEmitCorrectValuesFromRepository() = runTest { fun contrastEmitCorrectValuesFromRepository() = runTest { val expectedContrast = 1.5f val contrastVal = 0.5f uiModeManager.setContrast(expectedContrast) val expectedContrastVal = ContrastUtils.toContrastLevel(contrastVal) uiModeManager.setContrast(contrastVal) val result = interactor.contrast.first() val result = interactor.contrast.first() assertThat(result).isEqualTo(expectedContrast) assertThat(result).isEqualTo(expectedContrastVal) } } } } Loading
src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt +6 −3 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.customization.picker.settings.data.repository package com.android.customization.picker.settings.data.repository import android.app.UiModeManager import android.app.UiModeManager import android.app.UiModeManager.ContrastUtils import com.android.wallpaper.picker.di.modules.BackgroundDispatcher import com.android.wallpaper.picker.di.modules.BackgroundDispatcher import com.android.wallpaper.system.UiModeManagerWrapper import com.android.wallpaper.system.UiModeManagerWrapper import java.util.concurrent.Executor import java.util.concurrent.Executor Loading @@ -35,16 +36,18 @@ constructor( uiModeManager: UiModeManagerWrapper, uiModeManager: UiModeManagerWrapper, @BackgroundDispatcher bgDispatcher: CoroutineDispatcher, @BackgroundDispatcher bgDispatcher: CoroutineDispatcher, ) { ) { var contrast: Flow<Float> = callbackFlow { var contrast: Flow<Int> = callbackFlow { val executor: Executor = bgDispatcher.asExecutor() val executor: Executor = bgDispatcher.asExecutor() val listener = val listener = UiModeManager.ContrastChangeListener { contrast -> UiModeManager.ContrastChangeListener { contrast -> // Emit the new contrast value whenever it changes // Emit the new contrast value whenever it changes trySend(contrast) trySend(ContrastUtils.toContrastLevel(contrast)) } } // Emit the current contrast value immediately // Emit the current contrast value immediately uiModeManager.getContrast()?.let { currentContrast -> trySend(currentContrast) } uiModeManager.getContrast()?.let { currentContrast -> trySend(ContrastUtils.toContrastLevel(currentContrast)) } uiModeManager.addContrastChangeListener(executor, listener) uiModeManager.addContrastChangeListener(executor, listener) Loading
src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -25,5 +25,5 @@ import kotlinx.coroutines.flow.Flow class ColorContrastSectionInteractor class ColorContrastSectionInteractor @Inject @Inject constructor(colorContrastSectionRepository: ColorContrastSectionRepository) { constructor(colorContrastSectionRepository: ColorContrastSectionRepository) { val contrast: Flow<Float> = colorContrastSectionRepository.contrast val contrast: Flow<Int> = colorContrastSectionRepository.contrast } }
src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionViewModel.kt +20 −24 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.customization.picker.settings.ui.viewmodel package com.android.customization.picker.settings.ui.viewmodel import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor Loading @@ -28,62 +32,54 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map class ColorContrastSectionViewModel class ColorContrastSectionViewModel private constructor( private constructor(colorContrastSectionInteractor: ColorContrastSectionInteractor) : ViewModel() { colorContrastSectionInteractor: ColorContrastSectionInteractor, ) : ViewModel() { val summary: Flow<ColorContrastSectionDataViewModel> = val summary: Flow<ColorContrastSectionDataViewModel> = colorContrastSectionInteractor.contrast.map { contrastValue -> colorContrastSectionInteractor.contrast.map { contrastValue -> when (contrastValue) { when (contrastValue) { ContrastValue.STANDARD.value -> CONTRAST_LEVEL_STANDARD -> ColorContrastSectionDataViewModel( ColorContrastSectionDataViewModel( Text.Resource(R.string.color_contrast_default_title), Text.Resource(R.string.color_contrast_default_title), Icon.Resource( Icon.Resource( res = R.drawable.ic_contrast_standard, res = R.drawable.ic_contrast_standard, contentDescription = null, contentDescription = null, ), ) ) ) CONTRAST_LEVEL_MEDIUM -> ContrastValue.MEDIUM.value -> ColorContrastSectionDataViewModel( ColorContrastSectionDataViewModel( Text.Resource(R.string.color_contrast_medium_title), Text.Resource(R.string.color_contrast_medium_title), Icon.Resource( Icon.Resource( res = R.drawable.ic_contrast_medium, res = R.drawable.ic_contrast_medium, contentDescription = null, contentDescription = null, ), ) ) ) CONTRAST_LEVEL_HIGH -> ContrastValue.HIGH.value -> ColorContrastSectionDataViewModel( ColorContrastSectionDataViewModel( Text.Resource(R.string.color_contrast_high_title), Text.Resource(R.string.color_contrast_high_title), Icon.Resource( Icon.Resource(res = R.drawable.ic_contrast_high, contentDescription = null), res = R.drawable.ic_contrast_high, contentDescription = null, ) ) ) else -> { else -> { println("Invalid contrast value: $contrastValue") Log.e(TAG, "Invalid contrast value: $contrastValue") throw IllegalArgumentException("Invalid contrast value") throw IllegalArgumentException("Invalid contrast value: $contrastValue") } } } } } } enum class ContrastValue(val value: Float) { STANDARD(0f), MEDIUM(0.5f), HIGH(1f) } @Singleton @Singleton class Factory class Factory @Inject @Inject constructor( constructor(private val colorContrastSectionInteractor: ColorContrastSectionInteractor) : private val colorContrastSectionInteractor: ColorContrastSectionInteractor, ViewModelProvider.Factory { ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST") return ColorContrastSectionViewModel( return ColorContrastSectionViewModel( colorContrastSectionInteractor = colorContrastSectionInteractor, colorContrastSectionInteractor = colorContrastSectionInteractor ) ) as T as T } } } } companion object { private const val TAG = "ColorContrastSectionViewModel" } } }
tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt +5 −2 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.customization.model.picker.settings.data.repository package com.android.customization.model.picker.settings.data.repository import android.app.UiModeManager.ContrastUtils import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository import com.android.wallpaper.testing.FakeUiModeManager import com.android.wallpaper.testing.FakeUiModeManager Loading Loading @@ -61,8 +62,10 @@ class ColorContrastSectionRepositoryTest { fun contrastFlowEmitsValues() = fun contrastFlowEmitsValues() = testScope.runTest { testScope.runTest { val nextContrastValues = listOf(0.5f, 0.7f, 0.8f) val nextContrastValues = listOf(0.5f, 0.7f, 0.8f) val expectedContrastValues = nextContrastValues.map { ContrastUtils.toContrastLevel(it) } // Set up a flow to collect all contrast values // Set up a flow to collect all contrast values val flowCollector = mutableListOf<Float>() val flowCollector = mutableListOf<Int>() // Start collecting values from the flow, using an unconfined dispatcher to start // Start collecting values from the flow, using an unconfined dispatcher to start // collecting from the flow right away (rather than explicitly calling `runCurrent`) // collecting from the flow right away (rather than explicitly calling `runCurrent`) // See https://developer.android.com/kotlin/flow/test#continuous-collection // See https://developer.android.com/kotlin/flow/test#continuous-collection Loading @@ -74,6 +77,6 @@ class ColorContrastSectionRepositoryTest { // Ignore the first contrast value from constructing the repository // Ignore the first contrast value from constructing the repository val collectedValues = flowCollector.drop(1) val collectedValues = flowCollector.drop(1) assertThat(collectedValues).containsExactlyElementsIn(nextContrastValues) assertThat(collectedValues).containsExactlyElementsIn(expectedContrastValues) } } } }
tests/robotests/src/com/android/customization/model/picker/settings/domain/interactor/ColorContrastSectionInteractorTest.kt +5 −3 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.customization.model.picker.settings.domain.interactor package com.android.customization.model.picker.settings.domain.interactor import android.app.UiModeManager.ContrastUtils import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor import com.android.wallpaper.testing.FakeUiModeManager import com.android.wallpaper.testing.FakeUiModeManager Loading Loading @@ -47,11 +48,12 @@ class ColorContrastSectionInteractorTest { @Test @Test fun contrastEmitCorrectValuesFromRepository() = runTest { fun contrastEmitCorrectValuesFromRepository() = runTest { val expectedContrast = 1.5f val contrastVal = 0.5f uiModeManager.setContrast(expectedContrast) val expectedContrastVal = ContrastUtils.toContrastLevel(contrastVal) uiModeManager.setContrast(contrastVal) val result = interactor.contrast.first() val result = interactor.contrast.first() assertThat(result).isEqualTo(expectedContrast) assertThat(result).isEqualTo(expectedContrastVal) } } } }