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