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

Commit a22b29fb authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Move volume panel from activity to a dialog." into main

parents 52aa023c e5b8df8e
Loading
Loading
Loading
Loading
+0 −8
Original line number Diff line number Diff line
@@ -906,14 +906,6 @@
                  android:exported="true"
                  />

        <activity
            android:name=".volume.panel.ui.activity.VolumePanelActivity"
            android:label="@string/accessibility_volume_settings"
            android:excludeFromRecents="true"
            android:exported="false"
            android:launchMode="singleInstance"
            android:theme="@style/Theme.VolumePanelActivity" />

        <activity android:name=".wallet.ui.WalletActivity"
                  android:label="@string/wallet_title"
                  android:theme="@style/Wallet.Theme"
+43 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * Copyright (C) 2024 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.
@@ -14,30 +14,30 @@
 * limitations under the License.
 */

package com.android.systemui.volume.panel.dagger
package com.android.systemui.statusbar.phone

import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import android.os.Bundle
import android.view.Gravity
import android.view.WindowManager

/** Provides Volume Panel coroutine tools. */
@Module
interface CoroutineModule {
/** [DialogDelegate] that configures a dialog to be an edge-to-edge one. */
class EdgeToEdgeDialogDelegate : DialogDelegate<SystemUIDialog> {

    companion object {

        /**
         * Provides a coroutine scope to use inside [VolumePanelScope].
         * [com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel] manages the
         * lifecycle of this scope. It's cancelled when the View Model is destroyed. This helps to
         * free occupied resources when volume panel is not shown.
         */
        @VolumePanelScope
        @Provides
        fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
        dialog.window?.apply {
            setGravity(Gravity.BOTTOM or Gravity.CENTER)
            attributes =
                attributes.apply {
                    fitInsetsSides = 0
                    attributes.apply {
                        layoutInDisplayCutoutMode =
                            WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
                    }
                }
        }
    }

    override fun getWidth(dialog: SystemUIDialog): Int = WindowManager.LayoutParams.MATCH_PARENT

    override fun getHeight(dialog: SystemUIDialog): Int = WindowManager.LayoutParams.MATCH_PARENT
}
+113 −1
Original line number Diff line number Diff line
@@ -17,13 +17,33 @@
package com.android.systemui.statusbar.phone

import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.GravityInt
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.theme.PlatformTheme
import com.android.systemui.res.R

/**
 * Create a [SystemUIDialog] with the given [content].
@@ -56,7 +76,73 @@ fun SystemUIDialogFactory.create(
    @GravityInt dialogGravity: Int? = null,
    content: @Composable (SystemUIDialog) -> Unit,
): ComponentSystemUIDialog {
    val dialog = create(context, theme, dismissOnDeviceLock, dialogGravity)
    return create(
        context = context,
        theme = theme,
        dismissOnDeviceLock = dismissOnDeviceLock,
        delegate =
            object : DialogDelegate<SystemUIDialog> {
                override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
                    super.onCreate(dialog, savedInstanceState)
                    dialogGravity?.let { dialog.window?.setGravity(it) }
                }
            },
        content = content,
    )
}

/** Same as [create] but creates a bottom sheet dialog. */
fun SystemUIDialogFactory.createBottomSheet(
    context: Context = this.applicationContext,
    theme: Int = R.style.Theme_SystemUI_BottomSheet,
    dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
    content: @Composable (SystemUIDialog) -> Unit,
): ComponentSystemUIDialog {
    return create(
        context = context,
        theme = theme,
        dismissOnDeviceLock = dismissOnDeviceLock,
        delegate = EdgeToEdgeDialogDelegate(),
        content = { dialog ->
            Box(
                modifier = Modifier.bottomSheetClickable { dialog.dismiss() },
                contentAlignment = Alignment.BottomCenter
            ) {
                val radius = dimensionResource(R.dimen.bottom_sheet_corner_radius)
                Surface(
                    modifier =
                        Modifier.bottomSheetPaddings()
                            // consume input so it doesn't get to the parent Composable
                            .bottomSheetClickable {}
                            // TODO(b/337205027) change width
                            .widthIn(max = 800.dp),
                    shape = RoundedCornerShape(topStart = radius, topEnd = radius),
                    color = MaterialTheme.colorScheme.surfaceContainer,
                ) {
                    Box(
                        Modifier.padding(
                            bottom =
                                with(LocalDensity.current) {
                                    WindowInsets.safeDrawing.getBottom(this).toDp()
                                }
                        )
                    ) {
                        content(dialog)
                    }
                }
            }
        },
    )
}

private fun SystemUIDialogFactory.create(
    context: Context,
    theme: Int,
    dismissOnDeviceLock: Boolean,
    delegate: DialogDelegate<SystemUIDialog>,
    content: @Composable (SystemUIDialog) -> Unit,
): ComponentSystemUIDialog {
    val dialog = create(context, theme, dismissOnDeviceLock, delegate)

    // Create the dialog so that it is properly constructed before we set the Compose content.
    // Otherwise, the ComposeView won't render properly.
@@ -79,3 +165,29 @@ fun SystemUIDialogFactory.create(

    return dialog
}

/** Adds paddings for the bottom sheet surface. */
@Composable
private fun Modifier.bottomSheetPaddings(): Modifier {
    val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
    return with(LocalDensity.current) {
        val insets = WindowInsets.safeDrawing
        // TODO(b/337205027) change paddings
        val horizontalPadding: Dp = if (isPortrait) 0.dp else 48.dp
        padding(
            start = insets.getLeft(this, LocalLayoutDirection.current).toDp() + horizontalPadding,
            top = insets.getTop(this).toDp(),
            end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding
        )
    }
}

/**
 * For some reason adding clickable modifier onto the VolumePanel affects the traversal order:
 * b/331155283.
 *
 * TODO(b/334870995) revert this to Modifier.clickable
 */
@Composable
private fun Modifier.bottomSheetClickable(onClick: () -> Unit) =
    pointerInput(onClick) { detectTapGestures { onClick() } }
+1 −1
Original line number Diff line number Diff line
@@ -65,7 +65,7 @@ constructor(
    ) {
        val dialog =
            dialogFactory.create(
                theme = R.style.Theme_VolumePanelActivity_Popup,
                theme = R.style.Theme_VolumePanel_Popup,
                dialogGravity = Gravity.BOTTOM,
            ) {
                PopupComposable(it, title, content)
+14 −86
Original line number Diff line number Diff line
@@ -16,40 +16,22 @@

package com.android.systemui.volume.panel.ui.composable

import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import com.android.compose.theme.PlatformTheme
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlin.math.max

private val padding = 24.dp

@@ -67,42 +49,23 @@ fun VolumePanelRoot(
        }
    }

    PlatformTheme(isSystemInDarkTheme()) {
    val state: VolumePanelState by viewModel.volumePanelState.collectAsState()
    val components by viewModel.componentsLayout.collectAsState(null)

    with(VolumePanelComposeScope(state)) {
            Box(
                modifier =
                    modifier
                        .fillMaxSize()
                        .volumePanelClick(onDismiss)
                        .volumePanelPaddings(isPortrait = isPortrait),
                contentAlignment = Alignment.BottomCenter,
            ) {
                val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
                Surface(
                    modifier = Modifier.volumePanelClick {},
                    shape = RoundedCornerShape(topStart = radius, topEnd = radius),
                    color = MaterialTheme.colorScheme.surfaceContainer,
                ) {
        components?.let { componentsState ->
            Components(
                componentsState,
                            Modifier.padding(
                modifier.padding(
                    start = padding,
                    top = padding,
                    end = padding,
                    bottom = 20.dp,
                )
                                .navigationBarsPadding()
            )
        }
    }
}
        }
    }
}

@Composable
private fun VolumePanelComposeScope.Components(
@@ -116,7 +79,7 @@ private fun VolumePanelComposeScope.Components(
            if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp)
        }
    Column(
        modifier = modifier.widthIn(max = 800.dp),
        modifier = modifier,
        verticalArrangement = arrangement,
    ) {
        if (isPortrait || isLargeScreen) {
@@ -153,38 +116,3 @@ private fun VolumePanelComposeScope.BottomBar(
        }
    }
}

/**
 * Makes sure volume panel stays symmetrically in the middle of the screen while still avoiding
 * being under the cutouts.
 */
@Composable
private fun Modifier.volumePanelPaddings(isPortrait: Boolean): Modifier {
    val cutout = WindowInsets.displayCutout
    return with(LocalDensity.current) {
        val horizontalCutout =
            max(
                cutout.getLeft(density = this, layoutDirection = LocalLayoutDirection.current),
                cutout.getRight(density = this, layoutDirection = LocalLayoutDirection.current)
            )
        val minHorizontalPadding = if (isPortrait) 0.dp else 48.dp
        val horizontalPadding = max(horizontalCutout.toDp(), minHorizontalPadding)

        padding(
            start = horizontalPadding,
            top = cutout.getTop(this).toDp(),
            end = horizontalPadding,
            bottom = cutout.getBottom(this).toDp(),
        )
    }
}

/**
 * For some reason adding clickable modifier onto the VolumePanel affects the traversal order:
 * b/331155283.
 *
 * TODO(b/334870995) revert this to Modifier.clickable
 */
@Composable
private fun Modifier.volumePanelClick(onClick: () -> Unit) =
    pointerInput(onClick) { detectTapGestures { onClick() } }
Loading