@momo-kits/native-kits 0.160.1-lottie.2-debug → 0.160.1-navigatorevent.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 (26) hide show
  1. package/compose/build.gradle.kts +9 -3
  2. package/compose/build.gradle.kts.backup +8 -2
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +9 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +8 -2
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +54 -34
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +4 -3
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +5 -1
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +5 -1
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +19 -14
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +1 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +19 -3
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationEvent.kt +53 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +15 -1
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +11 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +63 -49
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +62 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +22 -2
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +12 -4
  20. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +147 -0
  21. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +10 -4
  22. package/gradle/libs.versions.toml +2 -0
  23. package/gradle.properties +1 -1
  24. package/ios/Input/Input.swift +33 -4
  25. package/package.json +1 -1
  26. package/settings.gradle.kts +15 -3
@@ -30,7 +30,7 @@ kotlin {
30
30
  }
31
31
 
32
32
  val fwName = gitlabArtifactId
33
- val iosTargets = listOf(iosX64(), iosArm64(), iosSimulatorArm64())
33
+ val iosTargets = listOf(iosArm64(), iosSimulatorArm64())
34
34
  iosTargets.forEach {
35
35
  it.binaries.framework {
36
36
  baseName = fwName
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.160.1-lottie.2-debug"
43
+ version = "0.160.1-navigatorevent.1-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -73,6 +73,7 @@ kotlin {
73
73
  implementation(libs.jetbrains.serialization.json)
74
74
  implementation(libs.kotlinx.datetime)
75
75
  api(libs.native.max.api)
76
+ implementation(libs.compottie)
76
77
  }
77
78
  androidMain.dependencies {
78
79
  implementation(libs.ktor.client.okhttp)
@@ -108,12 +109,17 @@ repositories {
108
109
  }
109
110
  }
110
111
  mavenCentral()
112
+ maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
111
113
  maven {
112
114
  url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
113
115
  credentials {username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
114
116
  }
115
117
  maven {
116
- url = uri("http://nexus.mservice.com.vn:8081/repository/maven-public/")
118
+ url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
119
+ credentials {
120
+ username = "viewer"
121
+ password = "viewer"
122
+ }
117
123
  isAllowInsecureProtocol = true
118
124
  }
119
125
  }
@@ -30,7 +30,7 @@ kotlin {
30
30
  }
31
31
 
32
32
  val fwName = gitlabArtifactId
33
- val iosTargets = listOf(iosX64(), iosArm64(), iosSimulatorArm64())
33
+ val iosTargets = listOf(iosArm64(), iosSimulatorArm64())
34
34
  iosTargets.forEach {
35
35
  it.binaries.framework {
36
36
  baseName = fwName
@@ -73,6 +73,7 @@ kotlin {
73
73
  implementation(libs.jetbrains.serialization.json)
74
74
  implementation(libs.kotlinx.datetime)
75
75
  api(libs.native.max.api)
76
+ implementation(libs.compottie)
76
77
  }
77
78
  androidMain.dependencies {
78
79
  implementation(libs.ktor.client.okhttp)
@@ -108,12 +109,17 @@ repositories {
108
109
  }
109
110
  }
110
111
  mavenCentral()
112
+ maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
111
113
  maven {
112
114
  url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
113
115
  credentials {username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
114
116
  }
115
117
  maven {
116
- url = uri("http://nexus.mservice.com.vn:8081/repository/maven-public/")
118
+ url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
119
+ credentials {
120
+ username = "viewer"
121
+ password = "viewer"
122
+ }
117
123
  isAllowInsecureProtocol = true
118
124
  }
119
125
  }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.159.1-beta.12'
3
+ spec.version = '0.160.1-scrolltotop.22'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -0,0 +1,9 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ import androidx.compose.runtime.Composable
4
+
5
+ @Composable
6
+ internal actual fun RegisterScrollToTop(callback: (() -> Unit)?) = Unit
7
+
8
+ internal actual fun onNavigatorEntered(navigator: Navigator) = Unit
9
+ internal actual fun onNavigatorExited(navigator: Navigator) = Unit
@@ -70,7 +70,7 @@ actual fun getScreenHeight(): Dp {
70
70
  return getScreenDimensions().height.dp + if (getOSVersion() >= 35) 0.dp else AppStatusBar.current + AppNavigationBar.current
71
71
  }
72
72
 
73
- actual fun getOSVersion(): Int = Build.VERSION.SDK_INT
73
+ actual fun getOSVersion(): OSVersion = OSVersion.Android(sdk = Build.VERSION.SDK_INT)
74
74
 
75
75
  @Composable
76
76
  actual fun LottieAnimation(
@@ -78,8 +78,14 @@ actual fun LottieAnimation(
78
78
  tintColor: Color?,
79
79
  bgColor: Color?,
80
80
  placedAsOverlay: Boolean,
81
- modifier: Modifier
81
+ modifier: Modifier,
82
+ useCompose: Boolean
82
83
  ) {
84
+ if (useCompose) {
85
+ ComposeLottieAnimation(path, tintColor, bgColor, modifier)
86
+ return
87
+ }
88
+
83
89
  val json = readJson(path)
84
90
 
85
91
  if (json.isEmpty()) {
@@ -63,6 +63,7 @@ import androidx.compose.ui.platform.LocalDensity
63
63
  import androidx.compose.ui.platform.LocalFocusManager
64
64
  import androidx.compose.ui.platform.LocalSoftwareKeyboardController
65
65
  import androidx.compose.ui.text.style.TextOverflow
66
+ import androidx.compose.ui.text.TextStyle
66
67
  import androidx.compose.ui.unit.Constraints
67
68
  import androidx.compose.ui.unit.Dp
68
69
  import androidx.compose.ui.unit.IntOffset
@@ -78,6 +79,7 @@ import vn.momo.kits.const.Colors
78
79
  import vn.momo.kits.const.Radius
79
80
  import vn.momo.kits.const.Spacing
80
81
  import vn.momo.kits.const.Typography
82
+ import vn.momo.kits.modifier.conditional
81
83
  import vn.momo.kits.modifier.kitsAutomationId
82
84
  import vn.momo.kits.modifier.noFeedbackClickable
83
85
  import vn.momo.kits.modifier.setAutomationId
@@ -101,6 +103,9 @@ fun LiteScreen(
101
103
  useAnimationSearch: Boolean = true,
102
104
  titlePosition: TitlePosition = TitlePosition.LEFT,
103
105
  headerRightData: HeaderRightData? = null,
106
+ headerTintColor: Color? = null,
107
+ headerBackgroundColor: Color? = null,
108
+ headerSpaceBetween: Dp? = null,
104
109
  /* End of header props */
105
110
 
106
111
  screenContent: @Composable () -> Unit,
@@ -135,6 +140,9 @@ fun LiteScreen(
135
140
  titlePosition = titlePosition,
136
141
  useAnimationSearch = useAnimationSearch,
137
142
  headerRightData = headerRightData,
143
+ tintColor = headerTintColor,
144
+ headerBackgroundColor = headerBackgroundColor,
145
+ headerSpaceBetween = headerSpaceBetween,
138
146
  )
139
147
 
140
148
  Box(
@@ -166,6 +174,8 @@ private fun LiteScreenHeader(
166
174
  titlePosition: TitlePosition = TitlePosition.LEFT,
167
175
  useAnimationSearch: Boolean = true,
168
176
  onGoBack: (() -> Unit)? = null,
177
+ headerBackgroundColor: Color? = null,
178
+ headerSpaceBetween: Dp? = null,
169
179
  inputSearchProps: LiteInputSearchProps? = null,
170
180
  headerRight: @Composable (() -> Unit)? = null,
171
181
  ) {
@@ -242,6 +252,7 @@ private fun LiteScreenHeader(
242
252
  statusBarHeight,
243
253
  titlePosition,
244
254
  scrollPercentage,
255
+ headerSpaceBetween,
245
256
  ) {
246
257
  LiteScreenHeaderPolicy(
247
258
  useAnimationSearch = useAnimationSearch,
@@ -249,6 +260,7 @@ private fun LiteScreenHeader(
249
260
  statusBarHeight = statusBarHeight,
250
261
  titlePosition = titlePosition,
251
262
  scrollPercentage = scrollPercentage,
263
+ headerSpaceBetween = headerSpaceBetween,
252
264
  )
253
265
  }
254
266
 
@@ -260,17 +272,21 @@ private fun LiteScreenHeader(
260
272
  HeaderId.EXTENDED_HEADER_HEIGHT.toPx(),
261
273
  size.height,
262
274
  )
263
- drawRect(color = Colors.black_01)
264
- drawRect(
265
- brush = Brush.linearGradient(
266
- colors = listGradientColors,
267
- start = Offset.Zero,
268
- end = Offset(
269
- x = 0f,
270
- y = headerHeight * (1 - scrollPercentage.value),
271
- ),
275
+ headerBackgroundColor?.let {
276
+ drawRect(color = it)
277
+ } ?: run {
278
+ drawRect(color = Colors.black_01)
279
+ drawRect(
280
+ brush = Brush.linearGradient(
281
+ colors = listGradientColors,
282
+ start = Offset.Zero,
283
+ end = Offset(
284
+ x = 0f,
285
+ y = headerHeight * (1 - scrollPercentage.value),
286
+ ),
287
+ )
272
288
  )
273
- )
289
+ }
274
290
  },
275
291
  content = {
276
292
  if (onGoBack != null) {
@@ -279,17 +295,19 @@ private fun LiteScreenHeader(
279
295
  .size(28.dp)
280
296
  .layoutId(HeaderId.BACK_ID)
281
297
  .clip(CircleShape)
282
- .border(
283
- width = 0.2.dp,
284
- color = headerColor.borderColor,
285
- shape = CircleShape,
286
- )
287
- .background(color = headerColor.backgroundButton)
298
+ .conditional(inputSearchProps?.customBackIcon == null) {
299
+ this.border(
300
+ width = 0.2.dp,
301
+ color = headerColor.borderColor,
302
+ shape = CircleShape,
303
+ )
304
+ .background(color = headerColor.backgroundButton)
305
+ }
288
306
  .noFeedbackClickable(onClick = onGoBack)
289
307
  .setAutomationId("btn_navigation_back")
290
308
  .padding(Spacing.XS),
291
309
  ) {
292
- Icon(
310
+ inputSearchProps?.customBackIcon?.invoke() ?: Icon(
293
311
  source = "arrow-back",
294
312
  color = headerColor.tintIconColor,
295
313
  size = 20.dp,
@@ -338,6 +356,7 @@ private class LiteScreenHeaderPolicy(
338
356
  private val statusBarHeight: Dp,
339
357
  private val scrollPercentage: State<Float>,
340
358
  private val titlePosition: TitlePosition,
359
+ private val headerSpaceBetween: Dp? = null,
341
360
  ) : MeasurePolicy {
342
361
 
343
362
  override fun MeasureScope.measure(
@@ -345,6 +364,7 @@ private class LiteScreenHeaderPolicy(
345
364
  constraints: Constraints
346
365
  ): MeasureResult {
347
366
  val spacing12 = Spacing.M.roundToPx()
367
+ val spaceBetween = headerSpaceBetween?.roundToPx() ?: spacing12
348
368
  val statusBarPx = statusBarHeight.roundToPx()
349
369
  val scrollPercent = scrollPercentage.value
350
370
 
@@ -364,7 +384,7 @@ private class LiteScreenHeaderPolicy(
364
384
  )
365
385
  val inputSearchConstraints = if (isHeaderExtend) {
366
386
  val minWidth =
367
- if (useAnimationSearch) realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spacing12 * 2
387
+ if (useAnimationSearch) realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
368
388
  else realConstraints.maxWidth
369
389
  realConstraints.copy(
370
390
  maxWidth = (realConstraints.maxWidth * (1 - scrollPercent)).toInt()
@@ -372,8 +392,8 @@ private class LiteScreenHeaderPolicy(
372
392
  )
373
393
  } else {
374
394
  var spaceConsumed = 0
375
- if (backIconPlaceable.safeWidth != 0) spaceConsumed += backIconPlaceable.safeWidth + spacing12
376
- if (headerRightPlaceable.safeWidth != 0) spaceConsumed += headerRightPlaceable.safeWidth + spacing12
395
+ if (backIconPlaceable.safeWidth != 0) spaceConsumed += backIconPlaceable.safeWidth + spaceBetween
396
+ if (headerRightPlaceable.safeWidth != 0) spaceConsumed += headerRightPlaceable.safeWidth + spaceBetween
377
397
  realConstraints.copy(
378
398
  maxWidth = realConstraints.maxWidth - spaceConsumed
379
399
  )
@@ -382,7 +402,7 @@ private class LiteScreenHeaderPolicy(
382
402
  ?.measure(inputSearchConstraints)
383
403
  val titlePlaceable = measurables.find { it.layoutId == HeaderId.TITLE_ID }?.measure(
384
404
  constraints = realConstraints.copy(
385
- maxWidth = realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spacing12 * 2
405
+ maxWidth = realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
386
406
  )
387
407
  )
388
408
 
@@ -420,7 +440,7 @@ private class LiteScreenHeaderPolicy(
420
440
  x = startX,
421
441
  y = startY + backIconPlaceable.verticalCenterOffset(firstRowMaxHeight),
422
442
  )
423
- curX += backIconPlaceable.safeWidth + spacing12
443
+ curX += backIconPlaceable.safeWidth + spaceBetween
424
444
  }
425
445
 
426
446
  headerRightPlaceable?.place(
@@ -445,7 +465,7 @@ private class LiteScreenHeaderPolicy(
445
465
 
446
466
  val inputSearchOffset = if (isHeaderExtend) {
447
467
  IntOffset(
448
- x = startX + ((backIconPlaceable.safeWidth + spacing12) * (scrollPercent * 2f).coerceIn(
468
+ x = startX + ((backIconPlaceable.safeWidth + spaceBetween) * (scrollPercent * 2f).coerceIn(
449
469
  0f, 1f
450
470
  )).toInt(),
451
471
  y = (curY * (1 - scrollPercent)).toInt().coerceAtLeast(
@@ -504,6 +524,11 @@ data class LiteInputSearchProps(
504
524
 
505
525
  val placeHolder: String? = null,
506
526
 
527
+ val customBackIcon: @Composable (() -> Unit)? = null,
528
+ val customSearchIcon: @Composable (() -> Unit)? = null,
529
+ val customPlaceHolder: @Composable (() -> Unit)? = null,
530
+ val customTextStyle: TextStyle? = null,
531
+ val customIconClear: @Composable (() -> Unit)? = null,
507
532
  val iconRightTextField: @Composable ((Modifier) -> Unit)? = null,
508
533
  )
509
534
 
@@ -545,7 +570,6 @@ private fun LiteInputSearch(
545
570
  val textFieldModifier = remember(inputSearchProps.modifier) {
546
571
  inputSearchProps.modifier
547
572
  .weight(1f)
548
- .sizeIn(minHeight = 36.dp)
549
573
  }
550
574
  BasicTextField(
551
575
  value = textState,
@@ -554,7 +578,7 @@ private fun LiteInputSearch(
554
578
  keyboardOptions = inputSearchProps.keyboardOptions,
555
579
  keyboardActions = inputSearchProps.keyboardActions,
556
580
  modifier = textFieldModifier,
557
- textStyle = inputFieldStyle,
581
+ textStyle = inputSearchProps.customTextStyle ?: inputFieldStyle,
558
582
  singleLine = true,
559
583
  interactionSource = interactionSource,
560
584
  decorationBox = { innerTextField ->
@@ -592,10 +616,6 @@ private fun LiteInputSearch(
592
616
 
593
617
  Row(
594
618
  modifier = Modifier
595
- .background(
596
- color = theme.colors.background.surface,
597
- shape = RoundedCornerShape(Radius.XL),
598
- )
599
619
  .padding(
600
620
  horizontal = Spacing.M,
601
621
  vertical = Spacing.S,
@@ -603,7 +623,7 @@ private fun LiteInputSearch(
603
623
  horizontalArrangement = Arrangement.Start,
604
624
  verticalAlignment = Alignment.CenterVertically,
605
625
  ) {
606
- Icon(
626
+ inputSearchProps.customSearchIcon?.invoke() ?: Icon(
607
627
  source = "navigation_search",
608
628
  modifier = Modifier.padding(end = Spacing.XS),
609
629
  size = 24.dp,
@@ -614,9 +634,9 @@ private fun LiteInputSearch(
614
634
  contentAlignment = Alignment.CenterStart,
615
635
  ) {
616
636
  if (!placeHolder.isNullOrEmpty()) {
617
- Text(
637
+ inputSearchProps.customPlaceHolder?.invoke() ?: Text(
618
638
  text = placeHolder ?: "",
619
- style = Typography.bodyDefaultRegular,
639
+ style = inputSearchProps.customTextStyle ?: Typography.bodyDefaultRegular,
620
640
  maxLines = 1,
621
641
  color = theme.colors.text.hint,
622
642
  overflow = TextOverflow.Ellipsis
@@ -626,7 +646,7 @@ private fun LiteInputSearch(
626
646
  }
627
647
 
628
648
  if (isShowClear) {
629
- Icon(
649
+ inputSearchProps.customIconClear?.invoke() ?: Icon(
630
650
  source = "24_navigation_close_circle_full",
631
651
  size = 16.dp,
632
652
  color = theme.colors.text.hint,
@@ -717,4 +737,4 @@ fun Modifier.hideKeyboardOnTap() = composed {
717
737
  focusManager.clearFocus()
718
738
  }
719
739
  }
720
- }
740
+ }
@@ -59,8 +59,9 @@ import vn.momo.kits.modifier.DeprecatedModifier
59
59
  import vn.momo.kits.modifier.conditional
60
60
  import vn.momo.kits.modifier.shadow
61
61
  import vn.momo.kits.navigation.component.SnackBar
62
- import vn.momo.kits.platform.getOSVersion
62
+ import vn.momo.kits.platform.supportsImePadding
63
63
  import vn.momo.kits.utils.getAppStatusBarHeight
64
+ import vn.momo.kits.utils.getNavigationBarHeight
64
65
 
65
66
  enum class HeaderType {
66
67
  DEFAULT,
@@ -102,7 +103,7 @@ fun Screen(
102
103
  val keyboardController = LocalSoftwareKeyboardController.current
103
104
 
104
105
  val isKeyboardVisible = isKeyboardVisible()
105
- val indicator = getAppStatusBarHeight()
106
+ val indicator = getNavigationBarHeight()
106
107
  val bottomPadding = if (isKeyboardVisible) 0.dp else indicator
107
108
 
108
109
  val headerHeight = if (animatedHeader !== null)
@@ -134,7 +135,7 @@ fun Screen(
134
135
  Box(
135
136
  Modifier.fillMaxSize()
136
137
  .background(backgroundColor ?: AppTheme.current.colors.background.default)
137
- .conditional(useAvoidKeyboard && getOSVersion() > 29) {
138
+ .conditional(useAvoidKeyboard && supportsImePadding()) {
138
139
  imePadding()
139
140
  }.then(DeprecatedModifier())
140
141
  ) {
@@ -221,6 +221,7 @@ fun Input(
221
221
  onBlur: () -> Unit = {},
222
222
  loading: Boolean = false,
223
223
  required: Boolean = false,
224
+ maxLength: Int? = null,
224
225
  fontWeight: InputFontWeight = InputFontWeight.REGULAR,
225
226
  keyboardType: KeyboardType = KeyboardType.Text,
226
227
  modifier: Modifier = Modifier,
@@ -319,7 +320,10 @@ fun Input(
319
320
  onBlur()
320
321
  }
321
322
  },
322
- onValueChange = onChangeText,
323
+ onValueChange = { newText ->
324
+ val limitedText = maxLength?.let { newText.take(it) } ?: newText
325
+ onChangeText(limitedText)
326
+ },
323
327
  decorationBox = { innerTextField ->
324
328
  // Floating label
325
329
  if (floatingValue.isNotEmpty() || floatingIcon.isNotEmpty()) {
@@ -8,6 +8,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition
8
8
  import androidx.compose.animation.core.tween
9
9
  import androidx.compose.foundation.background
10
10
  import androidx.compose.foundation.border
11
+ import androidx.compose.ui.draw.alpha
11
12
  import androidx.compose.foundation.clickable
12
13
  import androidx.compose.foundation.layout.Arrangement
13
14
  import androidx.compose.foundation.layout.Box
@@ -129,8 +130,11 @@ fun InputOTP(
129
130
  if (!it.isFocused && isBlurred) onBlur()
130
131
  if (it.isFocused && !isBlurred) isBlurred = true
131
132
  },
132
- decorationBox = { _ ->
133
+ decorationBox = { innerTextField ->
133
134
  Box {
135
+ Box(
136
+ modifier = Modifier.fillMaxWidth().height(56.dp).alpha(0f)
137
+ ) { innerTextField() }
134
138
  if (floatingValue.isNotEmpty()) {
135
139
  Box(
136
140
  modifier = Modifier.wrapContentSize()
@@ -105,6 +105,8 @@ data class InputSearchProps(
105
105
  val iconModifier: Modifier = Modifier,
106
106
  val onClearPress: () -> Unit = {},
107
107
  val leftPosition: Dp? = null,
108
+ val placeholderCustomRender: (@Composable () -> Unit)? = null,
109
+ val searchIcon: (@Composable () -> Unit)? = null
108
110
  )
109
111
 
110
112
  @Composable
@@ -145,12 +147,13 @@ fun InputSearch(
145
147
  )
146
148
  }
147
149
 
148
- Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
149
- .fillMaxWidth()
150
- .height(36.dp)
151
- .conditional(IsShowBaseLineDebug) {
152
- border(1.dp, Colors.blue_03)
153
- }
150
+ Row(
151
+ verticalAlignment = Alignment.CenterVertically, modifier = Modifier
152
+ .fillMaxWidth()
153
+ .height(36.dp)
154
+ .conditional(IsShowBaseLineDebug) {
155
+ border(1.dp, Colors.blue_03)
156
+ }
154
157
  ) {
155
158
  BasicTextField(
156
159
  enabled = !inputSearchProps.disabled,
@@ -184,13 +187,14 @@ fun InputSearch(
184
187
  horizontalArrangement = Arrangement.Start,
185
188
  verticalAlignment = Alignment.CenterVertically
186
189
  ) {
190
+
187
191
  Row(
188
192
  modifier = Modifier.padding(
189
193
  horizontal = Spacing.M
190
194
  ),
191
195
  verticalAlignment = Alignment.CenterVertically
192
196
  ) {
193
- Icon(
197
+ inputSearchProps.searchIcon?.invoke() ?: Icon(
194
198
  source = "navigation_search",
195
199
  modifier = Modifier.padding(end = Spacing.XS),
196
200
  size = 24.dp,
@@ -198,13 +202,14 @@ fun InputSearch(
198
202
  )
199
203
  Box(Modifier.weight(1f)) {
200
204
  if (inputSearchProps.text.value.isEmpty()) {
201
- Text(
202
- text = inputSearchProps.placeholder,
203
- style = placeHolderStyle,
204
- maxLines = 1,
205
- color = placeholderColor,
206
- overflow = TextOverflow.Ellipsis
207
- )
205
+ inputSearchProps.placeholderCustomRender?.invoke()
206
+ ?: Text(
207
+ text = inputSearchProps.placeholder,
208
+ style = placeHolderStyle,
209
+ maxLines = 1,
210
+ color = placeholderColor,
211
+ overflow = TextOverflow.Ellipsis
212
+ )
208
213
  }
209
214
  innerTextField()
210
215
  }
@@ -96,4 +96,5 @@ data class KeyboardOptions(
96
96
  data class ScrollData(
97
97
  val scrollable: Boolean = true,
98
98
  val scrollState: ScrollableState? = null,
99
+ val scrollToTopCallback: (() -> Unit)? = null,
99
100
  )
@@ -4,6 +4,7 @@ import androidx.compose.animation.*
4
4
  import androidx.compose.animation.core.tween
5
5
  import androidx.compose.runtime.*
6
6
  import androidx.compose.ui.unit.Dp
7
+ import androidx.navigation.NavController
7
8
  import androidx.navigation.compose.NavHost
8
9
  import androidx.navigation.compose.composable
9
10
  import androidx.navigation.compose.rememberNavController
@@ -26,10 +27,17 @@ fun NavigationContainer(
26
27
  setNavigator: ((Navigator) -> Unit)? = null,
27
28
  statusBarHeight: Dp? = null,
28
29
  config: KitConfig? = null,
29
- language: String? = null
30
+ language: String? = null,
31
+ onNavigationEvent: ((NavigationEvent) -> Unit)? = null
30
32
  ){
31
33
  val navController = rememberNavController()
32
- val navigator = remember { Navigator(navController = navController, maxApi = maxApi) }
34
+ val navigator = remember { Navigator(navController = navController, maxApi = maxApi, onNavigationEvent = onNavigationEvent) }
35
+
36
+ val recomposeCount = remember { mutableStateOf(0) }
37
+ SideEffect {
38
+ recomposeCount.value += 1
39
+ onNavigationEvent?.invoke(NavigationEvent.ContainerRecompose(recomposeCount.value))
40
+ }
33
41
  val statusBarHeight = statusBarHeight ?: getAppStatusBarHeight()
34
42
  val navigationBarHeight = getNavigationBarHeight()
35
43
 
@@ -129,8 +137,16 @@ fun NavigationContainer(
129
137
  }
130
138
  }
131
139
 
132
- DisposableEffect(Unit) {
140
+ DisposableEffect(navController) {
141
+ val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
142
+ val current = DynamicScreenRegistry.getLatestScreen()
143
+ onNavigationEvent?.invoke(
144
+ NavigationEvent.DestinationChanged(destination.route, current?.id, current?.name)
145
+ )
146
+ }
147
+ navController.addOnDestinationChangedListener(listener)
133
148
  onDispose {
149
+ navController.removeOnDestinationChangedListener(listener)
134
150
  navigator.dispose()
135
151
  }
136
152
  }
@@ -0,0 +1,53 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ sealed class NavigationEvent {
4
+ abstract val label: String
5
+
6
+ data class Push(val screenName: String, val id: Int) : NavigationEvent() {
7
+ override val label get() = "Push(screen=$screenName, id=$id)"
8
+ }
9
+
10
+ data class Replace(val screenName: String, val id: Int) : NavigationEvent() {
11
+ override val label get() = "Replace(screen=$screenName, id=$id)"
12
+ }
13
+
14
+ data class Pop(val count: Int, val overlayType: OverplayComponentType?) : NavigationEvent() {
15
+ override val label get() = "Pop(count=$count, overlay=$overlayType)"
16
+ }
17
+
18
+ data class Present(val screenName: String, val id: Int) : NavigationEvent() {
19
+ override val label get() = "Present(screen=$screenName, id=$id)"
20
+ }
21
+
22
+ data class Reset(val screenName: String, val id: Int) : NavigationEvent() {
23
+ override val label get() = "Reset(screen=$screenName, id=$id)"
24
+ }
25
+
26
+ data class ShowModal(val parentId: Int, val barrierDismissible: Boolean) : NavigationEvent() {
27
+ override val label get() = "ShowModal(parentId=$parentId, barrierDismissible=$barrierDismissible)"
28
+ }
29
+
30
+ data class ShowBottomSheet(val parentId: Int, val barrierDismissible: Boolean) : NavigationEvent() {
31
+ override val label get() = "ShowBottomSheet(parentId=$parentId, barrierDismissible=$barrierDismissible)"
32
+ }
33
+
34
+ data class ShowSnackBar(val parentId: Int) : NavigationEvent() {
35
+ override val label get() = "ShowSnackBar(parentId=$parentId)"
36
+ }
37
+
38
+ data class DestinationChanged(
39
+ val route: String?,
40
+ val id: Int?,
41
+ val screenName: String?
42
+ ) : NavigationEvent() {
43
+ override val label get() = "DestinationChanged(route=$route, id=$id, screen=$screenName)"
44
+ }
45
+
46
+ data class ContainerRecompose(val recomposeCount: Int) : NavigationEvent() {
47
+ override val label get() = "ContainerRecompose(count=$recomposeCount)"
48
+ }
49
+
50
+ object Dispose : NavigationEvent() {
51
+ override val label get() = "Dispose"
52
+ }
53
+ }