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.Dp
28 import androidx.compose.ui.unit.IntOffset
29 import androidx.compose.ui.unit.dp
30 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
31
32 /**
33 * Modify element, which updates the height to be the same as the Notification stack height returned
34 * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight].
35 *
36 * @param view Notification stack scroll view
37 * @param totalVerticalPadding extra padding to be added to the received stack content height.
38 */
Modifiernull39 fun Modifier.notificationStackHeight(
40 view: NotificationScrollView,
41 totalVerticalPadding: Dp = 0.dp,
42 ) = this then StackLayoutElement(view, totalVerticalPadding)
43
44 private data class StackLayoutElement(
45 val view: NotificationScrollView,
46 val padding: Dp,
47 ) : ModifierNodeElement<StackLayoutNode>() {
48
49 override fun create(): StackLayoutNode = StackLayoutNode(view, padding)
50
51 override fun update(node: StackLayoutNode) {
52 check(view == node.view) { "Trying to reuse the node with a new View." }
53 if (node.padding != padding) {
54 node.padding = padding
55 node.invalidateMeasureIfAttached()
56 }
57 }
58 }
59
60 private class StackLayoutNode(val view: NotificationScrollView, var padding: Dp) :
61 LayoutModifierNode, Modifier.Node() {
62
<lambda>null63 private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() }
64
onAttachnull65 override fun onAttach() {
66 super.onAttach()
67 view.addStackHeightChangedListener(stackHeightChangedListener)
68 }
69
onDetachnull70 override fun onDetach() {
71 super.onDetach()
72 view.removeStackHeightChangedListener(stackHeightChangedListener)
73 }
74
measurenull75 override fun MeasureScope.measure(
76 measurable: Measurable,
77 constraints: Constraints
78 ): MeasureResult {
79 val contentHeight = padding.roundToPx() + view.intrinsicStackHeight
80 val placeable =
81 measurable.measure(
82 constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)
83 )
84
85 return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) }
86 }
87
toStringnull88 override fun toString(): String {
89 return "StackLayoutNode(view=$view padding:$padding)"
90 }
91
invalidateMeasureIfAttachednull92 fun invalidateMeasureIfAttached() {
93 if (isAttached) {
94 this.invalidateMeasurement()
95 }
96 }
97 }
98