@momo-kits/native-kits 0.164.1-trustsans-12-debug → 0.165.1-beta.10-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/.codegraph/codegraph.db +0 -0
- package/.codegraph/daemon.log +100 -0
- package/CLAUDE.md +1 -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/commonMain/kotlin/vn/momo/kits/application/Context.kt +18 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +30 -4
- 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/navigation/Navigation.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +66 -31
- 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/platform/BackGesture.kt +12 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +98 -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 +13 -3
- package/gradle.properties +1 -1
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/package.json +1 -1
- package/.codegraph/.gitignore +0 -5
|
Binary file
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 51873, v1.1.0). Idle timeout 300000ms.
|
|
2
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
3
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
4
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 58673, v1.1.0). Idle timeout 300000ms.
|
|
5
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
6
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
7
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 80799, v1.1.0). Idle timeout 300000ms.
|
|
8
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
9
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
10
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 34253, v1.1.0). Idle timeout 300000ms.
|
|
11
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
12
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
13
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 65894, v1.1.0). Idle timeout 300000ms.
|
|
14
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
15
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 446ms
|
|
16
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 219ms
|
|
17
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 212ms
|
|
18
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
19
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 68727, v1.1.0). Idle timeout 300000ms.
|
|
20
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
21
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 466ms
|
|
22
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 225ms
|
|
23
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 218ms
|
|
24
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 211ms
|
|
25
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 203ms
|
|
26
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 200ms
|
|
27
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 204ms
|
|
28
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
29
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 97060, v1.1.0). Idle timeout 300000ms.
|
|
30
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
31
|
+
[CodeGraph MCP] Caught up 1 file(s) changed since last run
|
|
32
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 485ms
|
|
33
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 328ms
|
|
34
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 280ms
|
|
35
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 208ms
|
|
36
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 206ms
|
|
37
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 178ms
|
|
38
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
39
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 2726, v1.1.0). Idle timeout 300000ms.
|
|
40
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
41
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 427ms
|
|
42
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 178ms
|
|
43
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 170ms
|
|
44
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 196ms
|
|
45
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 180ms
|
|
46
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 146ms
|
|
47
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 170ms
|
|
48
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 190ms
|
|
49
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 178ms
|
|
50
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
51
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 8036, v1.1.0). Idle timeout 300000ms.
|
|
52
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
53
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
54
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 14133, v1.1.0). Idle timeout 300000ms.
|
|
55
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
56
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
57
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 34560, v1.1.0). Idle timeout 300000ms.
|
|
58
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
59
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
60
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 73920, v1.1.0). Idle timeout 300000ms.
|
|
61
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
62
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
63
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 8855, v1.1.0). Idle timeout 300000ms.
|
|
64
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
65
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
66
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 39954, v1.1.0). Idle timeout 300000ms.
|
|
67
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
68
|
+
[CodeGraph MCP] Auto-synced 124 file(s) in 1892ms
|
|
69
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 247ms
|
|
70
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
71
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 70751, v1.1.0). Idle timeout 300000ms.
|
|
72
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
73
|
+
[CodeGraph MCP] Caught up 1 file(s) changed since last run
|
|
74
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
75
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 89552, v1.1.0). Idle timeout 300000ms.
|
|
76
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
77
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
78
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 98891, v1.1.0). Idle timeout 300000ms.
|
|
79
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
80
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
81
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 57037, v1.1.0). Idle timeout 300000ms.
|
|
82
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
83
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
|
84
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 32794, v1.1.0). Idle timeout 300000ms.
|
|
85
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
86
|
+
[CodeGraph MCP] Caught up 4 file(s) changed since last run
|
|
87
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
88
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 57725, v1.1.0). Idle timeout 300000ms.
|
|
89
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
90
|
+
[CodeGraph MCP] Caught up 17 file(s) changed since last run
|
|
91
|
+
[CodeGraph MCP] Auto-synced 13 file(s) in 241ms
|
|
92
|
+
[CodeGraph MCP] Auto-synced 13 file(s) in 246ms
|
|
93
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 181ms
|
|
94
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 186ms
|
|
95
|
+
[CodeGraph MCP] Auto-synced 1 file(s) in 176ms
|
|
96
|
+
[CodeGraph daemon] Shutting down (idle timeout; clients=0).
|
|
97
|
+
[CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 26309, v1.1.0). Idle timeout 300000ms.
|
|
98
|
+
[CodeGraph MCP] File watcher active — graph will auto-sync on changes
|
|
99
|
+
[CodeGraph MCP] Caught up 267 file(s) changed since last run
|
|
100
|
+
[CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
|
package/CLAUDE.md
CHANGED
|
@@ -41,6 +41,7 @@ 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`.
|
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
|
|
@@ -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 }
|
|
@@ -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
|
+
}
|
|
@@ -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,
|
|
@@ -33,6 +33,7 @@ class Navigation(
|
|
|
33
33
|
tintColor: Color? = null,
|
|
34
34
|
footerComponent: (@Composable () -> Unit)? = null,
|
|
35
35
|
footerTransparent: Boolean? = null,
|
|
36
|
+
footerUseMargin: Boolean? = null,
|
|
36
37
|
floatingButtonProps: FloatingButtonProps? = null,
|
|
37
38
|
keyboardOptions: KeyboardOptions? = null
|
|
38
39
|
) {
|
|
@@ -50,6 +51,7 @@ class Navigation(
|
|
|
50
51
|
tintColor = tintColor ?: options.tintColor,
|
|
51
52
|
footerComponent = footerComponent ?: options.footerComponent,
|
|
52
53
|
footerTransparent = footerTransparent ?: options.footerTransparent,
|
|
54
|
+
footerUseMargin = footerUseMargin ?: options.footerUseMargin,
|
|
53
55
|
floatingButtonProps = floatingButtonProps ?: options.floatingButtonProps,
|
|
54
56
|
keyboardOptions = keyboardOptions ?: options.keyboardOptions,
|
|
55
57
|
)
|
|
@@ -86,6 +88,7 @@ data class NavigationOptions(
|
|
|
86
88
|
val tintColor: Color? = null,
|
|
87
89
|
val footerComponent: @Composable (() -> Unit)? = null,
|
|
88
90
|
val footerTransparent: Boolean = false,
|
|
91
|
+
val footerUseMargin: Boolean = true,
|
|
89
92
|
val floatingButtonProps: FloatingButtonProps? = null,
|
|
90
93
|
val keyboardOptions: KeyboardOptions = KeyboardOptions()
|
|
91
94
|
) {
|
|
@@ -2,9 +2,7 @@ package vn.momo.kits.navigation
|
|
|
2
2
|
|
|
3
3
|
import androidx.compose.animation.core.tween
|
|
4
4
|
import androidx.compose.animation.fadeIn
|
|
5
|
-
import androidx.compose.animation.slideInHorizontally
|
|
6
5
|
import androidx.compose.animation.slideInVertically
|
|
7
|
-
import androidx.compose.animation.slideOutHorizontally
|
|
8
6
|
import androidx.compose.animation.slideOutVertically
|
|
9
7
|
import androidx.compose.runtime.Composable
|
|
10
8
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
@@ -19,7 +17,12 @@ import androidx.navigation.compose.NavHost
|
|
|
19
17
|
import androidx.navigation.compose.composable
|
|
20
18
|
import androidx.navigation.compose.rememberNavController
|
|
21
19
|
import androidx.navigation.toRoute
|
|
20
|
+
import kotlinx.coroutines.delay
|
|
21
|
+
import kotlinx.coroutines.flow.collectLatest
|
|
22
|
+
import vn.momo.kits.application.DesignSystemConfig
|
|
23
|
+
import vn.momo.kits.application.KitDesignSystemConfig
|
|
22
24
|
import vn.momo.kits.application.LocalContext
|
|
25
|
+
import vn.momo.kits.application.LocalDesignSystemConfig
|
|
23
26
|
import vn.momo.kits.application.LocalLocalize
|
|
24
27
|
import vn.momo.kits.application.Localize
|
|
25
28
|
import vn.momo.kits.application.MiniAppContext
|
|
@@ -33,6 +36,7 @@ import vn.momo.kits.platform.ProvideOsFontScale
|
|
|
33
36
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
34
37
|
import vn.momo.kits.utils.getNavigationBarHeight
|
|
35
38
|
import vn.momo.maxapi.IMaxApi
|
|
39
|
+
import kotlin.time.Duration.Companion.milliseconds
|
|
36
40
|
|
|
37
41
|
@Composable
|
|
38
42
|
fun NavigationContainer(
|
|
@@ -45,6 +49,7 @@ fun NavigationContainer(
|
|
|
45
49
|
setNavigator: ((Navigator) -> Unit)? = null,
|
|
46
50
|
statusBarHeight: Dp? = null,
|
|
47
51
|
localize: Localize? = null,
|
|
52
|
+
designSystemConfig: DesignSystemConfig? = null,
|
|
48
53
|
) {
|
|
49
54
|
val navController = rememberNavController()
|
|
50
55
|
val registry = remember { DynamicScreenRegistry() }
|
|
@@ -55,7 +60,19 @@ fun NavigationContainer(
|
|
|
55
60
|
val parentContext = LocalContext.current
|
|
56
61
|
val mergedContext = MiniAppContext.merge(parentContext, miniAppContext)
|
|
57
62
|
|
|
63
|
+
val stackTransition = remember { StackTransitionCoordinator() }
|
|
58
64
|
val theme = remember { mutableStateOf(initialTheme) }
|
|
65
|
+
val resolvedDesignSystemConfig =
|
|
66
|
+
designSystemConfig ?: LocalDesignSystemConfig.current ?: KitDesignSystemConfig.config
|
|
67
|
+
val headerBar = resolvedDesignSystemConfig?.headerBar
|
|
68
|
+
// RN parity: the theme's own header image wins; config headerBar is only the fallback.
|
|
69
|
+
val resolvedTheme = remember(theme.value, headerBar) {
|
|
70
|
+
if (theme.value.isHeaderImage() || headerBar.isNullOrEmpty()) {
|
|
71
|
+
theme.value
|
|
72
|
+
} else {
|
|
73
|
+
theme.value.copy(assets = theme.value.assets.copy(headerBackground = headerBar))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
59
76
|
val fallbackLocalize = remember { Localize() }
|
|
60
77
|
|
|
61
78
|
val resolvedLocalize = localize ?: fallbackLocalize
|
|
@@ -81,43 +98,59 @@ fun NavigationContainer(
|
|
|
81
98
|
LocalNavigator provides navigator,
|
|
82
99
|
LocalDynamicScreenRegistry provides registry,
|
|
83
100
|
LocalMaxApi provides maxApi,
|
|
84
|
-
AppTheme provides
|
|
101
|
+
AppTheme provides resolvedTheme,
|
|
85
102
|
AppStatusBar provides statusBarHeight,
|
|
86
103
|
AppNavigationBar provides navigationBarHeight,
|
|
87
104
|
LocalContext provides mergedContext,
|
|
105
|
+
LocalDesignSystemConfig provides resolvedDesignSystemConfig,
|
|
88
106
|
LocalLocalize provides resolvedLocalize,
|
|
107
|
+
LocalStackTransition provides stackTransition,
|
|
89
108
|
) {
|
|
90
109
|
LaunchedEffect(Unit) {
|
|
91
110
|
setNavigator?.invoke(navigator)
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
popExitTransition = {
|
|
105
|
-
slideOutHorizontally(
|
|
106
|
-
animationSpec = tween(300),
|
|
107
|
-
targetOffsetX = { it }
|
|
108
|
-
)
|
|
113
|
+
SwipeBackHaptic(maxApi)
|
|
114
|
+
|
|
115
|
+
// Gesture pops never reach Navigator.dismissScreen(), so prune the registry from
|
|
116
|
+
// the back stack itself. collectLatest lets a rapid re-push cancel a pending prune.
|
|
117
|
+
LaunchedEffect(navController, registry) {
|
|
118
|
+
navController.currentBackStack.collectLatest { entries ->
|
|
119
|
+
delay(STACK_TRANSITION_DURATION.milliseconds)
|
|
120
|
+
val aliveIds = entries.mapNotNullTo(mutableSetOf()) { entry ->
|
|
121
|
+
runCatching { entry.toRoute<DynamicScreenRoute>().id }.getOrNull()
|
|
122
|
+
?: runCatching { entry.toRoute<DynamicDialogRoute>().id }.getOrNull()
|
|
109
123
|
}
|
|
110
|
-
|
|
124
|
+
aliveIds.add(screenId)
|
|
125
|
+
registry.retainOnly(aliveIds)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// The transitions must live on the NavHost call site, not on the destinations:
|
|
130
|
+
// NavHost.uikit computes isDefaultTransition from these arguments alone, and while
|
|
131
|
+
// it is true the library paints its own hard-coded black iOS blackout that would
|
|
132
|
+
// sit over the pink scrim.
|
|
133
|
+
NavHost(
|
|
134
|
+
navController,
|
|
135
|
+
startDestination = startDestination,
|
|
136
|
+
enterTransition = { stackEnter() },
|
|
137
|
+
exitTransition = { stackExit() },
|
|
138
|
+
popEnterTransition = { stackPopEnter() },
|
|
139
|
+
popExitTransition = { stackPopExit() },
|
|
140
|
+
) {
|
|
141
|
+
composable<DynamicScreenRoute> { backStackEntry ->
|
|
111
142
|
val route = backStackEntry.toRoute<DynamicScreenRoute>()
|
|
112
143
|
val screen = registry.getScreen(route.id)
|
|
113
144
|
|
|
114
145
|
if (screen != null) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
StackTransitionDecor(id = route.id, isStackRoute = true) {
|
|
147
|
+
StackScreen(
|
|
148
|
+
id = route.id,
|
|
149
|
+
name = screen.name,
|
|
150
|
+
content = screen.content,
|
|
151
|
+
navigationOptions = screen.options
|
|
152
|
+
)
|
|
153
|
+
}
|
|
121
154
|
}
|
|
122
155
|
}
|
|
123
156
|
|
|
@@ -141,12 +174,14 @@ fun NavigationContainer(
|
|
|
141
174
|
val screen = registry.getScreen(route.id)
|
|
142
175
|
|
|
143
176
|
if (screen != null) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
StackTransitionDecor(id = route.id, isStackRoute = false) {
|
|
178
|
+
StackScreen(
|
|
179
|
+
id = route.id,
|
|
180
|
+
name = screen.name,
|
|
181
|
+
content = screen.content,
|
|
182
|
+
navigationOptions = screen.options
|
|
183
|
+
)
|
|
184
|
+
}
|
|
150
185
|
}
|
|
151
186
|
}
|
|
152
187
|
}
|