@momo-kits/native-kits 0.167.1-duo.1-debug → 0.167.1-duo.2-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +7 -0
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +13 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +21 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +11 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +47 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +14 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +37 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +53 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +169 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +4 -1
- package/gradle.properties +1 -1
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -185
- package/local.properties +0 -8
|
@@ -33,12 +33,12 @@ import androidx.compose.ui.unit.dp
|
|
|
33
33
|
import vn.momo.kits.components.Icon
|
|
34
34
|
import vn.momo.kits.components.Image
|
|
35
35
|
import vn.momo.kits.components.InputSearchProps
|
|
36
|
-
import vn.momo.kits.const.AppStatusBar
|
|
37
36
|
import vn.momo.kits.const.AppTheme
|
|
38
37
|
import vn.momo.kits.const.Colors
|
|
39
38
|
import vn.momo.kits.components.PopupNotifyProps
|
|
40
39
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
41
40
|
import vn.momo.kits.const.Spacing
|
|
41
|
+
import vn.momo.kits.layout.getScreenLayout
|
|
42
42
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
43
43
|
import vn.momo.kits.modifier.conditional
|
|
44
44
|
import vn.momo.kits.modifier.setAutomationId
|
|
@@ -47,6 +47,7 @@ import vn.momo.kits.navigation.LocalNavigator
|
|
|
47
47
|
import vn.momo.kits.navigation.LocalOptions
|
|
48
48
|
import vn.momo.kits.navigation.LocalScrollState
|
|
49
49
|
import vn.momo.kits.navigation.getInputSearchType
|
|
50
|
+
import vn.momo.kits.platform.getScreenDimensions
|
|
50
51
|
import vn.momo.kits.utils.horizontalSafeAreaPadding
|
|
51
52
|
|
|
52
53
|
@InternalApi
|
|
@@ -54,27 +55,44 @@ const val HEADER_HEIGHT = 52
|
|
|
54
55
|
internal enum class InputSearchType { None, Header, Animated }
|
|
55
56
|
enum class TitlePosition { LEFT, CENTER }
|
|
56
57
|
|
|
58
|
+
private val HEADER_ACTIONS_WIDTH = 172.dp
|
|
59
|
+
|
|
57
60
|
@Suppress("FrequentlyChangingValue")
|
|
58
61
|
@Composable
|
|
59
|
-
internal fun
|
|
62
|
+
internal fun headerOpacity(): Float {
|
|
60
63
|
val options = LocalOptions.current
|
|
61
|
-
val navigator = LocalNavigator.current
|
|
62
64
|
val scrollState = LocalScrollState.current
|
|
63
|
-
val headerRightWidthPx = LocalHeaderRightWidthPx.current
|
|
64
|
-
val inputSearchType = getInputSearchType(options)
|
|
65
|
-
|
|
66
65
|
val opacityHeight = when (val header = options.headerType) {
|
|
67
66
|
is HeaderType.Animated -> with(LocalDensity.current) { header.layoutOffSet.roundToPx() }
|
|
68
67
|
else -> HEADER_HEIGHT
|
|
69
68
|
}
|
|
70
69
|
val opacity by animateFloatAsState(
|
|
71
|
-
targetValue =
|
|
72
|
-
0f
|
|
73
|
-
|
|
74
|
-
(scrollState.value / opacityHeight.toFloat())
|
|
75
|
-
.coerceIn(0f, 1f)
|
|
70
|
+
targetValue = when {
|
|
71
|
+
options.headerType is HeaderType.Transparent -> 0f
|
|
72
|
+
opacityHeight <= 0 -> if (scrollState.value > 0) 1f else 0f
|
|
73
|
+
else -> (scrollState.value / opacityHeight.toFloat()).coerceIn(0f, 1f)
|
|
76
74
|
}
|
|
77
75
|
)
|
|
76
|
+
return opacity
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Back button and toolkit share the bar with the title unless the iPhone Duo side bar holds them.
|
|
80
|
+
@Composable
|
|
81
|
+
internal fun headerTitleMaxWidth(): Dp {
|
|
82
|
+
val layout = getScreenLayout()
|
|
83
|
+
if (layout.sideBar != null) return layout.contentWidth - Spacing.M * 2
|
|
84
|
+
return getScreenDimensions().width.dp - HEADER_ACTIONS_WIDTH
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Composable
|
|
88
|
+
internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
89
|
+
val options = LocalOptions.current
|
|
90
|
+
val navigator = LocalNavigator.current
|
|
91
|
+
val headerRightWidthPx = LocalHeaderRightWidthPx.current
|
|
92
|
+
val inputSearchType = getInputSearchType(options)
|
|
93
|
+
val layout = getScreenLayout()
|
|
94
|
+
val useSideBar = layout.sideBar != null
|
|
95
|
+
val opacity = headerOpacity()
|
|
78
96
|
|
|
79
97
|
val headerColor = getHeaderColor(options.headerType, opacity, options.tintColor, Colors.black_17)
|
|
80
98
|
|
|
@@ -97,7 +115,7 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
97
115
|
.takeUnless { options.headerType is HeaderType.Transparent }
|
|
98
116
|
|
|
99
117
|
Box(
|
|
100
|
-
Modifier.height(
|
|
118
|
+
Modifier.height(layout.headerHeight)
|
|
101
119
|
.fillMaxWidth()
|
|
102
120
|
.conditional(headerImage.isNullOrEmpty()) {
|
|
103
121
|
background(background.copy(alpha = animatedAlpha))
|
|
@@ -126,14 +144,14 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
126
144
|
}
|
|
127
145
|
}
|
|
128
146
|
Row(
|
|
129
|
-
modifier = Modifier.height(
|
|
147
|
+
modifier = Modifier.height(layout.headerBarHeight)
|
|
130
148
|
.fillMaxWidth()
|
|
131
149
|
.horizontalSafeAreaPadding()
|
|
132
150
|
.padding(horizontal = Spacing.M),
|
|
133
151
|
verticalAlignment = Alignment.CenterVertically,
|
|
134
152
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
135
153
|
) {
|
|
136
|
-
if(!options.hiddenBack) {
|
|
154
|
+
if (!options.hiddenBack && !useSideBar) {
|
|
137
155
|
BackButton(
|
|
138
156
|
borderColor = headerColor.borderColor,
|
|
139
157
|
backgroundButton = headerColor.backgroundButton,
|
|
@@ -158,13 +176,16 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
158
176
|
Box(Modifier.onGloballyPositioned {
|
|
159
177
|
if(headerRightWidthPx.intValue != it.size.width) headerRightWidthPx.intValue = it.size.width
|
|
160
178
|
}){
|
|
161
|
-
|
|
179
|
+
if (!(useSideBar && options.headerRight is HeaderRight.Toolkit)) {
|
|
180
|
+
HeaderRight(options.headerRight, options.tintColor, headerColor)
|
|
181
|
+
}
|
|
162
182
|
}
|
|
163
183
|
}
|
|
164
184
|
if (centerTitle) {
|
|
165
185
|
Box(
|
|
166
|
-
modifier = Modifier.height(
|
|
186
|
+
modifier = Modifier.height(layout.headerBarHeight)
|
|
167
187
|
.fillMaxWidth()
|
|
188
|
+
.horizontalSafeAreaPadding()
|
|
168
189
|
.padding(horizontal = Spacing.M),
|
|
169
190
|
contentAlignment = Alignment.Center
|
|
170
191
|
) {
|
|
@@ -15,8 +15,8 @@ import androidx.compose.ui.graphics.Color
|
|
|
15
15
|
import androidx.compose.ui.platform.LocalDensity
|
|
16
16
|
import androidx.compose.ui.unit.dp
|
|
17
17
|
import vn.momo.kits.components.Image
|
|
18
|
-
import vn.momo.kits.const.AppStatusBar
|
|
19
18
|
import vn.momo.kits.const.AppTheme
|
|
19
|
+
import vn.momo.kits.layout.getScreenLayout
|
|
20
20
|
import vn.momo.kits.modifier.conditional
|
|
21
21
|
import vn.momo.kits.navigation.LocalOptions
|
|
22
22
|
import vn.momo.kits.navigation.LocalScrollState
|
|
@@ -38,7 +38,7 @@ internal fun HeaderBackground() {
|
|
|
38
38
|
val scrollState = LocalScrollState.current
|
|
39
39
|
val backgroundColor = options.backgroundColor ?: AppTheme.current.colors.background.default
|
|
40
40
|
|
|
41
|
-
val minHeight =
|
|
41
|
+
val minHeight = getScreenLayout().headerHeight
|
|
42
42
|
val maxHeight = HEADER_BACKGROUND_HEIGHT
|
|
43
43
|
val opacity by animateFloatAsState(
|
|
44
44
|
targetValue = (1 - (scrollState.value * 1f / HEADER_HEIGHT * 1f)).coerceIn(0f, 1f),
|
|
@@ -23,7 +23,6 @@ import vn.momo.kits.components.Text
|
|
|
23
23
|
import vn.momo.kits.const.Spacing
|
|
24
24
|
import vn.momo.kits.const.Typography
|
|
25
25
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
26
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
27
26
|
|
|
28
27
|
@Composable
|
|
29
28
|
internal fun HeaderJourney(
|
|
@@ -33,7 +32,7 @@ internal fun HeaderJourney(
|
|
|
33
32
|
if (data !is HeaderTitle.Journey) return
|
|
34
33
|
if (data.isLoading) return TitleJourneyShimmer()
|
|
35
34
|
|
|
36
|
-
val maxWidth =
|
|
35
|
+
val maxWidth = headerTitleMaxWidth()
|
|
37
36
|
val tint = parseHeaderTint(data.tintColor) ?: tintIconColor
|
|
38
37
|
val iconTint = parseHeaderTint(data.iconColor) ?: tint
|
|
39
38
|
|
|
@@ -22,7 +22,6 @@ import vn.momo.kits.components.Text
|
|
|
22
22
|
import vn.momo.kits.const.Spacing
|
|
23
23
|
import vn.momo.kits.const.Typography
|
|
24
24
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
25
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
26
25
|
|
|
27
26
|
@Composable
|
|
28
27
|
internal fun HeaderLocation(
|
|
@@ -32,7 +31,7 @@ internal fun HeaderLocation(
|
|
|
32
31
|
if (data !is HeaderTitle.Location) return
|
|
33
32
|
if (data.isLoading) return TitleLocationShimmer()
|
|
34
33
|
|
|
35
|
-
val maxWidth =
|
|
34
|
+
val maxWidth = headerTitleMaxWidth()
|
|
36
35
|
val tint = parseHeaderTint(data.tintColor) ?: tintIconColor
|
|
37
36
|
|
|
38
37
|
Column(
|
|
@@ -6,6 +6,7 @@ import androidx.compose.foundation.background
|
|
|
6
6
|
import androidx.compose.foundation.border
|
|
7
7
|
import androidx.compose.foundation.layout.Arrangement
|
|
8
8
|
import androidx.compose.foundation.layout.Box
|
|
9
|
+
import androidx.compose.foundation.layout.Column
|
|
9
10
|
import androidx.compose.foundation.layout.Row
|
|
10
11
|
import androidx.compose.foundation.layout.height
|
|
11
12
|
import androidx.compose.foundation.layout.offset
|
|
@@ -63,7 +64,8 @@ fun HeaderRight(
|
|
|
63
64
|
internal fun Toolkit(
|
|
64
65
|
headerRight: HeaderRight.Toolkit,
|
|
65
66
|
tintColor: Color? = null,
|
|
66
|
-
headerColor: HeaderColor
|
|
67
|
+
headerColor: HeaderColor,
|
|
68
|
+
vertical: Boolean = false,
|
|
67
69
|
) {
|
|
68
70
|
val api = LocalMaxApi.current
|
|
69
71
|
val context = LocalContext.current
|
|
@@ -103,6 +105,23 @@ internal fun Toolkit(
|
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
fun onPressHelpCenter() {
|
|
109
|
+
if (context == null) return
|
|
110
|
+
api?.showHelpCenter(
|
|
111
|
+
mapOf(
|
|
112
|
+
"appId" to context.appId,
|
|
113
|
+
"code" to context.appCode,
|
|
114
|
+
"name" to context.appName,
|
|
115
|
+
"icon" to context.appIcon,
|
|
116
|
+
"description" to context.description
|
|
117
|
+
)
|
|
118
|
+
) {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fun onPressHome() {
|
|
122
|
+
api?.dismissAll { }
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
val navButtonConfig = getNavigationButtonConfig(
|
|
107
126
|
headerRight,
|
|
108
127
|
::onPressShortcut,
|
|
@@ -127,6 +146,37 @@ internal fun Toolkit(
|
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
148
|
|
|
149
|
+
if (vertical) {
|
|
150
|
+
Column(verticalArrangement = Arrangement.spacedBy(Spacing.M)) {
|
|
151
|
+
if (isShowShortcut) {
|
|
152
|
+
SideBarButton(
|
|
153
|
+
icon = icon,
|
|
154
|
+
headerColor = headerColor,
|
|
155
|
+
enabled = !isLoading,
|
|
156
|
+
showBadge = showBadge,
|
|
157
|
+
onClick = navButtonConfig.onPress,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
SideBarGroup(headerColor = headerColor, itemCount = if (context != null) 2 else 1) {
|
|
161
|
+
if (context != null) {
|
|
162
|
+
SideBarGroupItem(
|
|
163
|
+
icon = "help_center",
|
|
164
|
+
headerColor = headerColor,
|
|
165
|
+
automationId = "btn_navigation_help_center",
|
|
166
|
+
onClick = ::onPressHelpCenter,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
SideBarGroupItem(
|
|
170
|
+
icon = "16_basic_home",
|
|
171
|
+
headerColor = headerColor,
|
|
172
|
+
automationId = "btn_navigation_close",
|
|
173
|
+
onClick = ::onPressHome,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
130
180
|
Row(
|
|
131
181
|
verticalAlignment = Alignment.CenterVertically
|
|
132
182
|
) {
|
|
@@ -155,17 +205,7 @@ internal fun Toolkit(
|
|
|
155
205
|
source = "help_center",
|
|
156
206
|
size = 20.dp,
|
|
157
207
|
color = headerColor.tintIconColor,
|
|
158
|
-
modifier = Modifier.padding(4.dp).activeOpacityClickable
|
|
159
|
-
api?.showHelpCenter(
|
|
160
|
-
mapOf(
|
|
161
|
-
"appId" to context.appId,
|
|
162
|
-
"code" to context.appCode,
|
|
163
|
-
"name" to context.appName,
|
|
164
|
-
"icon" to context.appIcon,
|
|
165
|
-
"description" to context.description
|
|
166
|
-
)
|
|
167
|
-
) {}
|
|
168
|
-
}
|
|
208
|
+
modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressHelpCenter)
|
|
169
209
|
)
|
|
170
210
|
Box(
|
|
171
211
|
modifier = Modifier
|
|
@@ -178,9 +218,7 @@ internal fun Toolkit(
|
|
|
178
218
|
source = "16_basic_home",
|
|
179
219
|
size = 20.dp,
|
|
180
220
|
color = headerColor.tintIconColor,
|
|
181
|
-
modifier = Modifier.padding(4.dp).activeOpacityClickable
|
|
182
|
-
api?.dismissAll { }
|
|
183
|
-
}
|
|
221
|
+
modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressHome)
|
|
184
222
|
)
|
|
185
223
|
}
|
|
186
224
|
}
|
|
@@ -32,7 +32,6 @@ import vn.momo.kits.const.Typography
|
|
|
32
32
|
import vn.momo.kits.const.getFontFamily
|
|
33
33
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
34
34
|
import vn.momo.kits.navigation.component.HeaderTitle.User
|
|
35
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
36
35
|
|
|
37
36
|
@Composable
|
|
38
37
|
internal fun HeaderUser(
|
|
@@ -44,7 +43,7 @@ internal fun HeaderUser(
|
|
|
44
43
|
return TitleUserShimmer()
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
val maxWidth =
|
|
46
|
+
val maxWidth = headerTitleMaxWidth()
|
|
48
47
|
|
|
49
48
|
Row(
|
|
50
49
|
modifier = Modifier
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package vn.momo.kits.navigation.component
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.border
|
|
5
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
6
|
+
import androidx.compose.foundation.layout.Box
|
|
7
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
8
|
+
import androidx.compose.foundation.layout.Column
|
|
9
|
+
import androidx.compose.foundation.layout.ColumnScope
|
|
10
|
+
import androidx.compose.foundation.layout.Spacer
|
|
11
|
+
import androidx.compose.foundation.layout.absolutePadding
|
|
12
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
13
|
+
import androidx.compose.foundation.layout.height
|
|
14
|
+
import androidx.compose.foundation.layout.offset
|
|
15
|
+
import androidx.compose.foundation.layout.size
|
|
16
|
+
import androidx.compose.foundation.layout.width
|
|
17
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
18
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
19
|
+
import androidx.compose.runtime.Composable
|
|
20
|
+
import androidx.compose.ui.AbsoluteAlignment
|
|
21
|
+
import androidx.compose.ui.Alignment
|
|
22
|
+
import androidx.compose.ui.Modifier
|
|
23
|
+
import androidx.compose.ui.draw.clip
|
|
24
|
+
import androidx.compose.ui.draw.shadow
|
|
25
|
+
import androidx.compose.ui.graphics.Shape
|
|
26
|
+
import androidx.compose.ui.layout.layout
|
|
27
|
+
import androidx.compose.ui.unit.Dp
|
|
28
|
+
import androidx.compose.ui.unit.constrainHeight
|
|
29
|
+
import androidx.compose.ui.unit.dp
|
|
30
|
+
import vn.momo.kits.components.BadgeDot
|
|
31
|
+
import vn.momo.kits.components.DotSize
|
|
32
|
+
import vn.momo.kits.components.Icon
|
|
33
|
+
import vn.momo.kits.const.Colors
|
|
34
|
+
import vn.momo.kits.const.Radius
|
|
35
|
+
import vn.momo.kits.const.Spacing
|
|
36
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
37
|
+
import vn.momo.kits.layout.SideBarEdge
|
|
38
|
+
import vn.momo.kits.layout.getScreenLayout
|
|
39
|
+
import vn.momo.kits.modifier.activeOpacityClickable
|
|
40
|
+
import vn.momo.kits.modifier.conditional
|
|
41
|
+
import vn.momo.kits.modifier.setAutomationId
|
|
42
|
+
import vn.momo.kits.navigation.LocalNavigator
|
|
43
|
+
import vn.momo.kits.navigation.LocalOptions
|
|
44
|
+
|
|
45
|
+
private val SIDE_BAR_ICON_SIZE = 24.dp
|
|
46
|
+
|
|
47
|
+
// iPhone Duo: back and toolkit leave the bar for a column in the side safe area, as the system bars do.
|
|
48
|
+
// bottomInset is what footer / tab bar / home indicator take from the bottom; the column stays above it.
|
|
49
|
+
@Composable
|
|
50
|
+
internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifier) {
|
|
51
|
+
val options = LocalOptions.current
|
|
52
|
+
val navigator = LocalNavigator.current
|
|
53
|
+
val sideBar = getScreenLayout().sideBar ?: return
|
|
54
|
+
val toolkit = options.headerRight as? HeaderRight.Toolkit
|
|
55
|
+
if (options.headerType == HeaderType.None || (options.hiddenBack && toolkit == null)) return
|
|
56
|
+
|
|
57
|
+
val headerColor = getHeaderColor(options.headerType, headerOpacity(), options.tintColor, Colors.black_17)
|
|
58
|
+
val isRight = sideBar.edge == SideBarEdge.RIGHT
|
|
59
|
+
|
|
60
|
+
Column(
|
|
61
|
+
modifier = modifier
|
|
62
|
+
.align(if (isRight) AbsoluteAlignment.TopRight else AbsoluteAlignment.TopLeft)
|
|
63
|
+
.width(sideBar.width)
|
|
64
|
+
.layout { measurable, constraints ->
|
|
65
|
+
val placeable = measurable.measure(constraints.copy(minHeight = 0))
|
|
66
|
+
val maxY = constraints.maxHeight - (bottomInset + Spacing.XL).roundToPx() - placeable.height
|
|
67
|
+
val y = minOf(sideBar.top.roundToPx(), maxY).coerceAtLeast(0)
|
|
68
|
+
layout(placeable.width, constraints.constrainHeight(y + placeable.height)) {
|
|
69
|
+
placeable.place(0, y)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
.absolutePadding(left = if (isRight) Spacing.M else 0.dp, right = if (isRight) 0.dp else Spacing.M),
|
|
73
|
+
horizontalAlignment = if (isRight) AbsoluteAlignment.Left else AbsoluteAlignment.Right,
|
|
74
|
+
verticalArrangement = Arrangement.spacedBy(Spacing.M),
|
|
75
|
+
) {
|
|
76
|
+
if (options.hiddenBack) {
|
|
77
|
+
// keep the slot so the toolkit does not jump between root and pushed screens
|
|
78
|
+
Spacer(Modifier.size(DuoMetrics.controlSize))
|
|
79
|
+
} else {
|
|
80
|
+
SideBarButton(
|
|
81
|
+
icon = "arrow-back",
|
|
82
|
+
headerColor = headerColor,
|
|
83
|
+
automationId = "btn_navigation_back",
|
|
84
|
+
onClick = { navigator.onBackSafe { } },
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
if (toolkit != null) {
|
|
88
|
+
Toolkit(headerRight = toolkit, tintColor = options.tintColor, headerColor = headerColor, vertical = true)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Composable
|
|
94
|
+
internal fun SideBarButton(
|
|
95
|
+
icon: String,
|
|
96
|
+
headerColor: HeaderColor,
|
|
97
|
+
onClick: () -> Unit,
|
|
98
|
+
enabled: Boolean = true,
|
|
99
|
+
showBadge: Boolean = false,
|
|
100
|
+
automationId: String? = null,
|
|
101
|
+
) {
|
|
102
|
+
Box {
|
|
103
|
+
Box(
|
|
104
|
+
modifier = Modifier
|
|
105
|
+
.size(DuoMetrics.controlSize)
|
|
106
|
+
.sideBarGlass(headerColor, CircleShape)
|
|
107
|
+
.activeOpacityClickable(enabled = enabled, onClick = onClick)
|
|
108
|
+
.conditional(automationId != null) { setAutomationId(automationId.orEmpty()) },
|
|
109
|
+
contentAlignment = Alignment.Center,
|
|
110
|
+
) {
|
|
111
|
+
Icon(source = icon, size = SIDE_BAR_ICON_SIZE, color = headerColor.tintIconColor)
|
|
112
|
+
}
|
|
113
|
+
if (showBadge) {
|
|
114
|
+
BadgeDot(
|
|
115
|
+
size = DotSize.Small,
|
|
116
|
+
modifier = Modifier.align(Alignment.TopEnd).offset(x = -Spacing.XXS, y = Spacing.XXS),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Capsule grouping related controls, one item per 50pt.
|
|
123
|
+
@Composable
|
|
124
|
+
internal fun SideBarGroup(
|
|
125
|
+
headerColor: HeaderColor,
|
|
126
|
+
itemCount: Int,
|
|
127
|
+
content: @Composable ColumnScope.() -> Unit,
|
|
128
|
+
) {
|
|
129
|
+
val height = if (itemCount > 1) DuoMetrics.itemPitch * itemCount else DuoMetrics.controlSize
|
|
130
|
+
Column(
|
|
131
|
+
modifier = Modifier
|
|
132
|
+
.width(DuoMetrics.controlSize)
|
|
133
|
+
.height(height)
|
|
134
|
+
.sideBarGlass(headerColor, RoundedCornerShape(Radius.XL)),
|
|
135
|
+
content = content,
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Composable
|
|
140
|
+
internal fun ColumnScope.SideBarGroupItem(
|
|
141
|
+
icon: String,
|
|
142
|
+
headerColor: HeaderColor,
|
|
143
|
+
automationId: String,
|
|
144
|
+
onClick: () -> Unit,
|
|
145
|
+
) {
|
|
146
|
+
Box(
|
|
147
|
+
modifier = Modifier
|
|
148
|
+
.weight(1f)
|
|
149
|
+
.fillMaxWidth()
|
|
150
|
+
.activeOpacityClickable(onClick = onClick)
|
|
151
|
+
.setAutomationId(automationId),
|
|
152
|
+
contentAlignment = Alignment.Center,
|
|
153
|
+
) {
|
|
154
|
+
Icon(source = icon, size = SIDE_BAR_ICON_SIZE, color = headerColor.tintIconColor)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Dark glass under light icons (banner headers), light glass otherwise. The light one is opaque:
|
|
159
|
+
// Compose has no backdrop blur and a translucent fill shows its own shadow through (RN uses 85% white).
|
|
160
|
+
private fun Modifier.sideBarGlass(headerColor: HeaderColor, shape: Shape): Modifier =
|
|
161
|
+
if (headerColor.tintIconColor == Colors.black_01) {
|
|
162
|
+
clip(shape)
|
|
163
|
+
.background(Colors.black_20.copy(alpha = 0.5f))
|
|
164
|
+
.border(0.5.dp, Colors.black_01.copy(alpha = 0.2f), shape)
|
|
165
|
+
} else {
|
|
166
|
+
shadow(3.dp, shape)
|
|
167
|
+
.background(Colors.black_01)
|
|
168
|
+
.border(0.5.dp, Colors.black_20.copy(alpha = 0.08f), shape)
|
|
169
|
+
}
|
|
@@ -95,9 +95,12 @@ fun getAppStatusBarHeight(): Dp {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
internal val horizontalSafeAreaInsets: WindowInsets
|
|
99
|
+
@Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(WindowInsetsSides.Horizontal)
|
|
100
|
+
|
|
98
101
|
// Left/right safe area like SwiftUI (landscape camera/notch); consumed, so nested StackScreens don't pad twice.
|
|
99
102
|
internal fun Modifier.horizontalSafeAreaPadding(): Modifier = composed {
|
|
100
|
-
windowInsetsPadding(
|
|
103
|
+
windowInsetsPadding(horizontalSafeAreaInsets)
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
@Composable
|
package/gradle.properties
CHANGED
package/ios/native-kits.podspec
CHANGED