@momo-kits/native-kits 0.162.2-test.21-debug → 0.162.2-test.22-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 +1 -1
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +32 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +5 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +4 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +23 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +14 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +26 -3
- package/gradle.properties +1 -1
- package/package.json +1 -1
package/compose/build.gradle.kts
CHANGED
package/compose/compose.podspec
CHANGED
|
@@ -8,7 +8,10 @@ import android.graphics.PorterDuffColorFilter
|
|
|
8
8
|
import android.os.Build
|
|
9
9
|
import androidx.compose.foundation.layout.Box
|
|
10
10
|
import androidx.compose.runtime.Composable
|
|
11
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
11
12
|
import androidx.compose.runtime.getValue
|
|
13
|
+
import androidx.compose.runtime.rememberUpdatedState
|
|
14
|
+
import androidx.compose.runtime.snapshotFlow
|
|
12
15
|
import androidx.compose.ui.Modifier
|
|
13
16
|
import androidx.compose.ui.graphics.Color
|
|
14
17
|
import androidx.compose.ui.graphics.NativePaint
|
|
@@ -21,7 +24,7 @@ import androidx.compose.ui.unit.dp
|
|
|
21
24
|
import com.airbnb.lottie.LottieProperty
|
|
22
25
|
import com.airbnb.lottie.compose.LottieAnimation
|
|
23
26
|
import com.airbnb.lottie.compose.LottieCompositionSpec
|
|
24
|
-
import com.airbnb.lottie.compose.
|
|
27
|
+
import com.airbnb.lottie.compose.animateLottieCompositionAsState
|
|
25
28
|
import com.airbnb.lottie.compose.rememberLottieComposition
|
|
26
29
|
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
|
|
27
30
|
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
|
|
@@ -78,11 +81,14 @@ actual fun LottieAnimation(
|
|
|
78
81
|
tintColor: Color?,
|
|
79
82
|
bgColor: Color?,
|
|
80
83
|
placedAsOverlay: Boolean,
|
|
84
|
+
iterations: Int,
|
|
85
|
+
isPlaying: Boolean,
|
|
86
|
+
onEvent: ((String) -> Unit)?,
|
|
81
87
|
modifier: Modifier,
|
|
82
88
|
useCompose: Boolean
|
|
83
89
|
) {
|
|
84
90
|
if (useCompose) {
|
|
85
|
-
ComposeLottieAnimation(path, tintColor, bgColor, modifier)
|
|
91
|
+
ComposeLottieAnimation(path, tintColor, bgColor, iterations, isPlaying, onEvent, modifier)
|
|
86
92
|
return
|
|
87
93
|
}
|
|
88
94
|
|
|
@@ -110,9 +116,32 @@ actual fun LottieAnimation(
|
|
|
110
116
|
)
|
|
111
117
|
)
|
|
112
118
|
|
|
119
|
+
val lottieState = animateLottieCompositionAsState(
|
|
120
|
+
composition = composition,
|
|
121
|
+
iterations = iterations,
|
|
122
|
+
isPlaying = isPlaying,
|
|
123
|
+
restartOnPlay = false,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
val currentOnEvent by rememberUpdatedState(onEvent)
|
|
127
|
+
|
|
128
|
+
LaunchedEffect(isPlaying) {
|
|
129
|
+
currentOnEvent?.invoke(if (isPlaying) LottieEvent.Start else LottieEvent.Pause)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Only fire finish once the animation has actually advanced (armed by a not-at-end frame) then
|
|
133
|
+
// reaches the end, so a transient isAtEnd at init can't produce a spurious callback.
|
|
134
|
+
LaunchedEffect(lottieState) {
|
|
135
|
+
var armed = false
|
|
136
|
+
snapshotFlow { lottieState.isAtEnd }.collect { atEnd ->
|
|
137
|
+
if (!atEnd) armed = true
|
|
138
|
+
else if (armed) { armed = false; currentOnEvent?.invoke(LottieEvent.Finish) }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
113
142
|
LottieAnimation(
|
|
114
143
|
composition = composition,
|
|
115
|
-
|
|
144
|
+
progress = { lottieState.progress },
|
|
116
145
|
dynamicProperties = if (tintColor != null) dynamicProperties else null,
|
|
117
146
|
modifier = modifier
|
|
118
147
|
)
|
|
@@ -33,6 +33,7 @@ class Navigation(
|
|
|
33
33
|
tintColor: Color? = null,
|
|
34
34
|
footerComponent: (@Composable () -> Unit)? = null,
|
|
35
35
|
floatingButtonProps: FloatingButtonProps? = null,
|
|
36
|
+
applyContentBottomPadding: Boolean? = null,
|
|
36
37
|
keyboardOptions: KeyboardOptions? = null
|
|
37
38
|
) {
|
|
38
39
|
updateOptions(
|
|
@@ -49,6 +50,7 @@ class Navigation(
|
|
|
49
50
|
tintColor = tintColor ?: options.tintColor,
|
|
50
51
|
footerComponent = footerComponent ?: options.footerComponent,
|
|
51
52
|
floatingButtonProps = floatingButtonProps ?: options.floatingButtonProps,
|
|
53
|
+
applyContentBottomPadding = applyContentBottomPadding ?: options.applyContentBottomPadding,
|
|
52
54
|
keyboardOptions = keyboardOptions ?: options.keyboardOptions,
|
|
53
55
|
)
|
|
54
56
|
)
|
|
@@ -84,6 +86,9 @@ data class NavigationOptions(
|
|
|
84
86
|
val tintColor: Color? = null,
|
|
85
87
|
val footerComponent: @Composable (() -> Unit)? = null,
|
|
86
88
|
val floatingButtonProps: FloatingButtonProps? = null,
|
|
89
|
+
// Reserve the bottom safe-area (navigation bar) padding on the content when there is no footer.
|
|
90
|
+
// Set false for screens that draw edge-to-edge or handle their own bottom inset.
|
|
91
|
+
val applyContentBottomPadding: Boolean = true,
|
|
87
92
|
val keyboardOptions: KeyboardOptions = KeyboardOptions()
|
|
88
93
|
) {
|
|
89
94
|
internal fun hasFooter() : Boolean = footerComponent != null
|
|
@@ -208,7 +208,7 @@ internal fun StackScreen(
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
Box(Modifier.zIndex(6f)) {
|
|
211
|
-
FloatingContent()
|
|
211
|
+
FloatingContent(isBottomTabChild = isBottomTabChild)
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
OverplayView(isBottomTabChild = isBottomTabChild, id = id)
|
|
@@ -218,15 +218,13 @@ internal fun StackScreen(
|
|
|
218
218
|
|
|
219
219
|
@Suppress("FrequentlyChangingValue")
|
|
220
220
|
@Composable
|
|
221
|
-
internal fun FloatingContent() {
|
|
221
|
+
internal fun FloatingContent(isBottomTabChild: Boolean) {
|
|
222
222
|
val options = LocalOptions.current
|
|
223
223
|
val density = LocalDensity.current
|
|
224
224
|
val footerHeightPx = LocalFooterHeightPx.current
|
|
225
225
|
val scrollState = LocalScrollState.current
|
|
226
226
|
val navigationBar = AppNavigationBar.current
|
|
227
227
|
|
|
228
|
-
val isBottomTabChild = LocalNavigation.current.bottomTabIndex != -1
|
|
229
|
-
|
|
230
228
|
val fabProps = options.floatingButtonProps ?: return
|
|
231
229
|
val bottomPadding = fabProps.bottom
|
|
232
230
|
?: (Spacing.M + when {
|
|
@@ -281,8 +279,7 @@ internal fun ColumnScope.MainContent(content: @Composable () -> Unit, isBottomTa
|
|
|
281
279
|
.padding(
|
|
282
280
|
bottom = when {
|
|
283
281
|
options.hasFooter() || isBottomTabRoot -> 0.dp
|
|
284
|
-
|
|
285
|
-
// between the content and the keyboard (imePadding already lifted it).
|
|
282
|
+
!options.applyContentBottomPadding -> 0.dp
|
|
286
283
|
isBottomTabChild -> bottomTabBarInset() + (navigationBar - keyboardSizeState().value).coerceAtLeast(0.dp)
|
|
287
284
|
else -> navigationBar
|
|
288
285
|
}
|
|
@@ -318,7 +315,7 @@ internal fun FooterContent() {
|
|
|
318
315
|
val options = LocalOptions.current
|
|
319
316
|
if (options.hasFooter()) {
|
|
320
317
|
val keyboardSize = keyboardSizeState()
|
|
321
|
-
val bottomPadding = (AppNavigationBar.current
|
|
318
|
+
val bottomPadding = (AppNavigationBar.current - keyboardSize.value).coerceAtLeast(0.dp)
|
|
322
319
|
Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
|
|
323
320
|
}
|
|
324
321
|
}
|
|
@@ -5,10 +5,12 @@ import androidx.compose.foundation.background
|
|
|
5
5
|
import androidx.compose.foundation.layout.Box
|
|
6
6
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
7
|
import androidx.compose.runtime.Composable
|
|
8
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
8
9
|
import androidx.compose.runtime.getValue
|
|
10
|
+
import androidx.compose.runtime.rememberUpdatedState
|
|
11
|
+
import androidx.compose.runtime.snapshotFlow
|
|
9
12
|
import androidx.compose.ui.Modifier
|
|
10
13
|
import androidx.compose.ui.graphics.Color
|
|
11
|
-
import io.github.alexzhirkevich.compottie.Compottie
|
|
12
14
|
import io.github.alexzhirkevich.compottie.ExperimentalCompottieApi
|
|
13
15
|
import io.github.alexzhirkevich.compottie.LottieCompositionSpec
|
|
14
16
|
import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState
|
|
@@ -23,6 +25,9 @@ internal fun ComposeLottieAnimation(
|
|
|
23
25
|
path: String,
|
|
24
26
|
tintColor: Color?,
|
|
25
27
|
bgColor: Color?,
|
|
28
|
+
iterations: Int,
|
|
29
|
+
isPlaying: Boolean,
|
|
30
|
+
onEvent: ((String) -> Unit)?,
|
|
26
31
|
modifier: Modifier,
|
|
27
32
|
) {
|
|
28
33
|
val json = readJson(path)
|
|
@@ -49,9 +54,25 @@ internal fun ComposeLottieAnimation(
|
|
|
49
54
|
|
|
50
55
|
val lottieState = animateLottieCompositionAsState(
|
|
51
56
|
composition = composition,
|
|
52
|
-
iterations =
|
|
57
|
+
iterations = iterations,
|
|
58
|
+
isPlaying = isPlaying,
|
|
59
|
+
restartOnPlay = false,
|
|
53
60
|
)
|
|
54
61
|
|
|
62
|
+
val currentOnEvent by rememberUpdatedState(onEvent)
|
|
63
|
+
|
|
64
|
+
LaunchedEffect(isPlaying) {
|
|
65
|
+
currentOnEvent?.invoke(if (isPlaying) LottieEvent.Start else LottieEvent.Pause)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
LaunchedEffect(lottieState) {
|
|
69
|
+
var armed = false
|
|
70
|
+
snapshotFlow { lottieState.isAtEnd }.collect { atEnd ->
|
|
71
|
+
if (!atEnd) armed = true
|
|
72
|
+
else if (armed) { armed = false; currentOnEvent?.invoke(LottieEvent.Finish) }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
55
76
|
Box(modifier.background(bgColor ?: Color.Transparent)) {
|
|
56
77
|
Image(
|
|
57
78
|
painter = rememberLottiePainter(
|
|
@@ -43,12 +43,26 @@ fun supportsImePadding(): Boolean = when (val v = getOSVersion()) {
|
|
|
43
43
|
is OSVersion.Android -> v.sdk > 29
|
|
44
44
|
is OSVersion.IOS -> true
|
|
45
45
|
}
|
|
46
|
+
/** Sentinel for [LottieAnimation.iterations] meaning "loop forever" (kept as the default). */
|
|
47
|
+
const val LottieIterateForever: Int = Int.MAX_VALUE
|
|
48
|
+
|
|
49
|
+
/** Event names emitted by [LottieAnimation.onEvent]. */
|
|
50
|
+
object LottieEvent {
|
|
51
|
+
const val Start = "start" // playback (re)started — isPlaying became true
|
|
52
|
+
const val Pause = "pause" // playback paused — isPlaying became false
|
|
53
|
+
const val Finish = "finish" // completed all iterations; never for LottieIterateForever
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
@Composable
|
|
47
57
|
expect fun LottieAnimation(
|
|
48
58
|
path: String,
|
|
49
59
|
tintColor: Color? = null,
|
|
50
60
|
bgColor: Color? = null,
|
|
51
61
|
placedAsOverlay: Boolean = false,
|
|
62
|
+
iterations: Int = LottieIterateForever,
|
|
63
|
+
isPlaying: Boolean = true,
|
|
64
|
+
// Single listener for playback events; receives a LottieEvent.* string.
|
|
65
|
+
onEvent: ((String) -> Unit)? = null,
|
|
52
66
|
modifier: Modifier = Modifier,
|
|
53
67
|
useCompose: Boolean = false
|
|
54
68
|
)
|
|
@@ -11,6 +11,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|
|
11
11
|
import androidx.compose.runtime.getValue
|
|
12
12
|
import androidx.compose.runtime.mutableStateOf
|
|
13
13
|
import androidx.compose.runtime.remember
|
|
14
|
+
import androidx.compose.runtime.rememberUpdatedState
|
|
14
15
|
import androidx.compose.runtime.setValue
|
|
15
16
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
16
17
|
import androidx.compose.ui.InternalComposeUiApi
|
|
@@ -96,20 +97,41 @@ actual fun LottieAnimation(
|
|
|
96
97
|
tintColor: Color?,
|
|
97
98
|
bgColor: Color?,
|
|
98
99
|
placedAsOverlay: Boolean,
|
|
100
|
+
iterations: Int,
|
|
101
|
+
isPlaying: Boolean,
|
|
102
|
+
onEvent: ((String) -> Unit)?,
|
|
99
103
|
modifier: Modifier,
|
|
100
104
|
useCompose: Boolean
|
|
101
105
|
) {
|
|
102
106
|
if (useCompose) {
|
|
103
|
-
ComposeLottieAnimation(path, tintColor, bgColor, modifier)
|
|
107
|
+
ComposeLottieAnimation(path, tintColor, bgColor, iterations, isPlaying, onEvent, modifier)
|
|
104
108
|
return
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
val loopCount = if (iterations == LottieIterateForever) -1.0 else iterations.toDouble()
|
|
112
|
+
|
|
107
113
|
var animation by remember { mutableStateOf<CompatibleAnimation?>(null) }
|
|
114
|
+
val animationView = remember { mutableStateOf<CompatibleAnimationView?>(null) }
|
|
108
115
|
|
|
109
116
|
LaunchedEffect(Unit) {
|
|
110
117
|
animation = CompatibleAnimation(name = "compose-resources/composeResources/vn.momo.compose.resources/".plus(path), subdirectory = null, NSBundle.mainBundle)
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
val currentOnEvent by rememberUpdatedState(onEvent)
|
|
121
|
+
|
|
122
|
+
LaunchedEffect(isPlaying) {
|
|
123
|
+
currentOnEvent?.invoke(if (isPlaying) LottieEvent.Start else LottieEvent.Pause)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
LaunchedEffect(animationView.value, isPlaying) {
|
|
127
|
+
val view = animationView.value ?: return@LaunchedEffect
|
|
128
|
+
if (isPlaying) {
|
|
129
|
+
view.playWithCompletion(completion = { finished -> if (finished) currentOnEvent?.invoke(LottieEvent.Finish) })
|
|
130
|
+
} else {
|
|
131
|
+
view.pause()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
when (val value = animation) {
|
|
114
136
|
null -> {
|
|
115
137
|
Box(modifier)
|
|
@@ -126,7 +148,7 @@ actual fun LottieAnimation(
|
|
|
126
148
|
|
|
127
149
|
setBackgroundColor(bgColor?.toUIColor() ?: UIColor.clearColor)
|
|
128
150
|
|
|
129
|
-
setLoopAnimationCount(
|
|
151
|
+
setLoopAnimationCount(loopCount)
|
|
130
152
|
setAnimationSpeed(1.0)
|
|
131
153
|
|
|
132
154
|
if (tintColor != null) {
|
|
@@ -140,12 +162,13 @@ actual fun LottieAnimation(
|
|
|
140
162
|
setColorValue(uiColor, CompatibleAnimationKeypath("**.Stroke.Color"))
|
|
141
163
|
}
|
|
142
164
|
|
|
143
|
-
|
|
165
|
+
animationView.value = this
|
|
144
166
|
}
|
|
145
167
|
},
|
|
146
168
|
|
|
147
169
|
update = { view ->
|
|
148
170
|
view.setBackgroundColor(bgColor?.toUIColor() ?: UIColor.clearColor)
|
|
171
|
+
view.setLoopAnimationCount(loopCount)
|
|
149
172
|
|
|
150
173
|
if (tintColor != null) {
|
|
151
174
|
val uiColor = tintColor.toUIColor()
|
package/gradle.properties
CHANGED