@momo-kits/native-kits 0.163.1-beta.9-debug → 0.163.1-footer.1-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/settings.local.json +14 -0
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +5 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +19 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +5 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +7 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +1 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +67 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +5 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +15 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +27 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -1
- package/gradle.properties +1 -1
- package/ios/Application/Components.swift +14 -14
- package/ios/Application/HeaderRight.swift +45 -79
- package/ios/Application/Localize.swift +32 -0
- package/ios/Application/Navigation/NavigationContainer.swift +3 -0
- package/ios/Application/Navigation/component/Header.swift +1 -1
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +11 -0
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +196 -0
- package/ios/Button/Button.swift +14 -20
- package/ios/Popup/PopupDisplay.swift +1 -1
- package/ios/Popup/PopupNotify.swift +1 -1
- package/ios/Popup/PopupPromotion.swift +5 -11
- package/ios/Template/TrustBanner/TrustBanner.swift +2 -2
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -2
- package/local.properties +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebSearch",
|
|
5
|
+
"Bash(./gradlew assembleDebug 2>&1)",
|
|
6
|
+
"Bash(./gradlew :sample:androidApp:installDebug 2>&1)",
|
|
7
|
+
"Bash(./gradlew :compose:compileCommonMainKotlinMetadata -q)",
|
|
8
|
+
"Bash(mkdir -p compose/src/commonMain/composeResources/files)",
|
|
9
|
+
"Bash(cp sample/shared/src/commonMain/composeResources/files/anim_processing_transaction.json sample/shared/src/commonMain/composeResources/files/dot_loading.json sample/shared/src/commonMain/composeResources/files/face_loading.json sample/shared/src/commonMain/composeResources/files/lottie_circle_loader.json compose/src/commonMain/composeResources/files/)",
|
|
10
|
+
"Bash(git rm *)",
|
|
11
|
+
"Bash(./gradlew :sample:androidApp:compileDebugKotlin -q)"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
package/compose/build.gradle.kts
CHANGED
package/compose/compose.podspec
CHANGED
|
@@ -209,7 +209,6 @@ internal class CupertinoOverscrollEffect(
|
|
|
209
209
|
private fun availableDelta(delta: Offset, source: CupertinoScrollSource): Offset {
|
|
210
210
|
val (x, overscrollX) = availableDelta(delta.x, overscrollOffset.x, source)
|
|
211
211
|
val (y, overscrollY) = availableDelta(delta.y, overscrollOffset.y, source)
|
|
212
|
-
println("availableDelta delta=${delta.y} consumed y=$y overscrollY=$overscrollY source=${source}")
|
|
213
212
|
overscrollOffset = Offset(overscrollX, overscrollY)
|
|
214
213
|
|
|
215
214
|
return Offset(x, y)
|
|
@@ -29,6 +29,7 @@ import androidx.compose.ui.Alignment
|
|
|
29
29
|
import androidx.compose.ui.Modifier
|
|
30
30
|
import androidx.compose.ui.focus.onFocusChanged
|
|
31
31
|
import androidx.compose.ui.graphics.Color
|
|
32
|
+
import androidx.compose.ui.graphics.takeOrElse
|
|
32
33
|
import androidx.compose.ui.text.TextStyle
|
|
33
34
|
import androidx.compose.ui.text.input.KeyboardType
|
|
34
35
|
import androidx.compose.ui.text.style.TextOverflow
|
|
@@ -49,7 +50,7 @@ import vn.momo.kits.utils.ifTrue
|
|
|
49
50
|
internal fun RenderRightIconSearch(
|
|
50
51
|
loading: Boolean,
|
|
51
52
|
icon: String,
|
|
52
|
-
color: Color
|
|
53
|
+
color: Color?,
|
|
53
54
|
onClick: () -> Unit,
|
|
54
55
|
iconModifier: Modifier = Modifier
|
|
55
56
|
) {
|
|
@@ -57,7 +58,7 @@ internal fun RenderRightIconSearch(
|
|
|
57
58
|
Box(Modifier.padding(horizontal = Spacing.S)) {
|
|
58
59
|
CircularProgressIndicator(
|
|
59
60
|
modifier = Modifier.size(16.dp),
|
|
60
|
-
color = color,
|
|
61
|
+
color = color ?: AppTheme.current.colors.text.default,
|
|
61
62
|
trackColor = Color.Transparent,
|
|
62
63
|
strokeWidth = 2.dp
|
|
63
64
|
)
|
|
@@ -90,7 +91,7 @@ data class InputSearchProps(
|
|
|
90
91
|
val onPressButtonText: () -> Unit = {},
|
|
91
92
|
val disabled: Boolean = false,
|
|
92
93
|
val icon: String = "",
|
|
93
|
-
val iconColor: Color = Color
|
|
94
|
+
val iconColor: Color? = Color.Unspecified,
|
|
94
95
|
val onRightIconPressed: () -> Unit = {},
|
|
95
96
|
val onFocus: () -> Unit = {},
|
|
96
97
|
val onBlur: () -> Unit = {},
|
|
@@ -113,7 +114,6 @@ data class InputSearchProps(
|
|
|
113
114
|
fun InputSearch(
|
|
114
115
|
inputSearchProps: InputSearchProps = InputSearchProps(
|
|
115
116
|
text = remember { mutableStateOf("") },
|
|
116
|
-
iconColor = AppTheme.current.colors.text.default
|
|
117
117
|
),
|
|
118
118
|
) {
|
|
119
119
|
var isFocused by remember { mutableStateOf(false) }
|
|
@@ -121,7 +121,7 @@ fun InputSearch(
|
|
|
121
121
|
val disabledColor = AppTheme.current.colors.text.disable
|
|
122
122
|
var textColor = AppTheme.current.colors.text.default
|
|
123
123
|
var placeholderColor = AppTheme.current.colors.text.hint
|
|
124
|
-
var iconTintColor = inputSearchProps.iconColor
|
|
124
|
+
var iconTintColor = inputSearchProps.iconColor?.takeOrElse { AppTheme.current.colors.text.default }
|
|
125
125
|
|
|
126
126
|
if (inputSearchProps.disabled) {
|
|
127
127
|
textColor = disabledColor
|
|
@@ -147,6 +147,17 @@ object Colors {
|
|
|
147
147
|
val red_10 = Color(0xFFFEF4F4)
|
|
148
148
|
val red_11 = Color(0xFFFEF8F8)
|
|
149
149
|
|
|
150
|
+
// Translucent overlays, ported from the React kit's Consts (the reference
|
|
151
|
+
// palette). React spells them `#RRGGBBAA`; Compose's Color takes `0xAARRGGBB`.
|
|
152
|
+
val opacity_black_20 = Color(0x33000000)
|
|
153
|
+
val opacity_black_40 = Color(0x66000000)
|
|
154
|
+
val opacity_black_60 = Color(0x99000000)
|
|
155
|
+
val opacity_black_80 = Color(0xCC000000)
|
|
156
|
+
val opacity_white_20 = Color(0x33FFFFFF)
|
|
157
|
+
val opacity_white_40 = Color(0x66FFFFFF)
|
|
158
|
+
val opacity_white_60 = Color(0x99FFFFFF)
|
|
159
|
+
val opacity_white_80 = Color(0xCCFFFFFF)
|
|
160
|
+
|
|
150
161
|
val colorList: Map<String, Color> = mapOf(
|
|
151
162
|
"black_01" to black_01,
|
|
152
163
|
"black_02" to black_02,
|
|
@@ -302,5 +313,13 @@ object Colors {
|
|
|
302
313
|
"red_09" to red_09,
|
|
303
314
|
"red_10" to red_10,
|
|
304
315
|
"red_11" to red_11,
|
|
316
|
+
"opacity_black_20" to opacity_black_20,
|
|
317
|
+
"opacity_black_40" to opacity_black_40,
|
|
318
|
+
"opacity_black_60" to opacity_black_60,
|
|
319
|
+
"opacity_black_80" to opacity_black_80,
|
|
320
|
+
"opacity_white_20" to opacity_white_20,
|
|
321
|
+
"opacity_white_40" to opacity_white_40,
|
|
322
|
+
"opacity_white_60" to opacity_white_60,
|
|
323
|
+
"opacity_white_80" to opacity_white_80,
|
|
305
324
|
)
|
|
306
325
|
}
|
|
@@ -4,8 +4,8 @@ import androidx.compose.foundation.background
|
|
|
4
4
|
import androidx.compose.foundation.layout.Arrangement
|
|
5
5
|
import androidx.compose.foundation.layout.Box
|
|
6
6
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
7
|
-
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
8
7
|
import androidx.compose.foundation.layout.FlowRow
|
|
8
|
+
import androidx.compose.foundation.layout.PaddingValues
|
|
9
9
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
10
10
|
import androidx.compose.foundation.layout.padding
|
|
11
11
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
@@ -18,18 +18,13 @@ import androidx.compose.ui.unit.dp
|
|
|
18
18
|
import vn.momo.kits.components.Image
|
|
19
19
|
import vn.momo.kits.const.AppTheme
|
|
20
20
|
import vn.momo.kits.const.Radius
|
|
21
|
+
import vn.momo.kits.const.Spacing
|
|
21
22
|
|
|
22
|
-
/**
|
|
23
|
-
* A white rounded surface card that lays its [Item]s on a 12-column grid
|
|
24
|
-
* (React kit foundations/Layout/Card). 8dp gutter, 12dp padding, 12dp
|
|
25
|
-
* horizontal margin, Radius.M corners, background = theme surface, optional
|
|
26
|
-
* shadow or [backgroundImage]. Items flow left-to-right and wrap.
|
|
27
|
-
*/
|
|
28
|
-
@OptIn(ExperimentalLayoutApi::class)
|
|
29
23
|
@Composable
|
|
30
|
-
fun Card(
|
|
24
|
+
internal fun Card(
|
|
31
25
|
useShadow: Boolean = false,
|
|
32
26
|
backgroundImage: String? = null,
|
|
27
|
+
contentPadding: PaddingValues = PaddingValues(Spacing.M),
|
|
33
28
|
modifier: Modifier = Modifier,
|
|
34
29
|
content: @Composable () -> Unit,
|
|
35
30
|
) {
|
|
@@ -39,20 +34,12 @@ fun Card(
|
|
|
39
34
|
var surface = modifier.fillMaxWidth().padding(horizontal = 12.dp)
|
|
40
35
|
if (useShadow) surface = surface.shadow(4.dp, shape)
|
|
41
36
|
surface = surface.clip(shape)
|
|
42
|
-
// The React kit drops the surface colour when a backgroundImage is supplied
|
|
43
|
-
// (`!!backgroundImage && { backgroundColor: undefined }`).
|
|
44
37
|
if (backgroundImage == null) surface = surface.background(AppTheme.current.colors.background.surface)
|
|
45
38
|
Box(modifier = surface) {
|
|
46
|
-
// React renders this as an absolutely-filled child BEHIND the content
|
|
47
|
-
// (styles.imageBackground: position absolute, inset 0) — matchParentSize
|
|
48
|
-
// fills the card without contributing to its measured size.
|
|
49
39
|
if (backgroundImage != null) {
|
|
50
40
|
Image(source = backgroundImage, modifier = Modifier.matchParentSize())
|
|
51
41
|
}
|
|
52
|
-
BoxWithConstraints(modifier = Modifier.fillMaxWidth().padding(
|
|
53
|
-
// `- 1.dp / columns` mirrors the React kit's `- 1 / numberOfColumns`
|
|
54
|
-
// nudge: it keeps a full 12-span Item ~1dp narrower than the content
|
|
55
|
-
// box so sub-pixel rounding can never overflow and wrap the FlowRow.
|
|
42
|
+
BoxWithConstraints(modifier = Modifier.fillMaxWidth().padding(contentPadding)) {
|
|
56
43
|
val sizePerSpan = (maxWidth - gutter * (columns - 1)) / columns - 1.dp / columns
|
|
57
44
|
CompositionLocalProvider(LocalGridContext provides GridContext(columns, gutter, sizePerSpan)) {
|
|
58
45
|
FlowRow(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
package vn.momo.kits.layout
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.layout.
|
|
3
|
+
import androidx.compose.foundation.layout.Column
|
|
4
4
|
import androidx.compose.foundation.layout.height
|
|
5
5
|
import androidx.compose.foundation.layout.width
|
|
6
6
|
import androidx.compose.runtime.Composable
|
|
@@ -9,30 +9,18 @@ import androidx.compose.ui.Modifier
|
|
|
9
9
|
import androidx.compose.ui.unit.Dp
|
|
10
10
|
import androidx.compose.ui.unit.dp
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
* Grid context supplied by [Section] / [Card]: the per-span cell size + gutter,
|
|
14
|
-
* so an [Item] can resolve its pixel width from a 1-12 `widthSpan`. Mirrors the
|
|
15
|
-
* React kit's GridContext (foundations/Layout/GridSystem).
|
|
16
|
-
*/
|
|
17
|
-
data class GridContext(
|
|
12
|
+
internal data class GridContext(
|
|
18
13
|
val numberOfColumns: Int = 12,
|
|
19
14
|
val gutter: Dp = 12.dp,
|
|
20
15
|
val sizePerSpan: Dp = 0.dp,
|
|
21
16
|
) {
|
|
22
|
-
/** Width covering `span` columns + the (span-1) gutters between them. */
|
|
23
17
|
fun sizeForSpan(span: Int): Dp = sizePerSpan * span + gutter * (span - 1)
|
|
24
18
|
}
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
val LocalGridContext = compositionLocalOf<GridContext?> { null }
|
|
20
|
+
internal val LocalGridContext = compositionLocalOf<GridContext?> { null }
|
|
28
21
|
|
|
29
|
-
/**
|
|
30
|
-
* A grid cell. `widthSpan` (1-12) sets the width as a fraction of the parent
|
|
31
|
-
* Section/Card; the parent's flex-wrap flows the cells into rows. Rendered
|
|
32
|
-
* outside a grid it just wraps its content at its natural size.
|
|
33
|
-
*/
|
|
34
22
|
@Composable
|
|
35
|
-
fun Item(
|
|
23
|
+
internal fun Item(
|
|
36
24
|
widthSpan: Int = 12,
|
|
37
25
|
heightSpan: Int = 0,
|
|
38
26
|
modifier: Modifier = Modifier,
|
|
@@ -44,5 +32,7 @@ fun Item(
|
|
|
44
32
|
m = m.width(grid.sizeForSpan(widthSpan))
|
|
45
33
|
if (heightSpan > 0) m = m.height(grid.sizeForSpan(heightSpan))
|
|
46
34
|
}
|
|
47
|
-
|
|
35
|
+
// A Column (not a Box) so multiple children flow VERTICALLY instead of stacking
|
|
36
|
+
// on top of each other — matches the React Item's flex-column content.
|
|
37
|
+
Column(modifier = m) { content() }
|
|
48
38
|
}
|
|
@@ -3,7 +3,6 @@ package vn.momo.kits.layout
|
|
|
3
3
|
import androidx.compose.foundation.layout.Arrangement
|
|
4
4
|
import androidx.compose.foundation.layout.Box
|
|
5
5
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
6
|
-
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
7
6
|
import androidx.compose.foundation.layout.FlowRow
|
|
8
7
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
9
8
|
import androidx.compose.foundation.layout.padding
|
|
@@ -13,14 +12,8 @@ import androidx.compose.ui.Modifier
|
|
|
13
12
|
import androidx.compose.ui.unit.dp
|
|
14
13
|
import vn.momo.kits.components.Image
|
|
15
14
|
|
|
16
|
-
/**
|
|
17
|
-
* A transparent 12-column grid row that wraps its [Item]s (React kit
|
|
18
|
-
* foundations/Layout/Section). 12dp gutter; 12dp horizontal margin when
|
|
19
|
-
* `useMargin`; optional [backgroundImage]. Items flow left-to-right and wrap.
|
|
20
|
-
*/
|
|
21
|
-
@OptIn(ExperimentalLayoutApi::class)
|
|
22
15
|
@Composable
|
|
23
|
-
fun Section(
|
|
16
|
+
internal fun Section(
|
|
24
17
|
useMargin: Boolean = true,
|
|
25
18
|
backgroundImage: String? = null,
|
|
26
19
|
modifier: Modifier = Modifier,
|
|
@@ -30,15 +23,10 @@ fun Section(
|
|
|
30
23
|
val gutter = 12.dp
|
|
31
24
|
val margin = if (useMargin) 12.dp else 0.dp
|
|
32
25
|
Box(modifier = modifier.fillMaxWidth().padding(horizontal = margin)) {
|
|
33
|
-
// React renders this as an absolutely-filled child behind the content
|
|
34
|
-
// (styles.imageBackground: position absolute, inset 0).
|
|
35
26
|
if (backgroundImage != null) {
|
|
36
27
|
Image(source = backgroundImage, modifier = Modifier.matchParentSize())
|
|
37
28
|
}
|
|
38
29
|
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
|
|
39
|
-
// `- 1.dp / columns` mirrors the React kit's `- 1 / numberOfColumns`
|
|
40
|
-
// nudge — keeps a full 12-span Item ~1dp inside the content box so
|
|
41
|
-
// sub-pixel rounding can never overflow and wrap the FlowRow.
|
|
42
30
|
val sizePerSpan = (maxWidth - gutter * (columns - 1)) / columns - 1.dp / columns
|
|
43
31
|
CompositionLocalProvider(LocalGridContext provides GridContext(columns, gutter, sizePerSpan)) {
|
|
44
32
|
FlowRow(
|
|
@@ -28,6 +28,7 @@ import androidx.compose.runtime.remember
|
|
|
28
28
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
29
29
|
import androidx.compose.ui.Alignment
|
|
30
30
|
import androidx.compose.ui.Modifier
|
|
31
|
+
import androidx.compose.ui.graphics.Brush
|
|
31
32
|
import androidx.compose.ui.graphics.Color
|
|
32
33
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
33
34
|
import androidx.compose.ui.platform.LocalDensity
|
|
@@ -43,6 +44,7 @@ import vn.momo.kits.const.Radius
|
|
|
43
44
|
import vn.momo.kits.const.Spacing
|
|
44
45
|
import vn.momo.kits.application.LocalContext
|
|
45
46
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
47
|
+
import vn.momo.kits.const.AppNavigationBar
|
|
46
48
|
import vn.momo.kits.const.Typography
|
|
47
49
|
import vn.momo.kits.navigation.tracking.ScreenTracker
|
|
48
50
|
import vn.momo.kits.modifier.conditional
|
|
@@ -56,10 +58,11 @@ internal fun BottomSheet(
|
|
|
56
58
|
header: BottomHeader,
|
|
57
59
|
isSurface: Boolean = false,
|
|
58
60
|
barrierDismissible: Boolean = true,
|
|
61
|
+
draggable: Boolean = true,
|
|
62
|
+
useBottomInset: Boolean = true,
|
|
63
|
+
footerComponent: (@Composable () -> Unit)? = null,
|
|
59
64
|
onDismiss: (() -> Unit)?
|
|
60
65
|
) {
|
|
61
|
-
val navigator = LocalNavigator.current
|
|
62
|
-
|
|
63
66
|
val screenHeightDp = getScreenHeight()
|
|
64
67
|
val screenHeightPx = with(LocalDensity.current) { screenHeightDp.toPx() }
|
|
65
68
|
val sheetOffset = remember { Animatable(screenHeightPx) }
|
|
@@ -162,30 +165,35 @@ internal fun BottomSheet(
|
|
|
162
165
|
.noFeedbackClickable { },
|
|
163
166
|
contentAlignment = Alignment.BottomCenter
|
|
164
167
|
) {
|
|
165
|
-
|
|
168
|
+
val bottomInset = if (useBottomInset) {
|
|
169
|
+
(AppNavigationBar.current - keyboardSizeState().value).coerceAtLeast(0.dp)
|
|
170
|
+
} else 0.dp
|
|
171
|
+
Column(Modifier.padding(bottom = bottomInset)) {
|
|
166
172
|
Box(
|
|
167
173
|
modifier = Modifier
|
|
168
174
|
.height(72.dp)
|
|
169
175
|
.fillMaxWidth()
|
|
170
|
-
.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
change
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
.conditional(draggable) {
|
|
177
|
+
pointerInput(Unit) {
|
|
178
|
+
detectDragGestures(
|
|
179
|
+
onDrag = { change, dragAmount ->
|
|
180
|
+
change.consume()
|
|
181
|
+
coroutineScope.launch {
|
|
182
|
+
val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
|
|
183
|
+
sheetOffset.snapTo(newOffset)
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
onDragEnd = {
|
|
187
|
+
coroutineScope.launch {
|
|
188
|
+
if (sheetOffset.value > sheetCloseOffset) {
|
|
189
|
+
closeEvent()
|
|
190
|
+
} else {
|
|
191
|
+
sheetOffset.animateTo(0f)
|
|
192
|
+
}
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
|
-
|
|
188
|
-
|
|
195
|
+
)
|
|
196
|
+
}
|
|
189
197
|
}
|
|
190
198
|
) {
|
|
191
199
|
Column(
|
|
@@ -236,12 +244,50 @@ internal fun BottomSheet(
|
|
|
236
244
|
}
|
|
237
245
|
}
|
|
238
246
|
Divider()
|
|
239
|
-
|
|
247
|
+
if (footerComponent != null) {
|
|
248
|
+
Box(Modifier.weight(1f, fill = false)) {
|
|
249
|
+
content()
|
|
250
|
+
}
|
|
251
|
+
BottomSheetFooter(footerComponent)
|
|
252
|
+
} else {
|
|
253
|
+
content()
|
|
254
|
+
}
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
}
|
|
243
258
|
}
|
|
244
259
|
|
|
260
|
+
@Composable
|
|
261
|
+
private fun BottomSheetFooter(footerComponent: @Composable () -> Unit) {
|
|
262
|
+
val shadowBrush = remember {
|
|
263
|
+
Brush.verticalGradient(
|
|
264
|
+
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Box {
|
|
269
|
+
Box(
|
|
270
|
+
Modifier
|
|
271
|
+
.fillMaxWidth()
|
|
272
|
+
.background(AppTheme.current.colors.background.surface)
|
|
273
|
+
.conditional(IsShowBaseLineDebug) {
|
|
274
|
+
border(1.dp, Colors.blue_03)
|
|
275
|
+
}
|
|
276
|
+
.padding(vertical = Spacing.S)
|
|
277
|
+
) {
|
|
278
|
+
footerComponent()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Box(
|
|
282
|
+
modifier = Modifier
|
|
283
|
+
.fillMaxWidth()
|
|
284
|
+
.height(6.dp)
|
|
285
|
+
.offset(x = 0.dp, y = (-6).dp)
|
|
286
|
+
.background(shadowBrush)
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
245
291
|
sealed class BottomHeader {
|
|
246
292
|
data class Title(
|
|
247
293
|
val data: String = "Bottom Sheet Title",
|
|
@@ -32,6 +32,7 @@ class Navigation(
|
|
|
32
32
|
backgroundColor: Color? = null,
|
|
33
33
|
tintColor: Color? = null,
|
|
34
34
|
footerComponent: (@Composable () -> Unit)? = null,
|
|
35
|
+
footerTransparent: Boolean? = null,
|
|
35
36
|
floatingButtonProps: FloatingButtonProps? = null,
|
|
36
37
|
keyboardOptions: KeyboardOptions? = null
|
|
37
38
|
) {
|
|
@@ -48,6 +49,7 @@ class Navigation(
|
|
|
48
49
|
backgroundColor = backgroundColor ?: options.backgroundColor,
|
|
49
50
|
tintColor = tintColor ?: options.tintColor,
|
|
50
51
|
footerComponent = footerComponent ?: options.footerComponent,
|
|
52
|
+
footerTransparent = footerTransparent ?: options.footerTransparent,
|
|
51
53
|
floatingButtonProps = floatingButtonProps ?: options.floatingButtonProps,
|
|
52
54
|
keyboardOptions = keyboardOptions ?: options.keyboardOptions,
|
|
53
55
|
)
|
|
@@ -83,6 +85,9 @@ data class NavigationOptions(
|
|
|
83
85
|
val backgroundColor: Color? = null,
|
|
84
86
|
val tintColor: Color? = null,
|
|
85
87
|
val footerComponent: @Composable (() -> Unit)? = null,
|
|
88
|
+
// Transparent footer drops the surface background + shadow and floats over the content
|
|
89
|
+
// instead of taking a layout slot, so the content scrolls underneath it.
|
|
90
|
+
val footerTransparent: Boolean = false,
|
|
86
91
|
val floatingButtonProps: FloatingButtonProps? = null,
|
|
87
92
|
val keyboardOptions: KeyboardOptions = KeyboardOptions()
|
|
88
93
|
) {
|
|
@@ -139,10 +139,13 @@ class Navigator(
|
|
|
139
139
|
isSurface: Boolean = false,
|
|
140
140
|
barrierDismissible: Boolean = true,
|
|
141
141
|
onDismiss: (() -> Unit)? = null,
|
|
142
|
-
bottomSheetHeader: BottomHeader? = null
|
|
142
|
+
bottomSheetHeader: BottomHeader? = null,
|
|
143
|
+
draggable: Boolean = true,
|
|
144
|
+
useBottomInset: Boolean = true,
|
|
145
|
+
footerComponent: (@Composable () -> Unit)? = null
|
|
143
146
|
){
|
|
144
147
|
val id = currentScreen()?.id ?: -1
|
|
145
|
-
OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader)
|
|
148
|
+
OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader, draggable, useBottomInset, footerComponent)
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
|
|
@@ -257,6 +260,9 @@ sealed class OverplayComponentParams {
|
|
|
257
260
|
val onDismiss: (() -> Unit)? = null,
|
|
258
261
|
val barrierDismissible: Boolean = true,
|
|
259
262
|
val bottomSheetHeader: BottomHeader? = null,
|
|
263
|
+
val draggable: Boolean = true,
|
|
264
|
+
val useBottomInset: Boolean = true,
|
|
265
|
+
val footerComponent: (@Composable () -> Unit)? = null,
|
|
260
266
|
) : OverplayComponentParams()
|
|
261
267
|
|
|
262
268
|
class SnackBar : OverplayComponentParams()
|
|
@@ -288,10 +294,13 @@ object OverplayComponentRegistry {
|
|
|
288
294
|
barrierDismissible: Boolean = true,
|
|
289
295
|
onDismiss: (() -> Unit)?,
|
|
290
296
|
bottomSheetHeader: BottomHeader? = null,
|
|
297
|
+
draggable: Boolean = true,
|
|
298
|
+
useBottomInset: Boolean = true,
|
|
299
|
+
footerComponent: (@Composable () -> Unit)? = null,
|
|
291
300
|
){
|
|
292
301
|
val params = when(type){
|
|
293
302
|
OverplayComponentType.MODAL -> OverplayComponentParams.Modal(onDismiss, barrierDismissible)
|
|
294
|
-
OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader)
|
|
303
|
+
OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader, draggable, useBottomInset, footerComponent)
|
|
295
304
|
OverplayComponentType.SNACK_BAR -> OverplayComponentParams.SnackBar()
|
|
296
305
|
}
|
|
297
306
|
|
|
@@ -333,6 +342,9 @@ object OverplayComponentRegistry {
|
|
|
333
342
|
header = params.bottomSheetHeader ?: Title(),
|
|
334
343
|
isSurface = params.isSurface,
|
|
335
344
|
barrierDismissible = params.barrierDismissible,
|
|
345
|
+
draggable = params.draggable,
|
|
346
|
+
useBottomInset = params.useBottomInset,
|
|
347
|
+
footerComponent = params.footerComponent,
|
|
336
348
|
onDismiss = params.onDismiss
|
|
337
349
|
)
|
|
338
350
|
}
|
|
@@ -204,7 +204,14 @@ internal fun StackScreen(
|
|
|
204
204
|
|
|
205
205
|
Column(Modifier.zIndex(2f).fillMaxSize()) {
|
|
206
206
|
MainContent(content = content, isBottomTabRoot = isBottomTabRoot, isBottomTabChild = isBottomTabChild)
|
|
207
|
-
FooterContent()
|
|
207
|
+
if (!options.footerTransparent) FooterContent()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Transparent footer floats over the content so the content bleeds underneath it.
|
|
211
|
+
if (options.footerTransparent) {
|
|
212
|
+
Box(Modifier.zIndex(3f).align(Alignment.BottomCenter)) {
|
|
213
|
+
FooterContent()
|
|
214
|
+
}
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
Box(Modifier.zIndex(6f)) {
|
|
@@ -280,7 +287,7 @@ internal fun ColumnScope.MainContent(content: @Composable () -> Unit, isBottomTa
|
|
|
280
287
|
bottom = when {
|
|
281
288
|
options.hasFooter() || isBottomTabRoot -> 0.dp
|
|
282
289
|
isBottomTabChild -> bottomTabBarInset() + (navigationBar - keyboardSizeState().value).coerceAtLeast(0.dp)
|
|
283
|
-
!options.scrollData.scrollable -> 0.dp
|
|
290
|
+
!options.scrollData.scrollable && options.scrollData.scrollState == null -> 0.dp
|
|
284
291
|
else -> navigationBar
|
|
285
292
|
}
|
|
286
293
|
)
|
|
@@ -316,7 +323,11 @@ internal fun FooterContent() {
|
|
|
316
323
|
if (options.hasFooter()) {
|
|
317
324
|
val keyboardSize = keyboardSizeState()
|
|
318
325
|
val bottomPadding = (AppNavigationBar.current - keyboardSize.value).coerceAtLeast(0.dp)
|
|
319
|
-
Footer(
|
|
326
|
+
Footer(
|
|
327
|
+
footerComponent = options.footerComponent,
|
|
328
|
+
bottomPadding = bottomPadding,
|
|
329
|
+
transparent = options.footerTransparent
|
|
330
|
+
)
|
|
320
331
|
}
|
|
321
332
|
}
|
|
322
333
|
|
|
@@ -340,7 +351,7 @@ internal fun OverplayView(isBottomTabChild: Boolean, id: Int) {
|
|
|
340
351
|
}
|
|
341
352
|
|
|
342
353
|
@Composable
|
|
343
|
-
fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
354
|
+
fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp, transparent: Boolean = false) {
|
|
344
355
|
if (footerComponent == null) return
|
|
345
356
|
|
|
346
357
|
val footerHeightPx = LocalFooterHeightPx.current
|
|
@@ -351,13 +362,15 @@ fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
|
351
362
|
)
|
|
352
363
|
}
|
|
353
364
|
|
|
365
|
+
val backgroundColor = if (transparent) Color.Transparent else AppTheme.current.colors.background.surface
|
|
366
|
+
|
|
354
367
|
Box(Modifier.onGloballyPositioned {
|
|
355
368
|
if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
|
|
356
369
|
}) {
|
|
357
370
|
Box(
|
|
358
371
|
Modifier
|
|
359
372
|
.fillMaxWidth()
|
|
360
|
-
.background(
|
|
373
|
+
.background(backgroundColor)
|
|
361
374
|
.conditional(IsShowBaseLineDebug) {
|
|
362
375
|
border(1.dp, Colors.blue_03)
|
|
363
376
|
}
|
|
@@ -366,13 +379,15 @@ fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
|
366
379
|
footerComponent.invoke()
|
|
367
380
|
}
|
|
368
381
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
382
|
+
if (!transparent) {
|
|
383
|
+
Box(
|
|
384
|
+
modifier = Modifier
|
|
385
|
+
.fillMaxWidth()
|
|
386
|
+
.height(6.dp)
|
|
387
|
+
.offset(x = 0.dp, y = (-6).dp)
|
|
388
|
+
.background(shadowBrush)
|
|
389
|
+
)
|
|
390
|
+
}
|
|
376
391
|
}
|
|
377
392
|
}
|
|
378
393
|
|
|
@@ -31,7 +31,6 @@ object ScreenTracker {
|
|
|
31
31
|
val previousScreen = lastScreenName
|
|
32
32
|
lastScreenName = screenName
|
|
33
33
|
|
|
34
|
-
println("Tracking screen navigated native kit: $screenName")
|
|
35
34
|
maxApi?.track(
|
|
36
35
|
eventName = "auto_screen_navigated",
|
|
37
36
|
params = buildBaseParams(context, screenName).apply {
|
package/gradle.properties
CHANGED
|
@@ -100,6 +100,20 @@ public struct HeaderBackground: View {
|
|
|
100
100
|
.frame(height: height)
|
|
101
101
|
.shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
|
|
102
102
|
.background(backgroundColor)
|
|
103
|
+
.overlay(
|
|
104
|
+
headerTransparent ? AnyView(EmptyView()) :
|
|
105
|
+
AnyView(
|
|
106
|
+
Rectangle()
|
|
107
|
+
.fill(LinearGradient(
|
|
108
|
+
gradient: Gradient(colors: [
|
|
109
|
+
Color(red: 1, green: 0.8, blue: 0.87),
|
|
110
|
+
Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
|
|
111
|
+
]),
|
|
112
|
+
startPoint: .top,
|
|
113
|
+
endPoint: .bottom))
|
|
114
|
+
.opacity(opacity)
|
|
115
|
+
.frame(height: height))
|
|
116
|
+
)
|
|
103
117
|
if !headerTransparent {
|
|
104
118
|
Rectangle()
|
|
105
119
|
.fill(Color.black.opacity(0.1))
|
|
@@ -132,17 +146,6 @@ public struct HeaderBackground: View {
|
|
|
132
146
|
}
|
|
133
147
|
}
|
|
134
148
|
|
|
135
|
-
// MARK: - Header right width preference
|
|
136
|
-
|
|
137
|
-
/// Reports the measured width of the header-right area so the animated search
|
|
138
|
-
/// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
|
|
139
|
-
public struct HeaderRightWidthKey: PreferenceKey {
|
|
140
|
-
public static var defaultValue: CGFloat = 0
|
|
141
|
-
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
|
142
|
-
value = max(value, nextValue())
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
149
|
// MARK: - Bottom-tab root preference
|
|
147
150
|
|
|
148
151
|
/// Set by `BottomTab` on appear so its enclosing `StackScreen` can detect that
|
|
@@ -232,9 +235,6 @@ public struct Header: View {
|
|
|
232
235
|
|
|
233
236
|
if let headerRight = headerRight {
|
|
234
237
|
AnyView(headerRight())
|
|
235
|
-
.background(GeometryReader { geo in
|
|
236
|
-
Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
|
|
237
|
-
})
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
.padding(.horizontal, 12)
|