blob: 5055ee1d73f6c343aae49dcb21ce0cf2c420f01b [file] [log] [blame]
/*
* Copyright (C) 2022 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.compose
import android.content.Context
import android.graphics.Point
import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** The Compose facade, when Compose is available. */
object ComposeFacade : BaseComposeFacade {
override fun isComposeAvailable(): Boolean = true
override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl
override fun setPeopleSpaceActivityContent(
activity: ComponentActivity,
viewModel: PeopleViewModel,
onResult: (PeopleViewModel.Result) -> Unit,
) {
activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
}
override fun setCommunalEditWidgetActivityContent(
activity: ComponentActivity,
viewModel: BaseCommunalViewModel,
onOpenWidgetPicker: () -> Unit,
onEditDone: () -> Unit,
) {
activity.setContent {
PlatformTheme {
CommunalHub(
viewModel = viewModel,
onOpenWidgetPicker = onOpenWidgetPicker,
onEditDone = onEditDone,
)
}
}
}
override fun createFooterActionsView(
context: Context,
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
): View {
return ComposeView(context).apply {
setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
}
}
override fun createSceneContainerView(
scope: CoroutineScope,
context: Context,
viewModel: SceneContainerViewModel,
windowInsets: StateFlow<WindowInsets?>,
sceneByKey: Map<SceneKey, Scene>,
): View {
return ComposeView(context).apply {
setContent {
PlatformTheme {
DisplayCutoutProvider(
displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
) {
SceneContainer(
viewModel = viewModel,
sceneByKey =
sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
)
}
}
}
}
}
override fun createCommunalView(
context: Context,
viewModel: BaseCommunalViewModel,
): View {
return ComposeView(context).apply {
setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
}
}
override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
return ComposeView(context).apply {
setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
}
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private fun displayCutoutFromWindowInsets(
scope: CoroutineScope,
context: Context,
windowInsets: StateFlow<WindowInsets?>,
): StateFlow<DisplayCutout> =
windowInsets
.map {
val boundingRect = it?.displayCutout?.boundingRectTop
val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0
val left = boundingRect?.left?.toDp(context) ?: 0.dp
val top = boundingRect?.top?.toDp(context) ?: 0.dp
val right = boundingRect?.right?.toDp(context) ?: 0.dp
val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp
val location =
when {
width <= 0f -> CutoutLocation.NONE
left <= 0.dp -> CutoutLocation.LEFT
right >= getDisplayWidth(context) -> CutoutLocation.RIGHT
else -> CutoutLocation.CENTER
}
DisplayCutout(
left,
top,
right,
bottom,
location,
)
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private fun getDisplayWidth(context: Context): Dp {
val point = Point()
checkNotNull(context.display).getRealSize(point)
return point.x.dp
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private fun Int.toDp(context: Context): Dp {
return (this.toFloat() / context.resources.displayMetrics.density).dp
}
}