@momo-kits/native-kits 0.161.1-beta.15-debug → 0.161.2-beta.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.
- package/compose/build.gradle.kts +9 -3
- package/compose/build.gradle.kts.backup +8 -2
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +9 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +8 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +324 -117
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +4 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +5 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +5 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +19 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +27 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +1 -28
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +63 -49
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +23 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +12 -4
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +33 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +18 -8
- package/gradle/libs.versions.toml +3 -1
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +2 -6
- package/ios/Input/Input.swift +50 -21
- package/ios/Input/InputPhoneNumber.swift +17 -17
- package/ios/StatusBarTap/StatusBarTap.h +13 -0
- package/ios/StatusBarTap/StatusBarTap.m +75 -0
- package/ios/Typography/Text.swift +19 -14
- package/ios/Typography/Typography.swift +22 -1
- package/ios/native-kits.podspec +2 -1
- package/package.json +1 -1
- package/settings.gradle.kts +15 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
|
+
import androidx.annotation.FloatRange
|
|
3
4
|
import androidx.compose.animation.animateContentSize
|
|
4
5
|
import androidx.compose.foundation.ScrollState
|
|
5
6
|
import androidx.compose.foundation.background
|
|
@@ -9,22 +10,18 @@ import androidx.compose.foundation.interaction.FocusInteraction
|
|
|
9
10
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
10
11
|
import androidx.compose.foundation.layout.Arrangement
|
|
11
12
|
import androidx.compose.foundation.layout.Box
|
|
12
|
-
import androidx.compose.foundation.layout.Column
|
|
13
13
|
import androidx.compose.foundation.layout.Row
|
|
14
14
|
import androidx.compose.foundation.layout.WindowInsets
|
|
15
15
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
16
16
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
17
17
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
18
|
-
import androidx.compose.foundation.layout.height
|
|
19
18
|
import androidx.compose.foundation.layout.ime
|
|
20
19
|
import androidx.compose.foundation.layout.navigationBars
|
|
21
20
|
import androidx.compose.foundation.layout.offset
|
|
22
21
|
import androidx.compose.foundation.layout.padding
|
|
23
22
|
import androidx.compose.foundation.layout.size
|
|
24
|
-
import androidx.compose.foundation.layout.sizeIn
|
|
25
23
|
import androidx.compose.foundation.rememberScrollState
|
|
26
24
|
import androidx.compose.foundation.shape.CircleShape
|
|
27
|
-
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
28
25
|
import androidx.compose.foundation.text.BasicTextField
|
|
29
26
|
import androidx.compose.foundation.text.KeyboardActions
|
|
30
27
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
@@ -48,6 +45,7 @@ import androidx.compose.ui.draw.clip
|
|
|
48
45
|
import androidx.compose.ui.draw.drawBehind
|
|
49
46
|
import androidx.compose.ui.draw.drawWithContent
|
|
50
47
|
import androidx.compose.ui.geometry.Offset
|
|
48
|
+
import androidx.compose.ui.geometry.Rect
|
|
51
49
|
import androidx.compose.ui.graphics.Brush
|
|
52
50
|
import androidx.compose.ui.graphics.Color
|
|
53
51
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
@@ -63,27 +61,57 @@ import androidx.compose.ui.platform.LocalDensity
|
|
|
63
61
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
64
62
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
65
63
|
import androidx.compose.ui.text.style.TextOverflow
|
|
64
|
+
import androidx.compose.ui.text.TextStyle
|
|
66
65
|
import androidx.compose.ui.unit.Constraints
|
|
67
66
|
import androidx.compose.ui.unit.Dp
|
|
68
67
|
import androidx.compose.ui.unit.IntOffset
|
|
69
68
|
import androidx.compose.ui.unit.LayoutDirection
|
|
70
69
|
import androidx.compose.ui.unit.dp
|
|
71
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
|
|
72
76
|
import kotlinx.coroutines.flow.collectLatest
|
|
73
77
|
import kotlinx.coroutines.flow.mapNotNull
|
|
74
78
|
import vn.momo.kits.components.Icon
|
|
75
79
|
import vn.momo.kits.components.Text
|
|
76
80
|
import vn.momo.kits.const.AppTheme
|
|
77
81
|
import vn.momo.kits.const.Colors
|
|
78
|
-
import vn.momo.kits.const.Radius
|
|
79
82
|
import vn.momo.kits.const.Spacing
|
|
80
83
|
import vn.momo.kits.const.Typography
|
|
84
|
+
import vn.momo.kits.modifier.conditional
|
|
81
85
|
import vn.momo.kits.modifier.kitsAutomationId
|
|
82
86
|
import vn.momo.kits.modifier.noFeedbackClickable
|
|
83
87
|
import vn.momo.kits.modifier.setAutomationId
|
|
84
88
|
import vn.momo.kits.modifier.shadow
|
|
85
89
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
86
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 }
|
|
87
115
|
|
|
88
116
|
@Composable
|
|
89
117
|
fun LiteScreen(
|
|
@@ -101,6 +129,9 @@ fun LiteScreen(
|
|
|
101
129
|
useAnimationSearch: Boolean = true,
|
|
102
130
|
titlePosition: TitlePosition = TitlePosition.LEFT,
|
|
103
131
|
headerRightData: HeaderRightData? = null,
|
|
132
|
+
headerTintColor: Color? = null,
|
|
133
|
+
headerBackgroundColor: Color? = null,
|
|
134
|
+
headerSpaceBetween: Dp? = null,
|
|
104
135
|
/* End of header props */
|
|
105
136
|
|
|
106
137
|
screenContent: @Composable () -> Unit,
|
|
@@ -109,45 +140,46 @@ fun LiteScreen(
|
|
|
109
140
|
|
|
110
141
|
val finalScrollState = scrollState ?: rememberScrollState()
|
|
111
142
|
|
|
112
|
-
|
|
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,
|
|
113
157
|
modifier = Modifier
|
|
114
158
|
.fillMaxSize()
|
|
115
|
-
.background(color = backgroundColor)
|
|
116
159
|
.hideKeyboardOnTap(),
|
|
160
|
+
contentModifier = contentModifier,
|
|
117
161
|
verticalArrangement = verticalArrangement,
|
|
118
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,
|
|
119
174
|
) {
|
|
120
|
-
|
|
121
|
-
var res = Modifier.weight(1f)
|
|
122
|
-
if (scrollable) {
|
|
123
|
-
res = res.verticalScroll(finalScrollState)
|
|
124
|
-
}
|
|
125
|
-
res
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
LiteScreenHeader(
|
|
129
|
-
scrollState = finalScrollState,
|
|
130
|
-
title = title,
|
|
131
|
-
headerRight = headerRight,
|
|
132
|
-
headerType = headerType,
|
|
133
|
-
onGoBack = goBack,
|
|
134
|
-
inputSearchProps = inputSearchProps,
|
|
135
|
-
titlePosition = titlePosition,
|
|
136
|
-
useAnimationSearch = useAnimationSearch,
|
|
137
|
-
headerRightData = headerRightData,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
Box(
|
|
141
|
-
modifier = contentModifier,
|
|
142
|
-
contentAlignment = Alignment.TopCenter,
|
|
143
|
-
) {
|
|
144
|
-
content()
|
|
145
|
-
}
|
|
175
|
+
content()
|
|
146
176
|
}
|
|
147
177
|
}
|
|
148
178
|
|
|
149
179
|
private object HeaderId {
|
|
150
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"
|
|
151
183
|
const val BACK_ID = "${PACKAGE_NAME}.back"
|
|
152
184
|
const val HEADER_RIGHT_ID = "${PACKAGE_NAME}.headerRight"
|
|
153
185
|
const val INPUT_SEARCH_ID = "${PACKAGE_NAME}.inputSearch"
|
|
@@ -157,8 +189,12 @@ private object HeaderId {
|
|
|
157
189
|
}
|
|
158
190
|
|
|
159
191
|
@Composable
|
|
160
|
-
private fun
|
|
192
|
+
private fun LiteScreenLayout(
|
|
161
193
|
scrollState: ScrollState?,
|
|
194
|
+
modifier: Modifier = Modifier,
|
|
195
|
+
contentModifier: Modifier = Modifier,
|
|
196
|
+
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
|
197
|
+
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
162
198
|
title: String? = null,
|
|
163
199
|
tintColor: Color? = null,
|
|
164
200
|
headerRightData: HeaderRightData? = null,
|
|
@@ -166,13 +202,15 @@ private fun LiteScreenHeader(
|
|
|
166
202
|
titlePosition: TitlePosition = TitlePosition.LEFT,
|
|
167
203
|
useAnimationSearch: Boolean = true,
|
|
168
204
|
onGoBack: (() -> Unit)? = null,
|
|
205
|
+
headerBackgroundColor: Color? = null,
|
|
206
|
+
headerSpaceBetween: Dp? = null,
|
|
169
207
|
inputSearchProps: LiteInputSearchProps? = null,
|
|
170
208
|
headerRight: @Composable (() -> Unit)? = null,
|
|
209
|
+
content: @Composable () -> Unit,
|
|
171
210
|
) {
|
|
172
211
|
val statusBarHeight = getAppStatusBarHeight()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return
|
|
212
|
+
val isHeaderNone = remember(headerType) {
|
|
213
|
+
headerType == HeaderType.NONE
|
|
176
214
|
}
|
|
177
215
|
val theme = AppTheme.current
|
|
178
216
|
val density = LocalDensity.current
|
|
@@ -180,9 +218,12 @@ private fun LiteScreenHeader(
|
|
|
180
218
|
val isHeaderExtend = remember(headerType) {
|
|
181
219
|
headerType == HeaderType.EXTENDED
|
|
182
220
|
}
|
|
183
|
-
val backgroundHeight = remember(isHeaderExtend, statusBarHeight) {
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
|
186
227
|
}
|
|
187
228
|
val listGradientColors = remember {
|
|
188
229
|
listOf(
|
|
@@ -225,68 +266,104 @@ private fun LiteScreenHeader(
|
|
|
225
266
|
)
|
|
226
267
|
}
|
|
227
268
|
|
|
228
|
-
val
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.layoutId(HeaderId.TITLE_ID)
|
|
232
|
-
.graphicsLayer {
|
|
233
|
-
alpha = if (isHeaderExtend && inputSearchProps != null && useAnimationSearch)
|
|
234
|
-
(1 - scrollPercentage.value * 2).coerceIn(0f, 1f)
|
|
235
|
-
else 1f
|
|
236
|
-
}
|
|
269
|
+
val animationOption = LocalAnimationOption.current
|
|
270
|
+
val animationOpacityModifier = Modifier.graphicsLayer {
|
|
271
|
+
alpha = animationOption?.animateOpacity ?: 1f
|
|
237
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
|
+
}
|
|
238
282
|
|
|
239
283
|
val policy = remember(
|
|
240
284
|
useAnimationSearch,
|
|
285
|
+
isHeaderNone,
|
|
241
286
|
isHeaderExtend,
|
|
242
287
|
statusBarHeight,
|
|
243
288
|
titlePosition,
|
|
244
289
|
scrollPercentage,
|
|
290
|
+
headerSpaceBetween,
|
|
291
|
+
animationOption,
|
|
292
|
+
verticalArrangement,
|
|
293
|
+
horizontalAlignment,
|
|
245
294
|
) {
|
|
246
|
-
|
|
295
|
+
LiteScreenLayoutPolicy(
|
|
247
296
|
useAnimationSearch = useAnimationSearch,
|
|
297
|
+
isHeaderNone = isHeaderNone,
|
|
248
298
|
isHeaderExtend = isHeaderExtend,
|
|
249
299
|
statusBarHeight = statusBarHeight,
|
|
250
300
|
titlePosition = titlePosition,
|
|
251
301
|
scrollPercentage = scrollPercentage,
|
|
302
|
+
headerSpaceBetween = headerSpaceBetween,
|
|
303
|
+
animationOption = animationOption,
|
|
304
|
+
verticalArrangement = verticalArrangement,
|
|
305
|
+
horizontalAlignment = horizontalAlignment,
|
|
252
306
|
)
|
|
253
307
|
}
|
|
254
308
|
|
|
255
309
|
Layout(
|
|
256
|
-
modifier =
|
|
257
|
-
.animateContentSize()
|
|
258
|
-
.drawBehind {
|
|
259
|
-
val headerHeight = max(
|
|
260
|
-
HeaderId.EXTENDED_HEADER_HEIGHT.toPx(),
|
|
261
|
-
size.height,
|
|
262
|
-
)
|
|
263
|
-
drawRect(color = Colors.black_01)
|
|
264
|
-
drawRect(
|
|
265
|
-
brush = Brush.linearGradient(
|
|
266
|
-
colors = listGradientColors,
|
|
267
|
-
start = Offset.Zero,
|
|
268
|
-
end = Offset(
|
|
269
|
-
x = 0f,
|
|
270
|
-
y = headerHeight * (1 - scrollPercentage.value),
|
|
271
|
-
),
|
|
272
|
-
)
|
|
273
|
-
)
|
|
274
|
-
},
|
|
310
|
+
modifier = modifier
|
|
311
|
+
.animateContentSize(),
|
|
275
312
|
content = {
|
|
276
|
-
|
|
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) {
|
|
277
323
|
Box(
|
|
278
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
|
|
279
360
|
.size(28.dp)
|
|
280
|
-
.layoutId(HeaderId.BACK_ID)
|
|
281
|
-
.clip(CircleShape)
|
|
282
361
|
.border(
|
|
283
362
|
width = 0.2.dp,
|
|
284
363
|
color = headerColor.borderColor,
|
|
285
364
|
shape = CircleShape,
|
|
286
365
|
)
|
|
287
366
|
.background(color = headerColor.backgroundButton)
|
|
288
|
-
.noFeedbackClickable(onClick = onGoBack)
|
|
289
|
-
.setAutomationId("btn_navigation_back")
|
|
290
367
|
.padding(Spacing.XS),
|
|
291
368
|
) {
|
|
292
369
|
Icon(
|
|
@@ -297,27 +374,30 @@ private fun LiteScreenHeader(
|
|
|
297
374
|
}
|
|
298
375
|
}
|
|
299
376
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
+
}
|
|
311
391
|
}
|
|
312
392
|
}
|
|
313
393
|
|
|
314
|
-
if (inputSearchProps != null) {
|
|
394
|
+
if (!isHeaderNone && inputSearchProps != null) {
|
|
315
395
|
LiteInputSearch(
|
|
316
396
|
modifier = Modifier.layoutId(HeaderId.INPUT_SEARCH_ID),
|
|
317
397
|
inputSearchProps = inputSearchProps,
|
|
318
398
|
)
|
|
319
399
|
}
|
|
320
|
-
if (title != null && (inputSearchProps == null || isHeaderExtend)) {
|
|
400
|
+
if (!isHeaderNone && title != null && (inputSearchProps == null || isHeaderExtend)) {
|
|
321
401
|
Text(
|
|
322
402
|
text = title,
|
|
323
403
|
color = headerColor.tintIconColor,
|
|
@@ -332,22 +412,39 @@ private fun LiteScreenHeader(
|
|
|
332
412
|
)
|
|
333
413
|
}
|
|
334
414
|
|
|
335
|
-
private class
|
|
415
|
+
private class LiteScreenLayoutPolicy(
|
|
336
416
|
private val useAnimationSearch: Boolean,
|
|
417
|
+
private val isHeaderNone: Boolean,
|
|
337
418
|
private val isHeaderExtend: Boolean,
|
|
338
419
|
private val statusBarHeight: Dp,
|
|
339
420
|
private val scrollPercentage: State<Float>,
|
|
340
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,
|
|
341
426
|
) : MeasurePolicy {
|
|
342
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
|
+
|
|
343
437
|
override fun MeasureScope.measure(
|
|
344
438
|
measurables: List<Measurable>,
|
|
345
439
|
constraints: Constraints
|
|
346
440
|
): MeasureResult {
|
|
347
441
|
val spacing12 = Spacing.M.roundToPx()
|
|
442
|
+
val spaceBetween = headerSpaceBetween?.roundToPx() ?: spacing12
|
|
348
443
|
val statusBarPx = statusBarHeight.roundToPx()
|
|
349
444
|
val scrollPercent = scrollPercentage.value
|
|
350
445
|
|
|
446
|
+
val contentMeasurable = measurables.find { it.layoutId == HeaderId.CONTENT_ID }
|
|
447
|
+
|
|
351
448
|
val realConstraints = constraints.copy(
|
|
352
449
|
minWidth = 0,
|
|
353
450
|
minHeight = 0,
|
|
@@ -362,9 +459,9 @@ private class LiteScreenHeaderPolicy(
|
|
|
362
459
|
maxWidth = realConstraints.maxWidth / 2,
|
|
363
460
|
)
|
|
364
461
|
)
|
|
365
|
-
val
|
|
462
|
+
val baseInputSearchConstraints = if (isHeaderExtend) {
|
|
366
463
|
val minWidth =
|
|
367
|
-
if (useAnimationSearch) realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth -
|
|
464
|
+
if (useAnimationSearch) realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
|
|
368
465
|
else realConstraints.maxWidth
|
|
369
466
|
realConstraints.copy(
|
|
370
467
|
maxWidth = (realConstraints.maxWidth * (1 - scrollPercent)).toInt()
|
|
@@ -372,17 +469,18 @@ private class LiteScreenHeaderPolicy(
|
|
|
372
469
|
)
|
|
373
470
|
} else {
|
|
374
471
|
var spaceConsumed = 0
|
|
375
|
-
if (backIconPlaceable.safeWidth != 0) spaceConsumed += backIconPlaceable.safeWidth +
|
|
376
|
-
if (headerRightPlaceable.safeWidth != 0) spaceConsumed += headerRightPlaceable.safeWidth +
|
|
472
|
+
if (backIconPlaceable.safeWidth != 0) spaceConsumed += backIconPlaceable.safeWidth + spaceBetween
|
|
473
|
+
if (headerRightPlaceable.safeWidth != 0) spaceConsumed += headerRightPlaceable.safeWidth + spaceBetween
|
|
377
474
|
realConstraints.copy(
|
|
378
475
|
maxWidth = realConstraints.maxWidth - spaceConsumed
|
|
379
476
|
)
|
|
380
477
|
}
|
|
478
|
+
val inputSearchConstraints = baseInputSearchConstraints.withAnimationTargetBounds()
|
|
381
479
|
val inputSearchPlaceable = measurables.find { it.layoutId == HeaderId.INPUT_SEARCH_ID }
|
|
382
480
|
?.measure(inputSearchConstraints)
|
|
383
481
|
val titlePlaceable = measurables.find { it.layoutId == HeaderId.TITLE_ID }?.measure(
|
|
384
482
|
constraints = realConstraints.copy(
|
|
385
|
-
maxWidth = realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth -
|
|
483
|
+
maxWidth = realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
|
|
386
484
|
)
|
|
387
485
|
)
|
|
388
486
|
|
|
@@ -392,6 +490,7 @@ private class LiteScreenHeaderPolicy(
|
|
|
392
490
|
if (!isHeaderExtend) {
|
|
393
491
|
add(inputSearchPlaceable.safeHeight)
|
|
394
492
|
}
|
|
493
|
+
add(HEADER_HEIGHT.dp.roundToPx())
|
|
395
494
|
if (isHeaderExtend) {
|
|
396
495
|
add(titlePlaceable.safeHeight)
|
|
397
496
|
}
|
|
@@ -401,37 +500,86 @@ private class LiteScreenHeaderPolicy(
|
|
|
401
500
|
if (isHeaderExtend) {
|
|
402
501
|
defaultHeight += inputSearchPlaceable.safeHeight + spacing12
|
|
403
502
|
}
|
|
404
|
-
val
|
|
503
|
+
val headerHeight = when {
|
|
504
|
+
isHeaderNone -> statusBarPx
|
|
405
505
|
!useAnimationSearch && !isHeaderExtend -> defaultHeight
|
|
406
506
|
else -> (defaultHeight - scrollPercent * (defaultHeight - statusBarPx - HEADER_HEIGHT.dp.roundToPx())).toInt()
|
|
407
507
|
}
|
|
408
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
|
+
|
|
409
541
|
return layout(
|
|
410
|
-
width =
|
|
411
|
-
height =
|
|
542
|
+
width = layoutWidth,
|
|
543
|
+
height = layoutHeight,
|
|
412
544
|
) {
|
|
545
|
+
val progress = animationOption?.progress?.value ?: 1f
|
|
546
|
+
|
|
413
547
|
val startX = spacing12
|
|
414
|
-
val startY =
|
|
548
|
+
val startY = headerY + statusBarPx
|
|
415
549
|
var curX = startX
|
|
416
550
|
var curY = startY
|
|
417
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
|
+
|
|
418
566
|
if (backIconPlaceable != null) {
|
|
419
567
|
backIconPlaceable.place(
|
|
420
568
|
x = startX,
|
|
421
569
|
y = startY + backIconPlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
422
570
|
)
|
|
423
|
-
curX += backIconPlaceable.safeWidth +
|
|
571
|
+
curX += backIconPlaceable.safeWidth + spaceBetween
|
|
424
572
|
}
|
|
425
573
|
|
|
426
574
|
headerRightPlaceable?.place(
|
|
427
|
-
x =
|
|
575
|
+
x = layoutWidth - spacing12 - headerRightPlaceable.safeWidth,
|
|
428
576
|
y = startY + headerRightPlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
429
577
|
)
|
|
430
578
|
|
|
431
579
|
val titleOffset = IntOffset(
|
|
432
580
|
x = if (titlePosition == TitlePosition.LEFT) curX
|
|
433
581
|
else titlePlaceable.horizontalCenterOffset(
|
|
434
|
-
space =
|
|
582
|
+
space = layoutWidth,
|
|
435
583
|
layoutDirection = layoutDirection,
|
|
436
584
|
),
|
|
437
585
|
y = startY + titlePlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
@@ -440,17 +588,19 @@ private class LiteScreenHeaderPolicy(
|
|
|
440
588
|
titlePlaceable?.place(titleOffset)
|
|
441
589
|
|
|
442
590
|
if (backIconPlaceable != null || headerRightPlaceable != null || titlePlaceable != null) {
|
|
443
|
-
curY += firstRowMaxHeight
|
|
591
|
+
curY += firstRowMaxHeight
|
|
444
592
|
}
|
|
445
593
|
|
|
446
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
|
+
)
|
|
447
599
|
IntOffset(
|
|
448
|
-
x = startX + ((backIconPlaceable.safeWidth +
|
|
600
|
+
x = startX + ((backIconPlaceable.safeWidth + spaceBetween) * (scrollPercent * 2f).coerceIn(
|
|
449
601
|
0f, 1f
|
|
450
602
|
)).toInt(),
|
|
451
|
-
y =
|
|
452
|
-
startY + inputSearchPlaceable.verticalCenterOffset(firstRowMaxHeight)
|
|
453
|
-
),
|
|
603
|
+
y = y,
|
|
454
604
|
)
|
|
455
605
|
} else {
|
|
456
606
|
IntOffset(
|
|
@@ -458,8 +608,15 @@ private class LiteScreenHeaderPolicy(
|
|
|
458
608
|
y = startY + inputSearchPlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
459
609
|
)
|
|
460
610
|
}
|
|
611
|
+
val finalPosition = lerp(
|
|
612
|
+
searchStartPosition,
|
|
613
|
+
inputSearchOffset,
|
|
614
|
+
progress,
|
|
615
|
+
)
|
|
461
616
|
|
|
462
|
-
inputSearchPlaceable?.place(
|
|
617
|
+
inputSearchPlaceable?.place(
|
|
618
|
+
finalPosition
|
|
619
|
+
)
|
|
463
620
|
}
|
|
464
621
|
}
|
|
465
622
|
|
|
@@ -468,6 +625,54 @@ private class LiteScreenHeaderPolicy(
|
|
|
468
625
|
private val Placeable?.safeWidth
|
|
469
626
|
get() = this?.width ?: 0
|
|
470
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
|
+
|
|
471
676
|
private fun Placeable?.verticalCenterOffset(space: Int): Int {
|
|
472
677
|
if (this == null) return 0
|
|
473
678
|
return Alignment.CenterVertically.align(safeHeight, space)
|
|
@@ -504,6 +709,12 @@ data class LiteInputSearchProps(
|
|
|
504
709
|
|
|
505
710
|
val placeHolder: String? = null,
|
|
506
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,
|
|
507
718
|
val iconRightTextField: @Composable ((Modifier) -> Unit)? = null,
|
|
508
719
|
)
|
|
509
720
|
|
|
@@ -545,7 +756,6 @@ private fun LiteInputSearch(
|
|
|
545
756
|
val textFieldModifier = remember(inputSearchProps.modifier) {
|
|
546
757
|
inputSearchProps.modifier
|
|
547
758
|
.weight(1f)
|
|
548
|
-
.sizeIn(minHeight = 36.dp)
|
|
549
759
|
}
|
|
550
760
|
BasicTextField(
|
|
551
761
|
value = textState,
|
|
@@ -554,8 +764,9 @@ private fun LiteInputSearch(
|
|
|
554
764
|
keyboardOptions = inputSearchProps.keyboardOptions,
|
|
555
765
|
keyboardActions = inputSearchProps.keyboardActions,
|
|
556
766
|
modifier = textFieldModifier,
|
|
557
|
-
textStyle = inputFieldStyle,
|
|
767
|
+
textStyle = inputSearchProps.customTextStyle ?: inputFieldStyle,
|
|
558
768
|
singleLine = true,
|
|
769
|
+
cursorBrush = inputSearchProps.cursorBrush ?: SolidColor(Color.Black),
|
|
559
770
|
interactionSource = interactionSource,
|
|
560
771
|
decorationBox = { innerTextField ->
|
|
561
772
|
val isShowClear by remember(inputSearchProps.clearCondition, isFocused.value) {
|
|
@@ -592,10 +803,6 @@ private fun LiteInputSearch(
|
|
|
592
803
|
|
|
593
804
|
Row(
|
|
594
805
|
modifier = Modifier
|
|
595
|
-
.background(
|
|
596
|
-
color = theme.colors.background.surface,
|
|
597
|
-
shape = RoundedCornerShape(Radius.XL),
|
|
598
|
-
)
|
|
599
806
|
.padding(
|
|
600
807
|
horizontal = Spacing.M,
|
|
601
808
|
vertical = Spacing.S,
|
|
@@ -603,7 +810,7 @@ private fun LiteInputSearch(
|
|
|
603
810
|
horizontalArrangement = Arrangement.Start,
|
|
604
811
|
verticalAlignment = Alignment.CenterVertically,
|
|
605
812
|
) {
|
|
606
|
-
Icon(
|
|
813
|
+
inputSearchProps.customSearchIcon?.invoke() ?: Icon(
|
|
607
814
|
source = "navigation_search",
|
|
608
815
|
modifier = Modifier.padding(end = Spacing.XS),
|
|
609
816
|
size = 24.dp,
|
|
@@ -614,9 +821,9 @@ private fun LiteInputSearch(
|
|
|
614
821
|
contentAlignment = Alignment.CenterStart,
|
|
615
822
|
) {
|
|
616
823
|
if (!placeHolder.isNullOrEmpty()) {
|
|
617
|
-
Text(
|
|
824
|
+
inputSearchProps.customPlaceHolder?.invoke() ?: Text(
|
|
618
825
|
text = placeHolder ?: "",
|
|
619
|
-
style = Typography.bodyDefaultRegular,
|
|
826
|
+
style = inputSearchProps.customTextStyle ?: Typography.bodyDefaultRegular,
|
|
620
827
|
maxLines = 1,
|
|
621
828
|
color = theme.colors.text.hint,
|
|
622
829
|
overflow = TextOverflow.Ellipsis
|
|
@@ -626,7 +833,7 @@ private fun LiteInputSearch(
|
|
|
626
833
|
}
|
|
627
834
|
|
|
628
835
|
if (isShowClear) {
|
|
629
|
-
Icon(
|
|
836
|
+
inputSearchProps.customIconClear?.invoke() ?: Icon(
|
|
630
837
|
source = "24_navigation_close_circle_full",
|
|
631
838
|
size = 16.dp,
|
|
632
839
|
color = theme.colors.text.hint,
|
|
@@ -717,4 +924,4 @@ fun Modifier.hideKeyboardOnTap() = composed {
|
|
|
717
924
|
focusManager.clearFocus()
|
|
718
925
|
}
|
|
719
926
|
}
|
|
720
|
-
}
|
|
927
|
+
}
|