@momo-kits/native-kits 0.160.9-debug → 0.160.10-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
CHANGED
|
@@ -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
|
|
@@ -70,13 +68,17 @@ import androidx.compose.ui.unit.IntOffset
|
|
|
70
68
|
import androidx.compose.ui.unit.LayoutDirection
|
|
71
69
|
import androidx.compose.ui.unit.dp
|
|
72
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
|
|
73
76
|
import kotlinx.coroutines.flow.collectLatest
|
|
74
77
|
import kotlinx.coroutines.flow.mapNotNull
|
|
75
78
|
import vn.momo.kits.components.Icon
|
|
76
79
|
import vn.momo.kits.components.Text
|
|
77
80
|
import vn.momo.kits.const.AppTheme
|
|
78
81
|
import vn.momo.kits.const.Colors
|
|
79
|
-
import vn.momo.kits.const.Radius
|
|
80
82
|
import vn.momo.kits.const.Spacing
|
|
81
83
|
import vn.momo.kits.const.Typography
|
|
82
84
|
import vn.momo.kits.modifier.conditional
|
|
@@ -86,6 +88,30 @@ import vn.momo.kits.modifier.setAutomationId
|
|
|
86
88
|
import vn.momo.kits.modifier.shadow
|
|
87
89
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
88
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 }
|
|
89
115
|
|
|
90
116
|
@Composable
|
|
91
117
|
fun LiteScreen(
|
|
@@ -114,48 +140,46 @@ fun LiteScreen(
|
|
|
114
140
|
|
|
115
141
|
val finalScrollState = scrollState ?: rememberScrollState()
|
|
116
142
|
|
|
117
|
-
|
|
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,
|
|
118
157
|
modifier = Modifier
|
|
119
158
|
.fillMaxSize()
|
|
120
|
-
.background(color = backgroundColor)
|
|
121
159
|
.hideKeyboardOnTap(),
|
|
160
|
+
contentModifier = contentModifier,
|
|
122
161
|
verticalArrangement = verticalArrangement,
|
|
123
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,
|
|
124
174
|
) {
|
|
125
|
-
|
|
126
|
-
var res = Modifier.weight(1f)
|
|
127
|
-
if (scrollable) {
|
|
128
|
-
res = res.verticalScroll(finalScrollState)
|
|
129
|
-
}
|
|
130
|
-
res
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
LiteScreenHeader(
|
|
134
|
-
scrollState = finalScrollState,
|
|
135
|
-
title = title,
|
|
136
|
-
headerRight = headerRight,
|
|
137
|
-
headerType = headerType,
|
|
138
|
-
onGoBack = goBack,
|
|
139
|
-
inputSearchProps = inputSearchProps,
|
|
140
|
-
titlePosition = titlePosition,
|
|
141
|
-
useAnimationSearch = useAnimationSearch,
|
|
142
|
-
headerRightData = headerRightData,
|
|
143
|
-
tintColor = headerTintColor,
|
|
144
|
-
headerBackgroundColor = headerBackgroundColor,
|
|
145
|
-
headerSpaceBetween = headerSpaceBetween,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
Box(
|
|
149
|
-
modifier = contentModifier,
|
|
150
|
-
contentAlignment = Alignment.TopCenter,
|
|
151
|
-
) {
|
|
152
|
-
content()
|
|
153
|
-
}
|
|
175
|
+
content()
|
|
154
176
|
}
|
|
155
177
|
}
|
|
156
178
|
|
|
157
179
|
private object HeaderId {
|
|
158
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"
|
|
159
183
|
const val BACK_ID = "${PACKAGE_NAME}.back"
|
|
160
184
|
const val HEADER_RIGHT_ID = "${PACKAGE_NAME}.headerRight"
|
|
161
185
|
const val INPUT_SEARCH_ID = "${PACKAGE_NAME}.inputSearch"
|
|
@@ -165,8 +189,12 @@ private object HeaderId {
|
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
@Composable
|
|
168
|
-
private fun
|
|
192
|
+
private fun LiteScreenLayout(
|
|
169
193
|
scrollState: ScrollState?,
|
|
194
|
+
modifier: Modifier = Modifier,
|
|
195
|
+
contentModifier: Modifier = Modifier,
|
|
196
|
+
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
|
197
|
+
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
170
198
|
title: String? = null,
|
|
171
199
|
tintColor: Color? = null,
|
|
172
200
|
headerRightData: HeaderRightData? = null,
|
|
@@ -178,11 +206,11 @@ private fun LiteScreenHeader(
|
|
|
178
206
|
headerSpaceBetween: Dp? = null,
|
|
179
207
|
inputSearchProps: LiteInputSearchProps? = null,
|
|
180
208
|
headerRight: @Composable (() -> Unit)? = null,
|
|
209
|
+
content: @Composable () -> Unit,
|
|
181
210
|
) {
|
|
182
211
|
val statusBarHeight = getAppStatusBarHeight()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
212
|
+
val isHeaderNone = remember(headerType) {
|
|
213
|
+
headerType == HeaderType.NONE
|
|
186
214
|
}
|
|
187
215
|
val theme = AppTheme.current
|
|
188
216
|
val density = LocalDensity.current
|
|
@@ -190,9 +218,12 @@ private fun LiteScreenHeader(
|
|
|
190
218
|
val isHeaderExtend = remember(headerType) {
|
|
191
219
|
headerType == HeaderType.EXTENDED
|
|
192
220
|
}
|
|
193
|
-
val backgroundHeight = remember(isHeaderExtend, statusBarHeight) {
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
}
|
|
196
227
|
}
|
|
197
228
|
val listGradientColors = remember {
|
|
198
229
|
listOf(
|
|
@@ -235,79 +266,107 @@ private fun LiteScreenHeader(
|
|
|
235
266
|
)
|
|
236
267
|
}
|
|
237
268
|
|
|
238
|
-
val
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.layoutId(HeaderId.TITLE_ID)
|
|
242
|
-
.graphicsLayer {
|
|
243
|
-
alpha = if (isHeaderExtend && inputSearchProps != null && useAnimationSearch)
|
|
244
|
-
(1 - scrollPercentage.value * 2).coerceIn(0f, 1f)
|
|
245
|
-
else 1f
|
|
246
|
-
}
|
|
269
|
+
val animationOption = LocalAnimationOption.current
|
|
270
|
+
val animationOpacityModifier = Modifier.graphicsLayer {
|
|
271
|
+
alpha = animationOption?.animateOpacity ?: 1f
|
|
247
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
|
+
}
|
|
248
282
|
|
|
249
283
|
val policy = remember(
|
|
250
284
|
useAnimationSearch,
|
|
285
|
+
isHeaderNone,
|
|
251
286
|
isHeaderExtend,
|
|
252
287
|
statusBarHeight,
|
|
253
288
|
titlePosition,
|
|
254
289
|
scrollPercentage,
|
|
255
290
|
headerSpaceBetween,
|
|
291
|
+
animationOption,
|
|
292
|
+
verticalArrangement,
|
|
293
|
+
horizontalAlignment,
|
|
256
294
|
) {
|
|
257
|
-
|
|
295
|
+
LiteScreenLayoutPolicy(
|
|
258
296
|
useAnimationSearch = useAnimationSearch,
|
|
297
|
+
isHeaderNone = isHeaderNone,
|
|
259
298
|
isHeaderExtend = isHeaderExtend,
|
|
260
299
|
statusBarHeight = statusBarHeight,
|
|
261
300
|
titlePosition = titlePosition,
|
|
262
301
|
scrollPercentage = scrollPercentage,
|
|
263
302
|
headerSpaceBetween = headerSpaceBetween,
|
|
303
|
+
animationOption = animationOption,
|
|
304
|
+
verticalArrangement = verticalArrangement,
|
|
305
|
+
horizontalAlignment = horizontalAlignment,
|
|
264
306
|
)
|
|
265
307
|
}
|
|
266
308
|
|
|
267
309
|
Layout(
|
|
268
|
-
modifier =
|
|
269
|
-
.animateContentSize()
|
|
270
|
-
.drawBehind {
|
|
271
|
-
val headerHeight = max(
|
|
272
|
-
HeaderId.EXTENDED_HEADER_HEIGHT.toPx(),
|
|
273
|
-
size.height,
|
|
274
|
-
)
|
|
275
|
-
headerBackgroundColor?.let {
|
|
276
|
-
drawRect(color = it)
|
|
277
|
-
} ?: run {
|
|
278
|
-
drawRect(color = Colors.black_01)
|
|
279
|
-
drawRect(
|
|
280
|
-
brush = Brush.linearGradient(
|
|
281
|
-
colors = listGradientColors,
|
|
282
|
-
start = Offset.Zero,
|
|
283
|
-
end = Offset(
|
|
284
|
-
x = 0f,
|
|
285
|
-
y = headerHeight * (1 - scrollPercentage.value),
|
|
286
|
-
),
|
|
287
|
-
)
|
|
288
|
-
)
|
|
289
|
-
}
|
|
290
|
-
},
|
|
310
|
+
modifier = modifier
|
|
311
|
+
.animateContentSize(),
|
|
291
312
|
content = {
|
|
292
|
-
|
|
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) {
|
|
293
323
|
Box(
|
|
294
324
|
modifier = Modifier
|
|
295
|
-
.
|
|
296
|
-
.
|
|
297
|
-
.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
color = headerColor.borderColor,
|
|
302
|
-
shape = CircleShape,
|
|
325
|
+
.layoutId(HeaderId.BACKGROUND_ID)
|
|
326
|
+
.then(animationOpacityModifier)
|
|
327
|
+
.drawBehind {
|
|
328
|
+
val headerHeight = max(
|
|
329
|
+
HeaderId.EXTENDED_HEADER_HEIGHT.toPx(),
|
|
330
|
+
size.height,
|
|
303
331
|
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
332
|
+
headerBackgroundColor?.let {
|
|
333
|
+
drawRect(color = it)
|
|
334
|
+
} ?: run {
|
|
335
|
+
drawRect(color = Colors.black_01)
|
|
336
|
+
drawRect(
|
|
337
|
+
brush = Brush.linearGradient(
|
|
338
|
+
colors = listGradientColors,
|
|
339
|
+
start = Offset.Zero,
|
|
340
|
+
end = Offset(
|
|
341
|
+
x = 0f,
|
|
342
|
+
y = headerHeight * (1 - scrollPercentage.value),
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!isHeaderNone && onGoBack != null) {
|
|
352
|
+
val coreModifier = Modifier
|
|
353
|
+
.layoutId(HeaderId.BACK_ID)
|
|
354
|
+
.then(animationOpacityModifier)
|
|
355
|
+
.clip(CircleShape)
|
|
356
|
+
.noFeedbackClickable(onClick = onGoBack)
|
|
357
|
+
.setAutomationId("btn_navigation_back")
|
|
358
|
+
inputSearchProps?.customBackIcon?.invoke(coreModifier) ?: Box(
|
|
359
|
+
modifier = coreModifier
|
|
360
|
+
.size(28.dp)
|
|
361
|
+
.border(
|
|
362
|
+
width = 0.2.dp,
|
|
363
|
+
color = headerColor.borderColor,
|
|
364
|
+
shape = CircleShape,
|
|
365
|
+
)
|
|
366
|
+
.background(color = headerColor.backgroundButton)
|
|
308
367
|
.padding(Spacing.XS),
|
|
309
368
|
) {
|
|
310
|
-
|
|
369
|
+
Icon(
|
|
311
370
|
source = "arrow-back",
|
|
312
371
|
color = headerColor.tintIconColor,
|
|
313
372
|
size = 20.dp,
|
|
@@ -315,27 +374,30 @@ private fun LiteScreenHeader(
|
|
|
315
374
|
}
|
|
316
375
|
}
|
|
317
376
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
+
}
|
|
329
391
|
}
|
|
330
392
|
}
|
|
331
393
|
|
|
332
|
-
if (inputSearchProps != null) {
|
|
394
|
+
if (!isHeaderNone && inputSearchProps != null) {
|
|
333
395
|
LiteInputSearch(
|
|
334
396
|
modifier = Modifier.layoutId(HeaderId.INPUT_SEARCH_ID),
|
|
335
397
|
inputSearchProps = inputSearchProps,
|
|
336
398
|
)
|
|
337
399
|
}
|
|
338
|
-
if (title != null && (inputSearchProps == null || isHeaderExtend)) {
|
|
400
|
+
if (!isHeaderNone && title != null && (inputSearchProps == null || isHeaderExtend)) {
|
|
339
401
|
Text(
|
|
340
402
|
text = title,
|
|
341
403
|
color = headerColor.tintIconColor,
|
|
@@ -350,15 +412,28 @@ private fun LiteScreenHeader(
|
|
|
350
412
|
)
|
|
351
413
|
}
|
|
352
414
|
|
|
353
|
-
private class
|
|
415
|
+
private class LiteScreenLayoutPolicy(
|
|
354
416
|
private val useAnimationSearch: Boolean,
|
|
417
|
+
private val isHeaderNone: Boolean,
|
|
355
418
|
private val isHeaderExtend: Boolean,
|
|
356
419
|
private val statusBarHeight: Dp,
|
|
357
420
|
private val scrollPercentage: State<Float>,
|
|
358
421
|
private val titlePosition: TitlePosition,
|
|
359
422
|
private val headerSpaceBetween: Dp? = null,
|
|
423
|
+
private val animationOption: AnimationOption?,
|
|
424
|
+
private val verticalArrangement: Arrangement.Vertical,
|
|
425
|
+
private val horizontalAlignment: Alignment.Horizontal,
|
|
360
426
|
) : MeasurePolicy {
|
|
361
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
|
+
|
|
362
437
|
override fun MeasureScope.measure(
|
|
363
438
|
measurables: List<Measurable>,
|
|
364
439
|
constraints: Constraints
|
|
@@ -368,6 +443,8 @@ private class LiteScreenHeaderPolicy(
|
|
|
368
443
|
val statusBarPx = statusBarHeight.roundToPx()
|
|
369
444
|
val scrollPercent = scrollPercentage.value
|
|
370
445
|
|
|
446
|
+
val contentMeasurable = measurables.find { it.layoutId == HeaderId.CONTENT_ID }
|
|
447
|
+
|
|
371
448
|
val realConstraints = constraints.copy(
|
|
372
449
|
minWidth = 0,
|
|
373
450
|
minHeight = 0,
|
|
@@ -382,7 +459,7 @@ private class LiteScreenHeaderPolicy(
|
|
|
382
459
|
maxWidth = realConstraints.maxWidth / 2,
|
|
383
460
|
)
|
|
384
461
|
)
|
|
385
|
-
val
|
|
462
|
+
val baseInputSearchConstraints = if (isHeaderExtend) {
|
|
386
463
|
val minWidth =
|
|
387
464
|
if (useAnimationSearch) realConstraints.maxWidth - backIconPlaceable.safeWidth - headerRightPlaceable.safeWidth - spaceBetween * 2
|
|
388
465
|
else realConstraints.maxWidth
|
|
@@ -398,6 +475,7 @@ private class LiteScreenHeaderPolicy(
|
|
|
398
475
|
maxWidth = realConstraints.maxWidth - spaceConsumed
|
|
399
476
|
)
|
|
400
477
|
}
|
|
478
|
+
val inputSearchConstraints = baseInputSearchConstraints.withAnimationTargetBounds()
|
|
401
479
|
val inputSearchPlaceable = measurables.find { it.layoutId == HeaderId.INPUT_SEARCH_ID }
|
|
402
480
|
?.measure(inputSearchConstraints)
|
|
403
481
|
val titlePlaceable = measurables.find { it.layoutId == HeaderId.TITLE_ID }?.measure(
|
|
@@ -412,6 +490,7 @@ private class LiteScreenHeaderPolicy(
|
|
|
412
490
|
if (!isHeaderExtend) {
|
|
413
491
|
add(inputSearchPlaceable.safeHeight)
|
|
414
492
|
}
|
|
493
|
+
add(HEADER_HEIGHT.dp.roundToPx())
|
|
415
494
|
if (isHeaderExtend) {
|
|
416
495
|
add(titlePlaceable.safeHeight)
|
|
417
496
|
}
|
|
@@ -421,20 +500,69 @@ private class LiteScreenHeaderPolicy(
|
|
|
421
500
|
if (isHeaderExtend) {
|
|
422
501
|
defaultHeight += inputSearchPlaceable.safeHeight + spacing12
|
|
423
502
|
}
|
|
424
|
-
val
|
|
503
|
+
val headerHeight = when {
|
|
504
|
+
isHeaderNone -> statusBarPx
|
|
425
505
|
!useAnimationSearch && !isHeaderExtend -> defaultHeight
|
|
426
506
|
else -> (defaultHeight - scrollPercent * (defaultHeight - statusBarPx - HEADER_HEIGHT.dp.roundToPx())).toInt()
|
|
427
507
|
}
|
|
428
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
|
+
|
|
429
541
|
return layout(
|
|
430
|
-
width =
|
|
431
|
-
height =
|
|
542
|
+
width = layoutWidth,
|
|
543
|
+
height = layoutHeight,
|
|
432
544
|
) {
|
|
545
|
+
val progress = animationOption?.progress?.value ?: 1f
|
|
546
|
+
|
|
433
547
|
val startX = spacing12
|
|
434
|
-
val startY =
|
|
548
|
+
val startY = headerY + statusBarPx
|
|
435
549
|
var curX = startX
|
|
436
550
|
var curY = startY
|
|
437
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
|
+
|
|
438
566
|
if (backIconPlaceable != null) {
|
|
439
567
|
backIconPlaceable.place(
|
|
440
568
|
x = startX,
|
|
@@ -444,14 +572,14 @@ private class LiteScreenHeaderPolicy(
|
|
|
444
572
|
}
|
|
445
573
|
|
|
446
574
|
headerRightPlaceable?.place(
|
|
447
|
-
x =
|
|
575
|
+
x = layoutWidth - spacing12 - headerRightPlaceable.safeWidth,
|
|
448
576
|
y = startY + headerRightPlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
449
577
|
)
|
|
450
578
|
|
|
451
579
|
val titleOffset = IntOffset(
|
|
452
580
|
x = if (titlePosition == TitlePosition.LEFT) curX
|
|
453
581
|
else titlePlaceable.horizontalCenterOffset(
|
|
454
|
-
space =
|
|
582
|
+
space = layoutWidth,
|
|
455
583
|
layoutDirection = layoutDirection,
|
|
456
584
|
),
|
|
457
585
|
y = startY + titlePlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
@@ -460,17 +588,19 @@ private class LiteScreenHeaderPolicy(
|
|
|
460
588
|
titlePlaceable?.place(titleOffset)
|
|
461
589
|
|
|
462
590
|
if (backIconPlaceable != null || headerRightPlaceable != null || titlePlaceable != null) {
|
|
463
|
-
curY += firstRowMaxHeight
|
|
591
|
+
curY += firstRowMaxHeight
|
|
464
592
|
}
|
|
465
593
|
|
|
466
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
|
+
)
|
|
467
599
|
IntOffset(
|
|
468
600
|
x = startX + ((backIconPlaceable.safeWidth + spaceBetween) * (scrollPercent * 2f).coerceIn(
|
|
469
601
|
0f, 1f
|
|
470
602
|
)).toInt(),
|
|
471
|
-
y =
|
|
472
|
-
startY + inputSearchPlaceable.verticalCenterOffset(firstRowMaxHeight)
|
|
473
|
-
),
|
|
603
|
+
y = y,
|
|
474
604
|
)
|
|
475
605
|
} else {
|
|
476
606
|
IntOffset(
|
|
@@ -478,8 +608,15 @@ private class LiteScreenHeaderPolicy(
|
|
|
478
608
|
y = startY + inputSearchPlaceable.verticalCenterOffset(firstRowMaxHeight),
|
|
479
609
|
)
|
|
480
610
|
}
|
|
611
|
+
val finalPosition = lerp(
|
|
612
|
+
searchStartPosition,
|
|
613
|
+
inputSearchOffset,
|
|
614
|
+
progress,
|
|
615
|
+
)
|
|
481
616
|
|
|
482
|
-
inputSearchPlaceable?.place(
|
|
617
|
+
inputSearchPlaceable?.place(
|
|
618
|
+
finalPosition
|
|
619
|
+
)
|
|
483
620
|
}
|
|
484
621
|
}
|
|
485
622
|
|
|
@@ -488,6 +625,54 @@ private class LiteScreenHeaderPolicy(
|
|
|
488
625
|
private val Placeable?.safeWidth
|
|
489
626
|
get() = this?.width ?: 0
|
|
490
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
|
+
|
|
491
676
|
private fun Placeable?.verticalCenterOffset(space: Int): Int {
|
|
492
677
|
if (this == null) return 0
|
|
493
678
|
return Alignment.CenterVertically.align(safeHeight, space)
|
|
@@ -524,7 +709,8 @@ data class LiteInputSearchProps(
|
|
|
524
709
|
|
|
525
710
|
val placeHolder: String? = null,
|
|
526
711
|
|
|
527
|
-
val
|
|
712
|
+
val cursorBrush: Brush? = null,
|
|
713
|
+
val customBackIcon: @Composable ((Modifier) -> Unit)? = null,
|
|
528
714
|
val customSearchIcon: @Composable (() -> Unit)? = null,
|
|
529
715
|
val customPlaceHolder: @Composable (() -> Unit)? = null,
|
|
530
716
|
val customTextStyle: TextStyle? = null,
|
|
@@ -580,6 +766,7 @@ private fun LiteInputSearch(
|
|
|
580
766
|
modifier = textFieldModifier,
|
|
581
767
|
textStyle = inputSearchProps.customTextStyle ?: inputFieldStyle,
|
|
582
768
|
singleLine = true,
|
|
769
|
+
cursorBrush = inputSearchProps.cursorBrush ?: SolidColor(Color.Black),
|
|
583
770
|
interactionSource = interactionSource,
|
|
584
771
|
decorationBox = { innerTextField ->
|
|
585
772
|
val isShowClear by remember(inputSearchProps.clearCondition, isFocused.value) {
|
package/gradle.properties
CHANGED