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

Commit 60c985fc authored by William Xiao's avatar William Xiao
Browse files

Add new communal location for UMO

The communal hub UI is accessed from an edge swipe from either the
lock screen or from on top of the dream. We intend for the UMO to be
visible there if media is playing or has played recently.

Bug: 308638964
Bug: 304584416
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Test: atest MediaHierarchyManagerTest
Change-Id: If68dee0d3b56df43e53d7066f9aed1ed2c59afb9
parent 1c93bfcc
Loading
Loading
Loading
Loading
+33 −12
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -29,12 +30,9 @@ import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.transitions
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel

object Scenes {
    val Blank = SceneKey(name = "blank")
    val Communal = SceneKey(name = "communal")
}
import kotlinx.coroutines.flow.transform

object Communal {
    object Elements {
@@ -43,7 +41,7 @@ object Communal {
}

val sceneTransitions = transitions {
    from(Scenes.Blank, to = Scenes.Communal) {
    from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) {
        spec = tween(durationMillis = 500)

        translate(Communal.Elements.Content, Edge.Right)
@@ -58,8 +56,14 @@ val sceneTransitions = transitions {
 * handling and transitions before the full Flexiglass layout is ready.
 */
@Composable
fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
    val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
fun CommunalContainer(
    modifier: Modifier = Modifier,
    viewModel: CommunalViewModel,
) {
    val currentScene: SceneKey by
        viewModel.currentScene
            .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
            .collectAsState(TransitionSceneKey.Blank)

    // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
    var showSceneTransitionLayout by remember { mutableStateOf(true) }
@@ -70,16 +74,19 @@ fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewMode
    SceneTransitionLayout(
        modifier = modifier.fillMaxSize(),
        currentScene = currentScene,
        onChangeScene = setCurrentScene,
        onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
        transitions = sceneTransitions,
    ) {
        scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
        scene(
            TransitionSceneKey.Blank,
            userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal)
        ) {
            BlankScene { showSceneTransitionLayout = false }
        }

        scene(
            Scenes.Communal,
            userActions = mapOf(Swipe.Right to Scenes.Blank),
            TransitionSceneKey.Communal,
            userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank),
        ) {
            CommunalScene(viewModel, modifier = modifier)
        }
@@ -121,3 +128,17 @@ private fun SceneScope.CommunalScene(
) {
    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
}

// TODO(b/293899074): Remove these conversions once Compose can be used throughout SysUI.
object TransitionSceneKey {
    val Blank = CommunalSceneKey.Blank.toTransitionSceneKey()
    val Communal = CommunalSceneKey.Communal.toTransitionSceneKey()
}

fun CommunalSceneKey.toTransitionSceneKey(): SceneKey {
    return SceneKey(name = toString(), identity = this)
}

fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
    return this.identity as CommunalSceneKey
}
+21 −0
Original line number Diff line number Diff line
package com.android.systemui.communal.data.repository

import com.android.systemui.FeatureFlags
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

/** Encapsulates the state of communal mode. */
interface CommunalRepository {
    /** Whether communal features are enabled. */
    val isCommunalEnabled: Boolean

    /**
     * Target scene as requested by the underlying [SceneTransitionLayout] or through
     * [setDesiredScene].
     */
    val desiredScene: StateFlow<CommunalSceneKey>

    /** Updates the requested scene. */
    fun setDesiredScene(desiredScene: CommunalSceneKey)
}

@SysUISingleton
@@ -23,4 +36,12 @@ constructor(
        get() =
            featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
                featureFlags.communalHub()

    private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
        MutableStateFlow(CommunalSceneKey.Blank)
    override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()

    override fun setDesiredScene(desiredScene: CommunalSceneKey) {
        _desiredScene.value = desiredScene
    }
}
+21 −0
Original line number Diff line number Diff line
@@ -19,10 +19,13 @@ package com.android.systemui.communal.domain.interactor
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map

/** Encapsulates business-logic related to communal mode. */
@SysUISingleton
@@ -47,4 +50,22 @@ constructor(
     * (have an allocated id).
     */
    val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets

    /**
     * Target scene as requested by the underlying [SceneTransitionLayout] or through
     * [onSceneChanged].
     */
    val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene

    /**
     * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
     * [CommunalSceneKey.Communal].
     */
    val isCommunalShowing: Flow<Boolean> =
        communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }

    /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
    fun onSceneChanged(newScene: CommunalSceneKey) {
        communalRepository.setDesiredScene(newScene)
    }
}
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.communal.shared.model

/** Definition of the possible scenes for the communal UI. */
sealed class CommunalSceneKey(
    private val loggingName: String,
) {
    /** The communal scene containing the hub UI. */
    object Communal : CommunalSceneKey("communal")

    /** The default scene, shows nothing and is only there to allow swiping to communal. */
    object Blank : CommunalSceneKey("blank")

    override fun toString(): String {
        return loggingName
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -20,11 +20,13 @@ import android.appwidget.AppWidgetHost
import android.content.Context
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map

@SysUISingleton
@@ -33,7 +35,7 @@ class CommunalViewModel
constructor(
    @Application private val context: Context,
    private val appWidgetHost: AppWidgetHost,
    communalInteractor: CommunalInteractor,
    private val communalInteractor: CommunalInteractor,
    tutorialInteractor: CommunalTutorialInteractor,
) {
    /** Whether communal hub should show tutorial content. */
@@ -54,4 +56,9 @@ constructor(
                )
            }
        }

    val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
    fun onSceneChanged(scene: CommunalSceneKey) {
        communalInteractor.onSceneChanged(scene)
    }
}
Loading