@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.
Files changed (34) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +1 -2
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -1
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +5 -5
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +19 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +5 -18
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +7 -17
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +1 -13
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +67 -21
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +5 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +15 -3
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +27 -12
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -1
  16. package/gradle.properties +1 -1
  17. package/ios/Application/Components.swift +14 -14
  18. package/ios/Application/HeaderRight.swift +45 -79
  19. package/ios/Application/Localize.swift +32 -0
  20. package/ios/Application/Navigation/NavigationContainer.swift +3 -0
  21. package/ios/Application/Navigation/component/Header.swift +1 -1
  22. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +11 -0
  23. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +196 -0
  24. package/ios/Button/Button.swift +14 -20
  25. package/ios/Popup/PopupDisplay.swift +1 -1
  26. package/ios/Popup/PopupNotify.swift +1 -1
  27. package/ios/Popup/PopupPromotion.swift +5 -11
  28. package/ios/Template/TrustBanner/TrustBanner.swift +2 -2
  29. package/ios/native-kits.podspec +1 -1
  30. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  31. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +1 -1
  32. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -2
  33. package/local.properties +2 -2
  34. 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
+ }
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.163.1-beta.9-debug"
43
+ version = "0.163.1-footer.1-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.163.1-beta.9-debug'
3
+ spec.version = '0.163.1-footer.1-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -167,8 +167,7 @@ private fun ToolkitHeaderRight(
167
167
  if (toolResponse != null) {
168
168
  headerRight?.toolCallback?.invoke(toolResponse)
169
169
  }
170
- } catch (e: Exception) {
171
- println("Error parsing response: $e")
170
+ } catch (_: Exception) {
172
171
  }
173
172
  }
174
173
  }
@@ -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(0xff),
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(12.dp)) {
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.Box
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
- /** Provided by Section/Card; null when an Item is rendered outside a grid. */
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
- Box(modifier = m) { content() }
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
- Column {
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
- .pointerInput(Unit) {
171
- detectDragGestures(
172
- onDrag = { change, dragAmount ->
173
- change.consume()
174
- coroutineScope.launch {
175
- val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
176
- sheetOffset.snapTo(newOffset)
177
- }
178
- },
179
- onDragEnd = {
180
- coroutineScope.launch {
181
- if (sheetOffset.value > sheetCloseOffset) {
182
- closeEvent()
183
- } else {
184
- sheetOffset.animateTo(0f)
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
- content()
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(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
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(AppTheme.current.colors.background.surface)
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
- Box(
370
- modifier = Modifier
371
- .fillMaxWidth()
372
- .height(6.dp)
373
- .offset(x = 0.dp, y = (-6).dp)
374
- .background(shadowBrush)
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
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.163.1-beta.9
21
+ version=0.163.1-footer.1
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -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)