Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +68 −13 Original line number Diff line number Diff line Loading @@ -19,17 +19,20 @@ package com.android.systemui.communal.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.compose.animation.scene.SceneScope import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection Loading @@ -39,8 +42,11 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory import javax.inject.Inject import kotlin.math.min import kotlin.math.roundToInt /** Renders the content of the glanceable hub. */ class CommunalContent Loading @@ -48,6 +54,7 @@ class CommunalContent constructor( private val viewModel: CommunalViewModel, private val interactionHandler: SmartspaceInteractionHandler, private val communalSettingsInteractor: CommunalSettingsInteractor, private val dialogFactory: SystemUIDialogFactory, private val lockSection: LockSection, private val bottomAreaSection: BottomAreaSection, Loading Loading @@ -77,12 +84,21 @@ constructor( sceneScope = this@Content, ) } if (communalSettingsInteractor.isV2FlagEnabled()) { Icon( painter = painterResource(id = R.drawable.ic_lock), contentDescription = null, tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.element(Communal.Elements.LockIcon), ) } else { with(lockSection) { LockIcon( overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.element(Communal.Elements.LockIcon), ) } } with(bottomAreaSection) { IndicationArea( Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth() Loading @@ -98,14 +114,42 @@ constructor( val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) val lockIconPlaceable = if (communalSettingsInteractor.isV2FlagEnabled()) { val lockIconSizeInt = lockIconSize.roundToPx() lockIconMeasurable.measure( Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt) ) } else { lockIconMeasurable.measure(noMinConstraints) } val lockIconBounds = if (communalSettingsInteractor.isV2FlagEnabled()) { val lockIconDistanceFromBottom = min( (constraints.maxHeight * lockIconPercentDistanceFromBottom) .roundToInt(), lockIconMinDistanceFromBottom.roundToPx(), ) val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2 val y = constraints.maxHeight - lockIconDistanceFromBottom - lockIconPlaceable.height IntRect( left = x, top = y, right = x + lockIconPlaceable.width, bottom = y + lockIconPlaceable.height, ) } else { IntRect( left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], ) } val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints) Loading @@ -129,12 +173,17 @@ constructor( val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height bottomAreaPlaceable.place(x = 0, y = bottomAreaTop) val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx() screensaverButtonPlaceable?.place( x = constraints.maxWidth - screensaverButtonSizeInt - Dimensions.ItemSpacing.roundToPx(), y = lockIconBounds.top, screensaverButtonPaddingInt, y = constraints.maxHeight - screensaverButtonSizeInt - screensaverButtonPaddingInt, ) } } Loading @@ -142,6 +191,12 @@ constructor( } companion object { val screensaverButtonSize: Dp = 64.dp private val screensaverButtonSize: Dp = 64.dp private val screensaverButtonPadding: Dp = 24.dp // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area // position are sorted. private val lockIconSize: Dp = 54.dp private val lockIconPercentDistanceFromBottom = 0.1f private val lockIconMinDistanceFromBottom = 70.dp } } packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +28 −0 Original line number Diff line number Diff line Loading @@ -304,6 +304,34 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT } } @Test fun screensaverDisabledByUser() = testScope.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id, ) assertThat(enabledState).isFalse() } @Test fun screensaverEnabledByUser() = testScope.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id, ) assertThat(enabledState).isTrue() } private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +30 −12 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest Loading @@ -29,12 +30,16 @@ import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.whenever Loading @@ -56,10 +61,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test fun shouldShowDreamButtonOnHub_trueWhenCanDream() = fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() = with(kosmos) { runTest { whenever(dreamManager.canStartDreaming(any())).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(true) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) Loading @@ -68,11 +72,10 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test fun shouldShowDreamButtonOnHub_falseWhenCannotDream() = fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = with(kosmos) { runTest { whenever(dreamManager.canStartDreaming(any())).thenReturn(false) whenever(batteryController.isPluggedIn()).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(false) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) assertThat(shouldShowButton).isFalse() Loading @@ -80,25 +83,40 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = fun onShowDreamButtonTap_dreamsEnabled_startsDream() = with(kosmos) { runTest { whenever(dreamManager.canStartDreaming(any())).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(false) val currentUser = fakeUserRepository.asMainUser() kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 1, currentUser.id, ) runCurrent() val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) assertThat(shouldShowButton).isFalse() underTest.onShowDreamButtonTap() runCurrent() verify(dreamManager).startDream() } } @Test fun onShowDreamButtonTap_startsDream() = fun onShowDreamButtonTap_dreamsDisabled_startsActivity() = with(kosmos) { runTest { val currentUser = fakeUserRepository.asMainUser() kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 0, currentUser.id, ) runCurrent() underTest.onShowDreamButtonTap() runCurrent() verify(dreamManager).startDream() verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt()) } } } packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +17 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,8 @@ interface CommunalSettingsRepository { /** A [CommunalEnabledState] for the specified user. */ fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * Loading Loading @@ -138,6 +140,20 @@ constructor( .flowOn(bgDispatcher) } override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> = secureSettings .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED)) // Force an update .onStart { emit(Unit) } .map { secureSettings.getIntForUser( Settings.Secure.SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_SETTING_DEFAULT, user.id, ) == 1 } .flowOn(bgDispatcher) override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( Loading Loading @@ -182,6 +198,7 @@ constructor( companion object { const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background" private const val ENABLED_SETTING_DEFAULT = 1 private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0 } } Loading packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +6 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,12 @@ constructor( // Start this eagerly since the value is accessed synchronously in many places. .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) /** Whether or not screensaver (dreams) is enabled for the currently selected user. */ val isScreensaverEnabled: Flow<Boolean> = userInteractor.selectedUserInfo.flatMapLatest { user -> repository.getScreensaverEnabledState(user) } /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +68 −13 Original line number Diff line number Diff line Loading @@ -19,17 +19,20 @@ package com.android.systemui.communal.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.compose.animation.scene.SceneScope import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection Loading @@ -39,8 +42,11 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory import javax.inject.Inject import kotlin.math.min import kotlin.math.roundToInt /** Renders the content of the glanceable hub. */ class CommunalContent Loading @@ -48,6 +54,7 @@ class CommunalContent constructor( private val viewModel: CommunalViewModel, private val interactionHandler: SmartspaceInteractionHandler, private val communalSettingsInteractor: CommunalSettingsInteractor, private val dialogFactory: SystemUIDialogFactory, private val lockSection: LockSection, private val bottomAreaSection: BottomAreaSection, Loading Loading @@ -77,12 +84,21 @@ constructor( sceneScope = this@Content, ) } if (communalSettingsInteractor.isV2FlagEnabled()) { Icon( painter = painterResource(id = R.drawable.ic_lock), contentDescription = null, tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.element(Communal.Elements.LockIcon), ) } else { with(lockSection) { LockIcon( overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.element(Communal.Elements.LockIcon), ) } } with(bottomAreaSection) { IndicationArea( Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth() Loading @@ -98,14 +114,42 @@ constructor( val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) val lockIconPlaceable = if (communalSettingsInteractor.isV2FlagEnabled()) { val lockIconSizeInt = lockIconSize.roundToPx() lockIconMeasurable.measure( Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt) ) } else { lockIconMeasurable.measure(noMinConstraints) } val lockIconBounds = if (communalSettingsInteractor.isV2FlagEnabled()) { val lockIconDistanceFromBottom = min( (constraints.maxHeight * lockIconPercentDistanceFromBottom) .roundToInt(), lockIconMinDistanceFromBottom.roundToPx(), ) val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2 val y = constraints.maxHeight - lockIconDistanceFromBottom - lockIconPlaceable.height IntRect( left = x, top = y, right = x + lockIconPlaceable.width, bottom = y + lockIconPlaceable.height, ) } else { IntRect( left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], ) } val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints) Loading @@ -129,12 +173,17 @@ constructor( val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height bottomAreaPlaceable.place(x = 0, y = bottomAreaTop) val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx() screensaverButtonPlaceable?.place( x = constraints.maxWidth - screensaverButtonSizeInt - Dimensions.ItemSpacing.roundToPx(), y = lockIconBounds.top, screensaverButtonPaddingInt, y = constraints.maxHeight - screensaverButtonSizeInt - screensaverButtonPaddingInt, ) } } Loading @@ -142,6 +191,12 @@ constructor( } companion object { val screensaverButtonSize: Dp = 64.dp private val screensaverButtonSize: Dp = 64.dp private val screensaverButtonPadding: Dp = 24.dp // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area // position are sorted. private val lockIconSize: Dp = 54.dp private val lockIconPercentDistanceFromBottom = 0.1f private val lockIconMinDistanceFromBottom = 70.dp } }
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +28 −0 Original line number Diff line number Diff line Loading @@ -304,6 +304,34 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT } } @Test fun screensaverDisabledByUser() = testScope.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id, ) assertThat(enabledState).isFalse() } @Test fun screensaverEnabledByUser() = testScope.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id, ) assertThat(enabledState).isTrue() } private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +30 −12 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest Loading @@ -29,12 +30,16 @@ import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.whenever Loading @@ -56,10 +61,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test fun shouldShowDreamButtonOnHub_trueWhenCanDream() = fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() = with(kosmos) { runTest { whenever(dreamManager.canStartDreaming(any())).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(true) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) Loading @@ -68,11 +72,10 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test fun shouldShowDreamButtonOnHub_falseWhenCannotDream() = fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = with(kosmos) { runTest { whenever(dreamManager.canStartDreaming(any())).thenReturn(false) whenever(batteryController.isPluggedIn()).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(false) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) assertThat(shouldShowButton).isFalse() Loading @@ -80,25 +83,40 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = fun onShowDreamButtonTap_dreamsEnabled_startsDream() = with(kosmos) { runTest { whenever(dreamManager.canStartDreaming(any())).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(false) val currentUser = fakeUserRepository.asMainUser() kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 1, currentUser.id, ) runCurrent() val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) assertThat(shouldShowButton).isFalse() underTest.onShowDreamButtonTap() runCurrent() verify(dreamManager).startDream() } } @Test fun onShowDreamButtonTap_startsDream() = fun onShowDreamButtonTap_dreamsDisabled_startsActivity() = with(kosmos) { runTest { val currentUser = fakeUserRepository.asMainUser() kosmos.fakeSettings.putIntForUser( Settings.Secure.SCREENSAVER_ENABLED, 0, currentUser.id, ) runCurrent() underTest.onShowDreamButtonTap() runCurrent() verify(dreamManager).startDream() verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt()) } } }
packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +17 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,8 @@ interface CommunalSettingsRepository { /** A [CommunalEnabledState] for the specified user. */ fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * Loading Loading @@ -138,6 +140,20 @@ constructor( .flowOn(bgDispatcher) } override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> = secureSettings .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED)) // Force an update .onStart { emit(Unit) } .map { secureSettings.getIntForUser( Settings.Secure.SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_SETTING_DEFAULT, user.id, ) == 1 } .flowOn(bgDispatcher) override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( Loading Loading @@ -182,6 +198,7 @@ constructor( companion object { const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background" private const val ENABLED_SETTING_DEFAULT = 1 private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0 } } Loading
packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +6 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,12 @@ constructor( // Start this eagerly since the value is accessed synchronously in many places. .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) /** Whether or not screensaver (dreams) is enabled for the currently selected user. */ val isScreensaverEnabled: Flow<Boolean> = userInteractor.selectedUserInfo.flatMapLatest { user -> repository.getScreensaverEnabledState(user) } /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * Loading