@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
|
@@ -59,8 +59,9 @@ import vn.momo.kits.modifier.DeprecatedModifier
|
|
|
59
59
|
import vn.momo.kits.modifier.conditional
|
|
60
60
|
import vn.momo.kits.modifier.shadow
|
|
61
61
|
import vn.momo.kits.navigation.component.SnackBar
|
|
62
|
-
import vn.momo.kits.platform.
|
|
62
|
+
import vn.momo.kits.platform.supportsImePadding
|
|
63
63
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
64
|
+
import vn.momo.kits.utils.getNavigationBarHeight
|
|
64
65
|
|
|
65
66
|
enum class HeaderType {
|
|
66
67
|
DEFAULT,
|
|
@@ -102,7 +103,7 @@ fun Screen(
|
|
|
102
103
|
val keyboardController = LocalSoftwareKeyboardController.current
|
|
103
104
|
|
|
104
105
|
val isKeyboardVisible = isKeyboardVisible()
|
|
105
|
-
val indicator =
|
|
106
|
+
val indicator = getNavigationBarHeight()
|
|
106
107
|
val bottomPadding = if (isKeyboardVisible) 0.dp else indicator
|
|
107
108
|
|
|
108
109
|
val headerHeight = if (animatedHeader !== null)
|
|
@@ -134,7 +135,7 @@ fun Screen(
|
|
|
134
135
|
Box(
|
|
135
136
|
Modifier.fillMaxSize()
|
|
136
137
|
.background(backgroundColor ?: AppTheme.current.colors.background.default)
|
|
137
|
-
.conditional(useAvoidKeyboard &&
|
|
138
|
+
.conditional(useAvoidKeyboard && supportsImePadding()) {
|
|
138
139
|
imePadding()
|
|
139
140
|
}.then(DeprecatedModifier())
|
|
140
141
|
) {
|
|
@@ -21,6 +21,10 @@ import androidx.compose.ui.graphics.Color
|
|
|
21
21
|
import androidx.compose.ui.graphics.PathEffect
|
|
22
22
|
import androidx.compose.ui.graphics.StrokeCap
|
|
23
23
|
import androidx.compose.ui.unit.dp
|
|
24
|
+
import io.ktor.util.Platform
|
|
25
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
26
|
+
import vn.momo.kits.const.Colors
|
|
27
|
+
import vn.momo.kits.modifier.conditional
|
|
24
28
|
import vn.momo.kits.platform.getPlatformName
|
|
25
29
|
import vn.momo.kits.platform.getStatusBarHeight
|
|
26
30
|
|
|
@@ -221,6 +221,7 @@ fun Input(
|
|
|
221
221
|
onBlur: () -> Unit = {},
|
|
222
222
|
loading: Boolean = false,
|
|
223
223
|
required: Boolean = false,
|
|
224
|
+
maxLength: Int? = null,
|
|
224
225
|
fontWeight: InputFontWeight = InputFontWeight.REGULAR,
|
|
225
226
|
keyboardType: KeyboardType = KeyboardType.Text,
|
|
226
227
|
modifier: Modifier = Modifier,
|
|
@@ -319,7 +320,10 @@ fun Input(
|
|
|
319
320
|
onBlur()
|
|
320
321
|
}
|
|
321
322
|
},
|
|
322
|
-
onValueChange =
|
|
323
|
+
onValueChange = { newText ->
|
|
324
|
+
val limitedText = maxLength?.let { newText.take(it) } ?: newText
|
|
325
|
+
onChangeText(limitedText)
|
|
326
|
+
},
|
|
323
327
|
decorationBox = { innerTextField ->
|
|
324
328
|
// Floating label
|
|
325
329
|
if (floatingValue.isNotEmpty() || floatingIcon.isNotEmpty()) {
|
|
@@ -8,6 +8,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition
|
|
|
8
8
|
import androidx.compose.animation.core.tween
|
|
9
9
|
import androidx.compose.foundation.background
|
|
10
10
|
import androidx.compose.foundation.border
|
|
11
|
+
import androidx.compose.ui.draw.alpha
|
|
11
12
|
import androidx.compose.foundation.clickable
|
|
12
13
|
import androidx.compose.foundation.layout.Arrangement
|
|
13
14
|
import androidx.compose.foundation.layout.Box
|
|
@@ -129,8 +130,11 @@ fun InputOTP(
|
|
|
129
130
|
if (!it.isFocused && isBlurred) onBlur()
|
|
130
131
|
if (it.isFocused && !isBlurred) isBlurred = true
|
|
131
132
|
},
|
|
132
|
-
decorationBox = {
|
|
133
|
+
decorationBox = { innerTextField ->
|
|
133
134
|
Box {
|
|
135
|
+
Box(
|
|
136
|
+
modifier = Modifier.fillMaxWidth().height(56.dp).alpha(0f)
|
|
137
|
+
) { innerTextField() }
|
|
134
138
|
if (floatingValue.isNotEmpty()) {
|
|
135
139
|
Box(
|
|
136
140
|
modifier = Modifier.wrapContentSize()
|
|
@@ -105,6 +105,8 @@ data class InputSearchProps(
|
|
|
105
105
|
val iconModifier: Modifier = Modifier,
|
|
106
106
|
val onClearPress: () -> Unit = {},
|
|
107
107
|
val leftPosition: Dp? = null,
|
|
108
|
+
val placeholderCustomRender: (@Composable () -> Unit)? = null,
|
|
109
|
+
val searchIcon: (@Composable () -> Unit)? = null
|
|
108
110
|
)
|
|
109
111
|
|
|
110
112
|
@Composable
|
|
@@ -145,12 +147,13 @@ fun InputSearch(
|
|
|
145
147
|
)
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
Row(
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
Row(
|
|
151
|
+
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
|
152
|
+
.fillMaxWidth()
|
|
153
|
+
.height(36.dp)
|
|
154
|
+
.conditional(IsShowBaseLineDebug) {
|
|
155
|
+
border(1.dp, Colors.blue_03)
|
|
156
|
+
}
|
|
154
157
|
) {
|
|
155
158
|
BasicTextField(
|
|
156
159
|
enabled = !inputSearchProps.disabled,
|
|
@@ -184,13 +187,14 @@ fun InputSearch(
|
|
|
184
187
|
horizontalArrangement = Arrangement.Start,
|
|
185
188
|
verticalAlignment = Alignment.CenterVertically
|
|
186
189
|
) {
|
|
190
|
+
|
|
187
191
|
Row(
|
|
188
192
|
modifier = Modifier.padding(
|
|
189
193
|
horizontal = Spacing.M
|
|
190
194
|
),
|
|
191
195
|
verticalAlignment = Alignment.CenterVertically
|
|
192
196
|
) {
|
|
193
|
-
Icon(
|
|
197
|
+
inputSearchProps.searchIcon?.invoke() ?: Icon(
|
|
194
198
|
source = "navigation_search",
|
|
195
199
|
modifier = Modifier.padding(end = Spacing.XS),
|
|
196
200
|
size = 24.dp,
|
|
@@ -198,13 +202,14 @@ fun InputSearch(
|
|
|
198
202
|
)
|
|
199
203
|
Box(Modifier.weight(1f)) {
|
|
200
204
|
if (inputSearchProps.text.value.isEmpty()) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
205
|
+
inputSearchProps.placeholderCustomRender?.invoke()
|
|
206
|
+
?: Text(
|
|
207
|
+
text = inputSearchProps.placeholder,
|
|
208
|
+
style = placeHolderStyle,
|
|
209
|
+
maxLines = 1,
|
|
210
|
+
color = placeholderColor,
|
|
211
|
+
overflow = TextOverflow.Ellipsis
|
|
212
|
+
)
|
|
208
213
|
}
|
|
209
214
|
innerTextField()
|
|
210
215
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.CompositionLocalProvider
|
|
5
|
+
import vn.momo.kits.application.ScaleSizeMaxRate
|
|
6
|
+
|
|
7
|
+
@Composable
|
|
8
|
+
fun ScaleSizeScope(
|
|
9
|
+
scaleSizeMaxRate: Float? = null,
|
|
10
|
+
content: @Composable () -> Unit
|
|
11
|
+
) {
|
|
12
|
+
CompositionLocalProvider(
|
|
13
|
+
ScaleSizeMaxRate provides scaleSizeMaxRate,
|
|
14
|
+
) {
|
|
15
|
+
content()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -7,11 +7,15 @@ import androidx.compose.ui.text.TextStyle
|
|
|
7
7
|
import androidx.compose.ui.text.font.FontFamily
|
|
8
8
|
import androidx.compose.ui.text.font.FontWeight
|
|
9
9
|
import androidx.compose.ui.text.style.TextDecoration
|
|
10
|
-
import androidx.compose.ui.unit
|
|
10
|
+
import androidx.compose.ui.unit.Dp
|
|
11
|
+
import androidx.compose.ui.unit.TextUnit
|
|
12
|
+
import androidx.compose.ui.unit.TextUnitType
|
|
13
|
+
import androidx.compose.ui.unit.dp
|
|
14
|
+
import androidx.compose.ui.unit.sp
|
|
11
15
|
import org.jetbrains.compose.resources.Font
|
|
12
16
|
import org.jetbrains.compose.resources.FontResource
|
|
13
17
|
import org.jetbrains.compose.resources.InternalResourceApi
|
|
14
|
-
import vn.momo.kits.application.
|
|
18
|
+
import vn.momo.kits.application.ScaleSizeMaxRate
|
|
15
19
|
import vn.momo.kits.platform.getScreenDimensions
|
|
16
20
|
import vn.momo.uikits.resources.Res
|
|
17
21
|
import vn.momo.uikits.resources.momosignature
|
|
@@ -29,23 +33,34 @@ import kotlin.math.max
|
|
|
29
33
|
import kotlin.math.min
|
|
30
34
|
|
|
31
35
|
const val DEFAULT_SCREEN_SIZE = 375f
|
|
32
|
-
const val MAX_FONT_SCALE = 1.
|
|
36
|
+
const val MAX_FONT_SCALE = 1.5f
|
|
37
|
+
const val MAX_DEVICE_SCALE = 5
|
|
33
38
|
|
|
34
39
|
@Composable
|
|
35
40
|
fun scaleSize(size: Float): Float {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
val fontScale = LocalDensity.current.fontScale
|
|
41
|
+
val scaleSizeMaxRate: Float = ScaleSizeMaxRate.current ?: MAX_FONT_SCALE
|
|
42
|
+
val deviceWidth = getScreenDimensions().width
|
|
43
|
+
val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
val density = LocalDensity.current
|
|
46
|
+
val fontScale = density.fontScale
|
|
47
|
+
|
|
48
|
+
var fontSizeScaleDevice = size
|
|
49
|
+
var fontSizeScaleOS = size
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
if (deviceScale > 1) {
|
|
52
|
+
fontSizeScaleDevice =
|
|
53
|
+
min(deviceScale * fontSizeScaleDevice, fontSizeScaleDevice + MAX_DEVICE_SCALE)
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
if (fontScale > 1) {
|
|
57
|
+
fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * scaleSizeMaxRate)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return max(
|
|
61
|
+
fontSizeScaleDevice,
|
|
62
|
+
fontSizeScaleOS
|
|
63
|
+
)
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
@Composable
|
|
@@ -8,9 +8,6 @@ import androidx.navigation.compose.NavHost
|
|
|
8
8
|
import androidx.navigation.compose.composable
|
|
9
9
|
import androidx.navigation.compose.rememberNavController
|
|
10
10
|
import androidx.navigation.toRoute
|
|
11
|
-
import kotlinx.coroutines.flow.catch
|
|
12
|
-
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
13
|
-
import kotlinx.coroutines.flow.map
|
|
14
11
|
import vn.momo.kits.application.*
|
|
15
12
|
import vn.momo.kits.const.*
|
|
16
13
|
import vn.momo.kits.platform.ProvideNavigationEventDispatcherOwner
|
|
@@ -52,19 +49,6 @@ fun NavigationContainer(
|
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
LaunchedEffect(maxApi) {
|
|
56
|
-
val api = maxApi ?: return@LaunchedEffect
|
|
57
|
-
runCatching {
|
|
58
|
-
api.observer("use_font_scale")
|
|
59
|
-
.map { parseUseFontScale(it) }
|
|
60
|
-
.distinctUntilChanged()
|
|
61
|
-
.catch { /* ignore observer errors, keep current value */ }
|
|
62
|
-
.collect { enabled ->
|
|
63
|
-
UseFontScaleSystem = enabled
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
52
|
val startDestination = DynamicScreenRegistry.register(initialScreenName, initialScreen, options)
|
|
69
53
|
|
|
70
54
|
ProvideNavigationEventDispatcherOwner {
|
|
@@ -77,6 +61,7 @@ fun NavigationContainer(
|
|
|
77
61
|
ApplicationContext provides mergedContext,
|
|
78
62
|
AppConfig provides config,
|
|
79
63
|
AppLanguage provides language,
|
|
64
|
+
ScaleSizeMaxRate provides mergedContext?.scaleSizeMaxRate,
|
|
80
65
|
) {
|
|
81
66
|
LaunchedEffect(Unit) {
|
|
82
67
|
setNavigator?.invoke(navigator)
|
|
@@ -154,15 +139,3 @@ fun NavigationContainer(
|
|
|
154
139
|
val LocalMaxApi = staticCompositionLocalOf<IMaxApi?> {
|
|
155
140
|
error("No MaxApi provided")
|
|
156
141
|
}
|
|
157
|
-
|
|
158
|
-
private fun parseUseFontScale(raw: Any?): Boolean = when (raw) {
|
|
159
|
-
null -> true
|
|
160
|
-
is Boolean -> raw
|
|
161
|
-
is String -> raw.isBlank() || !raw.equals("false", ignoreCase = true)
|
|
162
|
-
is Number -> raw.toInt() != 0
|
|
163
|
-
is Map<*, *> -> {
|
|
164
|
-
val nested = raw["response"] ?: raw["value"] ?: raw["data"]
|
|
165
|
-
if (nested === raw) true else parseUseFontScale(nested)
|
|
166
|
-
}
|
|
167
|
-
else -> true
|
|
168
|
-
}
|
|
@@ -62,7 +62,7 @@ import vn.momo.kits.navigation.component.HeaderRight
|
|
|
62
62
|
import vn.momo.kits.navigation.component.HeaderType
|
|
63
63
|
import vn.momo.kits.navigation.component.InputSearchType
|
|
64
64
|
import vn.momo.kits.platform.BackHandler
|
|
65
|
-
import vn.momo.kits.platform.
|
|
65
|
+
import vn.momo.kits.platform.supportsImePadding
|
|
66
66
|
import vn.momo.kits.navigation.tracking.ScreenTracker
|
|
67
67
|
import vn.momo.kits.navigation.tracking.ScreenTrackingState
|
|
68
68
|
import kotlinx.coroutines.delay
|
|
@@ -150,6 +150,10 @@ internal fun StackScreen(
|
|
|
150
150
|
val footerHeightPx = remember { mutableIntStateOf(0) }
|
|
151
151
|
val headerRightWidthPx = remember { mutableIntStateOf(0) }
|
|
152
152
|
|
|
153
|
+
if (navigation.options.scrollData.scrollToTopCallback != null) {
|
|
154
|
+
RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
|
|
155
|
+
}
|
|
156
|
+
|
|
153
157
|
BackHandler(true) { navigator.onBackSafe() }
|
|
154
158
|
|
|
155
159
|
CompositionLocalProvider(
|
|
@@ -160,15 +164,16 @@ internal fun StackScreen(
|
|
|
160
164
|
LocalFooterHeightPx provides footerHeightPx,
|
|
161
165
|
LocalHeaderRightWidthPx provides headerRightWidthPx
|
|
162
166
|
) {
|
|
163
|
-
Box(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
Box(
|
|
168
|
+
Modifier
|
|
169
|
+
.fillMaxSize()
|
|
170
|
+
.background(options.backgroundColor ?: AppTheme.current.colors.background.default)
|
|
171
|
+
.conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
|
|
172
|
+
hideKeyboardOnTap()
|
|
173
|
+
}
|
|
174
|
+
.conditional(options.keyboardOptions.useAvoidKeyboard && supportsImePadding()) {
|
|
175
|
+
imePadding()
|
|
176
|
+
}
|
|
172
177
|
) {
|
|
173
178
|
Box(Modifier.zIndex(1f)) {
|
|
174
179
|
HeaderBackground()
|
|
@@ -178,7 +183,7 @@ internal fun StackScreen(
|
|
|
178
183
|
Header(onBackHandler)
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
Column
|
|
186
|
+
Column(Modifier.zIndex(6f)) {
|
|
182
187
|
SearchAnimated(isScrollInProgress = scrollInProcess)
|
|
183
188
|
}
|
|
184
189
|
|
|
@@ -190,7 +195,7 @@ internal fun StackScreen(
|
|
|
190
195
|
FooterContent()
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
Box(Modifier.zIndex(7f)){
|
|
198
|
+
Box(Modifier.zIndex(7f)) {
|
|
194
199
|
FloatingContent()
|
|
195
200
|
}
|
|
196
201
|
|
|
@@ -200,15 +205,15 @@ internal fun StackScreen(
|
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
@Composable
|
|
203
|
-
fun FloatingContent(){
|
|
208
|
+
fun FloatingContent() {
|
|
204
209
|
val options = LocalOptions.current
|
|
205
210
|
val density = LocalDensity.current
|
|
206
211
|
val footerHeightPx = LocalFooterHeightPx.current
|
|
207
212
|
val scrollState = LocalScrollState.current
|
|
208
213
|
|
|
209
214
|
val fabProps = options.floatingButtonProps ?: return
|
|
210
|
-
val bottomPadding = fabProps.bottom
|
|
211
|
-
|
|
215
|
+
val bottomPadding = fabProps.bottom
|
|
216
|
+
?: (Spacing.M + if (options.footerComponent != null) with(density) { footerHeightPx.intValue.toDp() } else 0.dp)
|
|
212
217
|
|
|
213
218
|
FloatingButton(
|
|
214
219
|
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
@@ -224,17 +229,19 @@ fun FloatingContent(){
|
|
|
224
229
|
}
|
|
225
230
|
|
|
226
231
|
@Composable
|
|
227
|
-
fun ColumnScope.MainContent(content: @Composable ()-> Unit){
|
|
232
|
+
fun ColumnScope.MainContent(content: @Composable () -> Unit) {
|
|
228
233
|
val options = LocalOptions.current
|
|
229
234
|
val inputSearchType = getInputSearchType(options)
|
|
230
235
|
val density = LocalDensity.current
|
|
231
236
|
val scrollState = LocalScrollState.current
|
|
232
237
|
|
|
233
|
-
Spacer(
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
Spacer(
|
|
239
|
+
Modifier.height(
|
|
240
|
+
if (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent))
|
|
241
|
+
AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp
|
|
242
|
+
)
|
|
236
243
|
)
|
|
237
|
-
if (inputSearchType == InputSearchType.Animated){
|
|
244
|
+
if (inputSearchType == InputSearchType.Animated) {
|
|
238
245
|
val scrollDp = with(density) { scrollState.value.toDp() }
|
|
239
246
|
|
|
240
247
|
val animatedTopPadding by animateDpAsState(
|
|
@@ -243,17 +250,18 @@ fun ColumnScope.MainContent(content: @Composable ()-> Unit){
|
|
|
243
250
|
)
|
|
244
251
|
Spacer(Modifier.height(animatedTopPadding))
|
|
245
252
|
}
|
|
246
|
-
Column
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
253
|
+
Column(
|
|
254
|
+
Modifier
|
|
255
|
+
.fillMaxWidth()
|
|
256
|
+
.weight(1f)
|
|
257
|
+
.conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
|
|
258
|
+
verticalScroll(scrollState)
|
|
259
|
+
}
|
|
252
260
|
) {
|
|
253
261
|
ScreenContent(content = content)
|
|
254
262
|
}
|
|
255
263
|
|
|
256
|
-
if (options.footerComponent != null){
|
|
264
|
+
if (options.footerComponent != null) {
|
|
257
265
|
val footerHeight = LocalFooterHeightPx.current
|
|
258
266
|
val footerHeightDp = with(density) { footerHeight.value.toDp() }
|
|
259
267
|
Spacer(Modifier.height(footerHeightDp))
|
|
@@ -261,17 +269,17 @@ fun ColumnScope.MainContent(content: @Composable ()-> Unit){
|
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
@Composable
|
|
264
|
-
fun ScreenContent(content: @Composable () -> Unit){
|
|
272
|
+
fun ScreenContent(content: @Composable () -> Unit) {
|
|
265
273
|
val scrollState = LocalScrollState.current
|
|
266
274
|
val options = LocalOptions.current
|
|
267
275
|
|
|
268
|
-
if (options.headerType is HeaderType.Animated){
|
|
276
|
+
if (options.headerType is HeaderType.Animated) {
|
|
269
277
|
val animatedHeader = options.headerType
|
|
270
278
|
Box {
|
|
271
|
-
Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)){
|
|
279
|
+
Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)) {
|
|
272
280
|
animatedHeader.composable.invoke(scrollState.value)
|
|
273
281
|
}
|
|
274
|
-
Box(Modifier.offset(x = 0.dp, y = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)){
|
|
282
|
+
Box(Modifier.offset(x = 0.dp, y = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)) {
|
|
275
283
|
content()
|
|
276
284
|
}
|
|
277
285
|
}
|
|
@@ -281,9 +289,9 @@ fun ScreenContent(content: @Composable () -> Unit){
|
|
|
281
289
|
}
|
|
282
290
|
|
|
283
291
|
@Composable
|
|
284
|
-
fun FooterContent(){
|
|
292
|
+
fun FooterContent() {
|
|
285
293
|
val options = LocalOptions.current
|
|
286
|
-
if (options.footerComponent != null){
|
|
294
|
+
if (options.footerComponent != null) {
|
|
287
295
|
val ime = WindowInsets.ime
|
|
288
296
|
val density = LocalDensity.current
|
|
289
297
|
val imeBottom = ime.getBottom(density)
|
|
@@ -295,11 +303,11 @@ fun FooterContent(){
|
|
|
295
303
|
}
|
|
296
304
|
|
|
297
305
|
@Composable
|
|
298
|
-
fun OverplayView(bottomTabIndex: Int, id: Int){
|
|
306
|
+
fun OverplayView(bottomTabIndex: Int, id: Int) {
|
|
299
307
|
val overplayType = OverplayComponentRegistry.getOverplayType()
|
|
300
308
|
|
|
301
309
|
if (overplayType != null) {
|
|
302
|
-
Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 8f).fillMaxSize()){
|
|
310
|
+
Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 8f).fillMaxSize()) {
|
|
303
311
|
if (bottomTabIndex != -1) return@Box
|
|
304
312
|
if (OverplayComponentRegistry.currentRootId() != id) return@Box
|
|
305
313
|
OverplayComponentRegistry.OverlayComponent()
|
|
@@ -322,22 +330,24 @@ fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
|
322
330
|
Box(Modifier.onGloballyPositioned {
|
|
323
331
|
if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
|
|
324
332
|
}) {
|
|
325
|
-
Box(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
+
Box(
|
|
334
|
+
Modifier
|
|
335
|
+
.fillMaxWidth()
|
|
336
|
+
.background(AppTheme.current.colors.background.surface)
|
|
337
|
+
.conditional(IsShowBaseLineDebug) {
|
|
338
|
+
border(1.dp, Colors.blue_03)
|
|
339
|
+
}
|
|
340
|
+
.padding(top = Spacing.S, start = Spacing.M, end = Spacing.M, bottom = bottomPadding + Spacing.S)
|
|
341
|
+
) {
|
|
333
342
|
footerComponent.invoke()
|
|
334
343
|
}
|
|
335
344
|
|
|
336
|
-
Box(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
345
|
+
Box(
|
|
346
|
+
modifier = Modifier
|
|
347
|
+
.fillMaxWidth()
|
|
348
|
+
.height(6.dp)
|
|
349
|
+
.offset(x = 0.dp, y = (-6).dp)
|
|
350
|
+
.background(shadowBrush)
|
|
341
351
|
)
|
|
342
352
|
}
|
|
343
353
|
}
|
|
@@ -347,6 +357,7 @@ data class InputSearchLayoutParams(
|
|
|
347
357
|
val startPadding: Dp,
|
|
348
358
|
val endPadding: Dp
|
|
349
359
|
)
|
|
360
|
+
|
|
350
361
|
@Composable
|
|
351
362
|
fun SearchAnimated(
|
|
352
363
|
isScrollInProgress: Boolean
|
|
@@ -461,6 +472,7 @@ internal fun isKeyboardVisible(): Boolean {
|
|
|
461
472
|
val density = LocalDensity.current
|
|
462
473
|
return ime.getBottom(density) > 0
|
|
463
474
|
}
|
|
475
|
+
|
|
464
476
|
private fun quantize(value: Int, stepPx: Int): Int {
|
|
465
477
|
if (stepPx <= 1) return value
|
|
466
478
|
return (value / stepPx) * stepPx
|
|
@@ -540,13 +552,15 @@ internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error(
|
|
|
540
552
|
|
|
541
553
|
val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
|
|
542
554
|
|
|
543
|
-
internal fun getInputSearchType(options: NavigationOptions): InputSearchType{
|
|
555
|
+
internal fun getInputSearchType(options: NavigationOptions): InputSearchType {
|
|
544
556
|
return when (val headerType = options.headerType) {
|
|
545
557
|
is HeaderType.DefaultOrExtended -> when {
|
|
546
558
|
headerType.inputSearchProps == null -> InputSearchType.None
|
|
547
559
|
headerType.useAnimated -> InputSearchType.Animated
|
|
548
560
|
else -> InputSearchType.Header
|
|
549
561
|
}
|
|
562
|
+
|
|
550
563
|
else -> InputSearchType.None
|
|
551
564
|
}
|
|
552
565
|
}
|
|
566
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package vn.momo.kits.platform
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.Image
|
|
4
|
+
import androidx.compose.foundation.background
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.runtime.Composable
|
|
8
|
+
import androidx.compose.runtime.getValue
|
|
9
|
+
import androidx.compose.ui.Modifier
|
|
10
|
+
import androidx.compose.ui.graphics.Color
|
|
11
|
+
import io.github.alexzhirkevich.compottie.Compottie
|
|
12
|
+
import io.github.alexzhirkevich.compottie.ExperimentalCompottieApi
|
|
13
|
+
import io.github.alexzhirkevich.compottie.LottieCompositionSpec
|
|
14
|
+
import io.github.alexzhirkevich.compottie.dynamic.rememberLottieDynamicProperties
|
|
15
|
+
import io.github.alexzhirkevich.compottie.rememberLottieComposition
|
|
16
|
+
import io.github.alexzhirkevich.compottie.rememberLottiePainter
|
|
17
|
+
import vn.momo.kits.utils.readJson
|
|
18
|
+
|
|
19
|
+
@OptIn(ExperimentalCompottieApi::class)
|
|
20
|
+
@Composable
|
|
21
|
+
internal fun ComposeLottieAnimation(
|
|
22
|
+
path: String,
|
|
23
|
+
tintColor: Color?,
|
|
24
|
+
bgColor: Color?,
|
|
25
|
+
modifier: Modifier,
|
|
26
|
+
) {
|
|
27
|
+
val json = readJson(path)
|
|
28
|
+
|
|
29
|
+
if (json.isEmpty()) {
|
|
30
|
+
Box(modifier.background(bgColor ?: Color.Transparent))
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
val composition by rememberLottieComposition(json) {
|
|
35
|
+
LottieCompositionSpec.JsonString(json)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Recolor only the fill/stroke color slots (matching the native lottie-ios per-keypath tint)
|
|
39
|
+
// instead of a global ColorFilter.tint, which would flatten a multicolor animation to a silhouette.
|
|
40
|
+
val dynamicProperties = if (tintColor != null) {
|
|
41
|
+
rememberLottieDynamicProperties(tintColor) {
|
|
42
|
+
shapeLayer("**") {
|
|
43
|
+
fill("**") { color { tintColor } }
|
|
44
|
+
stroke("**") { color { tintColor } }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Box(modifier.background(bgColor ?: Color.Transparent)) {
|
|
52
|
+
Image(
|
|
53
|
+
painter = rememberLottiePainter(
|
|
54
|
+
composition = composition,
|
|
55
|
+
iterations = Compottie.IterateForever,
|
|
56
|
+
dynamicProperties = dynamicProperties,
|
|
57
|
+
),
|
|
58
|
+
contentDescription = null,
|
|
59
|
+
modifier = Modifier.fillMaxSize(),
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -30,14 +30,35 @@ expect fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit
|
|
|
30
30
|
@Composable
|
|
31
31
|
expect fun getScreenHeight(): Dp
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
sealed interface OSVersion {
|
|
34
|
+
val value: Int
|
|
35
|
+
data class Android(val sdk: Int) : OSVersion { override val value: Int get() = sdk }
|
|
36
|
+
data class IOS(val major: Int) : OSVersion { override val value: Int get() = major }
|
|
34
37
|
|
|
38
|
+
operator fun compareTo(other: Int): Int = value.compareTo(other)
|
|
39
|
+
}
|
|
40
|
+
expect fun getOSVersion(): OSVersion
|
|
41
|
+
|
|
42
|
+
fun supportsImePadding(): Boolean = when (val v = getOSVersion()) {
|
|
43
|
+
is OSVersion.Android -> v.sdk > 29
|
|
44
|
+
is OSVersion.IOS -> true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param useCompose when `true`, renders via the pure-Compose Compottie engine instead of the
|
|
49
|
+
* platform-native engine (airbnb-lottie on Android, lottie-ios on iOS). Defaults to `false` for
|
|
50
|
+
* backward compatibility. Note: when `useCompose = true`, `tintColor` recolors only the fill/stroke
|
|
51
|
+
* color slots (matching the native lottie-ios per-keypath tint), so gradient and text-layer colors
|
|
52
|
+
* are left untinted; `placedAsOverlay` is a no-op.
|
|
53
|
+
*/
|
|
35
54
|
@Composable
|
|
36
55
|
expect fun LottieAnimation(
|
|
37
56
|
path: String,
|
|
38
57
|
tintColor: Color? = null,
|
|
39
58
|
bgColor: Color? = null,
|
|
40
|
-
|
|
59
|
+
placedAsOverlay: Boolean = false,
|
|
60
|
+
modifier: Modifier = Modifier,
|
|
61
|
+
useCompose: Boolean = false
|
|
41
62
|
)
|
|
42
63
|
|
|
43
64
|
expect fun NativePaint.setColor(
|