|
1 | 1 | package org.cru.godtools.ui.banner |
2 | 2 |
|
3 | | -import androidx.compose.foundation.layout.Column |
4 | | -import androidx.compose.foundation.layout.fillMaxWidth |
5 | | -import androidx.compose.foundation.layout.heightIn |
6 | | -import androidx.compose.material3.HorizontalDivider |
7 | | -import androidx.compose.material3.Icon |
8 | | -import androidx.compose.material3.LocalContentColor |
9 | | -import androidx.compose.material3.LocalMinimumInteractiveComponentSize |
10 | | -import androidx.compose.material3.MaterialTheme |
11 | | -import androidx.compose.material3.Surface |
12 | | -import androidx.compose.material3.Text |
13 | | -import androidx.compose.material3.TextButton |
14 | | -import androidx.compose.runtime.Composable |
15 | | -import androidx.compose.runtime.CompositionLocalProvider |
16 | | -import androidx.compose.ui.Modifier |
17 | | -import androidx.compose.ui.draw.alpha |
18 | | -import androidx.compose.ui.graphics.Color |
19 | | -import androidx.compose.ui.graphics.painter.Painter |
20 | | -import androidx.compose.ui.layout.Layout |
21 | | -import androidx.compose.ui.layout.layoutId |
22 | | -import androidx.compose.ui.unit.Constraints |
23 | | -import androidx.compose.ui.unit.Dp |
24 | | -import androidx.compose.ui.unit.IntOffset |
25 | | -import androidx.compose.ui.unit.constrain |
26 | | -import androidx.compose.ui.unit.dp |
27 | | -import androidx.compose.ui.unit.offset |
| 3 | +import com.slack.circuit.runtime.CircuitUiState |
28 | 4 |
|
29 | | -@Composable |
30 | | -internal fun Banner( |
31 | | - text: String, |
32 | | - primaryButton: String, |
33 | | - modifier: Modifier = Modifier, |
34 | | - primaryAction: () -> Unit = {}, |
35 | | - secondaryButton: String? = null, |
36 | | - secondaryAction: () -> Unit = {}, |
37 | | - icon: Painter? = null, |
38 | | - iconTint: Color = if (icon != null) LocalContentColor.current else Color.Unspecified, |
39 | | -) = Surface(modifier = modifier.fillMaxWidth()) { |
40 | | - Column { |
41 | | - CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) { |
42 | | - val iconNode = "icon" |
43 | | - val textNode = "text" |
44 | | - val primaryActionNode = "primaryAction" |
45 | | - val secondaryActionNode = "secondaryAction" |
| 5 | +object Banner { |
| 6 | + enum class Type { TOOL_LIST_FAVORITES, TUTORIAL_FEATURES } |
46 | 7 |
|
47 | | - Layout({ |
48 | | - Text(text, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.layoutId(textNode)) |
49 | | - TextButton( |
50 | | - onClick = primaryAction, |
51 | | - modifier = Modifier |
52 | | - .layoutId(primaryActionNode) |
53 | | - .heightIn(min = 36.dp) |
54 | | - ) { Text(primaryButton) } |
55 | | - if (secondaryButton != null) { |
56 | | - TextButton( |
57 | | - onClick = secondaryAction, |
58 | | - modifier = Modifier |
59 | | - .layoutId(secondaryActionNode) |
60 | | - .heightIn(min = 36.dp) |
61 | | - ) { Text(secondaryButton) } |
62 | | - } |
63 | | - if (icon != null) { |
64 | | - Icon(icon, contentDescription = null, tint = iconTint, modifier = Modifier.layoutId(iconNode)) |
65 | | - } |
66 | | - }) { measurables, constraints -> |
67 | | - require(constraints.hasBoundedWidth) { "Banner requires a bounded width" } |
68 | | - |
69 | | - val textMargin = 16.dp.roundToPx() |
70 | | - val actionMargin = 8.dp.roundToPx() |
71 | | - val iconSize = 40.dp.roundToPx() |
72 | | - |
73 | | - val iconPlaceable = measurables.firstOrNull { it.layoutId == iconNode } |
74 | | - ?.measure(constraints.constrain(Constraints.fixed(iconSize, iconSize))) |
75 | | - val textPlaceable = measurables.first { it.layoutId == textNode }.measure( |
76 | | - constraints |
77 | | - .offset(horizontal = iconPlaceable?.let { 0 - textMargin - it.width } ?: 0) |
78 | | - .offset(horizontal = -2 * textMargin) |
79 | | - ) |
80 | | - val primaryActionPlaceable = measurables.first { it.layoutId == primaryActionNode } |
81 | | - .measure(constraints.offset(horizontal = -2 * actionMargin)) |
82 | | - val secondaryActionPlaceable = measurables.firstOrNull { it.layoutId == secondaryActionNode } |
83 | | - ?.measure(constraints.offset(horizontal = -2 * actionMargin)) |
84 | | - |
85 | | - val bannerWidth = constraints.maxWidth |
86 | | - |
87 | | - when { |
88 | | - // single line layout |
89 | | - iconPlaceable == null && |
90 | | - textMargin + textPlaceable.width + 36.dp.roundToPx() + |
91 | | - primaryActionPlaceable.width + actionMargin + |
92 | | - (secondaryActionPlaceable?.let { it.width + actionMargin } ?: 0) < bannerWidth -> { |
93 | | - val bannerHeight = maxOf( |
94 | | - textPlaceable.height, |
95 | | - primaryActionPlaceable.height, |
96 | | - secondaryActionPlaceable?.height ?: 0 |
97 | | - ) + 10.dp.roundToPx() + actionMargin |
98 | | - |
99 | | - // calculate placeable positions |
100 | | - val centerLine = (bannerHeight + 2.dp.roundToPx()) / 2 |
101 | | - val primaryActionPosition = IntOffset( |
102 | | - bannerWidth - actionMargin - primaryActionPlaceable.width, |
103 | | - centerLine - (primaryActionPlaceable.height / 2) |
104 | | - ) |
105 | | - val secondaryActionPosition = IntOffset( |
106 | | - primaryActionPosition.x - actionMargin - (secondaryActionPlaceable?.width ?: 0), |
107 | | - centerLine - ((secondaryActionPlaceable?.height ?: 0) / 2) |
108 | | - ) |
109 | | - |
110 | | - layout(bannerWidth, bannerHeight) { |
111 | | - textPlaceable.placeRelative(textMargin, centerLine - (textPlaceable.height / 2)) |
112 | | - primaryActionPlaceable.placeRelative(primaryActionPosition) |
113 | | - secondaryActionPlaceable?.placeRelative(secondaryActionPosition) |
114 | | - } |
115 | | - } |
116 | | - |
117 | | - // default layout |
118 | | - else -> { |
119 | | - val iconPosition = when (iconPlaceable) { |
120 | | - null -> IntOffset.Zero |
121 | | - else -> IntOffset(textMargin, textMargin) |
122 | | - } |
123 | | - val textPosition = when (iconPlaceable) { |
124 | | - null -> IntOffset(textMargin, textMargin) |
125 | | - else -> IntOffset(iconPosition.x + iconPlaceable.width + textMargin, textMargin) |
126 | | - } |
127 | | - val primaryActionPosition = IntOffset( |
128 | | - bannerWidth - actionMargin - primaryActionPlaceable.width, |
129 | | - maxOf( |
130 | | - iconPlaceable?.let { iconPosition.y + it.height } ?: 0, |
131 | | - textPosition.y + textPlaceable.height, |
132 | | - ) + 12.dp.roundToPx() |
133 | | - ) |
134 | | - val secondaryActionPosition = when (secondaryActionPlaceable) { |
135 | | - null -> IntOffset.Zero |
136 | | - |
137 | | - else -> { |
138 | | - val sameLinePosition = IntOffset( |
139 | | - primaryActionPosition.x - actionMargin - secondaryActionPlaceable.width, |
140 | | - primaryActionPosition.y |
141 | | - ) |
142 | | - val nextLinePosition = IntOffset( |
143 | | - bannerWidth - actionMargin - secondaryActionPlaceable.width, |
144 | | - primaryActionPosition.y + primaryActionPlaceable.height + actionMargin |
145 | | - ) |
146 | | - sameLinePosition.takeUnless { it.x < actionMargin } ?: nextLinePosition |
147 | | - } |
148 | | - } |
149 | | - |
150 | | - val bannerHeight = maxOf( |
151 | | - iconPlaceable?.let { iconPosition.y + it.height + textMargin } ?: 0, |
152 | | - textPosition.y + textPlaceable.height + textMargin, |
153 | | - primaryActionPosition.y + primaryActionPlaceable.height + actionMargin, |
154 | | - secondaryActionPlaceable?.let { secondaryActionPosition.y + it.height + actionMargin } ?: 0, |
155 | | - ) |
156 | | - |
157 | | - layout(bannerWidth, bannerHeight) { |
158 | | - iconPlaceable?.placeRelative(iconPosition) |
159 | | - textPlaceable.placeRelative(textPosition) |
160 | | - primaryActionPlaceable.placeRelative(primaryActionPosition) |
161 | | - secondaryActionPlaceable?.placeRelative(secondaryActionPosition) |
162 | | - } |
163 | | - } |
164 | | - } |
165 | | - } |
166 | | - } |
167 | | - HorizontalDivider(modifier = Modifier.alpha(0.12f)) |
| 8 | + interface UiState : CircuitUiState { |
| 9 | + val type: Type |
168 | 10 | } |
169 | 11 | } |
0 commit comments