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