@momo-kits/native-kits 0.163.1-beta.7-debug → 0.163.1-beta.8-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.
Files changed (32) hide show
  1. package/.claude/settings.local.json +62 -0
  2. package/compose/build.gradle.kts +2 -1
  3. package/compose/build.gradle.kts.backup +1 -0
  4. package/compose/compose.podspec +1 -1
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +3 -10
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +11 -9
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +40 -55
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -6
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +22 -21
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +4 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +19 -40
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +11 -6
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +3 -4
  16. package/gradle/libs.versions.toml +2 -0
  17. package/gradle.properties +1 -1
  18. package/ios/Application/ApplicationEnvironment.swift +0 -1
  19. package/ios/Typography/Text.swift +7 -12
  20. package/ios/Typography/Typography.swift +9 -14
  21. package/local.properties +2 -2
  22. package/package.json +1 -1
  23. package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  24. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  25. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
  26. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  27. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  28. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
  29. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
  30. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
  31. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
  32. package/ios/Application/FontScaleStore.swift +0 -59
@@ -0,0 +1,62 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
5
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/maven-metadata.xml\")",
6
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/maven-metadata.xml\")",
7
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/0.144.6/skiko-0.144.6.module\")",
8
+ "Bash(curl -s -o /dev/null -w '%{http_code}' https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.__TRACKED_VAR__)",
9
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/\")",
10
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/\")",
11
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.148.1/skiko-android-0.148.1.pom\")",
12
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.5/skiko-android-0.144.5.pom\")",
13
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://dl.google.com/dl/android/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
14
+ "Bash(curl -s \"https://search.maven.org/solrsearch/select?q=a:skiko-android&rows=5&wt=json\")",
15
+ "Bash(curl -sL -o /dev/null -w \"%{http_code} %{url_effective}\\\\n\" \"https://repo1.maven.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
16
+ "Bash(curl *)",
17
+ "Bash(./gradlew :sample:androidApp:dependencies --configuration debugRuntimeClasspath)",
18
+ "Bash(./gradlew :sample:shared:compileDebugKotlinAndroid)",
19
+ "Bash(./gradlew :sample:androidApp:compileDebugKotlin)",
20
+ "Bash(ls -d *.xcodeproj)",
21
+ "Bash(grep -c ChipDemo.swift __TRACKED_VAR__/project.pbxproj)",
22
+ "Bash(grep -c PBXFileSystemSynchronizedRootGroup __TRACKED_VAR__/project.pbxproj)",
23
+ "Bash(grep -c InformationDemo.swift __TRACKED_VAR__/project.pbxproj)",
24
+ "Bash(plutil -lint project.pbxproj)",
25
+ "Bash(xcode-select -p)",
26
+ "Bash(/usr/bin/xcrun xcodebuild *)",
27
+ "Bash(echo \"exit: $?\")",
28
+ "Bash(/Applications/Xcode26.app/Contents/Developer/usr/bin/xcodebuild -version)",
29
+ "Bash(./gradlew :compose:tasks --all)",
30
+ "Bash(grep -n \"vanniktech\\\\|mavenPublishing\\\\|^publishing\\\\|version = gitlabVersion\\\\|namespace =\\\\|compileSdk =\\\\|minSdk =\\\\|GitLabPackages\\\\|name = \\\\\"\\\\${gitlabRepo}Packages\\\\\"\")",
31
+ "Bash(git checkout *)",
32
+ "Bash(./gradlew :compose:publishAllPublicationsToGitLabPackagesRepository --dry-run)",
33
+ "Bash(git fetch *)",
34
+ "Bash(git pull *)",
35
+ "Bash(git push *)",
36
+ "Bash(git branch *)",
37
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -list)",
38
+ "Bash(xcrun simctl *)",
39
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
40
+ "Bash(pod install *)",
41
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
42
+ "Bash(xcrun swiftc *)",
43
+ "Bash(echo \"=== exit: $? \\(1=none found\\) ===\")",
44
+ "Bash(cat Radius.swift)",
45
+ "Bash(cat Spacing.swift)",
46
+ "Bash(echo \"exit=$?\")",
47
+ "Bash(ruby *)",
48
+ "Bash(xcodebuild -list -workspace iosApp.xcworkspace)",
49
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' build)",
50
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
51
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
52
+ "Bash(xcodebuild build *)",
53
+ "Bash(git stash *)",
54
+ "Bash(plutil -lint sample/iosApp/iosApp.xcodeproj/project.pbxproj)",
55
+ "Bash(git add *)",
56
+ "Bash(git commit -m 'feat: DS-646 [ci build] *)",
57
+ "Bash(git commit -q -m 'fix: DS-656 [ci build] *)",
58
+ "Bash(xargs git log --oneline -1)",
59
+ "Bash(git rev-list *)"
60
+ ]
61
+ }
62
+ }
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.163.1-beta.7-debug"
43
+ version = "0.163.1-beta.8-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -77,6 +77,7 @@ kotlin {
77
77
  implementation(libs.kotlinx.datetime)
78
78
  api(libs.native.max.api)
79
79
  implementation(libs.compottie)
80
+ implementation(libs.gif.image)
80
81
  }
81
82
  androidMain.dependencies {
82
83
  implementation(libs.ktor.client.okhttp)
@@ -77,6 +77,7 @@ kotlin {
77
77
  implementation(libs.kotlinx.datetime)
78
78
  api(libs.native.max.api)
79
79
  implementation(libs.compottie)
80
+ implementation(libs.gif.image)
80
81
  }
81
82
  androidMain.dependencies {
82
83
  implementation(libs.ktor.client.okhttp)
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.163.1-beta.1'
3
+ spec.version = '0.162.2-beta.19'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -28,7 +28,7 @@ data class MiniAppContext(
28
28
  val providerId: String = "",
29
29
  val permissions: List<Map<String, Any>>? = emptyList(),
30
30
  val features: FeatureFlags? = null,
31
- val fontScaleConfig: FontScaleConfig? = null,
31
+ val scaleSizeMaxRate: Float? = null,
32
32
  ) {
33
33
  companion object {
34
34
 
@@ -60,7 +60,7 @@ data class MiniAppContext(
60
60
  providerId = parent.providerId.ifBlank { child.providerId },
61
61
  permissions = if (!parent.permissions.isNullOrEmpty()) parent.permissions else child.permissions,
62
62
  features = mergeFeatureFlags(parent.features, child.features),
63
- fontScaleConfig = parent.fontScaleConfig ?: child.fontScaleConfig,
63
+ scaleSizeMaxRate = parent.scaleSizeMaxRate ?: child.scaleSizeMaxRate,
64
64
  )
65
65
  }
66
66
 
@@ -98,11 +98,4 @@ val LocalComponentInformation = staticCompositionLocalOf<ComponentInformation?>
98
98
  null
99
99
  }
100
100
 
101
- @Immutable
102
- data class FontScaleConfig(
103
- val useOSFontScale: Boolean = true,
104
- val userScaleRate: Float? = null,
105
- val osFontScale: Float? = null,
106
- )
107
-
108
- val LocalFontScaleConfig = staticCompositionLocalOf { FontScaleConfig() }
101
+ val ScaleSizeMaxRate = staticCompositionLocalOf<Float?> { null }
@@ -95,11 +95,13 @@ private fun getStyle(size: Size): TextStyle {
95
95
  return styleCache[size] ?: Typography.actionDefaultBold
96
96
  }
97
97
 
98
- private fun getIconSize(size: Size): Dp = iconSizeCache[size] ?: 24.dp
98
+ @InternalApi
99
+ fun getIconSize(size: Size): Dp = iconSizeCache[size] ?: 24.dp
99
100
  private fun getIconSpace(size: Size): Dp = iconSpaceCache[size] ?: Spacing.S
100
101
 
102
+ @InternalApi
101
103
  @Composable
102
- internal fun getTextColor(loading: Boolean, type: ButtonType): Color {
104
+ fun getTextColor(loading: Boolean, type: ButtonType): Color {
103
105
  val theme = AppTheme.current
104
106
 
105
107
  return remember(type, theme, loading) {
@@ -140,7 +142,6 @@ fun RenderIcon(
140
142
  useTintColor: Boolean = true,
141
143
  icon: String = "",
142
144
  forceLoading: Boolean = false,
143
- bgColor: Color? = null,
144
145
  textColor: Color? = null,
145
146
  ) {
146
147
  val iconSize = remember(size) { getIconSize(size) }
@@ -156,9 +157,10 @@ fun RenderIcon(
156
157
  Box(modifier) {
157
158
  LottieAnimation(
158
159
  modifier = Modifier.size(iconSize),
159
- bgColor = bgColor,
160
+ bgColor = null,
160
161
  tintColor = color,
161
- path = "files/lottie_circle_loader"
162
+ path = "files/lottie_circle_loader",
163
+ useCompose = true
162
164
  )
163
165
  }
164
166
  } else if (icon.isNotEmpty()) {
@@ -183,8 +185,9 @@ private fun shouldLoadingOnLeft(iconLeft: String, iconRight: String): Boolean {
183
185
  }
184
186
  }
185
187
 
188
+ @InternalApi
186
189
  @Composable
187
- internal fun getTypeStyle(
190
+ fun getTypeStyle(
188
191
  type: ButtonType,
189
192
  color: Color? = AppTheme.current.colors.primary,
190
193
  size: Size,
@@ -219,8 +222,9 @@ internal fun getTypeStyle(
219
222
  }
220
223
  }
221
224
 
225
+ @InternalApi
222
226
  @Composable
223
- internal fun getButtonBackgroundColor(
227
+ fun getButtonBackgroundColor(
224
228
  loading: Boolean,
225
229
  type: ButtonType
226
230
  ): Color {
@@ -336,7 +340,6 @@ fun Button(
336
340
  useTintColor = useTintColor,
337
341
  icon = iconLeft,
338
342
  forceLoading = loading && loadingOnLeft,
339
- bgColor = bgColor,
340
343
  textColor = textColor
341
344
  )
342
345
  RenderTitle(size, title, textColor = textColor)
@@ -346,7 +349,6 @@ fun Button(
346
349
  useTintColor = useTintColor,
347
350
  icon = iconRight,
348
351
  forceLoading = loading && !loadingOnLeft,
349
- bgColor = bgColor,
350
352
  textColor = textColor
351
353
  )
352
354
  }
@@ -5,6 +5,7 @@ import androidx.compose.foundation.border
5
5
  import androidx.compose.foundation.clickable
6
6
  import androidx.compose.foundation.layout.Box
7
7
  import androidx.compose.foundation.layout.size
8
+ import androidx.compose.foundation.shape.CircleShape
8
9
  import androidx.compose.foundation.shape.RoundedCornerShape
9
10
  import androidx.compose.runtime.Composable
10
11
  import androidx.compose.runtime.remember
@@ -12,6 +13,7 @@ import androidx.compose.ui.Alignment
12
13
  import androidx.compose.ui.Modifier
13
14
  import androidx.compose.ui.draw.clip
14
15
  import androidx.compose.ui.graphics.Color
16
+ import androidx.compose.ui.graphics.Shape
15
17
  import androidx.compose.ui.unit.Dp
16
18
  import androidx.compose.ui.unit.dp
17
19
  import vn.momo.kits.application.IsShowBaseLineDebug
@@ -21,69 +23,58 @@ import vn.momo.kits.const.Radius
21
23
  import vn.momo.kits.modifier.conditional
22
24
 
23
25
  data class IconSpecs(
24
- val width: Dp,
25
- val height: Dp,
26
- val radius: Dp,
27
- val size: Dp
26
+ val box: Dp,
27
+ val icon: Dp,
28
28
  )
29
29
 
30
30
  enum class IconSize(val value: IconSpecs) {
31
- LARGE(
32
- IconSpecs(
33
- height = 48.dp,
34
- radius = Radius.XL,
35
- width = 48.dp,
36
- size = 48.dp
37
- )
38
- ),
39
- SMALL(
40
- IconSpecs(
41
- height = 40.dp,
42
- radius = Radius.XL,
43
- width = 40.dp,
44
- size = 40.dp
45
- )
46
- )
31
+ LARGE(IconSpecs(box = 48.dp, icon = 24.dp)),
32
+ MEDIUM(IconSpecs(box = 36.dp, icon = 16.dp)),
33
+ SMALL(IconSpecs(box = 28.dp, icon = 16.dp)),
47
34
  }
48
35
 
49
- // Pre-computed shape to avoid repeated creation
50
- private val iconButtonShape = RoundedCornerShape(Radius.XL)
36
+ enum class IconShape {
37
+ CIRCLE,
38
+ SQUARE,
39
+ }
51
40
 
52
- // Cache for icon sizes to avoid repeated calculations
53
- private val iconSizeMap = mapOf(
54
- IconSize.SMALL to 16.dp,
55
- IconSize.LARGE to 24.dp
56
- )
41
+ // Square keeps an 8dp rounded corner; circle is fully rounded regardless of size.
42
+ private val squareShape = RoundedCornerShape(Radius.S)
43
+
44
+ private fun IconShape.toShape(): Shape = when (this) {
45
+ IconShape.CIRCLE -> CircleShape
46
+ IconShape.SQUARE -> squareShape
47
+ }
57
48
 
58
49
  @Composable
59
- internal fun getIconStyle(type: ButtonType, color: Color? = null): Modifier {
50
+ internal fun getIconStyle(type: ButtonType, shape: Shape, color: Color? = null): Modifier {
60
51
  val theme = AppTheme.current
61
52
 
62
- return remember(type, color, theme) {
53
+ return remember(type, shape, color, theme) {
63
54
  when (type) {
64
55
  ButtonType.DISABLED -> Modifier
65
- .background(theme.colors.background.disable, iconButtonShape)
66
- .border(0.dp, Color.Unspecified, iconButtonShape)
56
+ .background(theme.colors.background.disable, shape)
57
+ .border(0.dp, Color.Unspecified, shape)
67
58
 
68
59
  ButtonType.PRIMARY -> Modifier
69
- .background(theme.colors.primary, iconButtonShape)
70
- .border(0.dp, Color.Unspecified, iconButtonShape)
60
+ .background(theme.colors.primary, shape)
61
+ .border(0.dp, Color.Unspecified, shape)
71
62
 
72
63
  ButtonType.SECONDARY -> Modifier
73
- .background(theme.colors.background.surface, iconButtonShape)
74
- .border(1.dp, theme.colors.border.default, iconButtonShape)
64
+ .background(theme.colors.background.surface, shape)
65
+ .border(1.dp, theme.colors.border.default, shape)
75
66
 
76
67
  ButtonType.OUTLINE -> Modifier
77
- .background(theme.colors.background.surface, iconButtonShape)
78
- .border(1.dp, color ?: theme.colors.primary, iconButtonShape)
68
+ .background(theme.colors.background.surface, shape)
69
+ .border(1.dp, color ?: theme.colors.primary, shape)
79
70
 
80
71
  ButtonType.TONAL -> Modifier
81
- .background(theme.colors.background.tonal, iconButtonShape)
82
- .border(0.dp, Color.Unspecified, iconButtonShape)
72
+ .background(theme.colors.background.tonal, shape)
73
+ .border(0.dp, Color.Unspecified, shape)
83
74
 
84
75
  ButtonType.DANGER -> Modifier
85
- .background(theme.colors.error.primary, iconButtonShape)
86
- .border(0.dp, Color.Unspecified, iconButtonShape)
76
+ .background(theme.colors.error.primary, shape)
77
+ .border(0.dp, Color.Unspecified, shape)
87
78
 
88
79
  ButtonType.TEXT -> Modifier
89
80
  }
@@ -103,38 +94,32 @@ fun IconButton(
103
94
  onClick: () -> Unit,
104
95
  type: ButtonType = ButtonType.PRIMARY,
105
96
  size: IconSize = IconSize.SMALL,
97
+ shape: IconShape = IconShape.CIRCLE,
106
98
  icon: String = "",
107
99
  ) {
108
- // Memoize icon size calculation
109
- val iconSize = remember(size) {
110
- iconSizeMap[size] ?: 16.dp
111
- }
100
+ val specs = size.value
112
101
 
113
- // Memoize enabled state
114
102
  val isEnabled = remember(type) {
115
103
  type != ButtonType.DISABLED
116
104
  }
117
105
 
118
- // Memoize radius for clipping
119
- val radius = remember(size) {
120
- size.value.radius
121
- }
106
+ val boxShape = remember(shape) { shape.toShape() }
122
107
 
123
108
  Box(
124
109
  modifier = Modifier
125
- .size(size.value.size)
126
- .then(getIconStyle(type))
110
+ .size(specs.box)
111
+ .then(getIconStyle(type, boxShape))
127
112
  .conditional(IsShowBaseLineDebug) {
128
113
  border(1.dp, Colors.blue_03)
129
114
  }
130
- .clip(RoundedCornerShape(radius))
115
+ .clip(boxShape)
131
116
  .clickable(enabled = isEnabled, onClick = onClick),
132
117
  contentAlignment = Alignment.Center
133
118
  ) {
134
119
  Icon(
135
120
  source = icon,
136
121
  color = getIconColor(type),
137
- size = iconSize,
122
+ size = specs.icon,
138
123
  )
139
124
  }
140
- }
125
+ }
@@ -1,7 +1,9 @@
1
1
  package vn.momo.kits.components
2
2
 
3
3
  import androidx.compose.foundation.border
4
+ import androidx.compose.foundation.layout.Box
4
5
  import androidx.compose.foundation.layout.BoxWithConstraints
6
+ import androidx.compose.foundation.layout.fillMaxSize
5
7
  import androidx.compose.runtime.Composable
6
8
  import androidx.compose.runtime.getValue
7
9
  import androidx.compose.runtime.mutableStateOf
@@ -17,10 +19,12 @@ import androidx.compose.ui.semantics.contentDescription
17
19
  import androidx.compose.ui.semantics.semantics
18
20
  import androidx.compose.ui.platform.LocalDensity
19
21
  import androidx.compose.ui.unit.dp
20
- import coil3.compose.AsyncImage
22
+ import coil3.compose.AsyncImage as CoilAsyncImage
21
23
  import vn.momo.kits.application.IsShowBaseLineDebug
22
24
  import vn.momo.kits.const.AppTheme
23
25
  import vn.momo.kits.const.Colors
26
+ import vn.momo.kits.gifimage.GifImage
27
+ import vn.momo.kits.gifimage.GifImageState
24
28
  import vn.momo.kits.modifier.conditional
25
29
 
26
30
  data class Options(
@@ -73,6 +77,19 @@ private fun getHostFromUrl(url: String): String? {
73
77
  }
74
78
  }
75
79
 
80
+ private fun isGifUrl(url: String): Boolean {
81
+ val path = url.substringBefore('#').substringBefore('?')
82
+ return path.endsWith(".gif", ignoreCase = true)
83
+ }
84
+
85
+ private fun GifImageState.toImageState(): ImageState {
86
+ return when (this) {
87
+ GifImageState.Success -> ImageState.Success
88
+ GifImageState.Error -> ImageState.Error
89
+ GifImageState.Loading -> ImageState.Loading
90
+ }
91
+ }
92
+
76
93
  // Optimized query parameter appending
77
94
  private fun appendSizeQueryParam(url: String, value: String): String {
78
95
  val fragmentIndex = url.indexOf('#')
@@ -95,12 +112,12 @@ fun Image(
95
112
  modifier: Modifier = Modifier,
96
113
  options: Options? = null,
97
114
  loading: Boolean = true,
115
+ onStateChanged: (ImageState) -> Unit = {},
98
116
  ) {
99
117
  val imageOptions = remember(options) {
100
118
  options ?: Options()
101
119
  }
102
120
 
103
- var imageState by remember { mutableStateOf(ImageState.Loading) }
104
121
  val density = LocalDensity.current
105
122
 
106
123
  BoxWithConstraints(
@@ -121,34 +138,46 @@ fun Image(
121
138
  processedUrl
122
139
  }
123
140
  }
124
- else -> source
141
+ else -> source.toString()
125
142
  }
126
143
  }
127
144
 
128
- AsyncImage(
129
- modifier = Modifier.matchParentSize(),
130
- model = urlToLoad,
131
- contentDescription = null,
132
- contentScale = imageOptions.contentScale,
133
- alignment = imageOptions.alignment,
134
- colorFilter = imageOptions.colorFilter,
135
- alpha = imageOptions.alpha,
136
- onLoading = { imageState = ImageState.Loading },
137
- onSuccess = { imageState = ImageState.Success },
138
- onError = { imageState = ImageState.Error },
139
- )
145
+ var imageState by remember(urlToLoad) { mutableStateOf(ImageState.Loading) }
146
+ fun updateImageState(state: ImageState) {
147
+ imageState = state
148
+ onStateChanged(state)
149
+ }
150
+
151
+ if (isGifUrl(urlToLoad)) {
152
+ GifImage(
153
+ modifier = Modifier.matchParentSize(),
154
+ url = urlToLoad,
155
+ contentDescription = null,
156
+ contentScale = imageOptions.contentScale,
157
+ alignment = imageOptions.alignment,
158
+ colorFilter = imageOptions.colorFilter,
159
+ alpha = imageOptions.alpha,
160
+ onStateChanged = { updateImageState(it.toImageState()) },
161
+ )
162
+ } else {
163
+ CoilAsyncImage(
164
+ modifier = Modifier.matchParentSize(),
165
+ model = urlToLoad,
166
+ contentDescription = null,
167
+ contentScale = imageOptions.contentScale,
168
+ alignment = imageOptions.alignment,
169
+ colorFilter = imageOptions.colorFilter,
170
+ alpha = imageOptions.alpha,
171
+ onLoading = { updateImageState(ImageState.Loading) },
172
+ onSuccess = { updateImageState(ImageState.Success) },
173
+ onError = { updateImageState(ImageState.Error) },
174
+ )
175
+ }
140
176
 
141
177
  when (imageState) {
142
- ImageState.Loading -> if (loading) Skeleton()
143
- ImageState.Error -> {
144
- val theme = AppTheme.current
145
- Icon(
146
- source = "media_fail",
147
- color = theme.colors.text.disable,
148
- modifier = Modifier.align(Alignment.Center)
149
- )
150
- }
151
178
  ImageState.Success -> {}
179
+ ImageState.Error -> ImageError()
180
+ ImageState.Loading -> if (loading) Skeleton()
152
181
  }
153
182
  }
154
183
  }
@@ -156,7 +185,7 @@ fun Image(
156
185
  // Extracted URL processing logic for better performance
157
186
  private fun processImageUrl(url: String, maxWidth: androidx.compose.ui.unit.Dp, density: androidx.compose.ui.unit.Density): String {
158
187
  val host = getHostFromUrl(url)
159
- return if (host != null && supportedDomains.contains(host)) {
188
+ return if (!isGifUrl(url) && host != null && supportedDomains.contains(host)) {
160
189
  val widthPx = with(density) { maxWidth.roundToPx() }
161
190
  val pixelRatio = density.density
162
191
  val pixelFitForWidth = widthPx * pixelRatio
@@ -167,6 +196,20 @@ private fun processImageUrl(url: String, maxWidth: androidx.compose.ui.unit.Dp,
167
196
  }
168
197
  }
169
198
 
199
+ @Composable
200
+ private fun ImageError() {
201
+ val theme = AppTheme.current
202
+ Box(
203
+ modifier = Modifier.fillMaxSize(),
204
+ contentAlignment = Alignment.Center
205
+ ) {
206
+ Icon(
207
+ source = "media_fail",
208
+ color = theme.colors.text.disable,
209
+ )
210
+ }
211
+ }
212
+
170
213
  @Composable
171
214
  fun Image(
172
215
  source: Painter,
@@ -185,4 +228,4 @@ fun Image(
185
228
  colorFilter = colorFilter,
186
229
  alpha = alpha,
187
230
  )
188
- }
231
+ }
@@ -0,0 +1,17 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.CompositionLocalProvider
5
+ import vn.momo.kits.application.ScaleSizeMaxRate
6
+
7
+ @Composable
8
+ fun ScaleSizeScope(
9
+ scaleSizeMaxRate: Float? = null,
10
+ content: @Composable () -> Unit
11
+ ) {
12
+ CompositionLocalProvider(
13
+ ScaleSizeMaxRate provides scaleSizeMaxRate,
14
+ ) {
15
+ content()
16
+ }
17
+ }
@@ -36,7 +36,6 @@ fun Text(
36
36
  softWrap: Boolean = true,
37
37
  accessibilityId: String? = null,
38
38
  inlineContent: Map<String, InlineTextContent> = mapOf(),
39
- userScaleRate: Float? = null,
40
39
  ) {
41
40
  Text(
42
41
  text = AnnotatedString(text),
@@ -53,8 +52,7 @@ fun Text(
53
52
  letterSpacing = letterSpacing,
54
53
  softWrap = softWrap,
55
54
  accessibilityId = accessibilityId,
56
- inlineContent = inlineContent,
57
- userScaleRate = userScaleRate
55
+ inlineContent = inlineContent
58
56
  )
59
57
  }
60
58
 
@@ -76,14 +74,13 @@ fun Text(
76
74
  softWrap: Boolean = true,
77
75
  accessibilityId: String? = null,
78
76
  inlineContent: Map<String, InlineTextContent> = mapOf(),
79
- userScaleRate: Float? = null,
80
77
  ) {
81
78
  // Cache theme access to avoid repeated lookups
82
79
  val theme = AppTheme.current
83
80
 
84
81
  // Call @Composable functions directly in composable context
85
- val scaledFontSize = scaleSize(style.fontSize, userScaleRate)
86
- val scaledLineHeight = scaleSize(style.lineHeight, userScaleRate)
82
+ val scaledFontSize = scaleSize(style.fontSize)
83
+ val scaledLineHeight = scaleSize(style.lineHeight)
87
84
  val fontFamilyResult = getFontFamily(fontFamily ?: theme.font, style.fontWeight)
88
85
 
89
86
  // Now memoize the results
@@ -11,7 +11,7 @@ import androidx.compose.ui.unit.*
11
11
  import org.jetbrains.compose.resources.Font
12
12
  import org.jetbrains.compose.resources.FontResource
13
13
  import org.jetbrains.compose.resources.InternalResourceApi
14
- import vn.momo.kits.application.LocalFontScaleConfig
14
+ import vn.momo.kits.application.ScaleSizeMaxRate
15
15
  import vn.momo.kits.platform.getScreenDimensions
16
16
  import vn.momo.uikits.resources.*
17
17
  import kotlin.math.max
@@ -22,58 +22,59 @@ const val MAX_FONT_SCALE = 1.5f
22
22
  const val MAX_DEVICE_SCALE = 5
23
23
 
24
24
  @Composable
25
- fun scaleSize(size: Float, userScaleRate: Float? = null): Float {
26
- val fontScaleConfig = LocalFontScaleConfig.current
27
- val useOSFontScale = fontScaleConfig.useOSFontScale
28
- val customRate = userScaleRate ?: fontScaleConfig.userScaleRate ?: 1f
25
+ fun scaleSize(size: Float): Float {
26
+ val scaleSizeMaxRate: Float = ScaleSizeMaxRate.current ?: MAX_FONT_SCALE
27
+ val deviceWidth = getScreenDimensions().width
28
+ val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
29
29
 
30
- val deviceScale = getScreenDimensions().width / DEFAULT_SCREEN_SIZE
31
- val fontScale = fontScaleConfig.osFontScale ?: LocalDensity.current.fontScale
32
-
33
- if (!useOSFontScale) {
34
- return if (customRate > 1f) customRate * size else size
35
- }
30
+ val density = LocalDensity.current
31
+ val fontScale = density.fontScale
36
32
 
37
33
  var fontSizeScaleDevice = size
38
34
  var fontSizeScaleOS = size
35
+
39
36
  if (deviceScale > 1) {
40
37
  fontSizeScaleDevice =
41
38
  min(deviceScale * fontSizeScaleDevice, fontSizeScaleDevice + MAX_DEVICE_SCALE)
42
39
  }
43
- if (fontScale > 1f) {
44
- fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * MAX_FONT_SCALE)
40
+
41
+ if (fontScale > 1) {
42
+ fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * scaleSizeMaxRate)
45
43
  }
46
44
 
47
- return max(fontSizeScaleDevice, fontSizeScaleOS)
45
+ return max(
46
+ fontSizeScaleDevice,
47
+ fontSizeScaleOS
48
+ )
48
49
  }
49
50
 
50
51
  @Composable
51
- fun scaleSize(size: TextUnit, userScaleRate: Float? = null): TextUnit {
52
+ fun scaleSize(size: TextUnit): TextUnit {
52
53
  if (!size.isSp) return size
53
54
 
54
55
  val density = LocalDensity.current
55
56
 
56
- val scaled = scaleSize(size.value, userScaleRate)
57
+ val scaled = scaleSize(size.value)
57
58
  val spValue = scaled / density.fontScale
58
59
 
59
60
  return TextUnit(value = spValue, type = TextUnitType.Sp)
60
61
  }
61
62
 
62
63
  @Composable
63
- fun scaleSize(size: Dp, userScaleRate: Float? = null): Dp {
64
- return scaleSize(size.value, userScaleRate).dp
64
+ fun scaleSize(size: Dp): Dp {
65
+ return scaleSize(size.value).dp
65
66
  }
66
67
 
67
68
  @Composable
68
- fun scaleSize(textStyle: TextStyle, userScaleRate: Float? = null): TextStyle {
69
+ fun scaleSize(textStyle: TextStyle): TextStyle {
69
70
  return textStyle.copy(
70
71
  fontSize = if (textStyle.fontSize != TextUnit.Unspecified)
71
- scaleSize(textStyle.fontSize, userScaleRate)
72
+ scaleSize(textStyle.fontSize)
72
73
  else
73
74
  TextUnit.Unspecified,
74
75
 
75
76
  lineHeight = if (textStyle.lineHeight != TextUnit.Unspecified)
76
- scaleSize(textStyle.lineHeight, userScaleRate)
77
+ scaleSize(textStyle.lineHeight)
77
78
  else
78
79
  TextUnit.Unspecified,
79
80
  )