@momo-kits/native-kits 0.162.22-debug → 0.162.23-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.22-debug"
43
+ version = "0.162.23-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-sp.9-debug'
3
+ spec.version = '0.162.2-test.22'
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,30 @@ 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
+ LaunchedEffect(lottieState) {
133
+ var armed = false
134
+ snapshotFlow { lottieState.isAtEnd }.collect { atEnd ->
135
+ if (!atEnd) armed = true
136
+ else if (armed) { armed = false; currentOnEvent?.invoke(LottieEvent.Finish) }
137
+ }
138
+ }
139
+
113
140
  LottieAnimation(
114
141
  composition = composition,
115
- iterations = LottieConstants.IterateForever,
142
+ progress = { lottieState.progress },
116
143
  dynamicProperties = if (tintColor != null) dynamicProperties else null,
117
144
  modifier = modifier
118
145
  )
@@ -1,2 +1,65 @@
1
1
  package vn.momo.kits.layout
2
2
 
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Arrangement
5
+ import androidx.compose.foundation.layout.Box
6
+ import androidx.compose.foundation.layout.BoxWithConstraints
7
+ import androidx.compose.foundation.layout.ExperimentalLayoutApi
8
+ import androidx.compose.foundation.layout.FlowRow
9
+ import androidx.compose.foundation.layout.fillMaxWidth
10
+ import androidx.compose.foundation.layout.padding
11
+ import androidx.compose.foundation.shape.RoundedCornerShape
12
+ import androidx.compose.runtime.Composable
13
+ import androidx.compose.runtime.CompositionLocalProvider
14
+ import androidx.compose.ui.Modifier
15
+ import androidx.compose.ui.draw.clip
16
+ import androidx.compose.ui.draw.shadow
17
+ import androidx.compose.ui.unit.dp
18
+ import vn.momo.kits.components.Image
19
+ import vn.momo.kits.const.AppTheme
20
+ import vn.momo.kits.const.Radius
21
+
22
+ /**
23
+ * A white rounded surface card that lays its [Item]s on a 12-column grid
24
+ * (React kit foundations/Layout/Card). 8dp gutter, 12dp padding, 12dp
25
+ * horizontal margin, Radius.M corners, background = theme surface, optional
26
+ * shadow or [backgroundImage]. Items flow left-to-right and wrap.
27
+ */
28
+ @OptIn(ExperimentalLayoutApi::class)
29
+ @Composable
30
+ fun Card(
31
+ useShadow: Boolean = false,
32
+ backgroundImage: String? = null,
33
+ modifier: Modifier = Modifier,
34
+ content: @Composable () -> Unit,
35
+ ) {
36
+ val columns = 12
37
+ val gutter = 8.dp
38
+ val shape = RoundedCornerShape(Radius.M)
39
+ var surface = modifier.fillMaxWidth().padding(horizontal = 12.dp)
40
+ if (useShadow) surface = surface.shadow(4.dp, shape)
41
+ surface = surface.clip(shape)
42
+ // The React kit drops the surface colour when a backgroundImage is supplied
43
+ // (`!!backgroundImage && { backgroundColor: undefined }`).
44
+ if (backgroundImage == null) surface = surface.background(AppTheme.current.colors.background.surface)
45
+ Box(modifier = surface) {
46
+ // React renders this as an absolutely-filled child BEHIND the content
47
+ // (styles.imageBackground: position absolute, inset 0) — matchParentSize
48
+ // fills the card without contributing to its measured size.
49
+ if (backgroundImage != null) {
50
+ Image(source = backgroundImage, modifier = Modifier.matchParentSize())
51
+ }
52
+ BoxWithConstraints(modifier = Modifier.fillMaxWidth().padding(12.dp)) {
53
+ // `- 1.dp / columns` mirrors the React kit's `- 1 / numberOfColumns`
54
+ // nudge: it keeps a full 12-span Item ~1dp narrower than the content
55
+ // box so sub-pixel rounding can never overflow and wrap the FlowRow.
56
+ val sizePerSpan = (maxWidth - gutter * (columns - 1)) / columns - 1.dp / columns
57
+ CompositionLocalProvider(LocalGridContext provides GridContext(columns, gutter, sizePerSpan)) {
58
+ FlowRow(
59
+ horizontalArrangement = Arrangement.spacedBy(gutter),
60
+ verticalArrangement = Arrangement.spacedBy(gutter),
61
+ ) { content() }
62
+ }
63
+ }
64
+ }
65
+ }
@@ -4,32 +4,45 @@ import androidx.compose.foundation.layout.Box
4
4
  import androidx.compose.foundation.layout.height
5
5
  import androidx.compose.foundation.layout.width
6
6
  import androidx.compose.runtime.Composable
7
+ import androidx.compose.runtime.compositionLocalOf
7
8
  import androidx.compose.ui.Modifier
8
9
  import androidx.compose.ui.unit.Dp
10
+ import androidx.compose.ui.unit.dp
9
11
 
12
+ /**
13
+ * Grid context supplied by [Section] / [Card]: the per-span cell size + gutter,
14
+ * so an [Item] can resolve its pixel width from a 1-12 `widthSpan`. Mirrors the
15
+ * React kit's GridContext (foundations/Layout/GridSystem).
16
+ */
10
17
  data class GridContext(
11
- val numberOfColumns: Number,
12
- val gutterSize: Number,
13
- val sizePerSpan: Number,
14
- val getSizeSpan: (Number) -> Number
15
- )
18
+ val numberOfColumns: Int = 12,
19
+ val gutter: Dp = 12.dp,
20
+ val sizePerSpan: Dp = 0.dp,
21
+ ) {
22
+ /** Width covering `span` columns + the (span-1) gutters between them. */
23
+ fun sizeForSpan(span: Int): Dp = sizePerSpan * span + gutter * (span - 1)
24
+ }
25
+
26
+ /** Provided by Section/Card; null when an Item is rendered outside a grid. */
27
+ val LocalGridContext = compositionLocalOf<GridContext?> { null }
16
28
 
29
+ /**
30
+ * A grid cell. `widthSpan` (1-12) sets the width as a fraction of the parent
31
+ * Section/Card; the parent's flex-wrap flows the cells into rows. Rendered
32
+ * outside a grid it just wraps its content at its natural size.
33
+ */
17
34
  @Composable
18
35
  fun Item(
19
- heightSpan: Number = 1,
20
- widthSpan: Number = 1,
21
- content: @Composable () -> Unit,
36
+ widthSpan: Int = 12,
37
+ heightSpan: Int = 0,
22
38
  modifier: Modifier = Modifier,
23
- grid: GridContext
39
+ content: @Composable () -> Unit,
24
40
  ) {
25
-
26
- val widthItem = grid.getSizeSpan(widthSpan)
27
- val heightItem = grid.getSizeSpan(heightSpan)
28
-
29
- Box(
30
- modifier = modifier.width(width = Dp(widthItem.toFloat()))
31
- .height(height = Dp(heightItem.toFloat()))
32
- ) {
33
- content()
41
+ val grid = LocalGridContext.current
42
+ var m = modifier
43
+ if (grid != null) {
44
+ m = m.width(grid.sizeForSpan(widthSpan))
45
+ if (heightSpan > 0) m = m.height(grid.sizeForSpan(heightSpan))
34
46
  }
35
- }
47
+ Box(modifier = m) { content() }
48
+ }
@@ -1,2 +1,51 @@
1
1
  package vn.momo.kits.layout
2
2
 
3
+ import androidx.compose.foundation.layout.Arrangement
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.BoxWithConstraints
6
+ import androidx.compose.foundation.layout.ExperimentalLayoutApi
7
+ import androidx.compose.foundation.layout.FlowRow
8
+ import androidx.compose.foundation.layout.fillMaxWidth
9
+ import androidx.compose.foundation.layout.padding
10
+ import androidx.compose.runtime.Composable
11
+ import androidx.compose.runtime.CompositionLocalProvider
12
+ import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.unit.dp
14
+ import vn.momo.kits.components.Image
15
+
16
+ /**
17
+ * A transparent 12-column grid row that wraps its [Item]s (React kit
18
+ * foundations/Layout/Section). 12dp gutter; 12dp horizontal margin when
19
+ * `useMargin`; optional [backgroundImage]. Items flow left-to-right and wrap.
20
+ */
21
+ @OptIn(ExperimentalLayoutApi::class)
22
+ @Composable
23
+ fun Section(
24
+ useMargin: Boolean = true,
25
+ backgroundImage: String? = null,
26
+ modifier: Modifier = Modifier,
27
+ content: @Composable () -> Unit,
28
+ ) {
29
+ val columns = 12
30
+ val gutter = 12.dp
31
+ val margin = if (useMargin) 12.dp else 0.dp
32
+ Box(modifier = modifier.fillMaxWidth().padding(horizontal = margin)) {
33
+ // React renders this as an absolutely-filled child behind the content
34
+ // (styles.imageBackground: position absolute, inset 0).
35
+ if (backgroundImage != null) {
36
+ Image(source = backgroundImage, modifier = Modifier.matchParentSize())
37
+ }
38
+ BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
39
+ // `- 1.dp / columns` mirrors the React kit's `- 1 / numberOfColumns`
40
+ // nudge — keeps a full 12-span Item ~1dp inside the content box so
41
+ // sub-pixel rounding can never overflow and wrap the FlowRow.
42
+ val sizePerSpan = (maxWidth - gutter * (columns - 1)) / columns - 1.dp / columns
43
+ CompositionLocalProvider(LocalGridContext provides GridContext(columns, gutter, sizePerSpan)) {
44
+ FlowRow(
45
+ horizontalArrangement = Arrangement.spacedBy(gutter),
46
+ verticalArrangement = Arrangement.spacedBy(gutter),
47
+ ) { content() }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -117,7 +117,7 @@ internal fun BottomSheet(
117
117
  easing = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f)
118
118
  )
119
119
  )
120
- navigator.pop()
120
+ OverplayComponentRegistry.hardClearAfterDismiss()
121
121
  }
122
122
  }
123
123
 
@@ -81,7 +81,7 @@ internal fun ModalScreen(
81
81
  targetValue = 0.8f,
82
82
  animationSpec = tween(durationMillis = 200)
83
83
  )
84
- navigator.pop()
84
+ OverplayComponentRegistry.hardClearAfterDismiss()
85
85
  }
86
86
  }
87
87
 
@@ -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,23 @@ fun supportsImePadding(): Boolean = when (val v = getOSVersion()) {
43
43
  is OSVersion.Android -> v.sdk > 29
44
44
  is OSVersion.IOS -> true
45
45
  }
46
+ const val LottieIterateForever: Int = Int.MAX_VALUE
47
+
48
+ object LottieEvent {
49
+ const val Start = "start"
50
+ const val Pause = "pause"
51
+ const val Finish = "finish"
52
+ }
53
+
46
54
  @Composable
47
55
  expect fun LottieAnimation(
48
56
  path: String,
49
57
  tintColor: Color? = null,
50
58
  bgColor: Color? = null,
51
59
  placedAsOverlay: Boolean = false,
60
+ iterations: Int = LottieIterateForever,
61
+ isPlaying: Boolean = true,
62
+ onEvent: ((String) -> Unit)? = null,
52
63
  modifier: Modifier = Modifier,
53
64
  useCompose: Boolean = false
54
65
  )
@@ -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.22
21
+ version=0.162.23
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.22-debug",
3
+ "version": "0.162.23-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},
package/publish.sh CHANGED
@@ -301,6 +301,9 @@ phase_publish_maven() {
301
301
 
302
302
  # Temporarily remove composeResources and compose { resources { ... } } block
303
303
  echo "📦 Backing up sample/shared composeResources and build.gradle.kts..."
304
+ # mv into an existing dir nests instead of renaming, so a backup left behind by a
305
+ # crashed run must be cleared first or the restore below puts back the wrong tree.
306
+ rm -rf sample/shared/src/commonMain/composeResources.backup
304
307
  if [ -d "sample/shared/src/commonMain/composeResources" ]; then
305
308
  mv sample/shared/src/commonMain/composeResources sample/shared/src/commonMain/composeResources.backup
306
309
  fi