@momo-kits/native-kits 0.151.2-test.11 → 0.151.2-test.13

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.
@@ -0,0 +1,54 @@
1
+ Pod::Spec.new do |spec|
2
+ spec.name = 'MoMoComposeKits'
3
+ spec.version = '0.0.1'
4
+ spec.homepage = 'MoMoComposeKits'
5
+ spec.source = { :http=> ''}
6
+ spec.authors = 'M_SERVICE'
7
+ spec.license = ''
8
+ spec.summary = 'MoMoComposeKits'
9
+ spec.vendored_frameworks = 'build/cocoapods/framework/compose.framework'
10
+ spec.libraries = 'c++'
11
+ spec.ios.deployment_target = '13.0'
12
+ spec.dependency 'lottie-ios', '4.4.3'
13
+
14
+ if !Dir.exist?('build/cocoapods/framework/compose.framework') || Dir.empty?('build/cocoapods/framework/compose.framework')
15
+ raise "
16
+
17
+ Kotlin framework 'compose' doesn't exist yet, so a proper Xcode project can't be generated.
18
+ 'pod install' should be executed after running ':generateDummyFramework' Gradle task:
19
+
20
+ ./gradlew :compose:generateDummyFramework
21
+
22
+ Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
23
+ end
24
+
25
+ spec.xcconfig = {
26
+ 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
27
+ }
28
+
29
+ spec.pod_target_xcconfig = {
30
+ 'KOTLIN_PROJECT_PATH' => ':compose',
31
+ 'PRODUCT_MODULE_NAME' => 'compose',
32
+ }
33
+
34
+ spec.script_phases = [
35
+ {
36
+ :name => 'Build MoMoComposeKits',
37
+ :execution_position => :before_compile,
38
+ :shell_path => '/bin/sh',
39
+ :script => <<-SCRIPT
40
+ if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
41
+ echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
42
+ exit 0
43
+ fi
44
+ set -ev
45
+ REPO_ROOT="$PODS_TARGET_SRCROOT"
46
+ "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
47
+ -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
48
+ -Pkotlin.native.cocoapods.archs="$ARCHS" \
49
+ -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
50
+ SCRIPT
51
+ }
52
+ ]
53
+ spec.resources = ['build/compose/cocoapods/compose-resources']
54
+ end
@@ -3,13 +3,23 @@ plugins {
3
3
  alias(libs.plugins.jetbrains.kotlin.multiplatform)
4
4
  alias(libs.plugins.compose)
5
5
  alias(libs.plugins.kotlin.compose.compiler)
6
+ id(libs.plugins.jetbrains.kotlin.cocoapods.get().pluginId)
6
7
  kotlin("plugin.serialization")
7
8
  id("maven-publish")
8
9
  }
10
+ fun getVersionFromPackageJson(): String {
11
+ val packageJsonFile = file("../package.json")
12
+ val packageJsonText = packageJsonFile.readText()
13
+ val versionRegex = "\"version\"\\s*:\\s*\"([^\"]*)\"".toRegex()
14
+ val matchResult = versionRegex.find(packageJsonText)
15
+ return matchResult?.groupValues?.get(1) ?: throw IllegalStateException("Version not found in package.json")
16
+ }
9
17
 
18
+ val libName = "MoMoComposeKits"
10
19
  val libGroup = "vn.momo.kits"
11
20
  val libVersion = "0.0.1"
12
21
 
22
+ version = getVersionFromPackageJson()
13
23
  kotlin {
14
24
  androidTarget {
15
25
  publishLibraryVariants("release")
@@ -26,11 +36,30 @@ kotlin {
26
36
 
27
37
  iosTargets.forEach { iosTarget ->
28
38
  iosTarget.binaries.framework {
29
- baseName = "lib"
39
+ baseName = libName
30
40
  isStatic = true
31
41
  }
32
42
  }
33
43
 
44
+ cocoapods {
45
+ name = libName
46
+ summary = libName
47
+ homepage = libName
48
+ authors = "M_SERVICE"
49
+ ios.deploymentTarget = "13.0"
50
+
51
+ framework {
52
+ isStatic = true
53
+ }
54
+
55
+ pod("lottie-ios") {
56
+ moduleName = "Lottie"
57
+ version = "4.4.3"
58
+ extraOpts += listOf("-compiler-option", "-fmodules")
59
+ }
60
+ }
61
+
62
+
34
63
  sourceSets {
35
64
  val commonMain by getting {
36
65
  dependencies {
@@ -47,8 +76,6 @@ kotlin {
47
76
  implementation(libs.coil.multiplatform.network.ktor)
48
77
  implementation(libs.jetbrains.serialization.json)
49
78
  implementation(libs.kotlinx.datetime)
50
- implementation(libs.compottie)
51
- implementation(libs.compottie.res)
52
79
  api(project(":NativeMaxApi"))
53
80
  }
54
81
  }
@@ -57,6 +84,7 @@ kotlin {
57
84
  implementation(libs.ktor.client.okhttp)
58
85
  implementation(libs.jetbrains.coroutines.android)
59
86
  api("androidx.activity:activity-compose:1.8.2")
87
+ implementation(libs.airbnb.lottie)
60
88
  }
61
89
  }
62
90
  if (isDebug) {
@@ -3,16 +3,28 @@ package vn.momo.kits.platform
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.res.Resources
5
5
  import android.graphics.BlurMaskFilter
6
+ import android.graphics.PorterDuff
7
+ import android.graphics.PorterDuffColorFilter
6
8
  import android.os.Build
7
9
  import androidx.compose.runtime.Composable
10
+ import androidx.compose.runtime.getValue
11
+ import androidx.compose.ui.Modifier
12
+ import androidx.compose.ui.graphics.Color
8
13
  import androidx.compose.ui.graphics.NativePaint
14
+ import androidx.compose.ui.graphics.toArgb
9
15
  import androidx.compose.ui.platform.LocalConfiguration
10
- import androidx.compose.ui.platform.LocalView
11
16
  import androidx.compose.ui.unit.Dp
12
17
  import androidx.compose.ui.unit.dp
13
- import androidx.compose.ui.window.DialogWindowProvider
18
+ import com.airbnb.lottie.LottieProperty
19
+ import com.airbnb.lottie.compose.LottieAnimation
20
+ import com.airbnb.lottie.compose.LottieCompositionSpec
21
+ import com.airbnb.lottie.compose.LottieConstants
22
+ import com.airbnb.lottie.compose.rememberLottieComposition
23
+ import com.airbnb.lottie.compose.rememberLottieDynamicProperties
24
+ import com.airbnb.lottie.compose.rememberLottieDynamicProperty
14
25
  import vn.momo.kits.const.AppNavigationBar
15
26
  import vn.momo.kits.const.AppStatusBar
27
+ import vn.momo.kits.utils.readJson
16
28
  import androidx.activity.compose.BackHandler as AndroidBackHandler
17
29
 
18
30
  actual fun getPlatformName(): String = "Android"
@@ -50,4 +62,36 @@ actual fun getScreenHeight(): Dp {
50
62
  return getScreenDimensions().height.dp + if (getAndroidBuildVersion() >= 35) 0.dp else AppStatusBar.current + AppNavigationBar.current
51
63
  }
52
64
 
53
- actual fun getAndroidBuildVersion(): Int = Build.VERSION.SDK_INT
65
+ actual fun getAndroidBuildVersion(): Int = Build.VERSION.SDK_INT
66
+
67
+ @Composable
68
+ actual fun LottieAnimation(
69
+ path: String,
70
+ tintColor: Color?,
71
+ bgColor: Color?,
72
+ modifier: Modifier
73
+ ) {
74
+ val composition by rememberLottieComposition(
75
+ LottieCompositionSpec.JsonString(readJson(path))
76
+ )
77
+
78
+ val colorFilter = PorterDuffColorFilter(
79
+ tintColor?.toArgb() ?: Color.White.toArgb(),
80
+ PorterDuff.Mode.SRC_ATOP
81
+ )
82
+
83
+ val dynamicProperties = rememberLottieDynamicProperties(
84
+ rememberLottieDynamicProperty(
85
+ property = LottieProperty.COLOR_FILTER,
86
+ value = colorFilter,
87
+ keyPath = arrayOf("**")
88
+ )
89
+ )
90
+
91
+ LottieAnimation(
92
+ composition = composition,
93
+ iterations = LottieConstants.IterateForever,
94
+ dynamicProperties = if (tintColor != null ) dynamicProperties else null,
95
+ modifier = modifier
96
+ )
97
+ }
@@ -12,10 +12,11 @@ import androidx.compose.foundation.layout.padding
12
12
  import androidx.compose.foundation.layout.size
13
13
  import androidx.compose.foundation.shape.RoundedCornerShape
14
14
  import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.CompositionLocalProvider
15
16
  import androidx.compose.runtime.remember
17
+ import androidx.compose.runtime.staticCompositionLocalOf
16
18
  import androidx.compose.ui.Alignment
17
19
  import androidx.compose.ui.Modifier
18
- import androidx.compose.ui.draw.alpha
19
20
  import androidx.compose.ui.draw.clip
20
21
  import androidx.compose.ui.graphics.Color
21
22
  import androidx.compose.ui.text.TextStyle
@@ -28,6 +29,7 @@ import vn.momo.kits.const.Radius
28
29
  import vn.momo.kits.const.Spacing
29
30
  import vn.momo.kits.const.Typography
30
31
  import vn.momo.kits.modifier.activeOpacityClickable
32
+ import vn.momo.kits.platform.LottieAnimation
31
33
 
32
34
  enum class ButtonType {
33
35
  PRIMARY,
@@ -105,8 +107,9 @@ fun getIconSpace(size: Size): Dp {
105
107
  }
106
108
 
107
109
  @Composable
108
- fun getTextColor(type: ButtonType): Color {
110
+ fun getTextColor(loading: Boolean, type: ButtonType): Color {
109
111
  val theme = AppTheme.current
112
+
110
113
  return remember(type, theme) {
111
114
  when (type) {
112
115
  ButtonType.DISABLED -> theme.colors.text.disable
@@ -116,28 +119,28 @@ fun getTextColor(type: ButtonType): Color {
116
119
  ButtonType.TONAL -> theme.colors.primary
117
120
  ButtonType.DANGER -> Colors.black_01
118
121
  ButtonType.TEXT -> theme.colors.primary
119
- }
122
+ }.withLoading(loading)
120
123
  }
121
124
  }
122
125
 
123
126
  @Composable
124
127
  fun RenderTitle(size: Size, title: String = "", type: ButtonType) {
125
128
  val style = remember(size) { getStyle(size) }
126
- val color = getTextColor(type)
129
+ val color = TextColor.current
127
130
  Text(style = style, text = title, color = color, overflow = TextOverflow.Ellipsis, maxLines = 1)
128
131
  }
129
132
 
130
133
  @Composable
131
134
  fun RenderLeading(
132
- loading: Boolean,
133
135
  size: Size,
134
136
  useTintColor: Boolean = true,
135
- type: ButtonType,
136
137
  iconLeft: String = "",
137
138
  ) {
139
+ val loading = IsLoading.current
140
+ val bgColor = BackgroundColor.current
138
141
  val iconSize = remember(size) { getIconSize(size) }
139
142
  val marginRight = remember(size) { getIconSpace(size) }
140
- val color = if (useTintColor) getTextColor(type) else Color.Unspecified
143
+ val color = if (useTintColor) TextColor.current else Color.Unspecified
141
144
 
142
145
  Row(
143
146
  verticalAlignment = Alignment.CenterVertically,
@@ -145,10 +148,11 @@ fun RenderLeading(
145
148
  ) {
146
149
  if (loading) {
147
150
  Box(Modifier.padding(end = marginRight)) {
148
- LottieView(
149
- path = "files/lottie_circle_loader.json",
150
- tintColor = color,
151
+ LottieAnimation(
151
152
  modifier = Modifier.size(iconSize),
153
+ bgColor = bgColor,
154
+ tintColor = color,
155
+ path = "files/lottie_circle_loader"
152
156
  )
153
157
  }
154
158
  } else {
@@ -169,9 +173,8 @@ fun RenderTrailing(
169
173
  iconRight: String,
170
174
  size: Size,
171
175
  useTintColor: Boolean,
172
- type: ButtonType,
173
176
  ) {
174
- val color = if (useTintColor) getTextColor(type) else Color.Unspecified
177
+ val color = if (useTintColor) TextColor.current else Color.Unspecified
175
178
  val marginLeft = remember(size) { getIconSpace(size) }
176
179
 
177
180
  if (iconRight.isNotEmpty()) {
@@ -192,39 +195,60 @@ fun getTypeStyle(
192
195
  size: Size,
193
196
  ): Modifier {
194
197
  val theme = AppTheme.current
198
+ val bgColor = BackgroundColor.current
195
199
  val radius = remember(size) { size.value.radius }
200
+ val modifier = Modifier.background(bgColor)
196
201
 
197
202
  return remember(type, color, theme, radius) {
198
203
  when (type) {
199
- ButtonType.DISABLED -> Modifier.background(theme.colors.background.disable)
204
+ ButtonType.DISABLED -> modifier
200
205
  .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
201
206
 
202
- ButtonType.PRIMARY -> Modifier.background(theme.colors.primary)
207
+ ButtonType.PRIMARY -> modifier
203
208
  .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
204
209
 
205
- ButtonType.SECONDARY -> Modifier
206
- .background(theme.colors.background.surface)
210
+ ButtonType.SECONDARY -> modifier
207
211
  .border(1.dp, theme.colors.border.default, RoundedCornerShape(radius))
208
212
 
209
- ButtonType.OUTLINE -> Modifier
210
- .background(theme.colors.background.surface)
213
+ ButtonType.OUTLINE -> modifier
211
214
  .border(
212
215
  1.dp,
213
216
  color ?: theme.colors.primary,
214
217
  RoundedCornerShape(radius)
215
218
  )
216
219
 
217
- ButtonType.TONAL -> Modifier.background(theme.colors.background.tonal)
220
+ ButtonType.TONAL -> modifier
218
221
  .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
219
222
 
220
- ButtonType.DANGER -> Modifier.background(theme.colors.error.primary)
223
+ ButtonType.DANGER -> modifier
221
224
  .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
222
225
 
223
- ButtonType.TEXT -> Modifier
226
+ ButtonType.TEXT -> modifier
224
227
  }
225
228
  }
226
229
  }
227
230
 
231
+ @Composable
232
+ fun getButtonBackgroundColor(
233
+ loading: Boolean,
234
+ type: ButtonType
235
+ ): Color {
236
+ val theme = AppTheme.current
237
+
238
+ return when (type) {
239
+ ButtonType.DISABLED -> theme.colors.background.disable.withLoading(loading)
240
+ ButtonType.PRIMARY -> theme.colors.primary.withLoading(loading)
241
+ ButtonType.SECONDARY -> theme.colors.background.surface.withLoading(loading)
242
+ ButtonType.OUTLINE -> theme.colors.background.surface.withLoading(loading)
243
+ ButtonType.TONAL -> theme.colors.background.tonal.withLoading(loading)
244
+ ButtonType.DANGER -> theme.colors.error.primary.withLoading(loading)
245
+ ButtonType.TEXT -> Color.Unspecified
246
+ }
247
+ }
248
+
249
+ fun Color.withLoading(loading: Boolean): Color =
250
+ this.copy(alpha = if (loading) 0.75f else 1f)
251
+
228
252
  /**
229
253
  * @param onClick called when this button is clicked
230
254
  * @param type [ButtonType] that define the type of button.
@@ -266,30 +290,36 @@ fun Button(
266
290
  )
267
291
 
268
292
  val sizeSpecs = remember(size) { size.value }
269
-
270
- Row(
271
- modifier = customModifier
272
- .then(getTypeStyle(type, size = size))
273
- .padding(horizontal = sizeSpecs.padding)
274
- .defaultMinSize(minWidth = sizeSpecs.width)
275
- .height(sizeSpecs.height)
276
- .alpha(if(loading) 0.75f else 1f),
277
- horizontalArrangement = Arrangement.Center,
278
- verticalAlignment = Alignment.CenterVertically,
279
- ) {
280
- RenderLeading(
281
- loading,
282
- size,
283
- useTintColor,
284
- type,
285
- iconLeft = iconLeft
286
- )
287
- RenderTitle(size, title, type = type)
288
- RenderTrailing(
289
- size = size,
290
- useTintColor = useTintColor,
291
- type = type,
292
- iconRight = iconRight
293
- )
293
+ CompositionLocalProvider(
294
+ IsLoading provides loading,
295
+ BackgroundColor provides getButtonBackgroundColor(loading, type),
296
+ TextColor provides getTextColor(loading, type)
297
+ ){
298
+ Row(
299
+ modifier = customModifier
300
+ .then(getTypeStyle(type, size = size))
301
+ .padding(horizontal = sizeSpecs.padding)
302
+ .defaultMinSize(minWidth = sizeSpecs.width)
303
+ .height(sizeSpecs.height),
304
+ horizontalArrangement = Arrangement.Center,
305
+ verticalAlignment = Alignment.CenterVertically,
306
+ ) {
307
+ RenderLeading(
308
+ size = size,
309
+ useTintColor = useTintColor,
310
+ iconLeft = iconLeft
311
+ )
312
+ RenderTitle(size, title, type = type)
313
+ RenderTrailing(
314
+ size = size,
315
+ useTintColor = useTintColor,
316
+ iconRight = iconRight
317
+ )
318
+ }
294
319
  }
295
320
  }
321
+
322
+ private val IsLoading = staticCompositionLocalOf<Boolean> { false }
323
+ private val BackgroundColor = staticCompositionLocalOf<Color> { Color.Transparent }
324
+ private val TextColor = staticCompositionLocalOf<Color> { Color.Transparent }
325
+
@@ -2,6 +2,7 @@ package vn.momo.kits.components.datetimepicker
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
4
  import androidx.compose.runtime.remember
5
+ import kotlinx.datetime.Clock
5
6
  import kotlinx.datetime.DatePeriod
6
7
  import kotlinx.datetime.LocalDateTime
7
8
  import kotlinx.datetime.TimeZone
@@ -9,8 +10,6 @@ import kotlinx.datetime.minus
9
10
  import kotlinx.datetime.number
10
11
  import kotlinx.datetime.plus
11
12
  import kotlinx.datetime.toLocalDateTime
12
- import kotlin.time.Clock.System.now
13
- import kotlin.time.ExperimentalTime
14
13
 
15
14
  /**
16
15
  * Format a LocalDateTime object into a string
@@ -150,9 +149,8 @@ val timeMode = listOf("AM", "PM")
150
149
  /**
151
150
  * Get today's date
152
151
  */
153
- @OptIn(ExperimentalTime::class)
154
152
  fun getCurrentDateTime(): LocalDateTime {
155
- return now().toLocalDateTime(TimeZone.currentSystemDefault())
153
+ return Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
156
154
  }
157
155
 
158
156
  /**
@@ -1,6 +1,8 @@
1
1
  package vn.momo.kits.platform
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
+ import androidx.compose.ui.Modifier
5
+ import androidx.compose.ui.graphics.Color
4
6
  import androidx.compose.ui.graphics.NativePaint
5
7
  import androidx.compose.ui.unit.Dp
6
8
 
@@ -25,4 +27,12 @@ expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
25
27
  @Composable
26
28
  expect fun getScreenHeight(): Dp
27
29
 
28
- expect fun getAndroidBuildVersion(): Int
30
+ expect fun getAndroidBuildVersion(): Int
31
+
32
+ @Composable
33
+ expect fun LottieAnimation(
34
+ path: String,
35
+ tintColor: Color? = null,
36
+ bgColor: Color? = null,
37
+ modifier: Modifier = Modifier
38
+ )
@@ -1,9 +1,15 @@
1
1
  package vn.momo.kits.utils
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.LaunchedEffect
5
+ import androidx.compose.runtime.MutableState
6
+ import androidx.compose.runtime.getValue
7
+ import androidx.compose.runtime.mutableStateOf
8
+ import androidx.compose.runtime.remember
4
9
  import org.jetbrains.compose.resources.DrawableResource
5
10
  import org.jetbrains.compose.resources.InternalResourceApi
6
11
  import org.jetbrains.compose.resources.ResourceItem
12
+ import org.jetbrains.compose.resources.readResourceBytes
7
13
 
8
14
  @OptIn(InternalResourceApi::class)
9
15
  @Composable
@@ -12,4 +18,40 @@ fun getResource(name: String): DrawableResource {
12
18
  "drawable:$name",
13
19
  setOf(ResourceItem(setOf(), "drawable/$name", -1, -1))
14
20
  )
21
+ }
22
+
23
+ @OptIn(InternalResourceApi::class)
24
+ @Composable
25
+ fun readJson(name: String): String {
26
+ val path = name.plus(".json")
27
+
28
+ val jsonContent by rememberState(path, { "" }) {
29
+ val cached = resourceCache.getOrPut(path) {
30
+ ResourceCache.JSON(
31
+ readResourceBytes(path).decodeToString()
32
+ )
33
+ } as ResourceCache.JSON
34
+ cached.json
35
+ }
36
+
37
+ return jsonContent
38
+ }
39
+
40
+ private val resourceCache = mutableMapOf<String, ResourceCache>()
41
+
42
+ private sealed interface ResourceCache {
43
+ class JSON(val json: String) : ResourceCache
44
+ }
45
+
46
+ @Composable
47
+ fun <T> rememberState(
48
+ key: Any,
49
+ getDefault: () -> T,
50
+ block: suspend () -> T
51
+ ): MutableState<T> {
52
+ val state = remember(key) { mutableStateOf(getDefault()) }
53
+ LaunchedEffect(key) {
54
+ state.value = block()
55
+ }
56
+ return state
15
57
  }
@@ -1,18 +1,33 @@
1
1
  package vn.momo.kits.platform
2
2
 
3
- import androidx.compose.foundation.ExperimentalFoundationApi
3
+ import androidx.compose.foundation.layout.Box
4
4
  import androidx.compose.foundation.layout.WindowInsets
5
5
  import androidx.compose.foundation.layout.asPaddingValues
6
+ import androidx.compose.foundation.layout.fillMaxSize
6
7
  import androidx.compose.foundation.layout.systemBars
7
8
  import androidx.compose.runtime.Composable
9
+ import androidx.compose.runtime.LaunchedEffect
10
+ import androidx.compose.runtime.getValue
11
+ import androidx.compose.runtime.mutableStateOf
12
+ import androidx.compose.runtime.remember
13
+ import androidx.compose.runtime.setValue
14
+ import androidx.compose.ui.Modifier
15
+ import androidx.compose.ui.graphics.Color
8
16
  import androidx.compose.ui.graphics.NativePaint
17
+ import androidx.compose.ui.graphics.toArgb
18
+ import androidx.compose.ui.interop.UIKitView
9
19
  import androidx.compose.ui.unit.Dp
10
20
  import androidx.compose.ui.unit.dp
21
+ import cocoapods.lottie_ios.CompatibleAnimation
22
+ import cocoapods.lottie_ios.CompatibleAnimationView
23
+ import cocoapods.lottie_ios.CompatibleAnimationKeypath
11
24
  import kotlinx.cinterop.ExperimentalForeignApi
12
25
  import kotlinx.cinterop.get
13
26
  import kotlinx.cinterop.memScoped
14
27
  import org.jetbrains.skia.FilterBlurMode
15
28
  import org.jetbrains.skia.MaskFilter
29
+ import platform.Foundation.NSBundle
30
+ import platform.UIKit.UIColor
16
31
  import platform.UIKit.UIScreen
17
32
 
18
33
 
@@ -46,4 +61,91 @@ actual fun getScreenHeight(): Dp {
46
61
  return getScreenDimensions().height.dp
47
62
  }
48
63
 
49
- actual fun getAndroidBuildVersion(): Int = 999
64
+ actual fun getAndroidBuildVersion(): Int = 999
65
+
66
+ @OptIn(ExperimentalForeignApi::class)
67
+ @Composable
68
+ actual fun LottieAnimation(
69
+ path: String,
70
+ tintColor: Color?,
71
+ bgColor: Color?,
72
+ modifier: Modifier
73
+ ) {
74
+ var animation by remember { mutableStateOf<CompatibleAnimation?>(null) }
75
+
76
+ LaunchedEffect(Unit) {
77
+ animation = CompatibleAnimation(name = "compose-resources/composeResources/vn.momo.compose.resources/".plus(path), subdirectory = null, NSBundle.mainBundle)
78
+ }
79
+
80
+ when (val value = animation) {
81
+ null -> {
82
+ Box(modifier)
83
+ }
84
+
85
+ else -> {
86
+ val factory = remember {
87
+ {
88
+ CompatibleAnimationView(value).also {
89
+ it.translatesAutoresizingMaskIntoConstraints = true
90
+ it.setBackgroundColor(bgColor?.toUIColor() ?: UIColor.whiteColor)
91
+ it.setLoopAnimationCount(-1.0)
92
+ it.setAnimationSpeed(1.0)
93
+
94
+ if (tintColor != null) {
95
+ val uiColor = tintColor.toUIColor()
96
+
97
+ it.setColorValue(
98
+ uiColor,
99
+ CompatibleAnimationKeypath("**.Fill 1.Color")
100
+ )
101
+ it.setColorValue(
102
+ uiColor,
103
+ CompatibleAnimationKeypath("**.Fill.Color")
104
+ )
105
+ it.setColorValue(
106
+ uiColor,
107
+ CompatibleAnimationKeypath("**.Color")
108
+ )
109
+
110
+ it.setColorValue(
111
+ uiColor,
112
+ CompatibleAnimationKeypath("**.Stroke 1.Color")
113
+ )
114
+ it.setColorValue(
115
+ uiColor,
116
+ CompatibleAnimationKeypath("**.Stroke.Color")
117
+ )
118
+ }
119
+
120
+
121
+
122
+ it.play()
123
+ }
124
+ }
125
+ }
126
+
127
+ Box(modifier) {
128
+ UIKitView(
129
+ modifier = Modifier.fillMaxSize(),
130
+ factory = factory,
131
+ )
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ fun Color.toUIColor(): UIColor {
138
+ val argb = this.toArgb()
139
+
140
+ val a = ((argb shr 24) and 0xFF) / 255.0
141
+ val r = ((argb shr 16) and 0xFF) / 255.0
142
+ val g = ((argb shr 8) and 0xFF) / 255.0
143
+ val b = (argb and 0xFF) / 255.0
144
+
145
+ return UIColor(
146
+ red = r,
147
+ green = g,
148
+ blue = b,
149
+ alpha = a
150
+ )
151
+ }
@@ -1,4 +1,3 @@
1
-
2
1
  import Foundation
3
2
  import SwiftUI
4
3
  import Lottie
@@ -23,12 +22,85 @@ public enum ButtonSize {
23
22
  case small
24
23
  }
25
24
 
26
- // MARK: - Button
25
+ public struct ButtonStyleData {
26
+ let background: Color
27
+ let content: Color
28
+ let border: Color?
29
+ let borderWidth: CGFloat
30
+
31
+ public init(
32
+ background: Color,
33
+ content: Color,
34
+ border: Color? = nil,
35
+ borderWidth: CGFloat = 1
36
+ ) {
37
+ self.background = background
38
+ self.content = content
39
+ self.border = border
40
+ self.borderWidth = borderWidth
41
+ }
42
+ }
43
+
44
+ // MARK: - ButtonType Extension (Style mapping)
45
+
46
+ public extension ButtonType {
47
+ var style: ButtonStyleData {
48
+ switch self {
49
+ case .primary:
50
+ return ButtonStyleData(
51
+ background: Colors.primary,
52
+ content: Colors.black01
53
+ )
54
+ case .danger:
55
+ return ButtonStyleData(
56
+ background: Colors.error,
57
+ content: Colors.black01
58
+ )
59
+ case .tonal:
60
+ return ButtonStyleData(
61
+ background: Colors.primaryLight,
62
+ content: Colors.primary
63
+ )
64
+ case .text:
65
+ return ButtonStyleData(
66
+ background: .clear,
67
+ content: Colors.primary
68
+ )
69
+ case .secondary:
70
+ return ButtonStyleData(
71
+ background: Colors.black01,
72
+ content: Colors.black17,
73
+ border: Colors.black04
74
+ )
75
+ case .outline:
76
+ return ButtonStyleData(
77
+ background: Colors.card,
78
+ content: Colors.primary,
79
+ border: Colors.primary
80
+ )
81
+ case .disabled:
82
+ return ButtonStyleData(
83
+ background: Colors.black05,
84
+ content: Colors.black06
85
+ )
86
+ }
87
+ }
88
+ }
89
+
90
+ // MARK: - Button View
27
91
 
28
92
  public struct Button: View {
29
93
  // MARK: Lifecycle
30
94
 
31
- public init(title: String = "", action: @escaping () -> Void, type: ButtonType = .primary, size: ButtonSize = .large, iconLeft: AnyView? = nil, iconRight: AnyView? = nil, loading: Bool = false) {
95
+ public init(
96
+ title: String = "",
97
+ action: @escaping () -> Void,
98
+ type: ButtonType = .primary,
99
+ size: ButtonSize = .large,
100
+ iconLeft: AnyView? = nil,
101
+ iconRight: AnyView? = nil,
102
+ loading: Bool = false
103
+ ) {
32
104
  self.title = title
33
105
  self.action = action
34
106
  self.type = type
@@ -38,117 +110,87 @@ public struct Button: View {
38
110
  self.loading = loading
39
111
  }
40
112
 
41
- // MARK: Public
42
-
43
- public var body: some View {
44
- SwiftUI.Button(action: action) {
45
- HStack {
46
- if loading {
47
- LottieView2(name: "lottie_circle_loader", loopMode: .loop)
48
- .frame(width: iconSize, height: iconSize)
49
- .colorMultiply(.white)
50
- } else if let iconLeft = iconLeft {
51
- iconLeft.frame(width: iconSize, height: iconSize)
52
- }
53
- renderTitle(title: title, type: type, size: size).lineLimit(1).truncationMode(.tail)
54
- if let iconRight = iconRight {
55
- iconRight.frame(width: iconSize, height: iconSize)
56
- }
57
- }
58
- .buttonSize(size)
59
- .buttonType(type)
60
- .opacity(loading ? 0.75 : 1.0)
61
- }
62
- .disabled(type == .disabled || loading)
63
- .clipShape(RoundedRectangle(cornerRadius: Radius.S))
64
- }
65
-
66
113
  // MARK: Internal
67
114
 
68
115
  var title: String
69
- var action: () -> Void = {}
70
- var type: ButtonType = .primary
71
- var size: ButtonSize = .large
116
+ var action: () -> Void
117
+ var type: ButtonType
118
+ var size: ButtonSize
72
119
  var iconLeft: AnyView?
73
120
  var iconRight: AnyView?
74
121
  var loading: Bool
75
-
122
+
123
+ // MARK: Private Helpers
124
+
76
125
  private var iconSize: CGFloat {
77
126
  switch size {
78
- case .large: return 24
79
- case .medium: return 16
80
- case .small: return 16
127
+ case .large: return 24
128
+ case .medium, .small: return 16
81
129
  }
82
130
  }
83
131
 
84
- func renderTitle(title: String, type: ButtonType, size: ButtonSize) -> Text {
132
+ private func height(for size: ButtonSize) -> CGFloat {
85
133
  switch size {
86
- case .large:
87
- return Text(title).font(.system(size: 16, weight: Font.Weight.bold))
88
- case .medium:
89
- return Text(title).font(.system(size: 14, weight: Font.Weight.bold))
90
- case .small:
91
- return Text(title).font(.system(size: 12, weight: Font.Weight.bold))
134
+ case .large: return 48
135
+ case .medium: return 36
136
+ case .small: return 28
92
137
  }
93
138
  }
94
- }
95
139
 
96
- private extension View {
97
- func buttonType(_ type: ButtonType) -> some View {
98
- switch type {
99
- case .primary:
100
- return AnyView(self
101
- .background(Colors.primary)
102
- .foregroundColor(Colors.black01)
103
- )
104
- case .danger:
105
- return AnyView(self
106
- .background(Colors.error)
107
- .foregroundColor(Colors.black01)
108
- )
109
- case .tonal:
110
- return AnyView(self
111
- .background(Colors.primaryLight)
112
- .foregroundColor(Colors.primary)
113
- )
114
- case .text:
115
- return AnyView(self
116
- .foregroundColor(Colors.primary)
117
- )
118
- case .secondary:
119
- return AnyView(self
120
- .background(Colors.black01)
121
- .foregroundColor(Colors.black17)
122
- .overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.black04, lineWidth: 1))
123
- )
124
- case .outline:
125
- return AnyView(self
126
- .background(Colors.card)
127
- .foregroundColor(Colors.primary)
128
- .overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.primary, lineWidth: 1))
129
- )
130
- case .disabled:
131
- return AnyView(self
132
- .background(Colors.black05)
133
- .foregroundColor(Colors.black06)
134
- )
140
+ private func padding(for size: ButtonSize) -> CGFloat {
141
+ switch size {
142
+ case .large: return Spacing.L
143
+ case .medium: return Spacing.M
144
+ case .small: return Spacing.S
135
145
  }
136
146
  }
137
147
 
138
- func buttonSize(_ size: ButtonSize) -> some View {
148
+ private func renderTitle(title: String, size: ButtonSize) -> Text {
139
149
  switch size {
140
150
  case .large:
141
- return self
142
- .frame(maxWidth: .infinity, minHeight: 48, maxHeight: 48)
143
- .padding([.leading, .trailing], Spacing.L)
151
+ return Text(title).font(.system(size: 16, weight: .bold))
144
152
  case .medium:
145
- return self
146
- .frame(minWidth: 80, maxWidth: .infinity, minHeight: 36, maxHeight: 36)
147
- .padding([.leading, .trailing], Spacing.M)
153
+ return Text(title).font(.system(size: 14, weight: .bold))
148
154
  case .small:
149
- return self
150
- .frame(minWidth: 56, maxWidth: 76, minHeight: 28, maxHeight: 28)
151
- .padding([.leading, .trailing], Spacing.S)
155
+ return Text(title).font(.system(size: 12, weight: .bold))
152
156
  }
153
157
  }
158
+
159
+ // MARK: View
160
+
161
+ public var body: some View {
162
+ let style = type.style
163
+
164
+ SwiftUI.Button(action: action) {
165
+ HStack(spacing: 8) {
166
+ if loading {
167
+ LottieView(name: "lottie_circle_loader", loopMode: .loop)
168
+ .frame(width: iconSize, height: iconSize)
169
+ .colorMultiply(style.content)
170
+ } else if let iconLeft = iconLeft {
171
+ iconLeft.frame(width: iconSize, height: iconSize)
172
+ }
173
+
174
+ renderTitle(title: title, size: size)
175
+ .lineLimit(1)
176
+ .truncationMode(.tail)
177
+
178
+ if let iconRight = iconRight {
179
+ iconRight.frame(width: iconSize, height: iconSize)
180
+ }
181
+ }
182
+ .frame(maxWidth: .infinity)
183
+ .padding(.horizontal, padding(for: size))
184
+ .frame(height: height(for: size))
185
+ .background(style.background)
186
+ .foregroundColor(style.content)
187
+ .overlay(
188
+ RoundedRectangle(cornerRadius: Radius.S)
189
+ .stroke(style.border ?? .clear, lineWidth: style.border != nil ? style.borderWidth : 0)
190
+ )
191
+ .clipShape(RoundedRectangle(cornerRadius: Radius.S))
192
+ .opacity(loading ? 0.75 : 1.0)
193
+ }
194
+ .disabled(type == .disabled || loading)
195
+ }
154
196
  }
@@ -9,7 +9,7 @@
9
9
  import SwiftUI
10
10
  import Lottie
11
11
 
12
- struct LottieView2: UIViewRepresentable {
12
+ struct LottieView: UIViewRepresentable {
13
13
  var name: String = ""
14
14
  var url: String?
15
15
  var loopMode: LottieLoopMode = .playOnce
@@ -19,7 +19,7 @@ struct LottieView2: UIViewRepresentable {
19
19
  // Add static cache for animations
20
20
  private static var animationCache = NSCache<NSString, LottieAnimation>()
21
21
 
22
- func makeUIView(context: UIViewRepresentableContext<LottieView2>) -> UIView {
22
+ func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
23
23
  let view = UIView(frame: .zero)
24
24
 
25
25
  // Configure Lottie with optimized settings
@@ -82,5 +82,5 @@ struct LottieView2: UIViewRepresentable {
82
82
  }
83
83
  }
84
84
 
85
- func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView2>) {}
85
+ func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {}
86
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.151.2-test.11",
3
+ "version": "0.151.2-test.13",
4
4
  "private": false,
5
5
  "dependencies": {
6
6
  "@momo-platform/native-max-api": "1.0.18"
@@ -1,39 +0,0 @@
1
- package vn.momo.kits.components
2
-
3
- import androidx.compose.runtime.Composable
4
- import androidx.compose.runtime.getValue
5
- import androidx.compose.ui.Modifier
6
- import androidx.compose.ui.graphics.Color
7
- import androidx.compose.ui.graphics.ColorFilter
8
- import io.github.alexzhirkevich.compottie.Compottie
9
- import io.github.alexzhirkevich.compottie.LottieCompositionSpec
10
- import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState
11
- import io.github.alexzhirkevich.compottie.rememberLottieComposition
12
- import io.github.alexzhirkevich.compottie.rememberLottiePainter
13
- import vn.momo.uikits.resources.Res
14
-
15
- @Composable
16
- fun LottieView(
17
- path: String,
18
- tintColor: Color? = null,
19
- modifier: Modifier = Modifier) {
20
- val composition by rememberLottieComposition {
21
- LottieCompositionSpec.JsonString(
22
- Res.readBytes(path).decodeToString()
23
- )
24
- }
25
-
26
- val progress by animateLottieCompositionAsState(
27
- composition = composition,
28
- iterations = Compottie.IterateForever
29
- )
30
-
31
- Image(
32
- source = rememberLottiePainter(
33
- composition = composition,
34
- progress = { progress }
35
- ),
36
- colorFilter = tintColor?.let { ColorFilter.tint(it) },
37
- modifier = modifier,
38
- )
39
- }