@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.
Files changed (49) hide show
  1. package/CLAUDE.md +2 -0
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -1
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +18 -0
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +6 -1
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +30 -4
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +1 -1
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +3 -1
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +8 -1
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +9 -4
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +4 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +3 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +104 -22
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -9
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +8 -4
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +2 -1
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +2 -2
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +38 -5
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +3 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +62 -33
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +22 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +25 -5
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +19 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +13 -2
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +75 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +3 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +5 -1
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -1
  37. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  38. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  39. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +10 -4
  40. package/gradle/libs.versions.toml +1 -1
  41. package/gradle.properties +1 -1
  42. package/ios/Application/Navigation/NavigatorManager.swift +0 -27
  43. package/ios/Colors+Radius+Spacing/Theme.swift +2 -2
  44. package/ios/Typography/KitDesignSystem.swift +17 -0
  45. package/ios/Typography/Text.swift +25 -1
  46. package/ios/native-kits.podspec +1 -1
  47. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  48. package/package.json +1 -1
  49. package/publish.sh +25 -0
@@ -0,0 +1,106 @@
1
+ package vn.momo.kits.navigation.component
2
+
3
+ import androidx.compose.foundation.layout.Arrangement
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.Column
6
+ import androidx.compose.foundation.layout.Row
7
+ import androidx.compose.foundation.layout.Spacer
8
+ import androidx.compose.foundation.layout.fillMaxWidth
9
+ import androidx.compose.foundation.layout.height
10
+ import androidx.compose.foundation.layout.width
11
+ import androidx.compose.foundation.layout.widthIn
12
+ import androidx.compose.foundation.shape.RoundedCornerShape
13
+ import androidx.compose.runtime.Composable
14
+ import androidx.compose.ui.Alignment
15
+ import androidx.compose.ui.Modifier
16
+ import androidx.compose.ui.draw.clip
17
+ import androidx.compose.ui.graphics.Color
18
+ import androidx.compose.ui.text.style.TextOverflow
19
+ import androidx.compose.ui.unit.dp
20
+ import vn.momo.kits.components.Icon
21
+ import vn.momo.kits.components.Skeleton
22
+ import vn.momo.kits.components.Text
23
+ import vn.momo.kits.const.Spacing
24
+ import vn.momo.kits.const.Typography
25
+ import vn.momo.kits.modifier.activeOpacityClickable
26
+ import vn.momo.kits.platform.getScreenDimensions
27
+
28
+ @Composable
29
+ internal fun HeaderJourney(
30
+ data: HeaderTitle,
31
+ tintIconColor: Color,
32
+ ) {
33
+ if (data !is HeaderTitle.Journey) return
34
+ if (data.isLoading) return TitleJourneyShimmer()
35
+
36
+ val maxWidth = getScreenDimensions().width.dp - 172.dp
37
+ val tint = parseHeaderTint(data.tintColor) ?: tintIconColor
38
+ val iconTint = parseHeaderTint(data.iconColor) ?: tint
39
+
40
+ Column(
41
+ modifier = Modifier
42
+ .widthIn(max = maxWidth)
43
+ .activeOpacityClickable(enabled = data.onPress != null, onClick = { data.onPress?.invoke() }),
44
+ verticalArrangement = Arrangement.Center,
45
+ ) {
46
+ Row(verticalAlignment = Alignment.CenterVertically) {
47
+ Text(
48
+ text = data.start,
49
+ color = tint,
50
+ style = Typography.actionXsBold,
51
+ maxLines = 1,
52
+ overflow = TextOverflow.Ellipsis,
53
+ modifier = Modifier.weight(1f, fill = false),
54
+ )
55
+ Spacer(Modifier.width(Spacing.S))
56
+ if (data.icon.isNotBlank()) {
57
+ Icon(source = data.icon, size = 16.dp, color = iconTint)
58
+ } else {
59
+ Spacer(Modifier.width(16.dp))
60
+ }
61
+ Spacer(Modifier.width(Spacing.S))
62
+ if (!data.end.isNullOrEmpty()) {
63
+ Text(
64
+ text = data.end,
65
+ color = tint,
66
+ style = Typography.actionXsBold,
67
+ maxLines = 1,
68
+ overflow = TextOverflow.Ellipsis,
69
+ modifier = Modifier.weight(1f, fill = false),
70
+ )
71
+ }
72
+ Spacer(Modifier.width(Spacing.XS))
73
+ Icon(source = "16_arrow_chevron_down_small", size = 16.dp, color = tint)
74
+ }
75
+ if (!data.description.isNullOrEmpty()) {
76
+ Text(
77
+ text = data.description,
78
+ color = tint,
79
+ style = Typography.descriptionXsRegular,
80
+ maxLines = 1,
81
+ overflow = TextOverflow.Ellipsis,
82
+ )
83
+ }
84
+ }
85
+ }
86
+
87
+ @Composable
88
+ private fun TitleJourneyShimmer() {
89
+ Column(modifier = Modifier.fillMaxWidth()) {
90
+ Box(modifier = Modifier.height(18.dp).width(120.dp).clip(RoundedCornerShape(8.dp))) { Skeleton() }
91
+ Spacer(Modifier.height(Spacing.XXS))
92
+ Box(modifier = Modifier.height(12.dp).width(120.dp).clip(RoundedCornerShape(8.dp))) { Skeleton() }
93
+ }
94
+ }
95
+
96
+ internal fun parseHeaderTint(value: String?): Color? {
97
+ val raw = value?.trim()?.removePrefix("#") ?: return null
98
+ val hex = when (raw.length) {
99
+ 3 -> "FF" + raw.map { "$it$it" }.joinToString("")
100
+ 6 -> "FF$raw"
101
+ 8 -> raw
102
+ else -> return null
103
+ }
104
+ val v = hex.toLongOrNull(16) ?: return null
105
+ return Color(v)
106
+ }
@@ -0,0 +1,75 @@
1
+ package vn.momo.kits.navigation.component
2
+
3
+ import androidx.compose.foundation.layout.Box
4
+ import androidx.compose.foundation.layout.Column
5
+ import androidx.compose.foundation.layout.Row
6
+ import androidx.compose.foundation.layout.Spacer
7
+ import androidx.compose.foundation.layout.fillMaxWidth
8
+ import androidx.compose.foundation.layout.height
9
+ import androidx.compose.foundation.layout.width
10
+ import androidx.compose.foundation.layout.widthIn
11
+ import androidx.compose.foundation.shape.RoundedCornerShape
12
+ import androidx.compose.runtime.Composable
13
+ import androidx.compose.ui.Alignment
14
+ import androidx.compose.ui.Modifier
15
+ import androidx.compose.ui.draw.clip
16
+ import androidx.compose.ui.graphics.Color
17
+ import androidx.compose.ui.text.style.TextOverflow
18
+ import androidx.compose.ui.unit.dp
19
+ import vn.momo.kits.components.Icon
20
+ import vn.momo.kits.components.Skeleton
21
+ import vn.momo.kits.components.Text
22
+ import vn.momo.kits.const.Spacing
23
+ import vn.momo.kits.const.Typography
24
+ import vn.momo.kits.modifier.activeOpacityClickable
25
+ import vn.momo.kits.platform.getScreenDimensions
26
+
27
+ @Composable
28
+ internal fun HeaderLocation(
29
+ data: HeaderTitle,
30
+ tintIconColor: Color,
31
+ ) {
32
+ if (data !is HeaderTitle.Location) return
33
+ if (data.isLoading) return TitleLocationShimmer()
34
+
35
+ val maxWidth = getScreenDimensions().width.dp - 172.dp
36
+ val tint = parseHeaderTint(data.tintColor) ?: tintIconColor
37
+
38
+ Column(
39
+ modifier = Modifier
40
+ .widthIn(max = maxWidth)
41
+ .activeOpacityClickable(enabled = data.onPress != null, onClick = { data.onPress?.invoke() }),
42
+ horizontalAlignment = Alignment.Start,
43
+ ) {
44
+ if (!data.description.isNullOrEmpty()) {
45
+ Text(
46
+ text = data.description,
47
+ color = tint,
48
+ style = Typography.descriptionXsRegular,
49
+ maxLines = 1,
50
+ overflow = TextOverflow.Ellipsis,
51
+ )
52
+ }
53
+ Row(verticalAlignment = Alignment.CenterVertically) {
54
+ Text(
55
+ text = data.location,
56
+ color = tint,
57
+ style = Typography.actionXsBold,
58
+ maxLines = 1,
59
+ overflow = TextOverflow.Ellipsis,
60
+ modifier = Modifier.weight(1f, fill = false),
61
+ )
62
+ Spacer(Modifier.width(Spacing.XS))
63
+ Icon(source = "16_arrow_chevron_down_small", size = 16.dp, color = tint)
64
+ }
65
+ }
66
+ }
67
+
68
+ @Composable
69
+ private fun TitleLocationShimmer() {
70
+ Column(modifier = Modifier.fillMaxWidth()) {
71
+ Box(modifier = Modifier.height(12.dp).width(120.dp).clip(RoundedCornerShape(8.dp))) { Skeleton() }
72
+ Spacer(Modifier.height(Spacing.XXS))
73
+ Box(modifier = Modifier.height(18.dp).width(120.dp).clip(RoundedCornerShape(8.dp))) { Skeleton() }
74
+ }
75
+ }
@@ -12,6 +12,8 @@ import vn.momo.kits.components.Text
12
12
  import vn.momo.kits.const.Typography
13
13
  import vn.momo.kits.modifier.setAutomationId
14
14
 
15
+ internal const val HEADER_FONT_FAMILY = "MoMoTrustDisplay"
16
+
15
17
  @Composable
16
18
  internal fun HeaderTitle(
17
19
  title: String = "",
@@ -27,6 +29,7 @@ internal fun HeaderTitle(
27
29
  fontSize = 15.sp,
28
30
  lineHeight = 22.sp,
29
31
  ),
32
+ fontFamily = HEADER_FONT_FAMILY,
30
33
  color = color,
31
34
  maxLines = 1,
32
35
  overflow = TextOverflow.Ellipsis
@@ -16,6 +16,7 @@ import androidx.compose.ui.draw.clip
16
16
  import androidx.compose.ui.draw.clipToBounds
17
17
  import androidx.compose.ui.layout.ContentScale
18
18
  import androidx.compose.ui.text.TextStyle
19
+ import androidx.compose.ui.text.font.FontWeight
19
20
  import androidx.compose.ui.text.style.TextOverflow
20
21
  import androidx.compose.ui.unit.Dp
21
22
  import androidx.compose.ui.unit.dp
@@ -28,6 +29,7 @@ import vn.momo.kits.const.AppTheme
28
29
  import vn.momo.kits.const.Colors
29
30
  import vn.momo.kits.const.Spacing
30
31
  import vn.momo.kits.const.Typography
32
+ import vn.momo.kits.const.getFontFamily
31
33
  import vn.momo.kits.modifier.activeOpacityClickable
32
34
  import vn.momo.kits.navigation.component.HeaderTitle.User
33
35
  import vn.momo.kits.platform.getScreenDimensions
@@ -77,6 +79,7 @@ internal fun HeaderUser(
77
79
  text = data.title,
78
80
  color = data.tintColor,
79
81
  style = Typography.actionXsBold,
82
+ fontFamily = HEADER_FONT_FAMILY,
80
83
  maxLines = 1,
81
84
  overflow = TextOverflow.Ellipsis
82
85
  )
@@ -303,7 +306,8 @@ private fun ManyAvatar(options: Options, modifier: Modifier = Modifier, urls: Av
303
306
  BasicText(
304
307
  text = "+$more",
305
308
  style = TextStyle(
306
- color = Colors.pink_03
309
+ color = Colors.pink_03,
310
+ fontFamily = getFontFamily(AppTheme.current.font, FontWeight.Normal)
307
311
  ),
308
312
  maxLines = 1,
309
313
  autoSize = TextAutoSize.StepBased(
@@ -0,0 +1,12 @@
1
+ package vn.momo.kits.platform
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.State
5
+
6
+ /** Fraction of container width, `null` when idle. Observe only — consuming would steal the
7
+ * gesture from NavHost. Always `null` on Android. */
8
+ @Composable
9
+ expect fun rememberBackGestureProgress(): State<Float?>
10
+
11
+ /** Android is a no-op: no host-owned recogniser to hand the gesture to. */
12
+ expect fun reportFloatingArrowBack(shouldUse: Boolean)
@@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
5
5
  import androidx.compose.ui.graphics.Color
6
6
  import androidx.compose.ui.graphics.NativePaint
7
7
  import androidx.compose.ui.unit.Dp
8
+ import vn.momo.kits.navigation.Navigator
8
9
 
9
10
  data class ScreenDimension(
10
11
  val width: Int,
@@ -24,8 +25,9 @@ expect fun getStatusBarHeight(): Dp
24
25
  @Composable
25
26
  expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
26
27
 
28
+ @Suppress("ParamsComparedByRef")
27
29
  @Composable
28
- expect fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit)
30
+ expect fun ProvideNavigationEventDispatcherOwner(navigator: Navigator, content: @Composable () -> Unit)
29
31
 
30
32
  @Composable
31
33
  expect fun getScreenHeight(): Dp
@@ -0,0 +1,45 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.DisposableEffect
5
+ import androidx.compose.runtime.remember
6
+ import androidx.navigationevent.NavigationEvent
7
+ import androidx.navigationevent.NavigationEventDispatcher
8
+ import androidx.navigationevent.NavigationEventInput
9
+
10
+ @Suppress("ParamsComparedByRef")
11
+ @Composable
12
+ internal fun AttachHostBackGesture(dispatcher: NavigationEventDispatcher, navigator: Navigator) {
13
+ val input = remember(dispatcher) { HostNavigationEventInput() }
14
+ val target = remember(input, navigator) {
15
+ object : HostBackTarget {
16
+ override fun canPop() = navigator.canPop()
17
+ override fun onBack() = navigator.onBackSafe()
18
+ override fun backStarted() = input.backStarted()
19
+ override fun backProgressed(progress: Float) = input.backProgressed(progress)
20
+ override fun backCompleted() = input.backCompleted()
21
+ override fun backCancelled() = input.backCancelled()
22
+ }
23
+ }
24
+
25
+ DisposableEffect(dispatcher, target) {
26
+ dispatcher.addInput(input)
27
+ HostBackGestureBridge.attach(target)
28
+ onDispose {
29
+ HostBackGestureBridge.detach(target)
30
+ dispatcher.removeInput(input)
31
+ }
32
+ }
33
+ }
34
+
35
+ internal class HostNavigationEventInput : NavigationEventInput() {
36
+ fun backStarted() = dispatchOnBackStarted(NavigationEvent(swipeEdge = NavigationEvent.EDGE_LEFT))
37
+
38
+ fun backProgressed(progress: Float) = dispatchOnBackProgressed(
39
+ NavigationEvent(swipeEdge = NavigationEvent.EDGE_LEFT, progress = progress)
40
+ )
41
+
42
+ fun backCancelled() = dispatchOnBackCancelled()
43
+
44
+ fun backCompleted() = dispatchOnBackCompleted()
45
+ }
@@ -0,0 +1,38 @@
1
+ package vn.momo.kits.platform
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.LaunchedEffect
5
+ import androidx.compose.runtime.State
6
+ import androidx.compose.runtime.mutableStateOf
7
+ import androidx.compose.runtime.remember
8
+ import androidx.compose.ui.InternalComposeUiApi
9
+ import androidx.compose.ui.backhandler.LocalCompatNavigationEventDispatcherOwner
10
+ import androidx.navigationevent.NavigationEventTransitionState
11
+ import vn.momo.kits.navigation.HostBackGestureBridge
12
+
13
+ // transitionState is a passive observer; its progress is touch.x / view width.
14
+ @OptIn(InternalComposeUiApi::class)
15
+ @Composable
16
+ actual fun rememberBackGestureProgress(): State<Float?> {
17
+ val dispatcher = LocalCompatNavigationEventDispatcherOwner.current?.navigationEventDispatcher
18
+ val progress = remember { mutableStateOf<Float?>(null) }
19
+
20
+ LaunchedEffect(dispatcher) {
21
+ if (dispatcher == null) {
22
+ progress.value = null
23
+ return@LaunchedEffect
24
+ }
25
+ dispatcher.transitionState.collect { state ->
26
+ progress.value = (state as? NavigationEventTransitionState.InProgress)
27
+ ?.takeIf { it.direction == NavigationEventTransitionState.TRANSITIONING_BACK }
28
+ ?.latestEvent
29
+ ?.progress
30
+ }
31
+ }
32
+
33
+ return progress
34
+ }
35
+
36
+ actual fun reportFloatingArrowBack(shouldUse: Boolean) {
37
+ HostBackGestureBridge.shouldUseFloatingArrowBack = shouldUse
38
+ }
@@ -17,6 +17,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi
17
17
  import androidx.compose.ui.InternalComposeUiApi
18
18
  import androidx.compose.ui.Modifier
19
19
  import androidx.compose.ui.backhandler.LocalCompatNavigationEventDispatcherOwner
20
+ import androidx.compose.ui.backhandler.BackHandler as ComposeBackHandler
20
21
  import androidx.compose.ui.graphics.Color
21
22
  import androidx.compose.ui.graphics.NativePaint
22
23
  import androidx.compose.ui.graphics.toArgb
@@ -38,6 +39,8 @@ import platform.Foundation.NSBundle
38
39
  import platform.UIKit.UIColor
39
40
  import platform.UIKit.UIDevice
40
41
  import platform.UIKit.UIScreen
42
+ import vn.momo.kits.navigation.AttachHostBackGesture
43
+ import vn.momo.kits.navigation.Navigator
41
44
 
42
45
  actual fun getPlatformName(): String = "iOS"
43
46
 
@@ -60,21 +63,24 @@ actual fun getStatusBarHeight(): Dp {
60
63
  return WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
61
64
  }
62
65
 
66
+ @Suppress("DEPRECATION")
67
+ @OptIn(ExperimentalComposeUiApi::class)
63
68
  @Composable
64
69
  actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
70
+ ComposeBackHandler(enabled = enabled, onBack = onBack)
65
71
  }
66
72
 
67
73
  @OptIn(InternalComposeUiApi::class)
68
74
  @Composable
69
- actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit) {
75
+ actual fun ProvideNavigationEventDispatcherOwner(navigator: Navigator, content: @Composable () -> Unit) {
70
76
  val navigationEventDispatcherOwner = remember {
71
77
  object : NavigationEventDispatcherOwner {
72
- override val navigationEventDispatcher = NavigationEventDispatcher().apply {
73
- isEnabled = false
74
- }
78
+ override val navigationEventDispatcher = NavigationEventDispatcher()
75
79
  }
76
80
  }
77
81
 
82
+ AttachHostBackGesture(navigationEventDispatcherOwner.navigationEventDispatcher, navigator)
83
+
78
84
  CompositionLocalProvider(
79
85
  LocalCompatNavigationEventDispatcherOwner provides navigationEventDispatcherOwner,
80
86
  content = content
@@ -1,5 +1,5 @@
1
1
  [versions]
2
- android-compileSdk = "36"
2
+ android-compileSdk = "37"
3
3
  android-minSdk = "24"
4
4
  android-targetSdk = "36"
5
5
  ktor-version = "3.0.0-rc-1"
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.34
21
+ version=0.162.35
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -1,19 +1,3 @@
1
- //
2
- // NavigatorManager.swift
3
- // MoMoUIKits
4
- //
5
- // Host bridge for the native iOS edge-swipe back — the SwiftUI mirror of Compose's
6
- // `ComposeNavigatorManager` (momo-app `vn.momo.compose.uikits.ComposeNavigatorManager`).
7
- //
8
- // The kit does NOT own the swipe gesture. In the host app a kit screen lives inside a
9
- // `UIHostingController` hosted by the mini-app container VC, and that VC owns the
10
- // `UIScreenEdgePanGestureRecognizer`. When the swipe fires the host asks this manager
11
- // whether a kit stack is mounted (`checkCanPop`) and, if so, routes the back into it
12
- // (`pop` → `Navigator.onBackSafe`) instead of dismissing the whole mini-app. Standalone
13
- // (no such host, e.g. the demo app) there is no edge swipe — same as Compose on iOS,
14
- // whose `BackHandler` is a no-op and which relies entirely on the host.
15
- //
16
-
17
1
  import Foundation
18
2
 
19
3
  @available(iOS 16.0, *)
@@ -21,28 +5,17 @@ public final class NavigatorManager {
21
5
  public static let shared = NavigatorManager()
22
6
  private init() {}
23
7
 
24
- /// Stack of mounted navigators; the last one is the visible top (nested mini-apps
25
- /// push more than one). Held strongly, mirroring `ComposeNavigatorManager` — entries
26
- /// are released by `NavigationContainer` on disappear, not by ARC.
27
8
  private var navigators: [Navigator] = []
28
9
 
29
- /// Idempotent: SwiftUI can re-run a view's `onAppear`, and a double push would need a
30
- /// matching double remove to unwind.
31
10
  public func push(_ navigator: Navigator) {
32
11
  guard !navigators.contains(where: { $0 === navigator }) else { return }
33
12
  navigators.append(navigator)
34
13
  }
35
14
 
36
- /// Removes by identity rather than `removeLast` so a spurious disappear of a covered
37
- /// (non-top) container can't drop the wrong navigator.
38
15
  public func remove(_ navigator: Navigator) {
39
16
  navigators.removeAll { $0 === navigator }
40
17
  }
41
18
 
42
- /// True while any kit stack is mounted — tells the host to route the back into the kit
43
- /// instead of dismissing the mini-app. Matches `ComposeNavigatorManager.checkCanPop`:
44
- /// it does not inspect stack depth; `onBackSafe` handles the at-root case itself by
45
- /// asking the host to dismiss (via `composeApi`).
46
19
  public func checkCanPop() -> Bool { !navigators.isEmpty }
47
20
 
48
21
  public func pop() { navigators.last?.onBackSafe() }
@@ -221,7 +221,7 @@ public let defaultTheme = Theme(
221
221
  container: Colors.blue08
222
222
  )
223
223
  ),
224
- font: "SFProText",
224
+ font: "MomoTrustSans",
225
225
  assets: ThemeAssets(headerBackground: nil)
226
226
  )
227
227
 
@@ -277,7 +277,7 @@ public let darkTheme = Theme(
277
277
  container: Colors.blue08
278
278
  )
279
279
  ),
280
- font: "SFProText",
280
+ font: "MomoTrustSans",
281
281
  assets: ThemeAssets(headerBackground: nil)
282
282
  )
283
283
 
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ /// Design-system rollout flag for the SwiftUI kit, mirroring Compose's `KitDesignSystem`.
4
+ /// The kit is sandboxed and can't read the host config, so the host writes `update(_:)` once at
5
+ /// startup from `DesignSystemConfig`. Startup-only and non-reactive, like `FontScaleStore` was
6
+ /// before it gained live updates: there is no SwiftUI navigation container to re-render from.
7
+ public final class KitDesignSystem {
8
+ public static let shared = KitDesignSystem()
9
+
10
+ public private(set) var isWhiteList: Bool = false
11
+
12
+ private init() {}
13
+
14
+ public func update(isWhiteList: Bool) {
15
+ self.isWhiteList = isWhiteList
16
+ }
17
+ }
@@ -99,6 +99,30 @@ public extension TypographyStyle {
99
99
  }
100
100
  }
101
101
 
102
+ private func momoFontName(family: String, weight: Font.Weight) -> String? {
103
+ // Ngoai whitelist thi tra nil de momoFont() roi ve .system(...), dung nhu truoc DS-791.
104
+ guard KitDesignSystem.shared.isWhiteList else { return nil }
105
+ guard family == "MomoTrustSans" else { return nil }
106
+ switch weight {
107
+ case .ultraLight, .thin: return "MomoTrustSans-ExtraLight"
108
+ case .light: return "MomoTrustSans-Light"
109
+ case .regular: return "MomoTrustSans-Regular"
110
+ case .medium: return "MomoTrustSans-Medium"
111
+ case .semibold: return "MomoTrustSans-SemiBold"
112
+ case .bold: return "MomoTrustSans-Bold"
113
+ case .heavy, .black: return "MomoTrustSans-ExtraBold"
114
+ default: return "MomoTrustSans-Regular"
115
+ }
116
+ }
117
+
118
+ private func momoFont(size: CGFloat, weight: Font.Weight, family: String) -> Font {
119
+ guard let name = momoFontName(family: family, weight: weight),
120
+ UIFont(name: name, size: size) != nil else {
121
+ return .system(size: size, weight: weight)
122
+ }
123
+ return .custom(name, fixedSize: size)
124
+ }
125
+
102
126
  public struct MomoText: View {
103
127
  @Environment(\.appTheme) private var theme
104
128
  @Environment(\.momoFontScale) private var fontScale
@@ -159,7 +183,7 @@ public struct MomoText: View {
159
183
  let isStrikethrough = strikethrough || typography.isStrikethrough
160
184
 
161
185
  var text = SwiftUI.Text(content)
162
- .font(fontScale.font(size: typography.fontSize, weight: typography.fontWeight))
186
+ .font(momoFont(size: fontScale(typography.fontSize), weight: typography.fontWeight, family: theme.font))
163
187
  .foregroundColor(textColor)
164
188
  .strikethrough(isStrikethrough, color: textColor)
165
189
  .underline(underline, color: textColor)
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.164.1-beta.2'
3
+ s.version = '0.164.1-swipeback.1'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.164.1-beta.2'
3
+ s.version = '0.164.1-swipeback.1'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.162.34-debug",
3
+ "version": "0.162.35-debug",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},
package/publish.sh CHANGED
@@ -372,10 +372,34 @@ phase_publish_npm() {
372
372
  # FIRST PUBLISH: DEBUG VERSION (with compose kits included)
373
373
  # ============================================================
374
374
  echo ""
375
+ # A bad token surfaces as a 404 on the PUT, after the working tree was already rewritten
376
+ # below — so verify auth first, before anything is touched.
377
+ if ! npm whoami --registry https://registry.npmjs.org >/dev/null 2>&1; then
378
+ echo "❌ Error: npm auth failed (npm whoami). Renew the token in .npmrc / ~/.npmrc and retry."
379
+ exit 1
380
+ fi
381
+
375
382
  echo "🔧 Preparing debug version with compose kits..."
376
383
 
377
384
  # Create debug version
378
385
  DEBUG_VERSION="${VERSION}-debug"
386
+
387
+ # set -e aborts mid-phase on any failure; put the tree back no matter where it stopped.
388
+ # Idempotent: the happy path already moved the backups away and reset the version.
389
+ restore_npm_workspace() {
390
+ [ -f .npmignore.backup ] && mv .npmignore.backup .npmignore
391
+ [ -f compose/build.gradle.kts.backup ] && mv compose/build.gradle.kts.backup compose/build.gradle.kts
392
+ local pkg_version
393
+ pkg_version=$(node -p "require('./package.json').version")
394
+ if [ "$pkg_version" != "$VERSION" ]; then
395
+ if [[ "$OSTYPE" == "darwin"* ]]; then
396
+ sed -i '' "s/\"version\": \"$pkg_version\"/\"version\": \"$VERSION\"/" package.json
397
+ else
398
+ sed -i "s/\"version\": \"$pkg_version\"/\"version\": \"$VERSION\"/" package.json
399
+ fi
400
+ fi
401
+ }
402
+ trap restore_npm_workspace EXIT
379
403
  echo "📦 Debug version: $DEBUG_VERSION"
380
404
 
381
405
  # Get current version from package.json (in case it's different from $VERSION)
@@ -482,6 +506,7 @@ EOF
482
506
  npm publish --tag $NPM_TAG --access=public
483
507
 
484
508
  echo "✅ Normal version published successfully!"
509
+ trap - EXIT
485
510
 
486
511
  echo ""
487
512
  echo "✅ PHASE 3 COMPLETED"