@momo-kits/native-kits 0.161.2-beta.9-debug → 0.161.2-nativeimage.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 -11
- package/compose/build.gradle.kts.backup +8 -10
- package/compose/compose.podspec +2 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/components/NativeImage.android.kt +71 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +24 -30
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +13 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/NativeImage.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +5 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +9 -45
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +11 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/components/NativeImage.ios.kt +142 -0
- package/example/ios/Example.xcodeproj/xcuserdata/thanhle.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
- package/example/ios/Example.xcworkspace/xcuserdata/thanhle.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/thanhle.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
- package/gradle/libs.versions.toml +14 -2
- package/gradle.properties +1 -1
- package/local.properties +2 -2
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -32
package/compose/build.gradle.kts
CHANGED
|
@@ -40,7 +40,7 @@ kotlin {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
cocoapods {
|
|
43
|
-
version = "0.161.2-
|
|
43
|
+
version = "0.161.2-nativeimage.1-debug"
|
|
44
44
|
summary = "IOS Shared module"
|
|
45
45
|
homepage = "https://momo.vn"
|
|
46
46
|
ios.deploymentTarget = "15.0"
|
|
@@ -55,6 +55,10 @@ kotlin {
|
|
|
55
55
|
version = "4.4.3"
|
|
56
56
|
extraOpts += listOf("-compiler-option", "-fmodules")
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
pod("SDWebImage") {
|
|
60
|
+
version = "~> 5.14.3"
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
sourceSets {
|
|
@@ -73,19 +77,13 @@ kotlin {
|
|
|
73
77
|
implementation(libs.jetbrains.serialization.json)
|
|
74
78
|
implementation(libs.kotlinx.datetime)
|
|
75
79
|
api(libs.native.max.api)
|
|
76
|
-
|
|
77
|
-
// compottie-core depends on keight-core directly, and keight (also a dep of compottie)
|
|
78
|
-
// also depends on keight-core transitively. Kotlin Native links both static archives
|
|
79
|
-
// into the framework causing duplicate symbols. Exclude keight-core so it is
|
|
80
|
-
// pulled only once through keight. String notation required for exclude lambda in KMP sourceSets.
|
|
81
|
-
implementation("io.github.alexzhirkevich:compottie:${libs.versions.compottie.get()}") {
|
|
82
|
-
exclude(group = "io.github.alexzhirkevich", module = "keight-core")
|
|
83
|
-
}
|
|
84
|
-
// AI-GENERATED END
|
|
80
|
+
implementation(libs.compottie)
|
|
85
81
|
}
|
|
86
82
|
androidMain.dependencies {
|
|
87
83
|
implementation(libs.ktor.client.okhttp)
|
|
88
84
|
implementation(libs.airbnb.lottie)
|
|
85
|
+
implementation(libs.coil.gif)
|
|
86
|
+
implementation(libs.coil.http)
|
|
89
87
|
}
|
|
90
88
|
iosMain.dependencies {
|
|
91
89
|
implementation(libs.ktor.client.darwin)
|
|
@@ -120,7 +118,7 @@ repositories {
|
|
|
120
118
|
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
|
121
119
|
maven {
|
|
122
120
|
url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
|
|
123
|
-
credentials {username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
|
|
121
|
+
credentials { username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
|
|
124
122
|
}
|
|
125
123
|
maven {
|
|
126
124
|
url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
|
|
@@ -55,6 +55,10 @@ kotlin {
|
|
|
55
55
|
version = "4.4.3"
|
|
56
56
|
extraOpts += listOf("-compiler-option", "-fmodules")
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
pod("SDWebImage") {
|
|
60
|
+
version = "~> 5.14.3"
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
sourceSets {
|
|
@@ -73,19 +77,13 @@ kotlin {
|
|
|
73
77
|
implementation(libs.jetbrains.serialization.json)
|
|
74
78
|
implementation(libs.kotlinx.datetime)
|
|
75
79
|
api(libs.native.max.api)
|
|
76
|
-
|
|
77
|
-
// compottie-core depends on keight-core directly, and keight (also a dep of compottie)
|
|
78
|
-
// also depends on keight-core transitively. Kotlin Native links both static archives
|
|
79
|
-
// into the framework causing duplicate symbols. Exclude keight-core so it is
|
|
80
|
-
// pulled only once through keight. String notation required for exclude lambda in KMP sourceSets.
|
|
81
|
-
implementation("io.github.alexzhirkevich:compottie:${libs.versions.compottie.get()}") {
|
|
82
|
-
exclude(group = "io.github.alexzhirkevich", module = "keight-core")
|
|
83
|
-
}
|
|
84
|
-
// AI-GENERATED END
|
|
80
|
+
implementation(libs.compottie)
|
|
85
81
|
}
|
|
86
82
|
androidMain.dependencies {
|
|
87
83
|
implementation(libs.ktor.client.okhttp)
|
|
88
84
|
implementation(libs.airbnb.lottie)
|
|
85
|
+
implementation(libs.coil.gif)
|
|
86
|
+
implementation(libs.coil.http)
|
|
89
87
|
}
|
|
90
88
|
iosMain.dependencies {
|
|
91
89
|
implementation(libs.ktor.client.darwin)
|
|
@@ -120,7 +118,7 @@ repositories {
|
|
|
120
118
|
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
|
121
119
|
maven {
|
|
122
120
|
url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
|
|
123
|
-
credentials {username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
|
|
121
|
+
credentials { username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
|
|
124
122
|
}
|
|
125
123
|
maven {
|
|
126
124
|
url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
|
package/compose/compose.podspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |spec|
|
|
2
2
|
spec.name = 'compose'
|
|
3
|
-
spec.version = '0.161.2-beta.
|
|
3
|
+
spec.version = '0.161.2-beta.15'
|
|
4
4
|
spec.homepage = 'https://momo.vn'
|
|
5
5
|
spec.source = { :http=> ''}
|
|
6
6
|
spec.authors = ''
|
|
@@ -9,6 +9,7 @@ Pod::Spec.new do |spec|
|
|
|
9
9
|
spec.vendored_frameworks = 'build/cocoapods/framework/kits.framework'
|
|
10
10
|
spec.libraries = 'c++'
|
|
11
11
|
spec.ios.deployment_target = '15.0'
|
|
12
|
+
spec.dependency 'SDWebImage', '~> 5.14.3'
|
|
12
13
|
spec.dependency 'lottie-ios', '4.4.3'
|
|
13
14
|
if !Dir.exist?('build/cocoapods/framework/kits.framework') || Dir.empty?('build/cocoapods/framework/kits.framework')
|
|
14
15
|
raise "
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import android.os.Build.VERSION.SDK_INT
|
|
4
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
5
|
+
import androidx.compose.runtime.*
|
|
6
|
+
import androidx.compose.ui.Alignment
|
|
7
|
+
import androidx.compose.ui.Modifier
|
|
8
|
+
import androidx.compose.ui.platform.LocalContext
|
|
9
|
+
import androidx.compose.ui.unit.dp
|
|
10
|
+
import coil3.SingletonImageLoader
|
|
11
|
+
import coil3.compose.AsyncImage
|
|
12
|
+
import coil3.gif.AnimatedImageDecoder
|
|
13
|
+
import coil3.gif.GifDecoder
|
|
14
|
+
import vn.momo.kits.const.AppTheme
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@Composable
|
|
18
|
+
actual fun BoxScope.NativeImageWrapper(
|
|
19
|
+
urlToLoad: Any,
|
|
20
|
+
modifier: Modifier,
|
|
21
|
+
options: Options?,
|
|
22
|
+
loading: Boolean
|
|
23
|
+
) {
|
|
24
|
+
val imageOptions = remember(options) {
|
|
25
|
+
options ?: Options()
|
|
26
|
+
}
|
|
27
|
+
var imageState by remember { mutableStateOf(ImageState.Loading) }
|
|
28
|
+
val context = LocalContext.current
|
|
29
|
+
val gifEnabledLoader = remember(context) {
|
|
30
|
+
if (isGifImage(urlToLoad)) {
|
|
31
|
+
SingletonImageLoader.get(context).newBuilder()
|
|
32
|
+
.components {
|
|
33
|
+
if (SDK_INT >= 28) {
|
|
34
|
+
add(AnimatedImageDecoder.Factory())
|
|
35
|
+
} else {
|
|
36
|
+
add(GifDecoder.Factory())
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
.build()
|
|
40
|
+
} else {
|
|
41
|
+
SingletonImageLoader.get(context)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
AsyncImage(
|
|
45
|
+
modifier = Modifier.matchParentSize(),
|
|
46
|
+
model = urlToLoad,
|
|
47
|
+
imageLoader = gifEnabledLoader,
|
|
48
|
+
contentDescription = null,
|
|
49
|
+
contentScale = imageOptions.contentScale,
|
|
50
|
+
alignment = imageOptions.alignment,
|
|
51
|
+
colorFilter = imageOptions.effectiveColorFilter,
|
|
52
|
+
alpha = imageOptions.alpha,
|
|
53
|
+
onLoading = { imageState = ImageState.Loading },
|
|
54
|
+
onSuccess = { imageState = ImageState.Success },
|
|
55
|
+
onError = { imageState = ImageState.Error },
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
when (imageState) {
|
|
59
|
+
ImageState.Loading -> if (loading) Skeleton(cornerRadius = options?.skeletonCornerRadius ?: 0.dp)
|
|
60
|
+
ImageState.Error -> {
|
|
61
|
+
val theme = AppTheme.current
|
|
62
|
+
Icon(
|
|
63
|
+
source = "media_fail",
|
|
64
|
+
color = theme.colors.text.disable,
|
|
65
|
+
modifier = Modifier.align(Alignment.Center)
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ImageState.Success -> {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -38,6 +38,7 @@ import androidx.compose.runtime.produceState
|
|
|
38
38
|
import androidx.compose.runtime.remember
|
|
39
39
|
import androidx.compose.runtime.rememberUpdatedState
|
|
40
40
|
import androidx.compose.runtime.snapshotFlow
|
|
41
|
+
import androidx.compose.runtime.staticCompositionLocalOf
|
|
41
42
|
import androidx.compose.ui.Alignment
|
|
42
43
|
import androidx.compose.ui.Modifier
|
|
43
44
|
import androidx.compose.ui.composed
|
|
@@ -48,6 +49,7 @@ import androidx.compose.ui.geometry.Offset
|
|
|
48
49
|
import androidx.compose.ui.geometry.Rect
|
|
49
50
|
import androidx.compose.ui.graphics.Brush
|
|
50
51
|
import androidx.compose.ui.graphics.Color
|
|
52
|
+
import androidx.compose.ui.graphics.SolidColor
|
|
51
53
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
52
54
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
53
55
|
import androidx.compose.ui.layout.Layout
|
|
@@ -60,19 +62,15 @@ import androidx.compose.ui.layout.layoutId
|
|
|
60
62
|
import androidx.compose.ui.platform.LocalDensity
|
|
61
63
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
62
64
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
63
|
-
import androidx.compose.ui.text.style.TextOverflow
|
|
64
65
|
import androidx.compose.ui.text.TextStyle
|
|
66
|
+
import androidx.compose.ui.text.style.TextOverflow
|
|
65
67
|
import androidx.compose.ui.unit.Constraints
|
|
66
68
|
import androidx.compose.ui.unit.Dp
|
|
67
69
|
import androidx.compose.ui.unit.IntOffset
|
|
68
70
|
import androidx.compose.ui.unit.LayoutDirection
|
|
69
71
|
import androidx.compose.ui.unit.dp
|
|
70
|
-
import androidx.compose.ui.unit.sp
|
|
71
|
-
import androidx.compose.runtime.staticCompositionLocalOf
|
|
72
|
-
import androidx.compose.ui.graphics.Color.Companion
|
|
73
|
-
import androidx.compose.ui.graphics.SolidColor
|
|
74
72
|
import androidx.compose.ui.unit.lerp
|
|
75
|
-
import
|
|
73
|
+
import androidx.compose.ui.unit.sp
|
|
76
74
|
import kotlinx.coroutines.flow.collectLatest
|
|
77
75
|
import kotlinx.coroutines.flow.mapNotNull
|
|
78
76
|
import vn.momo.kits.components.Icon
|
|
@@ -81,11 +79,11 @@ import vn.momo.kits.const.AppTheme
|
|
|
81
79
|
import vn.momo.kits.const.Colors
|
|
82
80
|
import vn.momo.kits.const.Spacing
|
|
83
81
|
import vn.momo.kits.const.Typography
|
|
84
|
-
import vn.momo.kits.modifier.conditional
|
|
85
82
|
import vn.momo.kits.modifier.kitsAutomationId
|
|
86
83
|
import vn.momo.kits.modifier.noFeedbackClickable
|
|
87
84
|
import vn.momo.kits.modifier.setAutomationId
|
|
88
85
|
import vn.momo.kits.modifier.shadow
|
|
86
|
+
import vn.momo.kits.navigation.component.HEADER_HEIGHT
|
|
89
87
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
90
88
|
import kotlin.math.max
|
|
91
89
|
import kotlin.math.roundToInt
|
|
@@ -441,6 +439,7 @@ private class LiteScreenLayoutPolicy(
|
|
|
441
439
|
val spacing12 = Spacing.M.roundToPx()
|
|
442
440
|
val spaceBetween = headerSpaceBetween?.roundToPx() ?: spacing12
|
|
443
441
|
val statusBarPx = statusBarHeight.roundToPx()
|
|
442
|
+
val headerRowDefault = HEADER_HEIGHT.dp.roundToPx()
|
|
444
443
|
val scrollPercent = scrollPercentage.value
|
|
445
444
|
|
|
446
445
|
val contentMeasurable = measurables.find { it.layoutId == HeaderId.CONTENT_ID }
|
|
@@ -484,26 +483,20 @@ private class LiteScreenLayoutPolicy(
|
|
|
484
483
|
)
|
|
485
484
|
)
|
|
486
485
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
add(inputSearchPlaceable.safeHeight)
|
|
492
|
-
}
|
|
493
|
-
add(HEADER_HEIGHT.dp.roundToPx())
|
|
494
|
-
if (isHeaderExtend) {
|
|
495
|
-
add(titlePlaceable.safeHeight)
|
|
496
|
-
}
|
|
497
|
-
}.max()
|
|
498
|
-
|
|
499
|
-
var defaultHeight = statusBarPx + spacing12 + firstRowMaxHeight + spacing12
|
|
486
|
+
/**
|
|
487
|
+
* replicate logic from [vn.momo.kits.navigation.component.HeaderBackground]
|
|
488
|
+
* */
|
|
489
|
+
var defaultHeight = statusBarPx + headerRowDefault
|
|
500
490
|
if (isHeaderExtend) {
|
|
501
|
-
defaultHeight
|
|
491
|
+
defaultHeight = 154.dp.roundToPx()
|
|
502
492
|
}
|
|
503
493
|
val headerHeight = when {
|
|
504
494
|
isHeaderNone -> statusBarPx
|
|
505
495
|
!useAnimationSearch && !isHeaderExtend -> defaultHeight
|
|
506
|
-
else ->
|
|
496
|
+
else -> {
|
|
497
|
+
val collapsableSpace = defaultHeight - statusBarPx - headerRowDefault
|
|
498
|
+
(defaultHeight - scrollPercent * collapsableSpace).toInt()
|
|
499
|
+
}
|
|
507
500
|
}
|
|
508
501
|
|
|
509
502
|
val layoutWidth = constraints.maxWidth
|
|
@@ -566,14 +559,14 @@ private class LiteScreenLayoutPolicy(
|
|
|
566
559
|
if (backIconPlaceable != null) {
|
|
567
560
|
backIconPlaceable.place(
|
|
568
561
|
x = startX,
|
|
569
|
-
y = startY + backIconPlaceable.verticalCenterOffset(
|
|
562
|
+
y = startY + backIconPlaceable.verticalCenterOffset(headerRowDefault),
|
|
570
563
|
)
|
|
571
564
|
curX += backIconPlaceable.safeWidth + spaceBetween
|
|
572
565
|
}
|
|
573
566
|
|
|
574
567
|
headerRightPlaceable?.place(
|
|
575
568
|
x = layoutWidth - spacing12 - headerRightPlaceable.safeWidth,
|
|
576
|
-
y = startY + headerRightPlaceable.verticalCenterOffset(
|
|
569
|
+
y = startY + headerRightPlaceable.verticalCenterOffset(headerRowDefault),
|
|
577
570
|
)
|
|
578
571
|
|
|
579
572
|
val titleOffset = IntOffset(
|
|
@@ -582,19 +575,19 @@ private class LiteScreenLayoutPolicy(
|
|
|
582
575
|
space = layoutWidth,
|
|
583
576
|
layoutDirection = layoutDirection,
|
|
584
577
|
),
|
|
585
|
-
y = startY + titlePlaceable.verticalCenterOffset(
|
|
578
|
+
y = startY + titlePlaceable.verticalCenterOffset(headerRowDefault),
|
|
586
579
|
)
|
|
587
580
|
|
|
588
581
|
titlePlaceable?.place(titleOffset)
|
|
589
582
|
|
|
590
583
|
if (backIconPlaceable != null || headerRightPlaceable != null || titlePlaceable != null) {
|
|
591
|
-
curY +=
|
|
584
|
+
curY += headerRowDefault
|
|
592
585
|
}
|
|
593
586
|
|
|
594
587
|
val inputSearchOffset = if (isHeaderExtend) {
|
|
595
|
-
val baseY = curY + inputSearchPlaceable.verticalCenterOffset(
|
|
588
|
+
val baseY = curY + inputSearchPlaceable.verticalCenterOffset(headerRowDefault)
|
|
596
589
|
val y = (baseY * (1 - scrollPercent)).toInt().coerceAtLeast(
|
|
597
|
-
startY + inputSearchPlaceable.verticalCenterOffset(
|
|
590
|
+
startY + inputSearchPlaceable.verticalCenterOffset(headerRowDefault)
|
|
598
591
|
)
|
|
599
592
|
IntOffset(
|
|
600
593
|
x = startX + ((backIconPlaceable.safeWidth + spaceBetween) * (scrollPercent * 2f).coerceIn(
|
|
@@ -605,7 +598,7 @@ private class LiteScreenLayoutPolicy(
|
|
|
605
598
|
} else {
|
|
606
599
|
IntOffset(
|
|
607
600
|
x = curX,
|
|
608
|
-
y = startY + inputSearchPlaceable.verticalCenterOffset(
|
|
601
|
+
y = startY + inputSearchPlaceable.verticalCenterOffset(headerRowDefault),
|
|
609
602
|
)
|
|
610
603
|
}
|
|
611
604
|
val finalPosition = lerp(
|
|
@@ -823,7 +816,8 @@ private fun LiteInputSearch(
|
|
|
823
816
|
if (!placeHolder.isNullOrEmpty()) {
|
|
824
817
|
inputSearchProps.customPlaceHolder?.invoke() ?: Text(
|
|
825
818
|
text = placeHolder ?: "",
|
|
826
|
-
style = inputSearchProps.customTextStyle
|
|
819
|
+
style = inputSearchProps.customTextStyle
|
|
820
|
+
?: Typography.bodyDefaultRegular,
|
|
827
821
|
maxLines = 1,
|
|
828
822
|
color = theme.colors.text.hint,
|
|
829
823
|
overflow = TextOverflow.Ellipsis
|
|
@@ -2,20 +2,18 @@ package vn.momo.kits.components
|
|
|
2
2
|
|
|
3
3
|
import androidx.compose.foundation.border
|
|
4
4
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
5
|
-
import androidx.compose.runtime
|
|
6
|
-
import androidx.compose.runtime.getValue
|
|
7
|
-
import androidx.compose.runtime.mutableStateOf
|
|
8
|
-
import androidx.compose.runtime.remember
|
|
9
|
-
import androidx.compose.runtime.setValue
|
|
5
|
+
import androidx.compose.runtime.*
|
|
10
6
|
import androidx.compose.ui.Alignment
|
|
11
7
|
import androidx.compose.ui.Modifier
|
|
8
|
+
import androidx.compose.ui.graphics.Color
|
|
12
9
|
import androidx.compose.ui.graphics.ColorFilter
|
|
13
10
|
import androidx.compose.ui.graphics.DefaultAlpha
|
|
14
11
|
import androidx.compose.ui.graphics.painter.Painter
|
|
15
12
|
import androidx.compose.ui.layout.ContentScale
|
|
13
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
16
14
|
import androidx.compose.ui.semantics.contentDescription
|
|
17
15
|
import androidx.compose.ui.semantics.semantics
|
|
18
|
-
import androidx.compose.ui.
|
|
16
|
+
import androidx.compose.ui.unit.Dp
|
|
19
17
|
import androidx.compose.ui.unit.dp
|
|
20
18
|
import coil3.compose.AsyncImage
|
|
21
19
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
@@ -28,7 +26,13 @@ data class Options(
|
|
|
28
26
|
val contentScale: ContentScale = ContentScale.Crop,
|
|
29
27
|
val colorFilter: ColorFilter? = null,
|
|
30
28
|
val alpha: Float = DefaultAlpha,
|
|
31
|
-
|
|
29
|
+
val skeletonCornerRadius: Dp = 0.dp,
|
|
30
|
+
val tint: Color? = null,
|
|
31
|
+
) {
|
|
32
|
+
|
|
33
|
+
val effectiveColorFilter: ColorFilter?
|
|
34
|
+
get() = tint?.let { ColorFilter.tint(it) } ?: colorFilter
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
enum class ImageState {
|
|
34
38
|
Loading, Success, Error
|
|
@@ -131,7 +135,7 @@ fun Image(
|
|
|
131
135
|
contentDescription = null,
|
|
132
136
|
contentScale = imageOptions.contentScale,
|
|
133
137
|
alignment = imageOptions.alignment,
|
|
134
|
-
colorFilter = imageOptions.
|
|
138
|
+
colorFilter = imageOptions.effectiveColorFilter,
|
|
135
139
|
alpha = imageOptions.alpha,
|
|
136
140
|
onLoading = { imageState = ImageState.Loading },
|
|
137
141
|
onSuccess = { imageState = ImageState.Success },
|
|
@@ -154,7 +158,7 @@ fun Image(
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
// Extracted URL processing logic for better performance
|
|
157
|
-
|
|
161
|
+
internal fun processImageUrl(url: String, maxWidth: androidx.compose.ui.unit.Dp, density: androidx.compose.ui.unit.Density): String {
|
|
158
162
|
val host = getHostFromUrl(url)
|
|
159
163
|
return if (host != null && supportedDomains.contains(host)) {
|
|
160
164
|
val widthPx = with(density) { maxWidth.roundToPx() }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import androidx.compose.foundation.border
|
|
5
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
6
|
+
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
7
|
+
import androidx.compose.runtime.Composable
|
|
8
|
+
import androidx.compose.runtime.remember
|
|
9
|
+
import androidx.compose.ui.Modifier
|
|
10
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
11
|
+
import androidx.compose.ui.semantics.contentDescription
|
|
12
|
+
import androidx.compose.ui.semantics.semantics
|
|
13
|
+
import androidx.compose.ui.unit.dp
|
|
14
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
15
|
+
import vn.momo.kits.const.Colors
|
|
16
|
+
import vn.momo.kits.modifier.conditional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
fun isGifImage(source: Any): Boolean {
|
|
20
|
+
if (source is String) {
|
|
21
|
+
return source.contains(".gif") ||
|
|
22
|
+
source.contains(".webp") ||
|
|
23
|
+
source.contains(".apng") ||
|
|
24
|
+
source.contains(".avif")
|
|
25
|
+
}
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private val urlCache = mutableMapOf<String, String>()
|
|
30
|
+
|
|
31
|
+
@Composable
|
|
32
|
+
fun NativeImage(
|
|
33
|
+
source: Any,
|
|
34
|
+
modifier: Modifier = Modifier,
|
|
35
|
+
options: Options? = null,
|
|
36
|
+
loading: Boolean = true
|
|
37
|
+
) {
|
|
38
|
+
BoxWithConstraints(
|
|
39
|
+
modifier = modifier
|
|
40
|
+
.conditional(IsShowBaseLineDebug) {
|
|
41
|
+
border(1.dp, Colors.blue_03)
|
|
42
|
+
}
|
|
43
|
+
.semantics { contentDescription = "img|$source" },
|
|
44
|
+
) {
|
|
45
|
+
val density = LocalDensity.current
|
|
46
|
+
val urlToLoad = remember(source, maxWidth, maxHeight, density) {
|
|
47
|
+
when (source) {
|
|
48
|
+
is String -> {
|
|
49
|
+
// Check cache first
|
|
50
|
+
val cacheKey = "$source|${maxWidth}|${density.density}"
|
|
51
|
+
urlCache[cacheKey] ?: run {
|
|
52
|
+
val processedUrl = processImageUrl(source, maxWidth, density)
|
|
53
|
+
urlCache[cacheKey] = processedUrl
|
|
54
|
+
processedUrl
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
else -> source
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
NativeImageWrapper(urlToLoad, modifier, options, loading)
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@Composable
|
|
69
|
+
expect fun BoxScope.NativeImageWrapper(
|
|
70
|
+
urlToLoad: Any,
|
|
71
|
+
modifier: Modifier = Modifier,
|
|
72
|
+
options: Options? = null,
|
|
73
|
+
loading: Boolean = true,
|
|
74
|
+
)
|
|
@@ -11,12 +11,15 @@ import androidx.compose.foundation.border
|
|
|
11
11
|
import androidx.compose.foundation.layout.Box
|
|
12
12
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
13
13
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
14
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
14
15
|
import androidx.compose.runtime.Composable
|
|
15
16
|
import androidx.compose.runtime.remember
|
|
16
17
|
import androidx.compose.ui.Modifier
|
|
18
|
+
import androidx.compose.ui.draw.clip
|
|
17
19
|
import androidx.compose.ui.draw.drawBehind
|
|
18
20
|
import androidx.compose.ui.geometry.Offset
|
|
19
21
|
import androidx.compose.ui.graphics.Brush
|
|
22
|
+
import androidx.compose.ui.unit.Dp
|
|
20
23
|
import androidx.compose.ui.unit.dp
|
|
21
24
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
22
25
|
import vn.momo.kits.const.Colors
|
|
@@ -34,7 +37,7 @@ private const val SHADOW_BRUSH_PERCENTAGE = 0.6f
|
|
|
34
37
|
private const val DEFAULT_DURATION_MILLIS = 1000
|
|
35
38
|
|
|
36
39
|
@Composable
|
|
37
|
-
fun Skeleton() {
|
|
40
|
+
fun Skeleton(cornerRadius: Dp = 0.dp) {
|
|
38
41
|
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
|
39
42
|
// Memoize width calculation
|
|
40
43
|
val maxWidthValue = remember(maxWidth) { maxWidth.value.toInt() }
|
|
@@ -42,6 +45,7 @@ fun Skeleton() {
|
|
|
42
45
|
Box(
|
|
43
46
|
modifier = Modifier
|
|
44
47
|
.fillMaxSize()
|
|
48
|
+
.clip(RoundedCornerShape(cornerRadius))
|
|
45
49
|
.background(Colors.black_05)
|
|
46
50
|
.conditional(IsShowBaseLineDebug) {
|
|
47
51
|
border(1.dp, Colors.blue_03)
|
|
@@ -13,7 +13,6 @@ import vn.momo.kits.navigation.component.HeaderBackProps
|
|
|
13
13
|
import vn.momo.kits.navigation.component.HeaderRight
|
|
14
14
|
import vn.momo.kits.navigation.component.HeaderTitle
|
|
15
15
|
import vn.momo.kits.navigation.component.HeaderType
|
|
16
|
-
import vn.momo.kits.navigation.component.TitlePosition
|
|
17
16
|
|
|
18
17
|
class Navigation(
|
|
19
18
|
val id: Int = -1,
|
|
@@ -29,7 +28,6 @@ class Navigation(
|
|
|
29
28
|
hiddenBack: Boolean? = null,
|
|
30
29
|
headerBackProps: HeaderBackProps? = null,
|
|
31
30
|
headerTitle: HeaderTitle? = null,
|
|
32
|
-
titlePosition: TitlePosition? = null,
|
|
33
31
|
headerRight: HeaderRight? = null,
|
|
34
32
|
headerType: HeaderType? = null,
|
|
35
33
|
scrollData: ScrollData? = null,
|
|
@@ -45,7 +43,6 @@ class Navigation(
|
|
|
45
43
|
hiddenBack = hiddenBack ?: options.hiddenBack,
|
|
46
44
|
headerBackProps = headerBackProps ?: options.headerBackProps,
|
|
47
45
|
headerTitle = headerTitle ?: options.headerTitle,
|
|
48
|
-
titlePosition = titlePosition ?: options.titlePosition,
|
|
49
46
|
headerRight = headerRight ?: options.headerRight,
|
|
50
47
|
headerType = headerType ?: options.headerType,
|
|
51
48
|
scrollData = scrollData ?: options.scrollData,
|
|
@@ -80,7 +77,6 @@ data class NavigationOptions(
|
|
|
80
77
|
val hiddenBack: Boolean = false,
|
|
81
78
|
val headerBackProps: HeaderBackProps = HeaderBackProps(),
|
|
82
79
|
val headerTitle: HeaderTitle = HeaderTitle.Default("Stack"),
|
|
83
|
-
val titlePosition: TitlePosition = TitlePosition.LEFT,
|
|
84
80
|
val headerRight: HeaderRight = HeaderRight.Toolkit(),
|
|
85
81
|
val headerType: HeaderType = HeaderType.Default(),
|
|
86
82
|
val scrollData: ScrollData = ScrollData(),
|
|
@@ -6,6 +6,7 @@ import androidx.compose.foundation.border
|
|
|
6
6
|
import androidx.compose.foundation.layout.Arrangement
|
|
7
7
|
import androidx.compose.foundation.layout.Box
|
|
8
8
|
import androidx.compose.foundation.layout.Row
|
|
9
|
+
import androidx.compose.foundation.layout.RowScope
|
|
9
10
|
import androidx.compose.foundation.layout.Spacer
|
|
10
11
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
12
|
import androidx.compose.foundation.layout.height
|
|
@@ -22,7 +23,6 @@ import androidx.compose.ui.graphics.Brush
|
|
|
22
23
|
import androidx.compose.ui.graphics.Color
|
|
23
24
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
24
25
|
import androidx.compose.ui.platform.LocalDensity
|
|
25
|
-
import androidx.compose.ui.text.style.TextAlign
|
|
26
26
|
import androidx.compose.ui.unit.Dp
|
|
27
27
|
import androidx.compose.ui.unit.dp
|
|
28
28
|
import vn.momo.kits.components.Icon
|
|
@@ -44,7 +44,6 @@ import vn.momo.kits.navigation.getInputSearchType
|
|
|
44
44
|
|
|
45
45
|
const val HEADER_HEIGHT = 52
|
|
46
46
|
enum class InputSearchType { None, Header, Animated }
|
|
47
|
-
enum class TitlePosition { LEFT, CENTER }
|
|
48
47
|
|
|
49
48
|
@Composable
|
|
50
49
|
fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
@@ -77,13 +76,6 @@ fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
77
76
|
AppTheme.current.colors.background.surface
|
|
78
77
|
|
|
79
78
|
if (options.headerType == HeaderType.None) return
|
|
80
|
-
|
|
81
|
-
val titlePosition = options.titlePosition
|
|
82
|
-
val titleColor = headerColor.tintIconColor
|
|
83
|
-
.copy(alpha = if (inputSearchType == InputSearchType.Animated) 1f - animatedAlpha else 1f)
|
|
84
|
-
// Centered title needs a full-width overlay to stay screen-centered, not boxed between back button and header right.
|
|
85
|
-
val centerTitle = titlePosition == TitlePosition.CENTER && options.headerTitle is HeaderTitle.Default
|
|
86
|
-
|
|
87
79
|
Box(
|
|
88
80
|
Modifier.height(AppStatusBar.current + HEADER_HEIGHT.dp)
|
|
89
81
|
.fillMaxWidth()
|
|
@@ -112,37 +104,17 @@ fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
112
104
|
Spacer(Modifier.width(Spacing.M))
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
tintIconColor = titleColor,
|
|
121
|
-
titlePosition = titlePosition,
|
|
122
|
-
modifier = Modifier.weight(1f)
|
|
123
|
-
)
|
|
124
|
-
}
|
|
107
|
+
HeaderContent(
|
|
108
|
+
options.headerTitle,
|
|
109
|
+
headerColor.tintIconColor
|
|
110
|
+
.copy(alpha = if (inputSearchType == InputSearchType.Animated) 1f - animatedAlpha else 1f)
|
|
111
|
+
)
|
|
125
112
|
Box(Modifier.onGloballyPositioned {
|
|
126
113
|
if(headerRightWidthPx.intValue != it.size.width) headerRightWidthPx.intValue = it.size.width
|
|
127
114
|
}){
|
|
128
115
|
HeaderRight(options.headerRight, options.tintColor, headerColor)
|
|
129
116
|
}
|
|
130
117
|
}
|
|
131
|
-
if (centerTitle) {
|
|
132
|
-
Box(
|
|
133
|
-
modifier = Modifier.height(HEADER_HEIGHT.dp)
|
|
134
|
-
.fillMaxWidth()
|
|
135
|
-
.padding(horizontal = Spacing.M),
|
|
136
|
-
contentAlignment = Alignment.Center
|
|
137
|
-
) {
|
|
138
|
-
HeaderContent(
|
|
139
|
-
headerTitle = options.headerTitle,
|
|
140
|
-
tintIconColor = titleColor,
|
|
141
|
-
titlePosition = titlePosition,
|
|
142
|
-
modifier = Modifier.fillMaxWidth()
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
118
|
VerticalShadow(opacity)
|
|
147
119
|
}
|
|
148
120
|
}
|
|
@@ -168,23 +140,15 @@ private fun BackButton(borderColor: Color, backgroundButton: Color, tintIconColo
|
|
|
168
140
|
}
|
|
169
141
|
|
|
170
142
|
@Composable
|
|
171
|
-
fun HeaderContent(
|
|
172
|
-
headerTitle: HeaderTitle,
|
|
173
|
-
tintIconColor: Color,
|
|
174
|
-
titlePosition: TitlePosition = TitlePosition.LEFT,
|
|
175
|
-
modifier: Modifier = Modifier,
|
|
176
|
-
){
|
|
143
|
+
fun RowScope.HeaderContent(headerTitle: HeaderTitle, tintIconColor: Color){
|
|
177
144
|
Box(
|
|
178
|
-
|
|
179
|
-
contentAlignment = if (titlePosition == TitlePosition.CENTER) Alignment.Center else Alignment.CenterStart
|
|
145
|
+
Modifier.weight(1f)
|
|
180
146
|
) {
|
|
181
147
|
when (headerTitle){
|
|
182
148
|
is HeaderTitle.Default -> {
|
|
183
149
|
HeaderTitle(
|
|
184
150
|
title = headerTitle.title,
|
|
185
|
-
color = tintIconColor
|
|
186
|
-
modifier = Modifier.fillMaxWidth(fraction = if (titlePosition == TitlePosition.CENTER) 0.5f else 1f),
|
|
187
|
-
textAlign = if (titlePosition == TitlePosition.CENTER) TextAlign.Center else TextAlign.Start
|
|
151
|
+
color = tintIconColor
|
|
188
152
|
)
|
|
189
153
|
}
|
|
190
154
|
is HeaderTitle.Journey -> {}
|
|
@@ -16,13 +16,11 @@ import vn.momo.kits.modifier.setAutomationId
|
|
|
16
16
|
fun HeaderTitle(
|
|
17
17
|
title: String = "",
|
|
18
18
|
color: Color? = null,
|
|
19
|
-
modifier: Modifier = Modifier.fillMaxWidth(),
|
|
20
|
-
textAlign: TextAlign = TextAlign.Start,
|
|
21
19
|
) {
|
|
22
20
|
Text(
|
|
23
|
-
modifier =
|
|
21
|
+
modifier = Modifier.fillMaxWidth().zIndex(1f).setAutomationId("title_navigation_header"),
|
|
24
22
|
text = title,
|
|
25
|
-
textAlign =
|
|
23
|
+
textAlign = TextAlign.Start,
|
|
26
24
|
style = Typography.actionSBold.copy(
|
|
27
25
|
fontSize = 15.sp,
|
|
28
26
|
lineHeight = 22.sp,
|
|
@@ -11,6 +11,7 @@ import androidx.compose.ui.graphics.Color
|
|
|
11
11
|
import io.github.alexzhirkevich.compottie.Compottie
|
|
12
12
|
import io.github.alexzhirkevich.compottie.ExperimentalCompottieApi
|
|
13
13
|
import io.github.alexzhirkevich.compottie.LottieCompositionSpec
|
|
14
|
+
import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState
|
|
14
15
|
import io.github.alexzhirkevich.compottie.dynamic.rememberLottieDynamicProperties
|
|
15
16
|
import io.github.alexzhirkevich.compottie.rememberLottieComposition
|
|
16
17
|
import io.github.alexzhirkevich.compottie.rememberLottiePainter
|
|
@@ -48,12 +49,21 @@ internal fun ComposeLottieAnimation(
|
|
|
48
49
|
null
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
val lottieState = animateLottieCompositionAsState(
|
|
53
|
+
composition = composition,
|
|
54
|
+
iterations = Compottie.IterateForever,
|
|
55
|
+
)
|
|
56
|
+
|
|
51
57
|
Box(modifier.background(bgColor ?: Color.Transparent)) {
|
|
52
58
|
Image(
|
|
53
59
|
painter = rememberLottiePainter(
|
|
54
60
|
composition = composition,
|
|
55
|
-
|
|
61
|
+
progress = lottieState::value,
|
|
56
62
|
dynamicProperties = dynamicProperties,
|
|
63
|
+
enableExpressions = false,
|
|
64
|
+
expressionEngineFactory = { _, _ ->
|
|
65
|
+
error("Lottie JS expressions are disabled in MoMo kits")
|
|
66
|
+
},
|
|
57
67
|
),
|
|
58
68
|
contentDescription = null,
|
|
59
69
|
modifier = Modifier.fillMaxSize(),
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
4
|
+
import androidx.compose.runtime.*
|
|
5
|
+
import androidx.compose.ui.Alignment
|
|
6
|
+
import androidx.compose.ui.Modifier
|
|
7
|
+
import androidx.compose.ui.graphics.Color
|
|
8
|
+
import androidx.compose.ui.layout.ContentScale
|
|
9
|
+
import androidx.compose.ui.unit.dp
|
|
10
|
+
import androidx.compose.ui.viewinterop.UIKitInteropProperties
|
|
11
|
+
import androidx.compose.ui.viewinterop.UIKitView
|
|
12
|
+
import cocoapods.SDWebImage.*
|
|
13
|
+
import kotlinx.cinterop.ExperimentalForeignApi
|
|
14
|
+
import platform.Foundation.NSURL
|
|
15
|
+
import platform.UIKit.UIImageView
|
|
16
|
+
import platform.UIKit.UIViewContentMode
|
|
17
|
+
import vn.momo.kits.const.AppTheme
|
|
18
|
+
import vn.momo.kits.platform.toUIColor
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
22
|
+
private val sdImageOptions = SDWebImageRetryFailed or
|
|
23
|
+
SDWebImageContinueInBackground or
|
|
24
|
+
SDWebImageScaleDownLargeImages or
|
|
25
|
+
SDWebImageHighPriority
|
|
26
|
+
|
|
27
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
28
|
+
@Composable
|
|
29
|
+
actual fun BoxScope.NativeImageWrapper(
|
|
30
|
+
urlToLoad: Any,
|
|
31
|
+
modifier: Modifier,
|
|
32
|
+
options: Options?,
|
|
33
|
+
loading: Boolean
|
|
34
|
+
) {
|
|
35
|
+
val imageOptions = remember(options) {
|
|
36
|
+
options ?: Options()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var imageState by remember { mutableStateOf(ImageState.Loading) }
|
|
40
|
+
(urlToLoad as? String)?.let { urlString ->
|
|
41
|
+
val contentModeValue = remember(imageOptions.contentScale) {
|
|
42
|
+
imageOptions.contentScale.toUIViewContentMode()
|
|
43
|
+
}
|
|
44
|
+
key(urlString) {
|
|
45
|
+
val isGif = isGifImage(urlString)
|
|
46
|
+
val imageView: UIImageView = remember {
|
|
47
|
+
if (isGif) {
|
|
48
|
+
SDAnimatedImageView().apply {
|
|
49
|
+
shouldCustomLoopCount = false
|
|
50
|
+
shouldIncrementalLoad = true
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
UIImageView()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
val loadImage: (Color?) -> Unit = { tint ->
|
|
59
|
+
imageState = ImageState.Loading
|
|
60
|
+
val context = tint?.let {
|
|
61
|
+
mapOf<Any?, Any?>(
|
|
62
|
+
SDWebImageContextImageTransformer to
|
|
63
|
+
SDImageTintTransformer.transformerWithColor(it.toUIColor()),
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
imageView.sd_setImageWithURL(
|
|
67
|
+
NSURL.URLWithString(urlString),
|
|
68
|
+
placeholderImage = null,
|
|
69
|
+
options = sdImageOptions,
|
|
70
|
+
context = context,
|
|
71
|
+
progress = null,
|
|
72
|
+
completed = { image, error, _, _ ->
|
|
73
|
+
imageState = if (image != null && error == null) {
|
|
74
|
+
ImageState.Success
|
|
75
|
+
} else {
|
|
76
|
+
ImageState.Error
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
val appliedTint = remember { mutableStateOf(imageOptions.tint) }
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
DisposableEffect(Unit) {
|
|
87
|
+
onDispose { imageView.sd_cancelCurrentImageLoad() }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
LaunchedEffect(Unit) {
|
|
91
|
+
appliedTint.value = imageOptions.tint
|
|
92
|
+
loadImage(imageOptions.tint)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
UIKitView(
|
|
96
|
+
modifier = Modifier.matchParentSize(),
|
|
97
|
+
properties = UIKitInteropProperties(),
|
|
98
|
+
factory = {
|
|
99
|
+
imageView.apply {
|
|
100
|
+
this.contentMode = contentModeValue
|
|
101
|
+
setAlpha(imageOptions.alpha.toDouble())
|
|
102
|
+
translatesAutoresizingMaskIntoConstraints = true
|
|
103
|
+
sd_imageTransition = SDWebImageTransition.fadeTransitionWithDuration(0.3)
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
update = { view ->
|
|
107
|
+
view.contentMode = contentModeValue
|
|
108
|
+
view.setAlpha(imageOptions.alpha.toDouble())
|
|
109
|
+
|
|
110
|
+
if (appliedTint.value != imageOptions.tint) {
|
|
111
|
+
appliedTint.value = imageOptions.tint
|
|
112
|
+
loadImage(imageOptions.tint)
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
when (imageState) {
|
|
119
|
+
ImageState.Loading -> if (loading) Skeleton(cornerRadius = options?.skeletonCornerRadius ?: 0.dp)
|
|
120
|
+
ImageState.Error -> {
|
|
121
|
+
val theme = AppTheme.current
|
|
122
|
+
Icon(
|
|
123
|
+
source = "media_fail",
|
|
124
|
+
color = theme.colors.text.disable,
|
|
125
|
+
modifier = Modifier.align(Alignment.Center)
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ImageState.Success -> {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private fun ContentScale.toUIViewContentMode(): UIViewContentMode = when (this) {
|
|
135
|
+
ContentScale.Crop, ContentScale.FillWidth, ContentScale.FillHeight ->
|
|
136
|
+
UIViewContentMode.UIViewContentModeScaleAspectFill
|
|
137
|
+
|
|
138
|
+
ContentScale.Fit, ContentScale.Inside -> UIViewContentMode.UIViewContentModeScaleAspectFit
|
|
139
|
+
ContentScale.FillBounds -> UIViewContentMode.UIViewContentModeScaleToFill
|
|
140
|
+
ContentScale.None -> UIViewContentMode.UIViewContentModeCenter
|
|
141
|
+
else -> UIViewContentMode.UIViewContentModeScaleAspectFill
|
|
142
|
+
}
|
|
Binary file
|
|
@@ -13,7 +13,7 @@ androidGradlePlugin = "8.13.2"
|
|
|
13
13
|
r8 = "8.9.35"
|
|
14
14
|
kotlinx-datetime = "0.7.1"
|
|
15
15
|
airbnb-lottie = "5.2.0"
|
|
16
|
-
compottie = "2.
|
|
16
|
+
compottie = "2.1.0"
|
|
17
17
|
androidx-activity = "1.9.1"
|
|
18
18
|
androidx-appcompat = "1.7.0"
|
|
19
19
|
material = "1.10.0"
|
|
@@ -21,6 +21,7 @@ maxapi = "0.1.1"
|
|
|
21
21
|
vanniktechMavenPublish = "0.34.0"
|
|
22
22
|
kits = "0.159.1-beta.7"
|
|
23
23
|
nativemaxapi = "0.0.6"
|
|
24
|
+
coilGif = "2.6.0"
|
|
24
25
|
|
|
25
26
|
[libraries]
|
|
26
27
|
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor-version" }
|
|
@@ -37,9 +38,20 @@ navigation-multiplatform = { module = "org.jetbrains.androidx.navigation:navigat
|
|
|
37
38
|
coil-multiplatform-core = { module = "io.coil-kt.coil3:coil-core", version.ref = "coil3-multiplatform" }
|
|
38
39
|
coil-multiplatform-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3-multiplatform" }
|
|
39
40
|
coil-multiplatform-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil3-multiplatform" }
|
|
41
|
+
coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil3-multiplatform" }
|
|
42
|
+
coil-giff = { module = "io.coil-kt:coil-gif", version.ref = "coilGif" }
|
|
43
|
+
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil3-multiplatform" }
|
|
44
|
+
coil-http = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil3-multiplatform" }
|
|
40
45
|
r8 = { module = "com.android.tools:r8", version.ref = "r8" }
|
|
41
46
|
airbnb-lottie = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "airbnb-lottie" }
|
|
42
|
-
|
|
47
|
+
# AI-GENERATED START
|
|
48
|
+
# Depend on compottie-core (not the `compottie` umbrella). The umbrella drags in the
|
|
49
|
+
# separate `keight` umbrella artifact, which collides with `keight-core` on the
|
|
50
|
+
# `findRoot__at__ScriptRuntime` symbol (duplicate symbol at the iOS `ld` link of
|
|
51
|
+
# shared.framework). compottie-core pulls only `keight-core` (which declares
|
|
52
|
+
# keight.js.JsAny, so the earlier unlinked-symbol error stays fixed too).
|
|
53
|
+
compottie = { module = "io.github.alexzhirkevich:compottie-core", version.ref = "compottie" }
|
|
54
|
+
# AI-GENERATED END
|
|
43
55
|
native-max-api = { group = "vn.momo.maxapi", name = "NativeMaxApi", version.ref = "nativemaxapi" }
|
|
44
56
|
kits = { module = "vn.momo.kits:kits", version.ref = "kits" }
|
|
45
57
|
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
|
package/gradle.properties
CHANGED
package/local.properties
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
# Location of the SDK. This is only used by Gradle.
|
|
5
5
|
# For customization when using a Version Control System, please read the
|
|
6
6
|
# header note.
|
|
7
|
-
#
|
|
8
|
-
sdk.dir=/Users/
|
|
7
|
+
#Tue Jun 17 09:25:26 ICT 2025
|
|
8
|
+
sdk.dir=/Users/thanhle/Library/Android/sdk
|
package/package.json
CHANGED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(yarn react-native:*)",
|
|
5
|
-
"Read(//Users/phuc/.claude/skills/ai-skill-generator/**)",
|
|
6
|
-
"Bash(python3 scripts/install_skill.py --update)",
|
|
7
|
-
"Bash(cd /Users/phuc/momo-kits/.claude/skills/design-system-kits && mkdir -p .claude/skills/design-system-kits && mv SKILL.md README.md references .claude/skills/design-system-kits/)",
|
|
8
|
-
"Bash(mv .claude/skills/design-system-kits/* . && rmdir .claude/skills/design-system-kits && rmdir .claude/skills)",
|
|
9
|
-
"Bash(python3 scripts/migrate_screen.py --dry-run sample/shared/src/commonMain/kotlin/screens/ScreenUsage.kt)",
|
|
10
|
-
"Bash(python3 scripts/migrate_screen.py --dry-run sample/shared/src/commonMain/kotlin/screens/AnimatedHeaderUsage.kt sample/shared/src/commonMain/kotlin/screens/SnackBarScreenUsage.kt 2>&1)",
|
|
11
|
-
"Bash(python3 scripts/migrate_screen.py --dry-run sample/shared/src/commonMain/kotlin/screens/SnackBarScreenUsage.kt)",
|
|
12
|
-
"Bash(cp sample/shared/src/commonMain/kotlin/screens/SnackBarScreenUsage.kt /tmp/SnackBarScreenUsage.kt.orig && python3 scripts/migrate_screen.py sample/shared/src/commonMain/kotlin/screens/SnackBarScreenUsage.kt)",
|
|
13
|
-
"Bash(cp -r /Users/phuc/.claude/skills/migrate-screen-to-navigation-container /Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container)",
|
|
14
|
-
"Bash(mkdir -p /Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/scripts && mv /Users/phuc/momo-native-kits-2/momo-native-kits/scripts/migrate_screen.py /Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/scripts/migrate_screen.py)",
|
|
15
|
-
"Bash(python3 .claude/skills/migrate-screen-to-navigation-container/scripts/generate_eval_cases.py)",
|
|
16
|
-
"Bash(SKILL=\".claude/skills/migrate-screen-to-navigation-container\" && python3 $SKILL/scripts/migrate_screen.py $SKILL/eval/input/ 2>&1 | tail -20)",
|
|
17
|
-
"Bash(SKILL=\".claude/skills/migrate-screen-to-navigation-container\" && cp -r $SKILL/eval/input $SKILL/eval/output && python3 $SKILL/scripts/migrate_screen.py $SKILL/eval/output/ 2>&1 | tail -20)",
|
|
18
|
-
"Bash(SKILL=\".claude/skills/migrate-screen-to-navigation-container\" && cp -r $SKILL/eval/input $SKILL/eval/output && python3 $SKILL/scripts/migrate_screen.py $SKILL/eval/output/ 2>&1 | tail -25)",
|
|
19
|
-
"Bash(SKILL=\".claude/skills/migrate-screen-to-navigation-container\" && cp -r \"$SKILL/eval/input\" \"$SKILL/eval/output\" && python3 \"$SKILL/scripts/migrate_screen.py\" \"$SKILL/eval/output/\" 2>&1 | tail -25)",
|
|
20
|
-
"Bash(SKILL=\"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container\"\ncp -r \"$SKILL/eval/input\" \"$SKILL/eval/output\"\npython3 \"$SKILL/scripts/migrate_screen.py\" \"$SKILL/eval/output/\" 2>&1 | tail -25)",
|
|
21
|
-
"Bash(cd \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container\" && python3 scripts/eval_migration.py 2>&1 | tail -80)",
|
|
22
|
-
"Bash(cd \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container\" && python3 scripts/eval_migration.py 2>&1 | head -30)",
|
|
23
|
-
"Bash(SKILL=\"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container\" && rm -rf \"$SKILL/eval/output\" && cp -r \"$SKILL/eval/input\" \"$SKILL/eval/output\" && python3 \"$SKILL/scripts/migrate_screen.py\" \"$SKILL/eval/output/\" 2>&1 | tail -5)",
|
|
24
|
-
"Bash(SKILL=\"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container\"; rm -rf \"${SKILL}/eval/output\"; cp -r \"${SKILL}/eval/input\" \"${SKILL}/eval/output\" && python3 \"${SKILL}/scripts/migrate_screen.py\" \"${SKILL}/eval/output/\" 2>&1 | tail -5)",
|
|
25
|
-
"Bash(rm -rf \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/eval/output\" && cp -r \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/eval/input\" \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/eval/output\" && python3 \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/scripts/migrate_screen.py\" \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/eval/output/\" 2>&1 | tail -5)",
|
|
26
|
-
"Bash(python3 \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/scripts/eval_migration.py\" 2>&1 | head -35)",
|
|
27
|
-
"Bash(python3 \"/Users/phuc/momo-native-kits-2/momo-native-kits/.claude/skills/migrate-screen-to-navigation-container/scripts/eval_migration.py\" 2>&1 | grep -E \"^=|Files eval|Passed|Failed|A0[0-9]|Per-Group|Failures\" | head -40)",
|
|
28
|
-
"Bash(find /Users/phuc/momo-kits -name \"*.kt\" 2>/dev/null | head -20)",
|
|
29
|
-
"Bash(find /Users/phuc -name \"*PaymentConfirmScreenFull*\" 2>/dev/null | head -10)"
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
}
|