@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.
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.162.2-test.21-debug"
43
+ version = "0.162.2-test.22-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.162.2-beta.20'
3
+ spec.version = '0.162.2-lottie.2'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -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.LottieConstants
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
- iterations = LottieConstants.IterateForever,
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
- // navigationBar must collapse with the IME too, else it leaves a gap
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 + bottomTabBarInset() - keyboardSize.value).coerceAtLeast(0.dp)
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 = Compottie.IterateForever,
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(-1.0)
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
- play()
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
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.162.2-test.21
21
+ version=0.162.2-test.22
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.162.2-test.21-debug",
3
+ "version": "0.162.2-test.22-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},