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

Commit 81ff9a0e authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Add NotificationScrimClip to QSFragmentCompose

This replicates the behavior from QSContainerImpl (fancy clipping).
It also fixes HUN showing QQS.

Bug: 353254131
Test: manual, expand, collapse, HUN
Flag: com.android.systemui.qs_ui_refactor_compose_fragment

Change-Id: I41bf67316a18f035812605e3eb4bdfcf4c7768fe
parent 192b6f9c
Loading
Loading
Loading
Loading
+37 −2
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
@@ -50,6 +52,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -59,6 +62,7 @@ import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -100,6 +104,17 @@ constructor(
    private val qqsPositionOnRoot = Rect()
    private val composeViewPositionOnScreen = Rect()

    // Inside object for namespacing
    private val notificationScrimClippingParams =
        object {
            var isEnabled by mutableStateOf(false)
            var leftInset by mutableStateOf(0)
            var rightInset by mutableStateOf(0)
            var top by mutableStateOf(0)
            var bottom by mutableStateOf(0)
            var radius by mutableStateOf(0)
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

@@ -126,7 +141,18 @@ constructor(

                    AnimatedVisibility(
                        visible = visible,
                        modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
                        modifier =
                            Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
                                notificationScrimClippingParams.isEnabled
                            ) {
                                Modifier.notificationScrimClip(
                                    notificationScrimClippingParams.leftInset,
                                    notificationScrimClippingParams.top,
                                    notificationScrimClippingParams.rightInset,
                                    notificationScrimClippingParams.bottom,
                                    notificationScrimClippingParams.radius,
                                )
                            }
                    ) {
                        AnimatedContent(targetState = qsState) {
                            when (it) {
@@ -280,7 +306,16 @@ constructor(
        cornerRadius: Int,
        visible: Boolean,
        fullWidth: Boolean
    ) {}
    ) {
        notificationScrimClippingParams.isEnabled = visible
        notificationScrimClippingParams.top = top
        notificationScrimClippingParams.bottom = bottom
        // Full width means that QS will show in the entire width allocated to it (for example
        // phone) vs. showing in a narrower column (for example, tablet portrait).
        notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset
        notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset
        notificationScrimClippingParams.radius = cornerRadius
    }

    override fun isFullyCollapsed(): Boolean {
        return viewModel.qsExpansionValue <= 0f
+117 −0
Original line number Diff line number Diff line
/*
 * 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.
 * 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.qs.composefragment.ui

import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo

/**
 * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
 * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
 * from the QS container.
 */
fun Modifier.notificationScrimClip(
    leftInset: Int,
    top: Int,
    rightInset: Int,
    bottom: Int,
    radius: Int
): Modifier {
    return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius)
}

private class NotificationScrimClipNode(
    var leftInset: Float,
    var top: Float,
    var rightInset: Float,
    var bottom: Float,
    var radius: Float,
) : DrawModifierNode, Modifier.Node() {
    private val path = Path()

    var invalidated = true

    override fun ContentDrawScope.draw() {
        if (invalidated) {
            path.rewind()
            path
                .asAndroidPath()
                .addRoundRect(
                    -leftInset,
                    top,
                    size.width + rightInset,
                    bottom,
                    radius,
                    radius,
                    android.graphics.Path.Direction.CW
                )
            invalidated = false
        }
        clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
    }
}

private data class NotificationScrimClipElement(
    val leftInset: Int,
    val top: Int,
    val rightInset: Int,
    val bottom: Int,
    val radius: Int,
) : ModifierNodeElement<NotificationScrimClipNode>() {
    override fun create(): NotificationScrimClipNode {
        return NotificationScrimClipNode(
            leftInset.toFloat(),
            top.toFloat(),
            rightInset.toFloat(),
            bottom.toFloat(),
            radius.toFloat(),
        )
    }

    override fun update(node: NotificationScrimClipNode) {
        val changed =
            node.leftInset != leftInset.toFloat() ||
                node.top != top.toFloat() ||
                node.rightInset != rightInset.toFloat() ||
                node.bottom != bottom.toFloat() ||
                node.radius != radius.toFloat()
        if (changed) {
            node.leftInset = leftInset.toFloat()
            node.top = top.toFloat()
            node.rightInset = rightInset.toFloat()
            node.bottom = bottom.toFloat()
            node.radius = radius.toFloat()
            node.invalidated = true
        }
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "notificationScrimClip"
        properties["leftInset"] = leftInset
        properties["top"] = top
        properties["rightInset"] = rightInset
        properties["bottom"] = bottom
        properties["radius"] = radius
    }
}