@momo-kits/native-kits 0.162.34-debug → 0.162.35-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 +2 -0
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +18 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +6 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +30 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +8 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +9 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +4 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +104 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +8 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +38 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +62 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +22 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +25 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +19 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +13 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +75 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +5 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +10 -4
- package/gradle/libs.versions.toml +1 -1
- package/gradle.properties +1 -1
- package/ios/Application/Navigation/NavigatorManager.swift +0 -27
- package/ios/Colors+Radius+Spacing/Theme.swift +2 -2
- package/ios/Typography/KitDesignSystem.swift +17 -0
- package/ios/Typography/Text.swift +25 -1
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/package.json +1 -1
- package/publish.sh +25 -0
package/CLAUDE.md
CHANGED
|
@@ -41,9 +41,11 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
|
|
|
41
41
|
|
|
42
42
|
- **Entry point:** wrap content in `NavigationContainer(...)` from `navigation/`. It builds the `Navigator`, takes a host-supplied `Localize` (falls back to an empty one), and provides the CompositionLocals below. **Do not use** `ApplicationContainer` (`application/NavigationContainer.kt`) — deprecated and does **not** wire `LocalLocalize` / `LocalNavigator` / `LocalMaxApi`.
|
|
43
43
|
- **Configuration flows via CompositionLocals**, not params. Public ones: `AppTheme`, `AppStatusBar`, `AppNavigationBar` (`const/Theme.kt`); `ApplicationContext`, `AppConfig`, `AppLanguage`, `LocalLocalize` (`application/`); `LocalNavigator`, `LocalMaxApi` (`navigation/`). Read with `X.current`.
|
|
44
|
+
- **Design-system config** (`trustBanner`, `headerBar`, `headerGradient`): the host seeds `KitDesignSystemConfig.update(...)` once (same pattern as `KitFontScale`); `NavigationContainer` provides it as `LocalDesignSystemConfig` (param `designSystemConfig` → parent → global) and applies `headerBar` as the header image when the theme has none.
|
|
44
45
|
- **Platform code** goes through `expect`/`actual` in `platform/` — never reference Android/iOS APIs from `commonMain` directly.
|
|
45
46
|
- **Native bridge:** `IMaxApi` (`vn.momo.maxapi`, exposed as `LocalMaxApi`) is the host-app bridge (dismiss, tracking, device info, language).
|
|
46
47
|
- Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
|
|
48
|
+
- **iOS swipe-back:** the host owns the only edge-pan recogniser and replays it through `HostBackGestureBridge` (`navigation/HostBackGestureBridge.kt`; the kit framework is never exported, so the host reaches it through its own Koin-provided facade): `checkCanPop` / `canPopInner` / `pop` route the back into the kit stack, `started` / `progressed` / `completed` / `cancelled` seek the NavHost transition, `shouldUseFloatingArrowBack` is set by bottom tabs. `NavigationContainer` attaches itself automatically (`AttachHostBackGesture`, iOS only, detached by identity on dispose) — hosts need no wrapper and no `setNavigator`. `maxApi` is a required parameter of `NavigationContainer` — every container, nested or root, is handed its host bridge explicitly.
|
|
47
49
|
|
|
48
50
|
## i18n / Localize (port of RN `Localize`)
|
|
49
51
|
|
package/compose/build.gradle.kts
CHANGED
package/compose/compose.podspec
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package vn.momo.kits.platform
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.State
|
|
5
|
+
import androidx.compose.runtime.mutableStateOf
|
|
6
|
+
import androidx.compose.runtime.remember
|
|
7
|
+
|
|
8
|
+
// No passive observer at androidx.activity 1.10.x; a second callback would steal the gesture.
|
|
9
|
+
@Composable
|
|
10
|
+
actual fun rememberBackGestureProgress(): State<Float?> = remember { mutableStateOf(null) }
|
|
11
|
+
|
|
12
|
+
actual fun reportFloatingArrowBack(shouldUse: Boolean) = Unit
|
|
@@ -30,6 +30,7 @@ import com.airbnb.lottie.compose.rememberLottieDynamicProperties
|
|
|
30
30
|
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
|
|
31
31
|
import vn.momo.kits.const.AppNavigationBar
|
|
32
32
|
import vn.momo.kits.const.AppStatusBar
|
|
33
|
+
import vn.momo.kits.navigation.Navigator
|
|
33
34
|
import vn.momo.kits.utils.readJson
|
|
34
35
|
import androidx.activity.compose.BackHandler as AndroidBackHandler
|
|
35
36
|
|
|
@@ -64,7 +65,7 @@ actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
@Composable
|
|
67
|
-
actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit) {
|
|
68
|
+
actual fun ProvideNavigationEventDispatcherOwner(navigator: Navigator, content: @Composable () -> Unit) {
|
|
68
69
|
content()
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Immutable
|
|
4
|
+
import androidx.compose.runtime.getValue
|
|
5
|
+
import androidx.compose.runtime.mutableStateOf
|
|
6
|
+
import androidx.compose.runtime.setValue
|
|
4
7
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
5
8
|
import vn.momo.kits.components.TrustBannerData
|
|
6
9
|
|
|
@@ -16,6 +19,17 @@ data class DesignSystemConfig(
|
|
|
16
19
|
val headerGradient: String? = null,
|
|
17
20
|
)
|
|
18
21
|
|
|
22
|
+
// App-wide holder seeded by the host (same pattern as KitFontScale), so direct NavigationContainer
|
|
23
|
+
// callers get the config without passing it. Snapshot state: a config fetched after first frame recomposes.
|
|
24
|
+
object KitDesignSystemConfig {
|
|
25
|
+
var config by mutableStateOf<DesignSystemConfig?>(null)
|
|
26
|
+
private set
|
|
27
|
+
|
|
28
|
+
fun update(config: DesignSystemConfig?) {
|
|
29
|
+
this.config = config
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
@Immutable
|
|
20
34
|
data class MiniAppContext(
|
|
21
35
|
val appIcon: String = "",
|
|
@@ -102,5 +116,9 @@ val LocalComponentInformation = staticCompositionLocalOf<ComponentInformation?>
|
|
|
102
116
|
null
|
|
103
117
|
}
|
|
104
118
|
|
|
119
|
+
val LocalDesignSystemConfig = staticCompositionLocalOf<DesignSystemConfig?> {
|
|
120
|
+
null
|
|
121
|
+
}
|
|
122
|
+
|
|
105
123
|
@Deprecated("Max font scale is fixed at MAX_FONT_SCALE (1.5); this CompositionLocal is no longer read.")
|
|
106
124
|
val ScaleSizeMaxRate = staticCompositionLocalOf<Float?> { null }
|
|
@@ -40,9 +40,11 @@ import vn.momo.kits.const.AppTheme
|
|
|
40
40
|
import vn.momo.kits.const.Colors
|
|
41
41
|
import vn.momo.kits.const.Spacing
|
|
42
42
|
import vn.momo.kits.const.Typography
|
|
43
|
+
import vn.momo.kits.const.getFontFamily
|
|
43
44
|
import vn.momo.kits.modifier.kitsAutomationId
|
|
44
45
|
import vn.momo.kits.modifier.noFeedbackClickable
|
|
45
46
|
import vn.momo.kits.modifier.setAutomationId
|
|
47
|
+
import vn.momo.kits.navigation.component.HEADER_FONT_FAMILY
|
|
46
48
|
import vn.momo.kits.navigation.component.HEADER_HEIGHT
|
|
47
49
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
48
50
|
import kotlin.math.max
|
|
@@ -360,6 +362,7 @@ private fun LiteScreenLayout(
|
|
|
360
362
|
text = title,
|
|
361
363
|
color = headerColor.tintIconColor,
|
|
362
364
|
style = titleStyle,
|
|
365
|
+
fontFamily = HEADER_FONT_FAMILY,
|
|
363
366
|
modifier = titleModifier,
|
|
364
367
|
maxLines = 1,
|
|
365
368
|
overflow = TextOverflow.Ellipsis
|
|
@@ -688,9 +691,11 @@ private fun LiteInputSearch(
|
|
|
688
691
|
inputSearchProps.isShowBtnText && !inputSearchProps.btnText.isNullOrEmpty()
|
|
689
692
|
}
|
|
690
693
|
}
|
|
691
|
-
val
|
|
694
|
+
val inputFieldFontFamily = getFontFamily(theme.font, Typography.bodyDefaultRegular.fontWeight)
|
|
695
|
+
val inputFieldStyle = remember(theme, inputFieldFontFamily) {
|
|
692
696
|
Typography.bodyDefaultRegular.copy(
|
|
693
697
|
color = theme.colors.text.default,
|
|
698
|
+
fontFamily = inputFieldFontFamily,
|
|
694
699
|
)
|
|
695
700
|
}
|
|
696
701
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package vn.momo.kits.components
|
|
2
2
|
|
|
3
|
+
import androidx.compose.foundation.Canvas
|
|
3
4
|
import androidx.compose.foundation.background
|
|
4
5
|
import androidx.compose.foundation.layout.Box
|
|
5
6
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
@@ -7,17 +8,42 @@ import androidx.compose.foundation.layout.height
|
|
|
7
8
|
import androidx.compose.foundation.layout.padding
|
|
8
9
|
import androidx.compose.runtime.Composable
|
|
9
10
|
import androidx.compose.ui.Modifier
|
|
11
|
+
import androidx.compose.ui.geometry.Offset
|
|
12
|
+
import androidx.compose.ui.graphics.PathEffect
|
|
13
|
+
import androidx.compose.ui.graphics.StrokeCap
|
|
10
14
|
import androidx.compose.ui.unit.dp
|
|
11
15
|
import vn.momo.kits.const.AppTheme
|
|
12
16
|
import vn.momo.kits.const.Spacing
|
|
13
17
|
|
|
18
|
+
enum class DividerType { DEFAULT, DASH }
|
|
19
|
+
|
|
14
20
|
@Composable
|
|
15
|
-
fun Divider() {
|
|
21
|
+
fun Divider(useMargin: Boolean = true, type: DividerType = DividerType.DEFAULT) {
|
|
22
|
+
val color = AppTheme.current.colors.border.default
|
|
23
|
+
val margin = if (useMargin) Spacing.XS else 0.dp
|
|
24
|
+
if (type == DividerType.DASH) {
|
|
25
|
+
Canvas(
|
|
26
|
+
modifier = Modifier
|
|
27
|
+
.fillMaxWidth()
|
|
28
|
+
.padding(vertical = margin)
|
|
29
|
+
.height(1.dp),
|
|
30
|
+
) {
|
|
31
|
+
drawLine(
|
|
32
|
+
color = color,
|
|
33
|
+
start = Offset(0f, size.height / 2f),
|
|
34
|
+
end = Offset(size.width, size.height / 2f),
|
|
35
|
+
strokeWidth = size.height,
|
|
36
|
+
cap = StrokeCap.Round,
|
|
37
|
+
pathEffect = PathEffect.dashPathEffect(floatArrayOf(4.dp.toPx(), 4.dp.toPx())),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
return
|
|
41
|
+
}
|
|
16
42
|
Box(
|
|
17
43
|
modifier = Modifier
|
|
18
44
|
.fillMaxWidth()
|
|
45
|
+
.padding(vertical = margin)
|
|
19
46
|
.height(1.dp)
|
|
20
|
-
.background(color =
|
|
21
|
-
.padding(vertical = Spacing.XS)
|
|
47
|
+
.background(color = color),
|
|
22
48
|
)
|
|
23
|
-
}
|
|
49
|
+
}
|
|
@@ -266,7 +266,7 @@ fun Input(
|
|
|
266
266
|
)
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
val passwordFontFamily = getFontFamily(
|
|
269
|
+
val passwordFontFamily = getFontFamily(AppTheme.current.font, FontWeight.Normal)
|
|
270
270
|
val passwordFontSize = scaleSize(24.sp)
|
|
271
271
|
val visualTransformation = remember(secureTextEntry, inputState.passHidden, passwordFontFamily) {
|
|
272
272
|
if (secureTextEntry && !inputState.passHidden)
|
|
@@ -44,6 +44,7 @@ import vn.momo.kits.const.Spacing
|
|
|
44
44
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
45
45
|
import vn.momo.kits.const.Colors
|
|
46
46
|
import vn.momo.kits.const.Typography
|
|
47
|
+
import vn.momo.kits.const.getFontFamily
|
|
47
48
|
import vn.momo.kits.modifier.conditional
|
|
48
49
|
import vn.momo.kits.utils.formatNumberToMoney
|
|
49
50
|
|
|
@@ -122,7 +123,8 @@ fun InputMoney(
|
|
|
122
123
|
color = textColor,
|
|
123
124
|
fontSize = 20.sp,
|
|
124
125
|
lineHeight = 24.sp,
|
|
125
|
-
fontWeight = fontWeight.value
|
|
126
|
+
fontWeight = fontWeight.value,
|
|
127
|
+
fontFamily = getFontFamily(AppTheme.current.font, fontWeight.value)
|
|
126
128
|
),
|
|
127
129
|
visualTransformation = CustomConverter(),
|
|
128
130
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
|
|
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.Dp
|
|
|
21
21
|
import androidx.compose.ui.unit.dp
|
|
22
22
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
23
23
|
import vn.momo.kits.const.*
|
|
24
|
+
import vn.momo.kits.const.getFontFamily
|
|
24
25
|
import vn.momo.kits.modifier.conditional
|
|
25
26
|
import vn.momo.kits.modifier.setAutomationId
|
|
26
27
|
|
|
@@ -97,7 +98,13 @@ fun InputPhoneNumber(
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
val (textColor, placeholderColor, iconTintColor) = colors
|
|
100
|
-
val
|
|
101
|
+
val baseTextStyle = size.values.textStyle
|
|
102
|
+
val textStyle = scaleSize(
|
|
103
|
+
baseTextStyle.copy(
|
|
104
|
+
color = textColor,
|
|
105
|
+
fontFamily = getFontFamily(AppTheme.current.font, baseTextStyle.fontWeight)
|
|
106
|
+
)
|
|
107
|
+
)
|
|
101
108
|
|
|
102
109
|
|
|
103
110
|
Column(modifier = modifier
|
|
@@ -42,6 +42,7 @@ import vn.momo.kits.const.Radius
|
|
|
42
42
|
import vn.momo.kits.const.Spacing
|
|
43
43
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
44
44
|
import vn.momo.kits.const.Typography
|
|
45
|
+
import vn.momo.kits.const.getFontFamily
|
|
45
46
|
import vn.momo.kits.const.scaleSize
|
|
46
47
|
import vn.momo.kits.modifier.conditional
|
|
47
48
|
import vn.momo.kits.utils.ifTrue
|
|
@@ -131,19 +132,23 @@ fun InputSearch(
|
|
|
131
132
|
val fontSize = 14.sp
|
|
132
133
|
val scaleFontSize = scaleSize(fontSize)
|
|
133
134
|
|
|
134
|
-
val
|
|
135
|
+
val fontFamily = getFontFamily(AppTheme.current.font, inputSearchProps.fontWeight.value)
|
|
136
|
+
|
|
137
|
+
val textStyle = remember(textColor, inputSearchProps.fontWeight.value, fontFamily) {
|
|
135
138
|
TextStyle(
|
|
136
139
|
color = textColor,
|
|
137
140
|
fontSize = scaleFontSize,
|
|
138
|
-
fontWeight = inputSearchProps.fontWeight.value
|
|
141
|
+
fontWeight = inputSearchProps.fontWeight.value,
|
|
142
|
+
fontFamily = fontFamily
|
|
139
143
|
)
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
val placeHolderStyle = remember(textColor, inputSearchProps.fontWeight.value) {
|
|
146
|
+
val placeHolderStyle = remember(textColor, inputSearchProps.fontWeight.value, fontFamily) {
|
|
143
147
|
TextStyle(
|
|
144
148
|
color = textColor,
|
|
145
149
|
fontSize = fontSize,
|
|
146
|
-
fontWeight = inputSearchProps.fontWeight.value
|
|
150
|
+
fontWeight = inputSearchProps.fontWeight.value,
|
|
151
|
+
fontFamily = fontFamily
|
|
147
152
|
)
|
|
148
153
|
}
|
|
149
154
|
|
|
@@ -21,6 +21,8 @@ import androidx.compose.ui.unit.sp
|
|
|
21
21
|
import androidx.compose.ui.zIndex
|
|
22
22
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
23
23
|
import vn.momo.kits.const.*
|
|
24
|
+
import vn.momo.kits.const.AppTheme
|
|
25
|
+
import vn.momo.kits.const.getFontFamily
|
|
24
26
|
import vn.momo.kits.modifier.InternalApi
|
|
25
27
|
import vn.momo.kits.modifier.conditional
|
|
26
28
|
|
|
@@ -75,7 +77,8 @@ fun InputTextArea(
|
|
|
75
77
|
color = textColor,
|
|
76
78
|
fontSize = scaleFontSize,
|
|
77
79
|
lineHeight = scaleLineHeight,
|
|
78
|
-
fontWeight = fontWeight.value
|
|
80
|
+
fontWeight = fontWeight.value,
|
|
81
|
+
fontFamily = getFontFamily(AppTheme.current.font, fontWeight.value)
|
|
79
82
|
),
|
|
80
83
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
|
|
81
84
|
modifier = Modifier.height(scaleHeight).onFocusChanged {
|
|
@@ -12,6 +12,7 @@ import androidx.compose.ui.Alignment
|
|
|
12
12
|
import androidx.compose.ui.Modifier
|
|
13
13
|
import androidx.compose.ui.graphics.Color
|
|
14
14
|
import androidx.compose.ui.text.TextStyle
|
|
15
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
15
16
|
import androidx.compose.ui.text.input.KeyboardType
|
|
16
17
|
import androidx.compose.ui.text.style.TextAlign
|
|
17
18
|
import androidx.compose.ui.unit.Dp
|
|
@@ -19,6 +20,7 @@ import androidx.compose.ui.unit.dp
|
|
|
19
20
|
import androidx.compose.ui.unit.sp
|
|
20
21
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
21
22
|
import vn.momo.kits.const.*
|
|
23
|
+
import vn.momo.kits.const.getFontFamily
|
|
22
24
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
23
25
|
import vn.momo.kits.modifier.conditional
|
|
24
26
|
|
|
@@ -108,6 +110,7 @@ private fun StepperNumberView(
|
|
|
108
110
|
color = textColor,
|
|
109
111
|
fontSize = scaledFontSize,
|
|
110
112
|
textAlign = TextAlign.Center,
|
|
113
|
+
fontFamily = getFontFamily(theme.font, FontWeight.Normal),
|
|
111
114
|
)
|
|
112
115
|
|
|
113
116
|
Box(
|
|
@@ -11,6 +11,9 @@ import androidx.compose.runtime.*
|
|
|
11
11
|
import androidx.compose.ui.Alignment
|
|
12
12
|
import androidx.compose.ui.Modifier
|
|
13
13
|
import androidx.compose.ui.graphics.Color
|
|
14
|
+
import androidx.compose.ui.graphics.drawscope.withTransform
|
|
15
|
+
import androidx.compose.ui.graphics.drawscope.DrawScope
|
|
16
|
+
import androidx.compose.ui.graphics.Path
|
|
14
17
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
15
18
|
import androidx.compose.ui.layout.positionInParent
|
|
16
19
|
import androidx.compose.ui.platform.LocalDensity
|
|
@@ -304,47 +307,126 @@ private fun CardTabBar(
|
|
|
304
307
|
onPressTabItem: (Int) -> Unit,
|
|
305
308
|
) {
|
|
306
309
|
val theme = AppTheme.current
|
|
307
|
-
|
|
308
|
-
|
|
310
|
+
val surface = theme.colors.background.surface
|
|
311
|
+
|
|
312
|
+
// react-kits `CardTabBar` draws the bar in TWO layers: a flat strip where every
|
|
313
|
+
// tab looks unselected, and one raised card laid OVER it for the active tab.
|
|
314
|
+
// The card is 4dp taller, sits `Spacing.XS` higher, and is joined to the strip
|
|
315
|
+
// on each side by a curved "ear" — the browser-tab silhouette that gives this
|
|
316
|
+
// variant its name. Rendering the active tab in place (just taller) loses both
|
|
317
|
+
// the overlap and the ears, and the strip used the theme's `tonal` pink where
|
|
318
|
+
// react-kits uses `Colors.black_02`, so the bar came out pink and flat.
|
|
319
|
+
val pathWidth = if (tabs.size > 2) 22.dp else 28.dp
|
|
320
|
+
val isFirst = selectedIndex == 0
|
|
321
|
+
val isLast = selectedIndex == tabs.lastIndex
|
|
322
|
+
|
|
323
|
+
// The bar is exactly as tall as the FLAT strip — react-kits' raised card is
|
|
324
|
+
// `position: absolute`, so it never adds height; it just overflows upward into
|
|
325
|
+
// the 4dp margin. Letting the 44dp card size this box instead left a 4dp band of
|
|
326
|
+
// background showing between the tabs and the panel they sit on.
|
|
327
|
+
BoxWithConstraints(
|
|
309
328
|
modifier = Modifier
|
|
310
329
|
.fillMaxWidth()
|
|
311
|
-
.padding(top = Spacing.XS)
|
|
312
|
-
|
|
330
|
+
.padding(top = Spacing.XS)
|
|
331
|
+
.height(CARD_TAB_HEIGHT),
|
|
313
332
|
) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
333
|
+
val itemWidth = maxWidth / tabs.size
|
|
334
|
+
|
|
335
|
+
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.Bottom) {
|
|
336
|
+
tabs.forEachIndexed { index, tab ->
|
|
337
|
+
Box(
|
|
338
|
+
modifier = Modifier
|
|
339
|
+
.width(itemWidth)
|
|
340
|
+
.height(CARD_TAB_HEIGHT)
|
|
341
|
+
.background(
|
|
342
|
+
color = Colors.black_02,
|
|
343
|
+
shape = RoundedCornerShape(topStart = Radius.M, topEnd = Radius.M),
|
|
344
|
+
)
|
|
345
|
+
.activeOpacityClickable { onPressTabItem(index) }
|
|
346
|
+
.padding(horizontal = Spacing.S),
|
|
347
|
+
contentAlignment = Alignment.Center,
|
|
348
|
+
) {
|
|
349
|
+
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
350
|
+
TabLeadingIcon(tab = tab, active = false, color = unselectedColor)
|
|
351
|
+
Text(
|
|
352
|
+
text = tab.title,
|
|
353
|
+
style = Typography.bodyDefaultRegular,
|
|
354
|
+
color = unselectedColor,
|
|
355
|
+
maxLines = 1,
|
|
356
|
+
overflow = TextOverflow.Ellipsis,
|
|
357
|
+
modifier = Modifier.weight(1f, fill = false),
|
|
358
|
+
)
|
|
359
|
+
TabTrailingBadge(tab)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
317
364
|
|
|
365
|
+
val active = tabs[selectedIndex]
|
|
366
|
+
Row(
|
|
367
|
+
modifier = Modifier
|
|
368
|
+
// Pinned to the strip's BOTTOM edge so the two always end on the same
|
|
369
|
+
// pixel; `requiredHeight` then grows it upward into the 4dp margin.
|
|
370
|
+
// Offsetting it up by hand instead left a sub-dp sliver of strip
|
|
371
|
+
// showing under the card once rounding got involved.
|
|
372
|
+
.align(Alignment.BottomStart)
|
|
373
|
+
.offset(x = itemWidth * selectedIndex - (if (isFirst) 0.dp else pathWidth))
|
|
374
|
+
// `requiredHeight`, not `height`: the bar box is only as tall as the
|
|
375
|
+
// flat strip, so a plain height would be coerced down to 40 and the
|
|
376
|
+
// raised card would stop 4dp short of the panel below, leaving a grey
|
|
377
|
+
// sliver. react-kits gets this free — its card is absolutely
|
|
378
|
+
// positioned and ignores the parent box entirely.
|
|
379
|
+
.requiredHeight(CARD_TAB_ACTIVE_HEIGHT),
|
|
380
|
+
verticalAlignment = Alignment.Bottom,
|
|
381
|
+
) {
|
|
382
|
+
// react-kits nudges each ear 8pt UNDER the card so the join has no seam.
|
|
383
|
+
if (!isFirst) CardTabEar(pathWidth, mirrored = true, color = surface, nudge = 8.dp)
|
|
318
384
|
Box(
|
|
319
385
|
modifier = Modifier
|
|
320
|
-
.
|
|
321
|
-
.
|
|
386
|
+
.width(itemWidth)
|
|
387
|
+
.fillMaxHeight()
|
|
322
388
|
.background(
|
|
323
|
-
color =
|
|
324
|
-
|
|
325
|
-
shape = RoundedCornerShape(
|
|
326
|
-
topStart = Radius.M,
|
|
327
|
-
topEnd = Radius.M,
|
|
328
|
-
),
|
|
389
|
+
color = surface,
|
|
390
|
+
shape = RoundedCornerShape(topStart = Radius.M, topEnd = Radius.M),
|
|
329
391
|
)
|
|
330
|
-
.activeOpacityClickable { onPressTabItem(
|
|
331
|
-
.padding(horizontal = Spacing.S),
|
|
392
|
+
.activeOpacityClickable { onPressTabItem(selectedIndex) }
|
|
393
|
+
.padding(horizontal = if (tabs.size == 3) Spacing.S else Spacing.M),
|
|
332
394
|
contentAlignment = Alignment.Center,
|
|
333
395
|
) {
|
|
334
396
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
335
|
-
TabLeadingIcon(tab =
|
|
397
|
+
TabLeadingIcon(tab = active, active = true, color = selectedColor)
|
|
336
398
|
Text(
|
|
337
|
-
text =
|
|
338
|
-
style =
|
|
339
|
-
color =
|
|
399
|
+
text = active.title,
|
|
400
|
+
style = Typography.headerSSemibold,
|
|
401
|
+
color = selectedColor,
|
|
340
402
|
maxLines = 1,
|
|
341
403
|
overflow = TextOverflow.Ellipsis,
|
|
342
404
|
modifier = Modifier.weight(1f, fill = false),
|
|
343
405
|
)
|
|
344
|
-
TabTrailingBadge(
|
|
406
|
+
TabTrailingBadge(active)
|
|
345
407
|
}
|
|
346
408
|
}
|
|
409
|
+
if (!isLast) CardTabEar(pathWidth, mirrored = false, color = surface, nudge = -8.dp)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private val CARD_TAB_HEIGHT = 40.dp
|
|
415
|
+
private val CARD_TAB_ACTIVE_HEIGHT = 44.dp
|
|
416
|
+
@Composable
|
|
417
|
+
private fun CardTabEar(width: Dp, mirrored: Boolean, color: Color, nudge: Dp) {
|
|
418
|
+
Canvas(modifier = Modifier.offset(x = nudge).width(width).height(CARD_TAB_ACTIVE_HEIGHT)) {
|
|
419
|
+
val path = Path().apply {
|
|
420
|
+
moveTo(0f, 0f)
|
|
421
|
+
lineTo(0.60765f, 0f)
|
|
422
|
+
cubicTo(6.47582f, 0f, 11.4819f, 4.23831f, 12.4602f, 10.0244f)
|
|
423
|
+
cubicTo(13.9248f, 18.6872f, 15.9294f, 30.37f, 16.6925f, 34f)
|
|
424
|
+
cubicTo(17.9536f, 40f, 22.259f, 44f, 27.9996f, 44f)
|
|
425
|
+
lineTo(0f, 44f)
|
|
426
|
+
close()
|
|
347
427
|
}
|
|
428
|
+
val draw: DrawScope.() -> Unit = { drawPath(path = path, color = color) }
|
|
429
|
+
if (mirrored) withTransform({ scale(-1f, 1f) }) { draw() } else draw()
|
|
348
430
|
}
|
|
349
431
|
}
|
|
350
432
|
|
|
@@ -78,15 +78,9 @@ fun Text(
|
|
|
78
78
|
// Cache theme access to avoid repeated lookups
|
|
79
79
|
val theme = AppTheme.current
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
val
|
|
83
|
-
val
|
|
84
|
-
val fontFamilyResult = getFontFamily(fontFamily ?: theme.font, style.fontWeight)
|
|
85
|
-
|
|
86
|
-
// Now memoize the results
|
|
87
|
-
val fontSize = remember(scaledFontSize) { scaledFontSize }
|
|
88
|
-
val lineHeight = remember(scaledLineHeight) { scaledLineHeight }
|
|
89
|
-
val font = remember(fontFamilyResult) { fontFamilyResult }
|
|
81
|
+
val fontSize = scaleSize(style.fontSize)
|
|
82
|
+
val lineHeight = scaleSize(style.lineHeight)
|
|
83
|
+
val font = getFontFamily(fontFamily ?: theme.font, style.fontWeight)
|
|
90
84
|
|
|
91
85
|
// Memoize color calculation
|
|
92
86
|
val textColor = remember(color, theme) {
|
|
@@ -47,9 +47,9 @@ fun Title(
|
|
|
47
47
|
val theme = AppTheme.current
|
|
48
48
|
val textStyle: TextStyle = when (type) {
|
|
49
49
|
TitleType.Card -> when (size) {
|
|
50
|
-
TitleSize.Small -> TextStyle(fontSize = 14.sp, lineHeight = 20.sp)
|
|
51
|
-
TitleSize.Medium -> TextStyle(fontSize = 16.sp, lineHeight = 22.sp)
|
|
52
|
-
TitleSize.Large -> TextStyle(fontSize = 18.sp, lineHeight = 26.sp)
|
|
50
|
+
TitleSize.Small -> TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.Bold)
|
|
51
|
+
TitleSize.Medium -> TextStyle(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.Bold)
|
|
52
|
+
TitleSize.Large -> TextStyle(fontSize = 18.sp, lineHeight = 26.sp, fontWeight = FontWeight.Bold)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
TitleType.Section -> when (size) {
|
|
@@ -89,7 +89,11 @@ fun Title(
|
|
|
89
89
|
.conditional(IsShowBaseLineDebug) {
|
|
90
90
|
border(1.dp, Colors.blue_03)
|
|
91
91
|
}
|
|
92
|
-
.
|
|
92
|
+
// react-kits' Title root is `styles.wrapper` = `{flexDirection:'row'}`
|
|
93
|
+
// plus, for a SECTION only, `marginTop: Spacing.S` (foundations/Title
|
|
94
|
+
// index.tsx). It has no padding at all, so the 8dp box here inset every
|
|
95
|
+
// title by 8dp against the web render.
|
|
96
|
+
.padding(top = if (type == TitleType.Section) Spacing.S else 0.dp),
|
|
93
97
|
verticalAlignment = Alignment.CenterVertically
|
|
94
98
|
) {
|
|
95
99
|
icon?.let {
|
|
@@ -18,6 +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.LocalDesignSystemConfig
|
|
21
22
|
import vn.momo.kits.application.LocalLocalize
|
|
22
23
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
23
24
|
import vn.momo.kits.const.Colors
|
|
@@ -68,7 +69,7 @@ fun TrustBanner(
|
|
|
68
69
|
trackEvent: ((String, Map<String, String>) -> Unit)? = null
|
|
69
70
|
) {
|
|
70
71
|
val localize = LocalLocalize.current
|
|
71
|
-
val trustBanner = defaultBanner
|
|
72
|
+
val trustBanner = LocalDesignSystemConfig.current?.trustBanner ?: defaultBanner
|
|
72
73
|
val trackParams = mapOf(
|
|
73
74
|
"service_name" to serviceName,
|
|
74
75
|
"screen_name" to screenName,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package vn.momo.kits.const
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.getValue
|
|
4
|
+
import androidx.compose.runtime.mutableStateOf
|
|
5
|
+
import androidx.compose.runtime.setValue
|
|
6
|
+
|
|
7
|
+
// Design-system rollout holder, same shape as KitFontScale: the kit is sandboxed and can't read the
|
|
8
|
+
// host config, so the host seeds this once at startup from DesignSystemConfig. getFontFamily() reads
|
|
9
|
+
// it to decide whether the new default font applies, so every screen follows the flag with no prop
|
|
10
|
+
// threading. Backed by snapshot state: writes recompose text automatically.
|
|
11
|
+
object KitDesignSystem {
|
|
12
|
+
var isWhiteList by mutableStateOf(false)
|
|
13
|
+
private set
|
|
14
|
+
|
|
15
|
+
fun update(isWhiteList: Boolean) {
|
|
16
|
+
this.isWhiteList = isWhiteList
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -117,7 +117,7 @@ val defaultTheme = Theme(
|
|
|
117
117
|
container = Colors.blue_08
|
|
118
118
|
)
|
|
119
119
|
),
|
|
120
|
-
font = "
|
|
120
|
+
font = "MomoTrustSans",
|
|
121
121
|
assets = ThemeAssets(
|
|
122
122
|
headerBackground = null
|
|
123
123
|
)
|
|
@@ -174,7 +174,7 @@ val darkTheme = Theme(
|
|
|
174
174
|
container = Colors.blue_08
|
|
175
175
|
)
|
|
176
176
|
),
|
|
177
|
-
font = "
|
|
177
|
+
font = "MomoTrustSans",
|
|
178
178
|
assets = ThemeAssets(
|
|
179
179
|
headerBackground = null
|
|
180
180
|
)
|
|
@@ -89,18 +89,29 @@ fun scaleSize(textStyle: TextStyle): TextStyle {
|
|
|
89
89
|
* SDK không đóng gói font riêng nữa: app host đã bundle sẵn toàn bộ font từ `font-source`
|
|
90
90
|
* cho cả React Native lẫn native, nên bản copy trong `composeResources` chỉ làm phình app.
|
|
91
91
|
*/
|
|
92
|
-
private const val
|
|
92
|
+
private const val LEGACY_DEFAULT_FONT_FILE = "SFProText-Regular.ttf"
|
|
93
|
+
private const val NEW_DEFAULT_FONT_FILE = "MomoTrustSans-Regular.ttf"
|
|
93
94
|
|
|
94
95
|
private val hostFontFiles: Map<String, String> = mapOf(
|
|
95
96
|
"SFProText-100" to "SFProText-Thin.ttf",
|
|
96
97
|
"SFProText-200" to "SFProText-Ultralight.ttf",
|
|
97
98
|
"SFProText-300" to "SFProText-Light.ttf",
|
|
98
|
-
"SFProText-400" to
|
|
99
|
+
"SFProText-400" to LEGACY_DEFAULT_FONT_FILE,
|
|
99
100
|
"SFProText-500" to "SFProText-Medium.ttf",
|
|
100
101
|
"SFProText-600" to "SFProText-Semibold.ttf",
|
|
101
102
|
"SFProText-700" to "SFProText-Bold.ttf",
|
|
102
103
|
"SFProText-800" to "SFProText-Heavy.ttf",
|
|
103
104
|
"SFProText-900" to "SFProText-Black.ttf",
|
|
105
|
+
|
|
106
|
+
"MomoTrustSans-100" to "MomoTrustSans-ExtraLight.ttf",
|
|
107
|
+
"MomoTrustSans-200" to "MomoTrustSans-ExtraLight.ttf",
|
|
108
|
+
"MomoTrustSans-300" to "MomoTrustSans-Light.ttf",
|
|
109
|
+
"MomoTrustSans-400" to "MomoTrustSans-Regular.ttf",
|
|
110
|
+
"MomoTrustSans-500" to "MomoTrustSans-Medium.ttf",
|
|
111
|
+
"MomoTrustSans-600" to "MomoTrustSans-SemiBold.ttf",
|
|
112
|
+
"MomoTrustSans-700" to "MomoTrustSans-Bold.ttf",
|
|
113
|
+
"MomoTrustSans-800" to "MomoTrustSans-ExtraBold.ttf",
|
|
114
|
+
"MomoTrustSans-900" to "MomoTrustSans-ExtraBold.ttf",
|
|
104
115
|
) + (1..9).flatMap { step ->
|
|
105
116
|
val weight = step * 100
|
|
106
117
|
listOf(
|
|
@@ -109,12 +120,34 @@ private val hostFontFiles: Map<String, String> = mapOf(
|
|
|
109
120
|
)
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Font mặc định mới, chỉ áp cho user nằm trong whitelist design system ([KitDesignSystem]).
|
|
125
|
+
* Ngoài whitelist thì quay về [LEGACY_DEFAULT_FONT_FAMILY] như trước.
|
|
126
|
+
*/
|
|
127
|
+
private const val NEW_DEFAULT_FONT_FAMILY = "MomoTrustSans"
|
|
128
|
+
private const val LEGACY_DEFAULT_FONT_FAMILY = "SFProText"
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Font thuoc bo design system moi. MoMoTrustDisplay chi duoc header dung tu DS-791
|
|
132
|
+
* (commit header title 5bfde6251); truoc do header lay font theo theme, nen ngoai
|
|
133
|
+
* whitelist ca hai deu ha ve [LEGACY_DEFAULT_FONT_FAMILY].
|
|
134
|
+
*/
|
|
135
|
+
private val newFontFamilies = setOf(NEW_DEFAULT_FONT_FAMILY, "MoMoTrustDisplay")
|
|
136
|
+
|
|
112
137
|
@Composable
|
|
113
138
|
fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
|
|
114
|
-
val
|
|
115
|
-
val
|
|
139
|
+
val isWhiteList = KitDesignSystem.isWhiteList
|
|
140
|
+
val resolvedFamily =
|
|
141
|
+
if (fontFamily in newFontFamilies && !isWhiteList) {
|
|
142
|
+
LEGACY_DEFAULT_FONT_FAMILY
|
|
143
|
+
} else {
|
|
144
|
+
fontFamily
|
|
145
|
+
}
|
|
146
|
+
val defaultFile = if (isWhiteList) NEW_DEFAULT_FONT_FILE else LEGACY_DEFAULT_FONT_FILE
|
|
147
|
+
val key = "$resolvedFamily-${fontWeight?.weight}"
|
|
148
|
+
val fileName = hostFontFiles[key] ?: defaultFile
|
|
116
149
|
return hostFontFamily(fileName)
|
|
117
|
-
?: hostFontFamily(
|
|
150
|
+
?: hostFontFamily(defaultFile)
|
|
118
151
|
?: FontFamily.Default
|
|
119
152
|
}
|
|
120
153
|
|