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