@momo-kits/native-kits 0.162.2-test.3-debug → 0.162.2-test.5-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.161.0-debug"
43
+ version = "0.162.2-test.5-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.161.0-debug"
43
+ version = gitlabVersion
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -86,14 +86,14 @@ kotlin {
86
86
  }
87
87
 
88
88
  android {
89
- namespace = "vn.momo.kits.kits"
90
- compileSdk = 35
89
+ namespace = "$gitlabGroup.$gitlabArtifactId"
90
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
91
91
  compileOptions {
92
92
  sourceCompatibility = JavaVersion.VERSION_17
93
93
  targetCompatibility = JavaVersion.VERSION_17
94
94
  }
95
95
  defaultConfig {
96
- minSdk = 24
96
+ minSdk = libs.versions.android.minSdk.get().toInt()
97
97
  }
98
98
  }
99
99
 
@@ -28,6 +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
32
  ) {
32
33
  companion object {
33
34
 
@@ -59,6 +60,7 @@ data class MiniAppContext(
59
60
  providerId = parent.providerId.ifBlank { child.providerId },
60
61
  permissions = if (!parent.permissions.isNullOrEmpty()) parent.permissions else child.permissions,
61
62
  features = mergeFeatureFlags(parent.features, child.features),
63
+ fontScaleConfig = parent.fontScaleConfig ?: child.fontScaleConfig,
62
64
  )
63
65
  }
64
66
 
@@ -102,4 +104,13 @@ val AppLanguage = staticCompositionLocalOf<String?> {
102
104
 
103
105
  val LocalComponentInformation = staticCompositionLocalOf<ComponentInformation?> {
104
106
  null
105
- }
107
+ }
108
+
109
+ @Immutable
110
+ data class FontScaleConfig(
111
+ val useOSFontScale: Boolean = false,
112
+ val userScaleRate: Float? = null,
113
+ )
114
+
115
+ // Single source of truth: chứa cả flag OS lẫn rate user tự chỉnh.
116
+ val LocalFontScaleConfig = staticCompositionLocalOf { FontScaleConfig() }
@@ -40,6 +40,7 @@ fun Text(
40
40
  softWrap: Boolean = true,
41
41
  accessibilityId: String? = null,
42
42
  inlineContent: Map<String, InlineTextContent> = mapOf(),
43
+ userScaleRate: Float? = null,
43
44
  ) {
44
45
  Text(
45
46
  text = AnnotatedString(text),
@@ -56,7 +57,8 @@ fun Text(
56
57
  letterSpacing = letterSpacing,
57
58
  softWrap = softWrap,
58
59
  accessibilityId = accessibilityId,
59
- inlineContent = inlineContent
60
+ inlineContent = inlineContent,
61
+ userScaleRate = userScaleRate
60
62
  )
61
63
  }
62
64
 
@@ -78,13 +80,14 @@ fun Text(
78
80
  softWrap: Boolean = true,
79
81
  accessibilityId: String? = null,
80
82
  inlineContent: Map<String, InlineTextContent> = mapOf(),
83
+ userScaleRate: Float? = null,
81
84
  ) {
82
85
  // Cache theme access to avoid repeated lookups
83
86
  val theme = AppTheme.current
84
87
 
85
88
  // Call @Composable functions directly in composable context
86
- val scaledFontSize = scaleSize(style.fontSize)
87
- val scaledLineHeight = scaleSize(style.lineHeight)
89
+ val scaledFontSize = scaleSize(style.fontSize, userScaleRate)
90
+ val scaledLineHeight = scaleSize(style.lineHeight, userScaleRate)
88
91
  val fontFamilyResult = getFontFamily(fontFamily ?: theme.font, style.fontWeight)
89
92
 
90
93
  // Now memoize the results
@@ -15,7 +15,7 @@ import androidx.compose.ui.unit.sp
15
15
  import org.jetbrains.compose.resources.Font
16
16
  import org.jetbrains.compose.resources.FontResource
17
17
  import org.jetbrains.compose.resources.InternalResourceApi
18
- import vn.momo.kits.application.FontScaleStore
18
+ import vn.momo.kits.application.LocalFontScaleConfig
19
19
  import vn.momo.kits.platform.getScreenDimensions
20
20
  import vn.momo.uikits.resources.Res
21
21
  import vn.momo.uikits.resources.momosignature
@@ -37,9 +37,13 @@ const val MAX_FONT_SCALE = 1.5f
37
37
  const val MAX_DEVICE_SCALE = 5
38
38
 
39
39
  @Composable
40
- fun scaleSize(size: Float): Float {
41
- val useDeviceFontScale = FontScaleStore.useDeviceFontScale
42
- val appFontScale = FontScaleStore.appFontScale
40
+ fun scaleSize(size: Float, userScaleRate: Float? = null): Float {
41
+ // single source of truth: useOSFontScale + userScaleRate cùng lấy từ LocalFontScaleConfig
42
+ val fontScaleConfig = LocalFontScaleConfig.current
43
+ val useOSFontScale = fontScaleConfig.useOSFontScale
44
+ // param userScaleRate override config; không có -> config.userScaleRate -> 1f (không scale)
45
+ val customRate = userScaleRate ?: fontScaleConfig.userScaleRate ?: 1f
46
+
43
47
  val deviceWidth = getScreenDimensions().width
44
48
  val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
45
49
 
@@ -54,12 +58,11 @@ fun scaleSize(size: Float): Float {
54
58
  min(deviceScale * fontSizeScaleDevice, fontSizeScaleDevice + MAX_DEVICE_SCALE)
55
59
  }
56
60
 
57
- if (useDeviceFontScale) {
58
- if (fontScale > 1) {
59
- fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * MAX_FONT_SCALE)
60
- }
61
- } else {
62
- fontSizeScaleOS = size * appFontScale
61
+ // useOSFontScale bật -> lấy scale của OS đè lên, ignore custom rate của user.
62
+ // tắt -> dùng custom rate user tự chỉnh. Cap cứng MAX_FONT_SCALE (150%) cả hai.
63
+ val appliedRate = if (useOSFontScale) fontScale else customRate
64
+ if (appliedRate > 1f) {
65
+ fontSizeScaleOS = min(appliedRate * fontSizeScaleOS, fontSizeScaleOS * MAX_FONT_SCALE)
63
66
  }
64
67
 
65
68
  return max(
@@ -69,32 +72,32 @@ fun scaleSize(size: Float): Float {
69
72
  }
70
73
 
71
74
  @Composable
72
- fun scaleSize(size: TextUnit): TextUnit {
75
+ fun scaleSize(size: TextUnit, userScaleRate: Float? = null): TextUnit {
73
76
  if (!size.isSp) return size
74
77
 
75
78
  val density = LocalDensity.current
76
79
 
77
- val scaled = scaleSize(size.value)
80
+ val scaled = scaleSize(size.value, userScaleRate)
78
81
  val spValue = scaled / density.fontScale
79
82
 
80
83
  return TextUnit(value = spValue, type = TextUnitType.Sp)
81
84
  }
82
85
 
83
86
  @Composable
84
- fun scaleSize(size: Dp): Dp {
85
- return scaleSize(size.value).dp
87
+ fun scaleSize(size: Dp, userScaleRate: Float? = null): Dp {
88
+ return scaleSize(size.value, userScaleRate).dp
86
89
  }
87
90
 
88
91
  @Composable
89
- fun scaleSize(textStyle: TextStyle): TextStyle {
92
+ fun scaleSize(textStyle: TextStyle, userScaleRate: Float? = null): TextStyle {
90
93
  return textStyle.copy(
91
94
  fontSize = if (textStyle.fontSize != TextUnit.Unspecified)
92
- scaleSize(textStyle.fontSize)
95
+ scaleSize(textStyle.fontSize, userScaleRate)
93
96
  else
94
97
  TextUnit.Unspecified,
95
98
 
96
99
  lineHeight = if (textStyle.lineHeight != TextUnit.Unspecified)
97
- scaleSize(textStyle.lineHeight)
100
+ scaleSize(textStyle.lineHeight, userScaleRate)
98
101
  else
99
102
  TextUnit.Unspecified,
100
103
  )
@@ -9,6 +9,7 @@ import androidx.navigation.compose.NavHost
9
9
  import androidx.navigation.compose.composable
10
10
  import androidx.navigation.compose.rememberNavController
11
11
  import androidx.navigation.toRoute
12
+ import kotlinx.coroutines.flow.collect
12
13
  import vn.momo.kits.application.*
13
14
  import vn.momo.kits.const.*
14
15
  import vn.momo.kits.platform.ProvideNavigationEventDispatcherOwner
@@ -37,6 +38,23 @@ fun NavigationContainer(
37
38
  val parentContext = ApplicationContext.current
38
39
  val mergedContext = MiniAppContext.merge(parentContext, applicationContext)
39
40
 
41
+ // FontScaleConfig host ghi xuống observer storage (single source of truth khi có).
42
+ var observerFontScaleConfig by remember { mutableStateOf<FontScaleConfig?>(null) }
43
+ LaunchedEffect(maxApi) {
44
+ val api = maxApi ?: return@LaunchedEffect
45
+ // getDataObserver = snapshot lúc mount; observer = subscribe các lần host ghi sau đó.
46
+ runCatching {
47
+ api.getDataObserver(FONT_SCALE_OBSERVER_KEY) { data ->
48
+ parseFontScaleConfig(data)?.let { observerFontScaleConfig = it }
49
+ }
50
+ }
51
+ runCatching {
52
+ api.observer(FONT_SCALE_OBSERVER_KEY).collect { data ->
53
+ observerFontScaleConfig = parseFontScaleConfig(data)
54
+ }
55
+ }
56
+ }
57
+
40
58
  val theme = remember { mutableStateOf(initialTheme) }
41
59
 
42
60
  LaunchedEffect(Unit) {
@@ -64,6 +82,7 @@ fun NavigationContainer(
64
82
  ApplicationContext provides mergedContext,
65
83
  AppConfig provides config,
66
84
  AppLanguage provides language,
85
+ LocalFontScaleConfig provides (observerFontScaleConfig ?: mergedContext?.fontScaleConfig ?: LocalFontScaleConfig.current),
67
86
  ) {
68
87
  LaunchedEffect(Unit) {
69
88
  setNavigator?.invoke(navigator)
@@ -142,3 +161,28 @@ fun NavigationContainer(
142
161
  val LocalMaxApi = staticCompositionLocalOf<IMaxApi?> {
143
162
  error("No MaxApi provided")
144
163
  }
164
+
165
+ // Key observer storage host app ghi FontScaleConfig. Native lowercase key.
166
+ private const val FONT_SCALE_OBSERVER_KEY = "font_scale_config"
167
+
168
+ // Host trả object {useOSFontScale, userScaleRate}; phòng marshaling bọc envelope / lệch kiểu.
169
+ private fun parseFontScaleConfig(raw: Map<String, Any?>?): FontScaleConfig? {
170
+ if (raw.isNullOrEmpty()) return null
171
+ @Suppress("UNCHECKED_CAST")
172
+ val data = (raw["response"] as? Map<String, Any?>)
173
+ ?: (raw["data"] as? Map<String, Any?>)
174
+ ?: raw
175
+ if (!data.containsKey("useOSFontScale") && !data.containsKey("userScaleRate")) return null
176
+ val useOS = when (val v = data["useOSFontScale"]) {
177
+ is Boolean -> v
178
+ is String -> v.equals("true", ignoreCase = true)
179
+ is Number -> v.toInt() != 0
180
+ else -> false
181
+ }
182
+ val rate = when (val v = data["userScaleRate"]) {
183
+ is Number -> v.toFloat()
184
+ is String -> v.toFloatOrNull()
185
+ else -> null
186
+ }
187
+ return FontScaleConfig(useOSFontScale = useOS, userScaleRate = rate)
188
+ }
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.162.2-test.3
21
+ version=0.162.2-test.5
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.162.2-test.3-debug",
3
+ "version": "0.162.2-test.5-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},
@@ -1,91 +0,0 @@
1
- package vn.momo.kits.application
2
-
3
- import androidx.compose.runtime.getValue
4
- import androidx.compose.runtime.mutableStateOf
5
- import androidx.compose.runtime.setValue
6
- import kotlinx.coroutines.CoroutineScope
7
- import kotlinx.coroutines.Dispatchers
8
- import kotlinx.coroutines.SupervisorJob
9
- import kotlinx.coroutines.launch
10
- import vn.momo.maxapi.IMaxApi
11
-
12
- private const val KEY_USE_DEVICE_FONT_SCALE = "use_device_font_scale"
13
- private const val KEY_APP_FONT_SCALE = "app_font_scale"
14
- private const val MIN_APP_FONT_SCALE = 1f
15
- private const val MAX_APP_FONT_SCALE = 1.5f
16
-
17
- /**
18
- * Single in-memory mirror of the Observer Storage font-scale source of truth
19
- * (keys [KEY_USE_DEVICE_FONT_SCALE] + [KEY_APP_FONT_SCALE]).
20
- *
21
- * scaleSize() reads this holder directly, so every Compose subtree across all
22
- * NavigationContainer roots reacts to a change at once — no per-root wiring.
23
- *
24
- * [start] subscribes once to storage through the given [IMaxApi]: it seeds the
25
- * current value (the observer Flow only replays emissions made after subscribe),
26
- * then collects live changes. The subscription lives on its own scope, so it
27
- * survives navigation between screens. The caller must pass a real (adapter-backed)
28
- * IMaxApi — a NoopMaxApi emits a single error frame, which is ignored here.
29
- */
30
- object FontScaleStore {
31
-
32
- var useDeviceFontScale by mutableStateOf(true)
33
- private set
34
- var appFontScale by mutableStateOf(1f)
35
- private set
36
-
37
- private var started = false
38
- private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
39
-
40
- fun start(maxApi: IMaxApi) {
41
- if (started) return
42
- started = true
43
-
44
- // Seed current persisted value.
45
- maxApi.getDataObserver(KEY_USE_DEVICE_FONT_SCALE) { map ->
46
- parseUseDevice(map)?.let { useDeviceFontScale = it }
47
- }
48
- maxApi.getDataObserver(KEY_APP_FONT_SCALE) { map ->
49
- parseScale(map)?.let { appFontScale = it }
50
- }
51
-
52
- // Live updates.
53
- scope.launch {
54
- maxApi.observer(KEY_USE_DEVICE_FONT_SCALE).collect { map ->
55
- parseUseDevice(map)?.let { useDeviceFontScale = it }
56
- }
57
- }
58
- scope.launch {
59
- maxApi.observer(KEY_APP_FONT_SCALE).collect { map ->
60
- parseScale(map)?.let { appFontScale = it }
61
- }
62
- }
63
- }
64
-
65
- /**
66
- * Callback map = serialized MaxApiResponse flattened: { status, response, errorCode, ... }.
67
- * A stored string scalar arrives under "response" as a String ("1.5" / "false").
68
- * Returns null for error frames (e.g. adapter-not-bound) and unset keys.
69
- */
70
- private fun response(map: Map<String, Any?>?): Any? {
71
- if (map == null) return null
72
- val errorCode = (map["errorCode"] as? Number)?.toInt() ?: 0
73
- if (errorCode != 0) return null
74
- return map["response"]
75
- }
76
-
77
- private fun parseScale(map: Map<String, Any?>?): Float? {
78
- val value = when (val r = response(map)) {
79
- is Number -> r.toFloat()
80
- is String -> r.toFloatOrNull()
81
- else -> null
82
- } ?: return null
83
- return value.coerceIn(MIN_APP_FONT_SCALE, MAX_APP_FONT_SCALE)
84
- }
85
-
86
- private fun parseUseDevice(map: Map<String, Any?>?): Boolean? = when (val r = response(map)) {
87
- is Boolean -> r
88
- is String -> r.toBooleanStrictOrNull()
89
- else -> null
90
- }
91
- }