@momo-kits/native-kits 0.162.1-beta.1-debug → 0.162.1-beta.12-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/CLAUDE.md +78 -0
- package/compose/build.gradle.kts +59 -58
- package/compose/build.gradle.kts.backup +61 -60
- package/compose/compose.podspec +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +13 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +5 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +21 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +2 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +6 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +5 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +21 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +66 -25
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +42 -28
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +4 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +25 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +4 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +4 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -10
- package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +46 -0
- package/gradle/libs.versions.toml +2 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +1 -0
- package/ios/Application/Components.swift +1 -0
- package/ios/Application/FloatingButton.swift +1 -0
- package/ios/Application/FontScaleStore.swift +59 -0
- package/ios/Badge/Badge.swift +1 -0
- package/ios/Badge/BadgeRibbon.swift +2 -0
- package/ios/Button/Button.swift +1 -1
- package/ios/Checkbox/Checkbox.swift +1 -0
- package/ios/Information/Information.swift +151 -0
- package/ios/Input/Input.swift +1 -0
- package/ios/Input/InputPhoneNumber.swift +1 -0
- package/ios/Input/InputSearch.swift +3 -0
- package/ios/Input/InputTextArea.swift +1 -0
- package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
- package/ios/Popup/PopupDisplay.swift +6 -0
- package/ios/Popup/PopupInput.swift +2 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
- package/ios/Typography/Text.swift +13 -7
- package/ios/Typography/Typography.swift +22 -8
- package/local.properties +2 -2
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -32
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Deprecated.kt +0 -107
|
@@ -18,8 +18,7 @@ import androidx.compose.ui.draw.clip
|
|
|
18
18
|
import androidx.compose.ui.graphics.Color
|
|
19
19
|
import androidx.compose.ui.layout.ContentScale
|
|
20
20
|
import androidx.compose.ui.unit.dp
|
|
21
|
-
import vn.momo.kits.application.
|
|
22
|
-
import vn.momo.kits.application.AppLanguage
|
|
21
|
+
import vn.momo.kits.application.LocalLocalize
|
|
23
22
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
24
23
|
import vn.momo.kits.const.Colors
|
|
25
24
|
import vn.momo.kits.const.Typography
|
|
@@ -68,9 +67,8 @@ fun TrustBanner(
|
|
|
68
67
|
onPress: ((Map<String, String>) -> Unit)? = null,
|
|
69
68
|
trackEvent: ((String, Map<String, String>) -> Unit)? = null
|
|
70
69
|
) {
|
|
71
|
-
val
|
|
72
|
-
val
|
|
73
|
-
val trustBanner = appConfig?.trustBanner ?: defaultBanner
|
|
70
|
+
val localize = LocalLocalize.current
|
|
71
|
+
val trustBanner = defaultBanner
|
|
74
72
|
val trackParams = mapOf(
|
|
75
73
|
"service_name" to serviceName,
|
|
76
74
|
"screen_name" to screenName,
|
|
@@ -124,7 +122,7 @@ fun TrustBanner(
|
|
|
124
122
|
.padding(start = 10.dp)
|
|
125
123
|
) {
|
|
126
124
|
Text(
|
|
127
|
-
text = trustBanner.content
|
|
125
|
+
text = localize.translateData(trustBanner.content),
|
|
128
126
|
modifier = Modifier.padding(bottom = 8.dp),
|
|
129
127
|
color = Color(contentColor),
|
|
130
128
|
style = Typography.descriptionDefaultRegular,
|
|
@@ -138,7 +136,7 @@ fun TrustBanner(
|
|
|
138
136
|
verticalAlignment = Alignment.CenterVertically,
|
|
139
137
|
) {
|
|
140
138
|
Text(
|
|
141
|
-
text = trustBanner.subContent
|
|
139
|
+
text = localize.translateData(trustBanner.subContent),
|
|
142
140
|
color = Color(subContentColor),
|
|
143
141
|
style = Typography.actionXsBold,
|
|
144
142
|
maxLines = 2
|
|
@@ -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.
|
|
14
|
+
import vn.momo.kits.application.LocalFontScaleConfig
|
|
15
15
|
import vn.momo.kits.platform.getScreenDimensions
|
|
16
16
|
import vn.momo.uikits.resources.*
|
|
17
17
|
import kotlin.math.max
|
|
@@ -22,59 +22,58 @@ 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): Float {
|
|
26
|
-
val
|
|
27
|
-
val
|
|
28
|
-
val
|
|
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
|
|
29
29
|
|
|
30
|
-
val
|
|
31
|
-
val fontScale =
|
|
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
|
+
}
|
|
32
36
|
|
|
33
37
|
var fontSizeScaleDevice = size
|
|
34
38
|
var fontSizeScaleOS = size
|
|
35
|
-
|
|
36
39
|
if (deviceScale > 1) {
|
|
37
40
|
fontSizeScaleDevice =
|
|
38
41
|
min(deviceScale * fontSizeScaleDevice, fontSizeScaleDevice + MAX_DEVICE_SCALE)
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * scaleSizeMaxRate)
|
|
43
|
+
if (fontScale > 1f) {
|
|
44
|
+
fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * MAX_FONT_SCALE)
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
return max(
|
|
46
|
-
fontSizeScaleDevice,
|
|
47
|
-
fontSizeScaleOS
|
|
48
|
-
)
|
|
47
|
+
return max(fontSizeScaleDevice, fontSizeScaleOS)
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
@Composable
|
|
52
|
-
fun scaleSize(size: TextUnit): TextUnit {
|
|
51
|
+
fun scaleSize(size: TextUnit, userScaleRate: Float? = null): TextUnit {
|
|
53
52
|
if (!size.isSp) return size
|
|
54
53
|
|
|
55
54
|
val density = LocalDensity.current
|
|
56
55
|
|
|
57
|
-
val scaled = scaleSize(size.value)
|
|
56
|
+
val scaled = scaleSize(size.value, userScaleRate)
|
|
58
57
|
val spValue = scaled / density.fontScale
|
|
59
58
|
|
|
60
59
|
return TextUnit(value = spValue, type = TextUnitType.Sp)
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
@Composable
|
|
64
|
-
fun scaleSize(size: Dp): Dp {
|
|
65
|
-
return scaleSize(size.value).dp
|
|
63
|
+
fun scaleSize(size: Dp, userScaleRate: Float? = null): Dp {
|
|
64
|
+
return scaleSize(size.value, userScaleRate).dp
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
@Composable
|
|
69
|
-
fun scaleSize(textStyle: TextStyle): TextStyle {
|
|
68
|
+
fun scaleSize(textStyle: TextStyle, userScaleRate: Float? = null): TextStyle {
|
|
70
69
|
return textStyle.copy(
|
|
71
70
|
fontSize = if (textStyle.fontSize != TextUnit.Unspecified)
|
|
72
|
-
scaleSize(textStyle.fontSize)
|
|
71
|
+
scaleSize(textStyle.fontSize, userScaleRate)
|
|
73
72
|
else
|
|
74
73
|
TextUnit.Unspecified,
|
|
75
74
|
|
|
76
75
|
lineHeight = if (textStyle.lineHeight != TextUnit.Unspecified)
|
|
77
|
-
scaleSize(textStyle.lineHeight)
|
|
76
|
+
scaleSize(textStyle.lineHeight, userScaleRate)
|
|
78
77
|
else
|
|
79
78
|
TextUnit.Unspecified,
|
|
80
79
|
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package vn.momo.kits.modifier
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.core.LinearEasing
|
|
4
|
+
import androidx.compose.animation.core.RepeatMode
|
|
5
|
+
import androidx.compose.animation.core.animateFloat
|
|
6
|
+
import androidx.compose.animation.core.infiniteRepeatable
|
|
7
|
+
import androidx.compose.animation.core.rememberInfiniteTransition
|
|
8
|
+
import androidx.compose.animation.core.tween
|
|
9
|
+
import androidx.compose.runtime.Composable
|
|
10
|
+
import androidx.compose.runtime.getValue
|
|
11
|
+
import androidx.compose.ui.Modifier
|
|
12
|
+
import androidx.compose.ui.draw.drawWithContent
|
|
13
|
+
import androidx.compose.ui.geometry.Offset
|
|
14
|
+
import androidx.compose.ui.graphics.Color
|
|
15
|
+
import androidx.compose.ui.graphics.drawscope.clipRect
|
|
16
|
+
import androidx.compose.ui.text.TextStyle
|
|
17
|
+
import androidx.compose.ui.text.drawText
|
|
18
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
19
|
+
import androidx.compose.ui.text.rememberTextMeasurer
|
|
20
|
+
import androidx.compose.ui.unit.sp
|
|
21
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
22
|
+
import vn.momo.kits.const.Colors
|
|
23
|
+
|
|
24
|
+
@Suppress("ExperimentalAnnotationRetention")
|
|
25
|
+
@RequiresOptIn(
|
|
26
|
+
message = "This is internal API of native-kits, become internal in next sprint",
|
|
27
|
+
level = RequiresOptIn.Level.WARNING,
|
|
28
|
+
)
|
|
29
|
+
@Retention(AnnotationRetention.BINARY)
|
|
30
|
+
@Target(
|
|
31
|
+
AnnotationTarget.CLASS,
|
|
32
|
+
AnnotationTarget.FUNCTION,
|
|
33
|
+
AnnotationTarget.PROPERTY,
|
|
34
|
+
)
|
|
35
|
+
annotation class InternalApi
|
|
36
|
+
|
|
37
|
+
@Composable
|
|
38
|
+
fun DeprecatedModifier(text: String = "Deprecated component"): Modifier = debugMarker(
|
|
39
|
+
label = text,
|
|
40
|
+
background = Colors.red_04.copy(alpha = 0.7f),
|
|
41
|
+
textColor = Colors.red_01,
|
|
42
|
+
animationLabel = "deprecatedMarquee",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@Composable
|
|
46
|
+
private fun debugMarker(
|
|
47
|
+
label: String,
|
|
48
|
+
background: Color,
|
|
49
|
+
textColor: Color,
|
|
50
|
+
animationLabel: String,
|
|
51
|
+
): Modifier {
|
|
52
|
+
if (!IsShowBaseLineDebug) return Modifier
|
|
53
|
+
|
|
54
|
+
val textMeasurer = rememberTextMeasurer()
|
|
55
|
+
val marquee = rememberInfiniteTransition(label = animationLabel)
|
|
56
|
+
val progress by marquee.animateFloat(
|
|
57
|
+
initialValue = 0f,
|
|
58
|
+
targetValue = 1f,
|
|
59
|
+
animationSpec = infiniteRepeatable(
|
|
60
|
+
animation = tween(durationMillis = 4000, easing = LinearEasing),
|
|
61
|
+
repeatMode = RepeatMode.Restart,
|
|
62
|
+
),
|
|
63
|
+
label = "${animationLabel}Progress",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return Modifier.drawWithContent {
|
|
67
|
+
drawContent()
|
|
68
|
+
drawRect(color = background)
|
|
69
|
+
|
|
70
|
+
if (size.width <= 0f || size.height <= 0f) return@drawWithContent
|
|
71
|
+
|
|
72
|
+
val fontPx = (size.width * 0.1f).coerceIn(12.sp.toPx(), 20.sp.toPx())
|
|
73
|
+
val style = TextStyle(
|
|
74
|
+
color = textColor,
|
|
75
|
+
fontSize = fontPx.toSp(),
|
|
76
|
+
fontWeight = FontWeight.Bold,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
val intrinsic = textMeasurer.measure(
|
|
80
|
+
text = label,
|
|
81
|
+
style = style,
|
|
82
|
+
maxLines = 1,
|
|
83
|
+
softWrap = false,
|
|
84
|
+
)
|
|
85
|
+
val textW = intrinsic.size.width
|
|
86
|
+
val textH = intrinsic.size.height
|
|
87
|
+
val y = (size.height - textH) / 2f
|
|
88
|
+
|
|
89
|
+
if (textW <= size.width) {
|
|
90
|
+
drawText(
|
|
91
|
+
textLayoutResult = intrinsic,
|
|
92
|
+
topLeft = Offset((size.width - textW) / 2f, y),
|
|
93
|
+
)
|
|
94
|
+
} else {
|
|
95
|
+
clipRect(right = size.width, bottom = size.height) {
|
|
96
|
+
val gap = textW * 0.5f
|
|
97
|
+
val travel = textW + gap
|
|
98
|
+
val x = -progress * travel
|
|
99
|
+
drawText(textLayoutResult = intrinsic, topLeft = Offset(x, y))
|
|
100
|
+
drawText(textLayoutResult = intrinsic, topLeft = Offset(x + travel, y))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -41,7 +41,7 @@ import vn.momo.kits.const.AppTheme
|
|
|
41
41
|
import vn.momo.kits.const.Colors
|
|
42
42
|
import vn.momo.kits.const.Radius
|
|
43
43
|
import vn.momo.kits.const.Spacing
|
|
44
|
-
import vn.momo.kits.application.
|
|
44
|
+
import vn.momo.kits.application.LocalContext
|
|
45
45
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
46
46
|
import vn.momo.kits.const.Typography
|
|
47
47
|
import vn.momo.kits.navigation.tracking.ScreenTracker
|
|
@@ -73,7 +73,7 @@ internal fun BottomSheet(
|
|
|
73
73
|
val coroutineScope = rememberCoroutineScope()
|
|
74
74
|
|
|
75
75
|
val maxApi = LocalMaxApi.current
|
|
76
|
-
val context =
|
|
76
|
+
val context = LocalContext.current
|
|
77
77
|
|
|
78
78
|
DisposableEffect(Unit) {
|
|
79
79
|
ScreenTracker.trackPopupDisplayed(
|
|
@@ -18,7 +18,7 @@ import androidx.compose.ui.draw.scale
|
|
|
18
18
|
import androidx.compose.ui.graphics.Color
|
|
19
19
|
import androidx.compose.ui.unit.dp
|
|
20
20
|
import kotlinx.coroutines.launch
|
|
21
|
-
import vn.momo.kits.application.
|
|
21
|
+
import vn.momo.kits.application.LocalContext
|
|
22
22
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
23
23
|
import vn.momo.kits.const.Colors
|
|
24
24
|
import vn.momo.kits.modifier.conditional
|
|
@@ -40,7 +40,7 @@ internal fun ModalScreen(
|
|
|
40
40
|
val coroutineScope = rememberCoroutineScope()
|
|
41
41
|
|
|
42
42
|
val maxApi = LocalMaxApi.current
|
|
43
|
-
val context =
|
|
43
|
+
val context = LocalContext.current
|
|
44
44
|
|
|
45
45
|
DisposableEffect(Unit) {
|
|
46
46
|
ScreenTracker.trackPopupDisplayed(
|
|
@@ -9,7 +9,8 @@ import vn.momo.kits.navigation.component.*
|
|
|
9
9
|
class Navigation(
|
|
10
10
|
val id: Int = -1,
|
|
11
11
|
val bottomTabIndex: Int = -1,
|
|
12
|
-
val initOptions: NavigationOptions? = null
|
|
12
|
+
val initOptions: NavigationOptions? = null,
|
|
13
|
+
private val registry: DynamicScreenRegistry? = null
|
|
13
14
|
) {
|
|
14
15
|
private val _options = mutableStateOf(initOptions ?: NavigationOptions())
|
|
15
16
|
val currentOptions: State<NavigationOptions> get() = _options
|
|
@@ -56,7 +57,7 @@ class Navigation(
|
|
|
56
57
|
private fun updateOptions(updated: NavigationOptions) {
|
|
57
58
|
_options.value = updated
|
|
58
59
|
if (bottomTabIndex != -1) setBottomTabOption(bottomTabIndex, updated)
|
|
59
|
-
if (id != -1)
|
|
60
|
+
if (id != -1) registry?.setOptions(id, updated)
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -22,50 +22,57 @@ fun NavigationContainer(
|
|
|
22
22
|
initialScreen: @Composable () -> Unit,
|
|
23
23
|
options: NavigationOptions? = null,
|
|
24
24
|
initialTheme: Theme = defaultTheme,
|
|
25
|
-
|
|
26
|
-
maxApi: IMaxApi
|
|
25
|
+
miniAppContext: MiniAppContext? = null,
|
|
26
|
+
maxApi: IMaxApi?,
|
|
27
27
|
setNavigator: ((Navigator) -> Unit)? = null,
|
|
28
28
|
statusBarHeight: Dp? = null,
|
|
29
|
-
|
|
30
|
-
language: String? = null
|
|
29
|
+
localize: Localize? = null,
|
|
31
30
|
){
|
|
32
31
|
val navController = rememberNavController()
|
|
33
|
-
val
|
|
32
|
+
val registry = remember { DynamicScreenRegistry() }
|
|
33
|
+
val navigator = remember { Navigator(navController = navController, maxApi = maxApi, registry = registry) }
|
|
34
34
|
val statusBarHeight = statusBarHeight ?: getAppStatusBarHeight()
|
|
35
35
|
val navigationBarHeight = getNavigationBarHeight()
|
|
36
36
|
|
|
37
|
-
val parentContext =
|
|
38
|
-
val mergedContext = MiniAppContext.merge(parentContext,
|
|
37
|
+
val parentContext = LocalContext.current
|
|
38
|
+
val mergedContext = MiniAppContext.merge(parentContext, miniAppContext)
|
|
39
39
|
|
|
40
40
|
val theme = remember { mutableStateOf(initialTheme) }
|
|
41
|
+
val fallbackLocalize = remember { Localize() }
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
val resolvedLocalize = localize ?: fallbackLocalize
|
|
44
|
+
|
|
45
|
+
var observerFontScaleConfig by remember { mutableStateOf(mergedContext?.fontScaleConfig) }
|
|
46
|
+
LaunchedEffect(maxApi, resolvedLocalize) {
|
|
47
|
+
val api = maxApi ?: return@LaunchedEffect
|
|
48
|
+
runCatching {
|
|
49
|
+
api.getLanguage(callback = { data ->
|
|
50
|
+
if (parseLanguage(data) == Localize.EN) {
|
|
51
|
+
resolvedLocalize.changeLanguage(Localize.EN)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
api.observer(FONT_SCALE_OBSERVER_KEY).collect { data ->
|
|
55
|
+
observerFontScaleConfig = parseFontScaleConfig(data)
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
val screenId = rememberSaveable {
|
|
54
|
-
|
|
60
|
+
val screenId = rememberSaveable { registry.nextId() }
|
|
61
|
+
registry.bind(screenId, initialScreenName, initialScreen, options)
|
|
55
62
|
val startDestination = remember(screenId) { DynamicScreenRoute(screenId) }
|
|
56
63
|
|
|
57
64
|
ProvideNavigationEventDispatcherOwner {
|
|
58
65
|
CompositionLocalProvider(
|
|
59
66
|
LocalNavigator provides navigator,
|
|
67
|
+
LocalDynamicScreenRegistry provides registry,
|
|
60
68
|
LocalMaxApi provides maxApi,
|
|
61
69
|
AppTheme provides theme.value,
|
|
62
70
|
AppStatusBar provides statusBarHeight,
|
|
63
71
|
AppNavigationBar provides navigationBarHeight,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
) {
|
|
72
|
+
LocalContext provides mergedContext,
|
|
73
|
+
LocalLocalize provides resolvedLocalize,
|
|
74
|
+
LocalFontScaleConfig provides (observerFontScaleConfig ?: LocalFontScaleConfig.current),
|
|
75
|
+
) {
|
|
69
76
|
LaunchedEffect(Unit) {
|
|
70
77
|
setNavigator?.invoke(navigator)
|
|
71
78
|
}
|
|
@@ -88,7 +95,7 @@ fun NavigationContainer(
|
|
|
88
95
|
}
|
|
89
96
|
) { backStackEntry ->
|
|
90
97
|
val route = backStackEntry.toRoute<DynamicScreenRoute>()
|
|
91
|
-
val screen =
|
|
98
|
+
val screen = registry.getScreen(route.id)
|
|
92
99
|
|
|
93
100
|
if (screen != null) {
|
|
94
101
|
StackScreen(
|
|
@@ -117,7 +124,7 @@ fun NavigationContainer(
|
|
|
117
124
|
}
|
|
118
125
|
) { backStackEntry ->
|
|
119
126
|
val route = backStackEntry.toRoute<DynamicDialogRoute>()
|
|
120
|
-
val screen =
|
|
127
|
+
val screen = registry.getScreen(route.id)
|
|
121
128
|
|
|
122
129
|
if (screen != null) {
|
|
123
130
|
StackScreen(
|
|
@@ -135,7 +142,7 @@ fun NavigationContainer(
|
|
|
135
142
|
DisposableEffect(Unit) {
|
|
136
143
|
onDispose {
|
|
137
144
|
navigator.dispose()
|
|
138
|
-
|
|
145
|
+
registry.unregisterScreen(screenId)
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
}
|
|
@@ -143,3 +150,37 @@ fun NavigationContainer(
|
|
|
143
150
|
val LocalMaxApi = staticCompositionLocalOf<IMaxApi?> {
|
|
144
151
|
error("No MaxApi provided")
|
|
145
152
|
}
|
|
153
|
+
|
|
154
|
+
private const val FONT_SCALE_OBSERVER_KEY = "font_scale_config"
|
|
155
|
+
|
|
156
|
+
private fun parseLanguage(raw: Map<String, Any?>?): String? {
|
|
157
|
+
val code = (raw?.get("response") as? String)?.trim()?.lowercase()
|
|
158
|
+
if (code.isNullOrEmpty()) return null
|
|
159
|
+
return if (code.startsWith(Localize.EN)) Localize.EN else Localize.VI
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun parseFontScaleConfig(raw: Map<String, Any?>?): FontScaleConfig? {
|
|
163
|
+
if (raw.isNullOrEmpty()) return null
|
|
164
|
+
@Suppress("UNCHECKED_CAST")
|
|
165
|
+
val data = (raw["response"] as? Map<String, Any?>)
|
|
166
|
+
?: (raw["data"] as? Map<String, Any?>)
|
|
167
|
+
?: raw
|
|
168
|
+
if (!data.containsKey("useOSFontScale") && !data.containsKey("userScaleRate")) return null
|
|
169
|
+
val useOS = when (val v = data["useOSFontScale"]) {
|
|
170
|
+
is Boolean -> v
|
|
171
|
+
is String -> v.equals("true", ignoreCase = true)
|
|
172
|
+
is Number -> v.toInt() != 0
|
|
173
|
+
else -> true
|
|
174
|
+
}
|
|
175
|
+
val rate = when (val v = data["userScaleRate"]) {
|
|
176
|
+
is Number -> v.toFloat()
|
|
177
|
+
is String -> v.toFloatOrNull()
|
|
178
|
+
else -> null
|
|
179
|
+
}
|
|
180
|
+
val osScale = when (val v = data["osFontScale"]) {
|
|
181
|
+
is Number -> v.toFloat()
|
|
182
|
+
is String -> v.toFloatOrNull()
|
|
183
|
+
else -> null
|
|
184
|
+
}
|
|
185
|
+
return FontScaleConfig(useOSFontScale = useOS, userScaleRate = rate, osFontScale = osScale)
|
|
186
|
+
}
|
|
@@ -9,6 +9,7 @@ import androidx.compose.runtime.staticCompositionLocalOf
|
|
|
9
9
|
import androidx.compose.ui.Alignment
|
|
10
10
|
import androidx.compose.ui.Modifier
|
|
11
11
|
import androidx.navigation.NavController
|
|
12
|
+
import androidx.navigation.toRoute
|
|
12
13
|
import kotlinx.coroutines.CoroutineScope
|
|
13
14
|
import kotlinx.coroutines.Dispatchers
|
|
14
15
|
import kotlinx.coroutines.SupervisorJob
|
|
@@ -25,27 +26,34 @@ import kotlin.time.Duration.Companion.milliseconds
|
|
|
25
26
|
|
|
26
27
|
class Navigator(
|
|
27
28
|
private val navController: NavController,
|
|
28
|
-
private val maxApi: IMaxApi
|
|
29
|
+
private val maxApi: IMaxApi?,
|
|
30
|
+
private val registry: DynamicScreenRegistry
|
|
29
31
|
) {
|
|
30
32
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
31
33
|
|
|
34
|
+
private fun currentScreen(): DynamicScreen? {
|
|
35
|
+
val entry = navController.currentBackStackEntry ?: return null
|
|
36
|
+
val id = runCatching { entry.toRoute<DynamicScreenRoute>().id }.getOrNull()
|
|
37
|
+
?: runCatching { entry.toRoute<DynamicDialogRoute>().id }.getOrNull()
|
|
38
|
+
?: return null
|
|
39
|
+
return registry.getScreen(id)
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
fun push(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
33
|
-
val route =
|
|
43
|
+
val route = registry.register(screenName, content, options)
|
|
34
44
|
navController.navigate(DynamicScreenRoute(route.id))
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
fun replace(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
push(screenName, content, options)
|
|
43
|
-
}
|
|
48
|
+
val replacedScreen = currentScreen()
|
|
49
|
+
replacedScreen?.let { registry.unregisterScreen(it.id) }
|
|
50
|
+
navController.popBackStack()
|
|
51
|
+
push(screenName, content, options)
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
fun onBackSafe(callBack: (() -> Unit)? = null) {
|
|
47
|
-
val
|
|
48
|
-
val options =
|
|
55
|
+
val currentScreen = currentScreen()
|
|
56
|
+
val options = currentScreen?.options
|
|
49
57
|
if (options?.onBackHandler != null) {
|
|
50
58
|
options.onBackHandler.invoke()
|
|
51
59
|
return
|
|
@@ -94,25 +102,24 @@ class Navigator(
|
|
|
94
102
|
|
|
95
103
|
private suspend fun dismissScreen() {
|
|
96
104
|
if (navController.previousBackStackEntry != null){
|
|
105
|
+
val dismissing = currentScreen()
|
|
97
106
|
navController.popBackStack()
|
|
98
107
|
delay(300L.milliseconds)
|
|
99
|
-
|
|
100
|
-
DynamicScreenRegistry.unregisterScreen(it1.id)
|
|
101
|
-
}
|
|
108
|
+
dismissing?.let { registry.unregisterScreen(it.id) }
|
|
102
109
|
} else {
|
|
103
110
|
maxApi?.dismiss { }
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
fun present(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
108
|
-
val route =
|
|
115
|
+
val route = registry.register(screenName, content, options)
|
|
109
116
|
navController.navigate(DynamicDialogRoute(route.id))
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
fun reset(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
|
|
113
|
-
|
|
120
|
+
registry.unregisterAll()
|
|
114
121
|
|
|
115
|
-
val route =
|
|
122
|
+
val route = registry.register(screenName, content, options)
|
|
116
123
|
navController.navigate(DynamicScreenRoute(route.id)) {
|
|
117
124
|
popUpTo(0) { inclusive = true }
|
|
118
125
|
}
|
|
@@ -123,7 +130,7 @@ class Navigator(
|
|
|
123
130
|
barrierDismissible: Boolean = true,
|
|
124
131
|
onDismiss: (() -> Unit)? = null
|
|
125
132
|
){
|
|
126
|
-
val id =
|
|
133
|
+
val id = currentScreen()?.id ?: -1
|
|
127
134
|
OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.MODAL, false, barrierDismissible, onDismiss)
|
|
128
135
|
}
|
|
129
136
|
|
|
@@ -134,12 +141,12 @@ class Navigator(
|
|
|
134
141
|
onDismiss: (() -> Unit)? = null,
|
|
135
142
|
bottomSheetHeader: BottomHeader? = null
|
|
136
143
|
){
|
|
137
|
-
val id =
|
|
144
|
+
val id = currentScreen()?.id ?: -1
|
|
138
145
|
OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader)
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
|
|
142
|
-
val id =
|
|
149
|
+
val id = currentScreen()?.id ?: -1
|
|
143
150
|
scope.launch {
|
|
144
151
|
OverplayComponentRegistry.registerOverplay(
|
|
145
152
|
id = id,
|
|
@@ -182,12 +189,11 @@ data class DynamicScreen(
|
|
|
182
189
|
var options: NavigationOptions? = null
|
|
183
190
|
)
|
|
184
191
|
|
|
185
|
-
|
|
192
|
+
class DynamicScreenRegistry {
|
|
186
193
|
private val screens = mutableMapOf<Int, DynamicScreen>()
|
|
187
|
-
private var idCounter = 1
|
|
188
194
|
|
|
189
195
|
fun register(screenName: String, content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
|
|
190
|
-
val id =
|
|
196
|
+
val id = nextGlobalId()
|
|
191
197
|
screens[id] = DynamicScreen(
|
|
192
198
|
id = id,
|
|
193
199
|
name = screenName,
|
|
@@ -197,7 +203,7 @@ object DynamicScreenRegistry {
|
|
|
197
203
|
return DynamicScreenRoute(id)
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
fun nextId(): Int =
|
|
206
|
+
fun nextId(): Int = nextGlobalId()
|
|
201
207
|
|
|
202
208
|
fun bind(id: Int, screenName: String, content: @Composable () -> Unit, options: NavigationOptions?) {
|
|
203
209
|
screens[id] = DynamicScreen(
|
|
@@ -206,17 +212,13 @@ object DynamicScreenRegistry {
|
|
|
206
212
|
content = content,
|
|
207
213
|
options = options
|
|
208
214
|
)
|
|
209
|
-
|
|
215
|
+
advanceGlobalIdPast(id) // keep counter ahead after process-death restore
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
fun unregisterScreen(id: Int) {
|
|
213
219
|
screens.remove(id)
|
|
214
220
|
}
|
|
215
221
|
|
|
216
|
-
fun getLatestScreen(): DynamicScreen?{
|
|
217
|
-
return screens.entries.lastOrNull()?.value
|
|
218
|
-
}
|
|
219
|
-
|
|
220
222
|
fun unregisterAll(){
|
|
221
223
|
val ids = screens.keys.toList()
|
|
222
224
|
for (id in ids) {
|
|
@@ -229,8 +231,20 @@ object DynamicScreenRegistry {
|
|
|
229
231
|
fun setOptions(id: Int, options: NavigationOptions){
|
|
230
232
|
screens[id]?.options = options
|
|
231
233
|
}
|
|
234
|
+
|
|
235
|
+
companion object {
|
|
236
|
+
private var globalIdCounter = 1
|
|
237
|
+
|
|
238
|
+
private fun nextGlobalId(): Int = globalIdCounter++
|
|
239
|
+
|
|
240
|
+
private fun advanceGlobalIdPast(id: Int) {
|
|
241
|
+
if (id >= globalIdCounter) globalIdCounter = id + 1
|
|
242
|
+
}
|
|
243
|
+
}
|
|
232
244
|
}
|
|
233
245
|
|
|
246
|
+
val LocalDynamicScreenRegistry = staticCompositionLocalOf<DynamicScreenRegistry?> { null }
|
|
247
|
+
|
|
234
248
|
|
|
235
249
|
sealed class OverplayComponentParams {
|
|
236
250
|
class Modal(
|
|
@@ -69,7 +69,7 @@ import vn.momo.kits.platform.supportsImePadding
|
|
|
69
69
|
import vn.momo.kits.navigation.tracking.ScreenTracker
|
|
70
70
|
import vn.momo.kits.navigation.tracking.ScreenTrackingState
|
|
71
71
|
import kotlinx.coroutines.delay
|
|
72
|
-
import vn.momo.kits.application.
|
|
72
|
+
import vn.momo.kits.application.LocalContext
|
|
73
73
|
import kotlin.time.Clock
|
|
74
74
|
import kotlin.time.Duration.Companion.milliseconds
|
|
75
75
|
import kotlin.time.ExperimentalTime
|
|
@@ -89,10 +89,11 @@ internal fun StackScreen(
|
|
|
89
89
|
) {
|
|
90
90
|
val navigator = LocalNavigator.current
|
|
91
91
|
val maxApi = LocalMaxApi.current
|
|
92
|
-
val context =
|
|
92
|
+
val context = LocalContext.current
|
|
93
93
|
val statusBar = AppStatusBar.current
|
|
94
94
|
val density = LocalDensity.current
|
|
95
|
-
val
|
|
95
|
+
val screenRegistry = LocalDynamicScreenRegistry.current
|
|
96
|
+
val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions, registry = screenRegistry) }
|
|
96
97
|
|
|
97
98
|
val options by navigation.currentOptions
|
|
98
99
|
|