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 be the same as the Notification stack height returned
32 * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight].
33 *
34 * @param view Notification stack scroll view
35 * @param padding extra padding in pixels to be added to the received content height.
36 */
Modifiernull37 fun Modifier.notificationStackHeight(view: NotificationScrollView, padding: Int = 0) =
38 this then StackLayoutElement(view, padding)
39
40 private data class StackLayoutElement(
41 val view: NotificationScrollView,
42 val padding: Int,
43 ) : ModifierNodeElement<StackLayoutNode>() {
44
45 override fun create(): StackLayoutNode = StackLayoutNode(view, padding)
46
47 override fun update(node: StackLayoutNode) {
48 check(view == node.view) { "Trying to reuse the node with a new View." }
49 if (node.padding != padding) {
50 node.padding = padding
51 node.invalidateMeasureIfAttached()
52 }
53 }
54 }
55
56 private class StackLayoutNode(val view: NotificationScrollView, var padding: Int) :
57 LayoutModifierNode, Modifier.Node() {
58
<lambda>null59 private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() }
60
onAttachnull61 override fun onAttach() {
62 super.onAttach()
63 view.addStackHeightChangedListener(stackHeightChangedListener)
64 }
65
onDetachnull66 override fun onDetach() {
67 super.onDetach()
68 view.removeStackHeightChangedListener(stackHeightChangedListener)
69 }
70
measurenull71 override fun MeasureScope.measure(
72 measurable: Measurable,
73 constraints: Constraints
74 ): MeasureResult {
75 val contentHeight = padding + view.intrinsicStackHeight
76 val placeable =
77 measurable.measure(
78 constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)
79 )
80
81 return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) }
82 }
83
toStringnull84 override fun toString(): String {
85 return "StackLayoutNode(view=$view padding:$padding)"
86 }
87
invalidateMeasureIfAttachednull88 fun invalidateMeasureIfAttached() {
89 if (isAttached) {
90 this.invalidateMeasurement()
91 }
92 }
93 }
94