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

Commit e5b8df8e authored by Anton Potapov's avatar Anton Potapov
Browse files

Move volume panel from activity to a dialog.

Flag: aconfig new_volume_panel TRUNKFOOD
Test: manual on phone and tablet. Open and close Volume Panel with x10
animation multiplier.
Fixes: 330808529
Fixes: 330795381
Fixes: 334123511
Fixes: 334145479

Change-Id: Ie9ddbd0c09904dd56e45aa4f7bdf2ac1259e18d6
parent 1baa70a0
Loading
Loading
Loading
Loading
+0 −8
Original line number Original line Diff line number Diff line
@@ -906,14 +906,6 @@
                  android:exported="true"
                  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"
        <activity android:name=".wallet.ui.WalletActivity"
                  android:label="@string/wallet_title"
                  android:label="@string/wallet_title"
                  android:theme="@style/Wallet.Theme"
                  android:theme="@style/Wallet.Theme"
+43 −0
Original line number Original line 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");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -14,30 +14,30 @@
 * limitations under the License.
 * 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 android.os.Bundle
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import android.view.Gravity
import dagger.Module
import android.view.WindowManager
import dagger.Provides
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob


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


    companion object {
    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {

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

    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 Original line Diff line number Diff line
@@ -17,13 +17,33 @@
package com.android.systemui.statusbar.phone
package com.android.systemui.statusbar.phone


import android.content.Context
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.GravityInt
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.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
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.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.compose.theme.PlatformTheme
import com.android.systemui.res.R


/**
/**
 * Create a [SystemUIDialog] with the given [content].
 * Create a [SystemUIDialog] with the given [content].
@@ -56,7 +76,73 @@ fun SystemUIDialogFactory.create(
    @GravityInt dialogGravity: Int? = null,
    @GravityInt dialogGravity: Int? = null,
    content: @Composable (SystemUIDialog) -> Unit,
    content: @Composable (SystemUIDialog) -> Unit,
): ComponentSystemUIDialog {
): 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.
    // Create the dialog so that it is properly constructed before we set the Compose content.
    // Otherwise, the ComposeView won't render properly.
    // Otherwise, the ComposeView won't render properly.
@@ -79,3 +165,29 @@ fun SystemUIDialogFactory.create(


    return dialog
    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 Original line Diff line number Diff line
@@ -65,7 +65,7 @@ constructor(
    ) {
    ) {
        val dialog =
        val dialog =
            dialogFactory.create(
            dialogFactory.create(
                theme = R.style.Theme_VolumePanelActivity_Popup,
                theme = R.style.Theme_VolumePanel_Popup,
                dialogGravity = Gravity.BOTTOM,
                dialogGravity = Gravity.BOTTOM,
            ) {
            ) {
                PopupComposable(it, title, content)
                PopupComposable(it, title, content)
+14 −86
Original line number Original line Diff line number Diff line
@@ -16,40 +16,22 @@


package com.android.systemui.volume.panel.ui.composable
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.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
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.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.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.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import kotlin.math.max


private val padding = 24.dp
private val padding = 24.dp


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


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


    with(VolumePanelComposeScope(state)) {
    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?.let { componentsState ->
            Components(
            Components(
                componentsState,
                componentsState,
                            Modifier.padding(
                modifier.padding(
                    start = padding,
                    start = padding,
                    top = padding,
                    top = padding,
                    end = padding,
                    end = padding,
                    bottom = 20.dp,
                    bottom = 20.dp,
                )
                )
                                .navigationBarsPadding()
            )
            )
        }
        }
    }
    }
}
}
        }
    }
}


@Composable
@Composable
private fun VolumePanelComposeScope.Components(
private fun VolumePanelComposeScope.Components(
@@ -116,7 +79,7 @@ private fun VolumePanelComposeScope.Components(
            if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp)
            if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp)
        }
        }
    Column(
    Column(
        modifier = modifier.widthIn(max = 800.dp),
        modifier = modifier,
        verticalArrangement = arrangement,
        verticalArrangement = arrangement,
    ) {
    ) {
        if (isPortrait || isLargeScreen) {
        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