1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.notifications.ui.composable
18 
19 import androidx.compose.ui.Modifier
20 import androidx.compose.ui.layout.Measurable
21 import androidx.compose.ui.layout.MeasureResult
22 import androidx.compose.ui.layout.MeasureScope
23 import androidx.compose.ui.node.LayoutModifierNode
24 import androidx.compose.ui.node.ModifierNodeElement
25 import androidx.compose.ui.node.invalidateMeasurement
26 import androidx.compose.ui.unit.Constraints
27 import androidx.compose.ui.unit.IntOffset
28 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
29 
30 /**
31  * Modify element, which updates the height to the height of current top heads up notification, or
32  * to 0 if there is none.
33  *
34  * @param view Notification stack scroll view
35  */
notificationHeadsUpHeightnull36 fun Modifier.notificationHeadsUpHeight(view: NotificationScrollView) =
37     this then HeadsUpLayoutElement(view)
38 
39 private data class HeadsUpLayoutElement(
40     val view: NotificationScrollView,
41 ) : ModifierNodeElement<HeadsUpLayoutNode>() {
42 
43     override fun create(): HeadsUpLayoutNode = HeadsUpLayoutNode(view)
44 
45     override fun update(node: HeadsUpLayoutNode) {
46         check(view == node.view) { "Trying to reuse the node with a new View." }
47     }
48 }
49 
50 private class HeadsUpLayoutNode(val view: NotificationScrollView) :
51     LayoutModifierNode, Modifier.Node() {
52 
<lambda>null53     private val headsUpHeightChangedListener = Runnable { invalidateMeasureIfAttached() }
54 
onAttachnull55     override fun onAttach() {
56         super.onAttach()
57         view.addHeadsUpHeightChangedListener(headsUpHeightChangedListener)
58     }
59 
onDetachnull60     override fun onDetach() {
61         super.onDetach()
62         view.removeHeadsUpHeightChangedListener(headsUpHeightChangedListener)
63     }
64 
measurenull65     override fun MeasureScope.measure(
66         measurable: Measurable,
67         constraints: Constraints
68     ): MeasureResult {
69         // TODO(b/339181697) make sure, that the row is already measured.
70         val contentHeight = view.topHeadsUpHeight
71         val placeable =
72             measurable.measure(
73                 constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)
74             )
75         return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) }
76     }
77 
toStringnull78     override fun toString(): String {
79         return "HeadsUpLayoutNode(view=$view)"
80     }
81 
invalidateMeasureIfAttachednull82     fun invalidateMeasureIfAttached() {
83         if (isAttached) {
84             this.invalidateMeasurement()
85         }
86     }
87 }
88