@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
|
@@ -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()
|
|
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
|
package/gradle.properties
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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(
|
|
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)
|
package/ios/native-kits.podspec
CHANGED
package/package.json
CHANGED
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"
|