@momo-kits/native-kits 0.165.2-beta.2 → 0.167.1-beta-search.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 (168) hide show
  1. package/CLAUDE.md +7 -0
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +190 -0
  4. package/compose/build.gradle.kts.backup +190 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +19 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +151 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +124 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +293 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +833 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +82 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +356 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +113 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +475 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +266 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +240 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +264 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +334 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +98 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +310 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +323 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +199 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +552 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +88 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +498 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +234 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +188 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +16 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +297 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +52 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +38 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +39 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +299 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +129 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +106 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +201 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +393 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +596 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +175 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +225 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +360 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +83 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +75 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +309 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +37 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +364 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +71 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +115 -0
  128. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +40 -0
  129. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  130. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  131. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  132. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  133. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +213 -0
  134. package/gradle/libs.versions.toml +69 -0
  135. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  136. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  137. package/gradle.properties +26 -0
  138. package/gradlew +252 -0
  139. package/gradlew.bat +94 -0
  140. package/ios/Application/Navigation/Overplay/ModalScreen.swift +3 -0
  141. package/ios/Popup/PopupDisplay.swift +1 -0
  142. package/ios/Popup/PopupNotify.swift +8 -1
  143. package/ios/Popup/PopupPromotion.swift +3 -3
  144. package/ios/Typography/FontScale.swift +3 -3
  145. package/ios/native-kits.podspec +1 -1
  146. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  147. package/ios-demo/README.md +120 -0
  148. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  149. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  150. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  151. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  152. package/local.properties +8 -0
  153. package/package.json +6 -6
  154. package/settings.gradle.kts +64 -0
  155. package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +0 -32
  156. package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  157. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  158. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +0 -16
  159. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  160. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
  161. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  162. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  163. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
  164. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
  165. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
  166. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +0 -58
  167. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +0 -58
  168. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -60
@@ -0,0 +1,833 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.annotation.FloatRange
4
+ import androidx.compose.animation.animateContentSize
5
+ import androidx.compose.foundation.*
6
+ import androidx.compose.foundation.gestures.detectTapGestures
7
+ import androidx.compose.foundation.interaction.FocusInteraction
8
+ import androidx.compose.foundation.interaction.MutableInteractionSource
9
+ import androidx.compose.foundation.layout.*
10
+ import androidx.compose.foundation.shape.CircleShape
11
+ import androidx.compose.foundation.text.BasicTextField
12
+ import androidx.compose.foundation.text.KeyboardActions
13
+ import androidx.compose.foundation.text.KeyboardOptions
14
+ import androidx.compose.runtime.*
15
+ import androidx.compose.ui.Alignment
16
+ import androidx.compose.ui.Modifier
17
+ import androidx.compose.ui.composed
18
+ import androidx.compose.ui.draw.clip
19
+ import androidx.compose.ui.draw.drawBehind
20
+ import androidx.compose.ui.draw.drawWithContent
21
+ import androidx.compose.ui.geometry.Offset
22
+ import androidx.compose.ui.geometry.Rect
23
+ import androidx.compose.ui.graphics.Brush
24
+ import androidx.compose.ui.graphics.Color
25
+ import androidx.compose.ui.graphics.SolidColor
26
+ import androidx.compose.ui.graphics.graphicsLayer
27
+ import androidx.compose.ui.input.pointer.pointerInput
28
+ import androidx.compose.ui.layout.*
29
+ import androidx.compose.ui.platform.LocalDensity
30
+ import androidx.compose.ui.platform.LocalFocusManager
31
+ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
32
+ import androidx.compose.ui.text.TextStyle
33
+ import androidx.compose.ui.text.style.TextOverflow
34
+ import androidx.compose.ui.unit.*
35
+ import kotlinx.coroutines.flow.collectLatest
36
+ import kotlinx.coroutines.flow.mapNotNull
37
+ import vn.momo.kits.components.Icon
38
+ import vn.momo.kits.components.Text
39
+ import vn.momo.kits.const.AppTheme
40
+ import vn.momo.kits.const.Colors
41
+ import vn.momo.kits.const.Spacing
42
+ import vn.momo.kits.const.Typography
43
+ import vn.momo.kits.const.getFontFamily
44
+ import vn.momo.kits.modifier.kitsAutomationId
45
+ import vn.momo.kits.modifier.noFeedbackClickable
46
+ import vn.momo.kits.modifier.setAutomationId
47
+ import vn.momo.kits.navigation.component.HEADER_FONT_FAMILY
48
+ import vn.momo.kits.navigation.component.HEADER_HEIGHT
49
+ import vn.momo.kits.utils.getAppStatusBarHeight
50
+ import vn.momo.kits.utils.horizontalSafeAreaPadding
51
+ import kotlin.math.max
52
+ import kotlin.math.roundToInt
53
+
54
+
55
+ data class AnimationOption(
56
+ val targetBounds: Rect,
57
+ val progress: State<Float>,
58
+ @param:FloatRange(from = 0.0, to = 1.0)
59
+ val opacityLimitFraction: Float = 0f,
60
+ ) {
61
+ val opacityCap: Float
62
+ get() = 1f - opacityLimitFraction
63
+
64
+ val animateOpacity: Float
65
+ get() {
66
+ if (progress.value < opacityLimitFraction) return 0f
67
+ return androidx.compose.ui.util.lerp(
68
+ 0f,
69
+ 1f,
70
+ (progress.value - opacityLimitFraction) / (opacityCap),
71
+ )
72
+ }
73
+ }
74
+
75
+ val LocalAnimationOption = staticCompositionLocalOf<AnimationOption?> { null }
76
+
77
+ @Composable
78
+ fun LiteScreen(
79
+ scrollable: Boolean = true,
80
+ scrollState: ScrollState? = null,
81
+ headerType: HeaderType = HeaderType.DEFAULT,
82
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
83
+ horizontalAlignment: Alignment.Horizontal = Alignment.Start,
84
+ backgroundColor: Color = AppTheme.current.colors.background.default,
85
+ /* Begin of header props */
86
+ title: String? = null,
87
+ inputSearchProps: LiteInputSearchProps? = null,
88
+ goBack: (() -> Unit)? = null,
89
+ headerRight: @Composable (() -> Unit)? = null,
90
+ useAnimationSearch: Boolean = true,
91
+ titlePosition: TitlePosition = TitlePosition.LEFT,
92
+ headerRightData: HeaderRightData? = null,
93
+ headerTintColor: Color? = null,
94
+ headerBackgroundColor: Color? = null,
95
+ headerSpaceBetween: Dp? = null,
96
+ /* End of header props */
97
+
98
+ screenContent: @Composable () -> Unit,
99
+ ) {
100
+ val content by rememberUpdatedState(screenContent)
101
+
102
+ val finalScrollState = scrollState ?: rememberScrollState()
103
+
104
+ val contentModifier = remember(
105
+ key1 = scrollable,
106
+ key2 = finalScrollState,
107
+ ) {
108
+ var res: Modifier = Modifier
109
+ .background(color = backgroundColor)
110
+ if (scrollable) {
111
+ res = res.verticalScroll(finalScrollState)
112
+ }
113
+ res
114
+ }
115
+
116
+ LiteScreenLayout(
117
+ scrollState = finalScrollState,
118
+ modifier = Modifier
119
+ .fillMaxSize()
120
+ .hideKeyboardOnTap()
121
+ .horizontalSafeAreaPadding(),
122
+ contentModifier = contentModifier,
123
+ verticalArrangement = verticalArrangement,
124
+ horizontalAlignment = horizontalAlignment,
125
+ title = title,
126
+ headerRight = headerRight,
127
+ headerType = headerType,
128
+ onGoBack = goBack,
129
+ inputSearchProps = inputSearchProps,
130
+ titlePosition = titlePosition,
131
+ useAnimationSearch = useAnimationSearch,
132
+ headerRightData = headerRightData,
133
+ tintColor = headerTintColor,
134
+ headerBackgroundColor = headerBackgroundColor,
135
+ headerSpaceBetween = headerSpaceBetween,
136
+ ) {
137
+ content()
138
+ }
139
+ }
140
+
141
+ private object HeaderId {
142
+ private const val PACKAGE_NAME = "vn.momo.compose.kits"
143
+ const val CONTENT_ID = "${PACKAGE_NAME}.content"
144
+ const val BACKGROUND_ID = "${PACKAGE_NAME}.background"
145
+ const val BACK_ID = "${PACKAGE_NAME}.back"
146
+ const val HEADER_RIGHT_ID = "${PACKAGE_NAME}.headerRight"
147
+ const val INPUT_SEARCH_ID = "${PACKAGE_NAME}.inputSearch"
148
+ const val TITLE_ID = "${PACKAGE_NAME}.title"
149
+
150
+ val EXTENDED_HEADER_HEIGHT = 154.dp
151
+ }
152
+
153
+ @Composable
154
+ private fun LiteScreenLayout(
155
+ scrollState: ScrollState?,
156
+ modifier: Modifier = Modifier,
157
+ contentModifier: Modifier = Modifier,
158
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
159
+ horizontalAlignment: Alignment.Horizontal = Alignment.Start,
160
+ title: String? = null,
161
+ tintColor: Color? = null,
162
+ headerRightData: HeaderRightData? = null,
163
+ headerType: HeaderType = HeaderType.DEFAULT,
164
+ titlePosition: TitlePosition = TitlePosition.LEFT,
165
+ useAnimationSearch: Boolean = true,
166
+ onGoBack: (() -> Unit)? = null,
167
+ headerBackgroundColor: Color? = null,
168
+ headerSpaceBetween: Dp? = null,
169
+ inputSearchProps: LiteInputSearchProps? = null,
170
+ headerRight: @Composable (() -> Unit)? = null,
171
+ content: @Composable () -> Unit,
172
+ ) {
173
+ val statusBarHeight = getAppStatusBarHeight()
174
+ val isHeaderNone = remember(headerType) {
175
+ headerType == HeaderType.NONE
176
+ }
177
+ val theme = AppTheme.current
178
+ val density = LocalDensity.current
179
+
180
+ val isHeaderExtend = remember(headerType) {
181
+ headerType == HeaderType.EXTENDED
182
+ }
183
+ val backgroundHeight = remember(isHeaderNone, isHeaderExtend, statusBarHeight) {
184
+ when {
185
+ isHeaderNone -> statusBarHeight
186
+ !isHeaderExtend -> statusBarHeight + HEADER_HEIGHT.dp
187
+ else -> HeaderId.EXTENDED_HEADER_HEIGHT
188
+ }
189
+ }
190
+ val listGradientColors = remember {
191
+ listOf(
192
+ Color(0xFFFDCADE),
193
+ Color(0x00FDCADE),
194
+ )
195
+ }
196
+
197
+ val headerColor = remember(tintColor, theme) {
198
+ if (tintColor == Colors.black_01) HeaderColor(
199
+ tintIconColor = tintColor,
200
+ backgroundButton = Colors.black_20.copy(alpha = 0.6f),
201
+ borderColor = Colors.black_01.copy(alpha = 0.2f)
202
+ )
203
+ else HeaderColor(
204
+ tintIconColor = tintColor ?: theme.colors.text.default,
205
+ backgroundButton = Colors.black_01.copy(alpha = 0.6f),
206
+ borderColor = Colors.black_20.copy(alpha = 0.2f)
207
+ )
208
+ }
209
+
210
+ val scrollPercentage = produceState(
211
+ initialValue = 0f,
212
+ key1 = useAnimationSearch,
213
+ key2 = scrollState,
214
+ key3 = backgroundHeight,
215
+ ) {
216
+ if (!useAnimationSearch) return@produceState
217
+ scrollState ?: return@produceState
218
+ val rangePx = with(density) { backgroundHeight.toPx() }
219
+ snapshotFlow { scrollState.value }.collectLatest {
220
+ value = (it / rangePx).coerceIn(0f, 1f)
221
+ }
222
+ }
223
+
224
+ val titleStyle = remember {
225
+ Typography.actionSBold.copy(
226
+ fontSize = 15.sp,
227
+ lineHeight = 22.sp,
228
+ )
229
+ }
230
+
231
+ val animationOption = LocalAnimationOption.current
232
+ val animationOpacityModifier = Modifier.graphicsLayer {
233
+ alpha = animationOption?.animateOpacity ?: 1f
234
+ }
235
+ val titleModifier = Modifier
236
+ .kitsAutomationId("title_navigation_header")
237
+ .layoutId(HeaderId.TITLE_ID)
238
+ .graphicsLayer {
239
+ val titleAlpha = if (isHeaderExtend && inputSearchProps != null && useAnimationSearch)
240
+ (1 - scrollPercentage.value * 2).coerceIn(0f, 1f)
241
+ else 1f
242
+ alpha = titleAlpha * (animationOption?.progress?.value ?: 1f)
243
+ }
244
+
245
+ val policy = remember(
246
+ useAnimationSearch,
247
+ isHeaderNone,
248
+ isHeaderExtend,
249
+ statusBarHeight,
250
+ titlePosition,
251
+ scrollPercentage,
252
+ headerSpaceBetween,
253
+ animationOption,
254
+ verticalArrangement,
255
+ horizontalAlignment,
256
+ ) {
257
+ LiteScreenLayoutPolicy(
258
+ useAnimationSearch = useAnimationSearch,
259
+ isHeaderNone = isHeaderNone,
260
+ isHeaderExtend = isHeaderExtend,
261
+ statusBarHeight = statusBarHeight,
262
+ titlePosition = titlePosition,
263
+ scrollPercentage = scrollPercentage,
264
+ headerSpaceBetween = headerSpaceBetween,
265
+ animationOption = animationOption,
266
+ verticalArrangement = verticalArrangement,
267
+ horizontalAlignment = horizontalAlignment,
268
+ )
269
+ }
270
+
271
+ Layout(
272
+ modifier = modifier
273
+ .animateContentSize(),
274
+ content = {
275
+ Box(
276
+ modifier = animationOpacityModifier
277
+ .layoutId(HeaderId.CONTENT_ID)
278
+ .then(contentModifier),
279
+ contentAlignment = Alignment.TopCenter,
280
+ ) {
281
+ content()
282
+ }
283
+
284
+ if (!isHeaderNone) {
285
+ Box(
286
+ modifier = Modifier
287
+ .layoutId(HeaderId.BACKGROUND_ID)
288
+ .then(animationOpacityModifier)
289
+ .drawBehind {
290
+ val headerHeight = max(
291
+ HeaderId.EXTENDED_HEADER_HEIGHT.toPx(),
292
+ size.height,
293
+ )
294
+ headerBackgroundColor?.let {
295
+ drawRect(color = it)
296
+ } ?: run {
297
+ drawRect(color = Colors.black_01)
298
+ drawRect(
299
+ brush = Brush.linearGradient(
300
+ colors = listGradientColors,
301
+ start = Offset.Zero,
302
+ end = Offset(
303
+ x = 0f,
304
+ y = headerHeight * (1 - scrollPercentage.value),
305
+ ),
306
+ )
307
+ )
308
+ }
309
+ },
310
+ )
311
+ }
312
+
313
+ if (!isHeaderNone && onGoBack != null) {
314
+ val coreModifier = Modifier
315
+ .layoutId(HeaderId.BACK_ID)
316
+ .then(animationOpacityModifier)
317
+ .clip(CircleShape)
318
+ .noFeedbackClickable(onClick = onGoBack)
319
+ .setAutomationId("btn_navigation_back")
320
+ inputSearchProps?.customBackIcon?.invoke(coreModifier) ?: Box(
321
+ modifier = coreModifier
322
+ .size(28.dp)
323
+ .border(
324
+ width = 0.2.dp,
325
+ color = headerColor.borderColor,
326
+ shape = CircleShape,
327
+ )
328
+ .background(color = headerColor.backgroundButton)
329
+ .padding(Spacing.XS),
330
+ ) {
331
+ Icon(
332
+ source = "arrow-back",
333
+ color = headerColor.tintIconColor,
334
+ size = 20.dp,
335
+ )
336
+ }
337
+ }
338
+
339
+ if (!isHeaderNone) {
340
+ Box(
341
+ modifier = Modifier
342
+ .layoutId(HeaderId.HEADER_RIGHT_ID)
343
+ .then(animationOpacityModifier)
344
+ ) {
345
+ if (headerRight != null) {
346
+ headerRight()
347
+ } else {
348
+ HeaderRight(
349
+ headerRight = headerRightData,
350
+ tintColor = tintColor,
351
+ )
352
+ }
353
+ }
354
+ }
355
+
356
+ if (!isHeaderNone && inputSearchProps != null) {
357
+ LiteInputSearch(
358
+ modifier = Modifier.layoutId(HeaderId.INPUT_SEARCH_ID),
359
+ inputSearchProps = inputSearchProps,
360
+ )
361
+ }
362
+ if (!isHeaderNone && title != null && (inputSearchProps == null || isHeaderExtend)) {
363
+ Text(
364
+ text = title,
365
+ color = headerColor.tintIconColor,
366
+ style = titleStyle,
367
+ fontFamily = HEADER_FONT_FAMILY,
368
+ modifier = titleModifier,
369
+ maxLines = 1,
370
+ overflow = TextOverflow.Ellipsis
371
+ )
372
+ }
373
+ },
374
+ measurePolicy = policy,
375
+ )
376
+ }
377
+
378
+ private class LiteScreenLayoutPolicy(
379
+ private val useAnimationSearch: Boolean,
380
+ private val isHeaderNone: Boolean,
381
+ private val isHeaderExtend: Boolean,
382
+ private val statusBarHeight: Dp,
383
+ private val scrollPercentage: State<Float>,
384
+ private val titlePosition: TitlePosition,
385
+ private val headerSpaceBetween: Dp? = null,
386
+ private val animationOption: AnimationOption?,
387
+ private val verticalArrangement: Arrangement.Vertical,
388
+ private val horizontalAlignment: Alignment.Horizontal,
389
+ ) : MeasurePolicy {
390
+
391
+ val searchStartPosition: IntOffset by lazy {
392
+ if (animationOption == null) return@lazy IntOffset.Zero
393
+ val offset = animationOption.targetBounds.topLeft
394
+ IntOffset(
395
+ x = offset.x.roundToInt(),
396
+ y = offset.y.roundToInt(),
397
+ )
398
+ }
399
+
400
+ override fun MeasureScope.measure(
401
+ measurables: List<Measurable>,
402
+ constraints: Constraints
403
+ ): MeasureResult {
404
+ val spacing12 = Spacing.M.roundToPx()
405
+ val spaceBetween = headerSpaceBetween?.roundToPx() ?: spacing12
406
+ val statusBarPx = statusBarHeight.roundToPx()
407
+ val headerRowDefault = HEADER_HEIGHT.dp.roundToPx()
408
+ val scrollPercent = scrollPercentage.value
409
+
410
+ val contentMeasurable = measurables.find { it.layoutId == HeaderId.CONTENT_ID }
411
+
412
+ val realConstraints = constraints.copy(
413
+ minWidth = 0,
414
+ minHeight = 0,
415
+ maxWidth = (constraints.maxWidth - spacing12 * 2)
416
+ .coerceAtLeast(0),
417
+ )
418
+ val backIconPlaceable =
419
+ measurables.find { it.layoutId == HeaderId.BACK_ID }?.measure(realConstraints)
420
+ val headerRightPlaceable =
421
+ measurables.find { it.layoutId == HeaderId.HEADER_RIGHT_ID }?.measure(
422
+ realConstraints.copy(
423
+ maxWidth = realConstraints.maxWidth / 2,
424
+ )
425
+ )
426
+ val baseInputSearchConstraints = if (isHeaderExtend) {
427
+ val minWidth =
428
+ if (useAnimationSearch) realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
429
+ else realConstraints.maxWidth
430
+ realConstraints.copy(
431
+ maxWidth = (realConstraints.maxWidth * (1 - scrollPercent)).toInt()
432
+ .coerceAtLeast(minWidth)
433
+ )
434
+ } else {
435
+ var spaceConsumed = 0
436
+ if (backIconPlaceable.safeWidth != 0) spaceConsumed += backIconPlaceable.safeWidth + spaceBetween
437
+ if (headerRightPlaceable.safeWidth != 0) spaceConsumed += headerRightPlaceable.safeWidth + spaceBetween
438
+ realConstraints.copy(
439
+ maxWidth = realConstraints.maxWidth - spaceConsumed
440
+ )
441
+ }
442
+ val inputSearchConstraints = baseInputSearchConstraints.withAnimationTargetBounds()
443
+ val inputSearchPlaceable = measurables.find { it.layoutId == HeaderId.INPUT_SEARCH_ID }
444
+ ?.measure(inputSearchConstraints)
445
+ val titlePlaceable = measurables.find { it.layoutId == HeaderId.TITLE_ID }?.measure(
446
+ constraints = realConstraints.copy(
447
+ maxWidth = realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
448
+ )
449
+ )
450
+
451
+ /**
452
+ * replicate logic from [vn.momo.kits.navigation.component.HeaderBackground]
453
+ * */
454
+ var defaultHeight = statusBarPx + headerRowDefault
455
+ if (isHeaderExtend) {
456
+ defaultHeight = 154.dp.roundToPx()
457
+ }
458
+ val headerHeight = when {
459
+ isHeaderNone -> statusBarPx
460
+ !useAnimationSearch && !isHeaderExtend -> defaultHeight
461
+ else -> {
462
+ val collapsableSpace = defaultHeight - statusBarPx - headerRowDefault
463
+ (defaultHeight - scrollPercent * collapsableSpace).toInt()
464
+ }
465
+ }
466
+
467
+ val layoutWidth = constraints.maxWidth
468
+ val layoutHeight = constraints.maxHeight
469
+ val contentHeight = (layoutHeight - headerHeight).coerceAtLeast(0)
470
+ val contentPlaceable = contentMeasurable?.measure(
471
+ constraints.copy(
472
+ minWidth = 0,
473
+ minHeight = contentHeight,
474
+ maxHeight = contentHeight,
475
+ )
476
+ )
477
+ val backgroundPlaceable = measurables.find { it.layoutId == HeaderId.BACKGROUND_ID }
478
+ ?.measure(
479
+ Constraints.fixed(
480
+ width = layoutWidth,
481
+ height = headerHeight,
482
+ )
483
+ )
484
+ val childrenHeights = intArrayOf(
485
+ headerHeight,
486
+ contentPlaceable.safeHeight,
487
+ )
488
+ val childrenY = IntArray(childrenHeights.size)
489
+ with(verticalArrangement) {
490
+ arrange(
491
+ totalSize = layoutHeight,
492
+ sizes = childrenHeights,
493
+ outPositions = childrenY,
494
+ )
495
+ }
496
+ val headerY = childrenY[0]
497
+ val contentY = childrenY[1]
498
+
499
+ return layout(
500
+ width = layoutWidth,
501
+ height = layoutHeight,
502
+ ) {
503
+ val progress = animationOption?.progress?.value ?: 1f
504
+
505
+ val startX = spacing12
506
+ val startY = headerY + statusBarPx
507
+ var curX = startX
508
+ var curY = startY
509
+
510
+ contentPlaceable?.place(
511
+ x = horizontalAlignment.align(
512
+ size = contentPlaceable.width,
513
+ space = layoutWidth,
514
+ layoutDirection = layoutDirection,
515
+ ),
516
+ y = contentY,
517
+ )
518
+
519
+ backgroundPlaceable?.place(
520
+ x = 0,
521
+ y = headerY,
522
+ )
523
+
524
+ if (backIconPlaceable != null) {
525
+ backIconPlaceable.place(
526
+ x = startX,
527
+ y = startY + backIconPlaceable.verticalCenterOffset(headerRowDefault),
528
+ )
529
+ curX += backIconPlaceable.safeWidth + spaceBetween
530
+ }
531
+
532
+ headerRightPlaceable?.place(
533
+ x = layoutWidth - spacing12 - headerRightPlaceable.safeWidth,
534
+ y = startY + headerRightPlaceable.verticalCenterOffset(headerRowDefault),
535
+ )
536
+
537
+ val titleOffset = IntOffset(
538
+ x = if (titlePosition == TitlePosition.LEFT) curX
539
+ else titlePlaceable.horizontalCenterOffset(
540
+ space = layoutWidth,
541
+ layoutDirection = layoutDirection,
542
+ ),
543
+ y = startY + titlePlaceable.verticalCenterOffset(headerRowDefault),
544
+ )
545
+
546
+ titlePlaceable?.place(titleOffset)
547
+
548
+ if (backIconPlaceable != null || headerRightPlaceable != null || titlePlaceable != null) {
549
+ curY += headerRowDefault
550
+ }
551
+
552
+ val inputSearchOffset = if (isHeaderExtend) {
553
+ val baseY = curY + inputSearchPlaceable.verticalCenterOffset(headerRowDefault)
554
+ val y = (baseY * (1 - scrollPercent)).toInt().coerceAtLeast(
555
+ startY + inputSearchPlaceable.verticalCenterOffset(headerRowDefault)
556
+ )
557
+ IntOffset(
558
+ x = startX + ((backIconPlaceable.safeWidth + spaceBetween) * (scrollPercent * 2f).coerceIn(
559
+ 0f, 1f
560
+ )).toInt(),
561
+ y = y,
562
+ )
563
+ } else {
564
+ IntOffset(
565
+ x = curX,
566
+ y = startY + inputSearchPlaceable.verticalCenterOffset(headerRowDefault),
567
+ )
568
+ }
569
+ val finalPosition = lerp(
570
+ searchStartPosition,
571
+ inputSearchOffset,
572
+ progress,
573
+ )
574
+
575
+ inputSearchPlaceable?.place(
576
+ finalPosition
577
+ )
578
+ }
579
+ }
580
+
581
+ private val Placeable?.safeHeight
582
+ get() = this?.height ?: 0
583
+ private val Placeable?.safeWidth
584
+ get() = this?.width ?: 0
585
+
586
+ private fun Constraints.withAnimationTargetBounds(): Constraints {
587
+ val option = animationOption ?: return this
588
+ val progress = option.progress.value
589
+ if (progress >= 1f) return this
590
+
591
+ val targetWidth = option.targetBounds.width.roundToInt().coerceAtLeast(0)
592
+ val targetHeight = option.targetBounds.height.roundToInt().coerceAtLeast(0)
593
+ val animatedMinWidth = lerpPx(targetWidth, minWidth, progress)
594
+ .coerceInBounds(maxWidth)
595
+ val animatedMaxWidth = lerpMaxPx(targetWidth, maxWidth, progress)
596
+ .coerceAtLeast(animatedMinWidth)
597
+ val animatedMinHeight = lerpPx(targetHeight, minHeight, progress)
598
+ .coerceInBounds(maxHeight)
599
+ val animatedMaxHeight = lerpMaxPx(targetHeight, maxHeight, progress)
600
+ .coerceAtLeast(animatedMinHeight)
601
+
602
+ return copy(
603
+ minWidth = animatedMinWidth,
604
+ maxWidth = animatedMaxWidth,
605
+ minHeight = animatedMinHeight,
606
+ maxHeight = animatedMaxHeight,
607
+ )
608
+ }
609
+
610
+ private fun lerpPx(
611
+ start: Int,
612
+ stop: Int,
613
+ fraction: Float,
614
+ ): Int = androidx.compose.ui.util.lerp(
615
+ start.toFloat(),
616
+ stop.toFloat(),
617
+ fraction,
618
+ ).roundToInt()
619
+
620
+ private fun lerpMaxPx(
621
+ start: Int,
622
+ stop: Int,
623
+ fraction: Float,
624
+ ): Int {
625
+ if (stop == Constraints.Infinity) return Constraints.Infinity
626
+ return lerpPx(start, stop, fraction)
627
+ }
628
+
629
+ private fun Int.coerceInBounds(max: Int): Int {
630
+ if (max == Constraints.Infinity) return coerceAtLeast(0)
631
+ return coerceIn(0, max)
632
+ }
633
+
634
+ private fun Placeable?.verticalCenterOffset(space: Int): Int {
635
+ if (this == null) return 0
636
+ return Alignment.CenterVertically.align(safeHeight, space)
637
+ }
638
+
639
+ private fun Placeable?.horizontalCenterOffset(
640
+ space: Int,
641
+ layoutDirection: LayoutDirection
642
+ ): Int {
643
+ if (this == null) return 0
644
+ return Alignment.CenterHorizontally.align(safeWidth, space, layoutDirection)
645
+ }
646
+ }
647
+
648
+ private val EMPTY_FUNC = {}
649
+
650
+ @Stable
651
+ data class LiteInputSearchProps(
652
+ val textFieldState: MutableState<String>,
653
+ val onValueChange: (String) -> Unit,
654
+ val onClear: () -> Unit = EMPTY_FUNC,
655
+ val clearCondition: (() -> Boolean)? = null,
656
+ val modifier: Modifier = Modifier,
657
+ val onFocused: (() -> Unit) = EMPTY_FUNC,
658
+ val keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
659
+ val keyboardActions: KeyboardActions = KeyboardActions.Default,
660
+ val onKeyboardAction: (() -> Unit) = EMPTY_FUNC,
661
+
662
+ val btnText: String? = null,
663
+ val isShowBtnText: Boolean = !btnText.isNullOrEmpty(),
664
+ val onPressBtnText: (() -> Unit) = EMPTY_FUNC,
665
+
666
+ val disabled: Boolean = false,
667
+
668
+ val placeHolder: String? = null,
669
+
670
+ val cursorBrush: Brush? = null,
671
+ val customBackIcon: @Composable ((Modifier) -> Unit)? = null,
672
+ val customSearchIcon: @Composable (() -> Unit)? = null,
673
+ val customPlaceHolder: @Composable (() -> Unit)? = null,
674
+ val customTextStyle: TextStyle? = null,
675
+ val customIconClear: @Composable (() -> Unit)? = null,
676
+ val iconRightTextField: @Composable ((Modifier) -> Unit)? = null,
677
+ )
678
+
679
+ @Composable
680
+ private fun LiteInputSearch(
681
+ modifier: Modifier = Modifier,
682
+ inputSearchProps: LiteInputSearchProps? = null,
683
+ ) {
684
+ inputSearchProps ?: return
685
+ val theme = AppTheme.current
686
+
687
+ val isFocused = remember { mutableStateOf(false) }
688
+ val interactionSource = remember { MutableInteractionSource() }
689
+ val textState by remember { inputSearchProps.textFieldState }
690
+
691
+ val isShowBtnText by remember(inputSearchProps.isShowBtnText, inputSearchProps.btnText) {
692
+ derivedStateOf {
693
+ inputSearchProps.isShowBtnText && !inputSearchProps.btnText.isNullOrEmpty()
694
+ }
695
+ }
696
+ val inputFieldFontFamily = getFontFamily(theme.font, Typography.bodyDefaultRegular.fontWeight)
697
+ val inputFieldStyle = remember(theme, inputFieldFontFamily) {
698
+ Typography.bodyDefaultRegular.copy(
699
+ color = theme.colors.text.default,
700
+ fontFamily = inputFieldFontFamily,
701
+ )
702
+ }
703
+
704
+ LaunchedEffect(inputSearchProps.onFocused) {
705
+ interactionSource.interactions.mapNotNull { it as? FocusInteraction }.collectLatest {
706
+ val isFocus = it is FocusInteraction.Focus
707
+ isFocused.value = isFocus
708
+ if (isFocus) inputSearchProps.onFocused()
709
+ }
710
+ }
711
+
712
+ Row(
713
+ modifier = modifier,
714
+ verticalAlignment = Alignment.CenterVertically,
715
+ ) {
716
+ val textFieldModifier = remember(inputSearchProps.modifier) {
717
+ inputSearchProps.modifier
718
+ .weight(1f)
719
+ }
720
+ BasicTextField(
721
+ value = textState,
722
+ onValueChange = inputSearchProps.onValueChange,
723
+ enabled = !inputSearchProps.disabled,
724
+ keyboardOptions = inputSearchProps.keyboardOptions,
725
+ keyboardActions = inputSearchProps.keyboardActions,
726
+ modifier = textFieldModifier,
727
+ textStyle = inputSearchProps.customTextStyle ?: inputFieldStyle,
728
+ singleLine = true,
729
+ cursorBrush = inputSearchProps.cursorBrush ?: SolidColor(Color.Black),
730
+ interactionSource = interactionSource,
731
+ decorationBox = { innerTextField ->
732
+ val isShowClear by remember(inputSearchProps.clearCondition, isFocused.value) {
733
+ derivedStateOf {
734
+ inputSearchProps.clearCondition?.invoke() == true || (isFocused.value && textState.isNotEmpty())
735
+ }
736
+ }
737
+ val placeHolder by produceState<String?>(null, inputSearchProps.placeHolder) {
738
+ if (inputSearchProps.placeHolder.isNullOrEmpty()) return@produceState
739
+ snapshotFlow { textState }.collectLatest {
740
+ value = if (it.isEmpty()) inputSearchProps.placeHolder else null
741
+ }
742
+ }
743
+ val iconRightModifier = remember {
744
+ Modifier
745
+ .padding(start = Spacing.L)
746
+ .drawWithContent {
747
+ val offsetX = -Spacing.S.toPx()
748
+ drawLine(
749
+ start = Offset(
750
+ x = offsetX,
751
+ y = 0f,
752
+ ),
753
+ end = Offset(
754
+ x = offsetX,
755
+ y = size.height,
756
+ ),
757
+ color = theme.colors.primary,
758
+ strokeWidth = 1.dp.toPx(),
759
+ )
760
+ drawContent()
761
+ }
762
+ }
763
+
764
+ Row(
765
+ modifier = Modifier
766
+ .padding(
767
+ horizontal = Spacing.M,
768
+ vertical = Spacing.S,
769
+ ),
770
+ horizontalArrangement = Arrangement.Start,
771
+ verticalAlignment = Alignment.CenterVertically,
772
+ ) {
773
+ inputSearchProps.customSearchIcon?.invoke() ?: Icon(
774
+ source = "navigation_search",
775
+ modifier = Modifier.padding(end = Spacing.XS),
776
+ size = 24.dp,
777
+ color = theme.colors.text.hint
778
+ )
779
+ Box(
780
+ modifier = Modifier.weight(1f),
781
+ contentAlignment = Alignment.CenterStart,
782
+ ) {
783
+ if (!placeHolder.isNullOrEmpty()) {
784
+ inputSearchProps.customPlaceHolder?.invoke() ?: Text(
785
+ text = placeHolder ?: "",
786
+ style = inputSearchProps.customTextStyle
787
+ ?: Typography.bodyDefaultRegular,
788
+ maxLines = 1,
789
+ color = theme.colors.text.hint,
790
+ overflow = TextOverflow.Ellipsis
791
+ )
792
+ }
793
+ innerTextField()
794
+ }
795
+
796
+ if (isShowClear) {
797
+ inputSearchProps.customIconClear?.invoke() ?: Icon(
798
+ source = "24_navigation_close_circle_full",
799
+ size = 16.dp,
800
+ color = theme.colors.text.hint,
801
+ modifier = Modifier.padding(start = Spacing.XS)
802
+ .noFeedbackClickable(onClick = inputSearchProps.onClear),
803
+ )
804
+ }
805
+
806
+ inputSearchProps.iconRightTextField?.invoke(iconRightModifier)
807
+ }
808
+ }
809
+ )
810
+
811
+ if (isShowBtnText) {
812
+ Text(
813
+ text = inputSearchProps.btnText ?: "",
814
+ style = Typography.actionDefaultBold,
815
+ color = theme.colors.text.default,
816
+ modifier = Modifier.padding(start = Spacing.L)
817
+ .noFeedbackClickable(onClick = inputSearchProps.onPressBtnText)
818
+ )
819
+ }
820
+ }
821
+ }
822
+
823
+ fun Modifier.hideKeyboardOnTap() = composed {
824
+ val focusManager = LocalFocusManager.current
825
+ val keyboardManager = LocalSoftwareKeyboardController.current
826
+
827
+ pointerInput(Unit) {
828
+ detectTapGestures {
829
+ keyboardManager?.hide()
830
+ focusManager.clearFocus()
831
+ }
832
+ }
833
+ }