@momo-kits/native-kits 0.168.1-beta.4-debug → 0.168.1-beta.5-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 +4 -4
- package/README.md +2 -10
- package/build.gradle.kts +1 -3
- package/compose/build.gradle.kts +20 -19
- package/compose/build.gradle.kts.backup +19 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +14 -23
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +5 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +14 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +1 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +1 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +1 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +2 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +124 -59
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +30 -44
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +32 -40
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +2 -6
- package/gradle/libs.versions.toml +4 -11
- package/gradle/wrapper/gradle-wrapper.properties +1 -3
- package/gradle.properties +3 -3
- package/ios/Layout/ScreenLayout.swift +5 -0
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +2 -3
- package/ios/Navigation/BottomTab/DuoTabBar.swift +94 -69
- package/ios/Navigation/Component/HeaderBackground.swift +22 -1
- package/ios/Navigation/StackScreen.swift +3 -7
- package/package.json +1 -1
- package/publish.sh +2 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +0 -37
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +0 -41
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +0 -44
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +0 -62
package/CLAUDE.md
CHANGED
|
@@ -7,7 +7,7 @@ Guidance for working in this repo. Keep it accurate — update it when structure
|
|
|
7
7
|
`momo-native-kits` — MoMo's design-system / UI-kit as a **Kotlin Multiplatform + Compose Multiplatform** library, published for Android (AAR/Maven) and iOS (static framework + CocoaPods). It is the Compose port of the React Native `momo-kits`. Consumed by mini-apps embedded in the host MoMo app.
|
|
8
8
|
|
|
9
9
|
- Artifact: `vn.momo.kits:kits` (version in `gradle.properties`). NPM mirror: `@momo-kits/native-kits`.
|
|
10
|
-
- Targets:
|
|
10
|
+
- Targets: `androidTarget` (JVM 17, compile/min/target SDK 36/24/36), `iosArm64`, `iosSimulatorArm64` (framework `baseName = "kits"`, `isStatic = true`).
|
|
11
11
|
- Kotlin 2.3.10, Compose MP 1.10.3.
|
|
12
12
|
|
|
13
13
|
## Modules
|
|
@@ -26,11 +26,11 @@ Guidance for working in this repo. Keep it accurate — update it when structure
|
|
|
26
26
|
|
|
27
27
|
## Build & verify
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
No unit-test suite is configured. Validate changes by compiling the relevant target(s); use the sample app for visual checks.
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
./gradlew :compose:compileCommonMainKotlinMetadata # validate commonMain (fast, do this first)
|
|
33
|
-
./gradlew :compose:
|
|
33
|
+
./gradlew :compose:compileDebugKotlinAndroid # Android target
|
|
34
34
|
./gradlew :compose:linkDebugFrameworkIosSimulatorArm64 # iOS framework (slow)
|
|
35
35
|
./gradlew :sample:androidApp:installDebug # run demo on device/emulator
|
|
36
36
|
```
|
|
@@ -50,7 +50,7 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
|
|
|
50
50
|
- **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`.
|
|
51
51
|
- **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.
|
|
52
52
|
- **Platform code** goes through `expect`/`actual` in `platform/` — never reference Android/iOS APIs from `commonMain` directly.
|
|
53
|
-
- **Native bridge:**
|
|
53
|
+
- **Native bridge:** `IMaxApi` (`vn.momo.maxapi`, exposed as `LocalMaxApi`) is the host-app bridge (dismiss, tracking, device info, language).
|
|
54
54
|
- Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
|
|
55
55
|
- **iOS swipe-back:** the host owns the only edge-pan recogniser and replays it through `HostBackGestureBridge` (`navigation/HostBackGestureBridge.kt`; the kit framework is never exported, so the host reaches it through its own Koin-provided facade): `checkCanPop` / `canPopInner` / `pop` route the back into the kit stack, `started` / `progressed` / `completed` / `cancelled` seek the NavHost transition, `shouldUseFloatingArrowBack` is set by bottom tabs. `NavigationContainer` attaches itself automatically (`AttachHostBackGesture`, iOS only, detached by identity on dispose) — hosts need no wrapper and no `setNavigator`. `maxApi` is a required parameter of `NavigationContainer` — every container, nested or root, is handed its host bridge explicitly.
|
|
56
56
|
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ ComposeKit là bộ UI Kit đa nền tảng phục vụ Mini App/SDK của MoMo.
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
55
|
### Yêu cầu môi trường
|
|
56
|
-
- JDK 17+
|
|
56
|
+
- JDK 17+, Gradle 8, Android Studio Hedgehog+ với Android SDK (compileSdk xem `libs.versions.toml`).
|
|
57
57
|
- Kotlin Multiplatform plugin + Compose Multiplatform (JetBrains 1.6.1+).
|
|
58
58
|
- Xcode 15, CocoaPods 1.12+ để build `iosApp` hoặc publish Pod.
|
|
59
59
|
- Truy cập mạng nội bộ GitLab Packages nếu cần publish/consume Maven repo `https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven`.
|
|
@@ -174,12 +174,4 @@ fun SuccessState() {
|
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
- `shared/build.gradle.kts` đã bật `coreLibraryDesugaringEnabled` để tương thích `compottie-dot`/`compottie-network` với `minSdk = 24`.
|
|
177
|
-
- Nếu animation dùng asset/font nội bộ, kết hợp `compottie-resources` (`rememberResourcesAssetsManager`, `rememberResourcesFontManager`) để trỏ về `composeResources`.
|
|
178
|
-
### NativeMaxApi 0.3.1
|
|
179
|
-
|
|
180
|
-
Compose dùng typed `vn.momo.maxapi.IMaxApi` do host truyền vào `NavigationContainer`.
|
|
181
|
-
Toolkit dùng overload `showToolkit(ToolkitOptions, callback)` để giữ nhóm tool tùy chỉnh,
|
|
182
|
-
nhãn đa ngôn ngữ, icon, badge và `useSystemTools`. Callback trả `actionKey`; request đang chờ
|
|
183
|
-
được hủy khi composition bị gỡ. Kits không đóng client do host sở hữu.
|
|
184
|
-
|
|
185
|
-
Chạy `./gradlew :compose:testAndroidHostTest` để kiểm tra hủy request và callback đến muộn.
|
|
177
|
+
- Nếu animation dùng asset/font nội bộ, kết hợp `compottie-resources` (`rememberResourcesAssetsManager`, `rememberResourcesFontManager`) để trỏ về `composeResources`.
|
package/build.gradle.kts
CHANGED
|
@@ -2,9 +2,7 @@ plugins {
|
|
|
2
2
|
// this is necessary to avoid the plugins to be loaded multiple times
|
|
3
3
|
// in each subproject's classloader
|
|
4
4
|
alias(libs.plugins.android.application) apply false
|
|
5
|
-
|
|
6
|
-
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
|
|
7
|
-
// AI-GENERATED END: use the AGP 9 Kotlin Multiplatform Android library target
|
|
5
|
+
alias(libs.plugins.android.library) apply false
|
|
8
6
|
alias(libs.plugins.compose) apply false
|
|
9
7
|
alias(libs.plugins.kotlin.compose.compiler) apply false
|
|
10
8
|
alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false
|
package/compose/build.gradle.kts
CHANGED
|
@@ -4,9 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
|
4
4
|
|
|
5
5
|
plugins {
|
|
6
6
|
alias(libs.plugins.jetbrains.kotlin.multiplatform)
|
|
7
|
-
|
|
8
|
-
alias(libs.plugins.android.kotlin.multiplatform.library)
|
|
9
|
-
// AI-GENERATED END: use the AGP 9 Kotlin Multiplatform Android library target
|
|
7
|
+
alias(libs.plugins.android.library)
|
|
10
8
|
alias(libs.plugins.compose)
|
|
11
9
|
alias(libs.plugins.kotlin.compose.compiler)
|
|
12
10
|
// alias(libs.plugins.vanniktech.mavenPublish)
|
|
@@ -24,18 +22,12 @@ val gitlabUser: String = findProperty("gitlab.user") as String? ?: ""
|
|
|
24
22
|
val gitlabKey: String = findProperty("gitlab.password") as String? ?: ""
|
|
25
23
|
|
|
26
24
|
kotlin {
|
|
27
|
-
|
|
28
|
-
android {
|
|
29
|
-
namespace = "vn.momo.kits.kits"
|
|
30
|
-
compileSdk = 36
|
|
31
|
-
minSdk = 24
|
|
32
|
-
androidResources.enable = true
|
|
33
|
-
withHostTest {}
|
|
25
|
+
androidTarget {
|
|
34
26
|
compilerOptions {
|
|
35
27
|
jvmTarget.set(JvmTarget.JVM_17)
|
|
36
28
|
}
|
|
29
|
+
publishLibraryVariants("release")
|
|
37
30
|
}
|
|
38
|
-
// AI-GENERATED END: configure the AGP 9 Android target and host tests
|
|
39
31
|
|
|
40
32
|
val fwName = gitlabArtifactId
|
|
41
33
|
val iosTargets = listOf(iosArm64(), iosSimulatorArm64())
|
|
@@ -48,7 +40,7 @@ kotlin {
|
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
cocoapods {
|
|
51
|
-
version = "0.168.1-beta.
|
|
43
|
+
version = "0.168.1-beta.5-debug"
|
|
52
44
|
summary = "IOS Shared module"
|
|
53
45
|
homepage = "https://momo.vn"
|
|
54
46
|
ios.deploymentTarget = "15.0"
|
|
@@ -94,14 +86,24 @@ kotlin {
|
|
|
94
86
|
iosMain.dependencies {
|
|
95
87
|
implementation(libs.ktor.client.darwin)
|
|
96
88
|
}
|
|
97
|
-
// AI-GENERATED START: verify toolkit request ownership
|
|
98
|
-
commonTest.dependencies {
|
|
99
|
-
implementation(kotlin("test"))
|
|
100
|
-
}
|
|
101
|
-
// AI-GENERATED END: verify toolkit request ownership
|
|
102
89
|
}
|
|
103
90
|
}
|
|
104
91
|
|
|
92
|
+
android {
|
|
93
|
+
namespace = "vn.momo.kits.kits"
|
|
94
|
+
compileSdk = 35
|
|
95
|
+
compileOptions {
|
|
96
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
97
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
98
|
+
}
|
|
99
|
+
defaultConfig {
|
|
100
|
+
minSdk = 24
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dependencies {
|
|
105
|
+
}
|
|
106
|
+
|
|
105
107
|
repositories {
|
|
106
108
|
google {
|
|
107
109
|
mavenContent {
|
|
@@ -151,9 +153,8 @@ compose {
|
|
|
151
153
|
// configure(
|
|
152
154
|
// KotlinMultiplatform(
|
|
153
155
|
// javadocJar = JavadocJar.Empty(),
|
|
154
|
-
// // AI-GENERATED START: the AGP KMP target publishes a single Android variant
|
|
155
156
|
// sourcesJar = true,
|
|
156
|
-
//
|
|
157
|
+
// androidVariantsToPublish = listOf("release")
|
|
157
158
|
// )
|
|
158
159
|
// )
|
|
159
160
|
//
|
|
@@ -4,9 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
|
4
4
|
|
|
5
5
|
plugins {
|
|
6
6
|
alias(libs.plugins.jetbrains.kotlin.multiplatform)
|
|
7
|
-
|
|
8
|
-
alias(libs.plugins.android.kotlin.multiplatform.library)
|
|
9
|
-
// AI-GENERATED END: use the AGP 9 Kotlin Multiplatform Android library target
|
|
7
|
+
alias(libs.plugins.android.library)
|
|
10
8
|
alias(libs.plugins.compose)
|
|
11
9
|
alias(libs.plugins.kotlin.compose.compiler)
|
|
12
10
|
alias(libs.plugins.vanniktech.mavenPublish)
|
|
@@ -24,18 +22,12 @@ val gitlabUser: String = findProperty("gitlab.user") as String? ?: ""
|
|
|
24
22
|
val gitlabKey: String = findProperty("gitlab.password") as String? ?: ""
|
|
25
23
|
|
|
26
24
|
kotlin {
|
|
27
|
-
|
|
28
|
-
android {
|
|
29
|
-
namespace = "$gitlabGroup.$gitlabArtifactId"
|
|
30
|
-
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
|
31
|
-
minSdk = libs.versions.android.minSdk.get().toInt()
|
|
32
|
-
androidResources.enable = true
|
|
33
|
-
withHostTest {}
|
|
25
|
+
androidTarget {
|
|
34
26
|
compilerOptions {
|
|
35
27
|
jvmTarget.set(JvmTarget.JVM_17)
|
|
36
28
|
}
|
|
29
|
+
publishLibraryVariants("release")
|
|
37
30
|
}
|
|
38
|
-
// AI-GENERATED END: configure the AGP 9 Android target and host tests
|
|
39
31
|
|
|
40
32
|
val fwName = gitlabArtifactId
|
|
41
33
|
val iosTargets = listOf(iosArm64(), iosSimulatorArm64())
|
|
@@ -94,14 +86,24 @@ kotlin {
|
|
|
94
86
|
iosMain.dependencies {
|
|
95
87
|
implementation(libs.ktor.client.darwin)
|
|
96
88
|
}
|
|
97
|
-
// AI-GENERATED START: verify toolkit request ownership
|
|
98
|
-
commonTest.dependencies {
|
|
99
|
-
implementation(kotlin("test"))
|
|
100
|
-
}
|
|
101
|
-
// AI-GENERATED END: verify toolkit request ownership
|
|
102
89
|
}
|
|
103
90
|
}
|
|
104
91
|
|
|
92
|
+
android {
|
|
93
|
+
namespace = "$gitlabGroup.$gitlabArtifactId"
|
|
94
|
+
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
|
95
|
+
compileOptions {
|
|
96
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
97
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
98
|
+
}
|
|
99
|
+
defaultConfig {
|
|
100
|
+
minSdk = libs.versions.android.minSdk.get().toInt()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dependencies {
|
|
105
|
+
}
|
|
106
|
+
|
|
105
107
|
repositories {
|
|
106
108
|
google {
|
|
107
109
|
mavenContent {
|
|
@@ -151,9 +153,8 @@ mavenPublishing {
|
|
|
151
153
|
configure(
|
|
152
154
|
KotlinMultiplatform(
|
|
153
155
|
javadocJar = JavadocJar.Empty(),
|
|
154
|
-
// AI-GENERATED START: the AGP KMP target publishes a single Android variant
|
|
155
156
|
sourcesJar = true,
|
|
156
|
-
|
|
157
|
+
androidVariantsToPublish = listOf("release")
|
|
157
158
|
)
|
|
158
159
|
)
|
|
159
160
|
|
|
@@ -2,18 +2,13 @@ package vn.momo.kits.application
|
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
4
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
5
|
-
|
|
6
|
-
import androidx.compose.runtime.DisposableEffect
|
|
7
|
-
// AI-GENERATED END: own the typed language request
|
|
5
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
8
6
|
import androidx.compose.runtime.remember
|
|
9
7
|
import vn.momo.kits.const.AppTheme
|
|
10
8
|
import vn.momo.kits.const.Theme
|
|
11
9
|
import vn.momo.kits.navigation.LocalMaxApi
|
|
12
10
|
import vn.momo.kits.platform.ProvideOsFontScale
|
|
13
|
-
// AI-GENERATED START: consume typed NativeMaxApi results
|
|
14
11
|
import vn.momo.maxapi.IMaxApi
|
|
15
|
-
import vn.momo.maxapi.runtime.ApiResult
|
|
16
|
-
// AI-GENERATED END: consume typed NativeMaxApi results
|
|
17
12
|
|
|
18
13
|
|
|
19
14
|
@Composable
|
|
@@ -32,25 +27,15 @@ fun WidgetContainer(
|
|
|
32
27
|
val resolvedLocalize = localize ?: LocalLocalize.current
|
|
33
28
|
val resolvedMaxApi = maxApi ?: LocalMaxApi.current
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
val
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (active && target != null && result is ApiResult.Success &&
|
|
43
|
-
result.value.trim().lowercase().startsWith(Localize.EN)) {
|
|
44
|
-
target.changeLanguage(Localize.EN)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}.getOrNull()
|
|
48
|
-
onDispose {
|
|
49
|
-
active = false
|
|
50
|
-
handle?.cancel()
|
|
30
|
+
LaunchedEffect(resolvedMaxApi, localize) {
|
|
31
|
+
val api = resolvedMaxApi ?: return@LaunchedEffect
|
|
32
|
+
val target = localize ?: return@LaunchedEffect
|
|
33
|
+
runCatching {
|
|
34
|
+
api.getLanguage(callback = { data ->
|
|
35
|
+
if (parseLanguage(data) == Localize.EN) target.changeLanguage(Localize.EN)
|
|
36
|
+
})
|
|
51
37
|
}
|
|
52
38
|
}
|
|
53
|
-
// AI-GENERATED END: cancel language delivery when its composition owner changes
|
|
54
39
|
|
|
55
40
|
ProvideOsFontScale {
|
|
56
41
|
CompositionLocalProvider(
|
|
@@ -63,3 +48,9 @@ fun WidgetContainer(
|
|
|
63
48
|
}
|
|
64
49
|
}
|
|
65
50
|
}
|
|
51
|
+
|
|
52
|
+
private fun parseLanguage(raw: Map<String, Any?>?): String? {
|
|
53
|
+
val code = (raw?.get("response") as? String)?.trim()?.lowercase()
|
|
54
|
+
if (code.isNullOrEmpty()) return null
|
|
55
|
+
return if (code.startsWith(Localize.EN)) Localize.EN else Localize.VI
|
|
56
|
+
}
|
|
@@ -86,6 +86,11 @@ internal object DuoMetrics {
|
|
|
86
86
|
// (x 550–643 of 669, clock digits centered on y 47); its reserved region is taller than the glyphs.
|
|
87
87
|
val innerStatusBarCenterY = 47.dp
|
|
88
88
|
val innerStatusBarWidth = 120.dp
|
|
89
|
+
// Host app's iOS 26 tab bar on the inner display in portrait: a 62pt glass pill sized to its tabs,
|
|
90
|
+
// QR as a 62pt platter beside it, ≥ 20pt from the sides, 13pt into the home indicator inset.
|
|
91
|
+
val floatingTabBarHeight = 62.dp
|
|
92
|
+
val floatingTabBarMargin = 20.dp
|
|
93
|
+
val floatingTabBarInsetOverlap = 13.dp
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
// Widest short side of any single-display iPhone.
|
|
@@ -37,10 +37,7 @@ import vn.momo.kits.platform.ProvideNavigationEventDispatcherOwner
|
|
|
37
37
|
import vn.momo.kits.platform.ProvideOsFontScale
|
|
38
38
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
39
39
|
import vn.momo.kits.utils.getNavigationBarHeight
|
|
40
|
-
// AI-GENERATED START: consume typed NativeMaxApi results
|
|
41
40
|
import vn.momo.maxapi.IMaxApi
|
|
42
|
-
import vn.momo.maxapi.runtime.ApiResult
|
|
43
|
-
// AI-GENERATED END: consume typed NativeMaxApi results
|
|
44
41
|
import kotlin.time.Duration.Companion.milliseconds
|
|
45
42
|
|
|
46
43
|
@Composable
|
|
@@ -58,11 +55,7 @@ fun NavigationContainer(
|
|
|
58
55
|
) {
|
|
59
56
|
val navController = rememberNavController()
|
|
60
57
|
val registry = remember { DynamicScreenRegistry() }
|
|
61
|
-
|
|
62
|
-
val navigator = remember(navController, maxApi, registry) {
|
|
63
|
-
Navigator(navController = navController, maxApi = maxApi, registry = registry)
|
|
64
|
-
}
|
|
65
|
-
// AI-GENERATED END: retain the current host client after recomposition
|
|
58
|
+
val navigator = remember { Navigator(navController = navController, maxApi = maxApi, registry = registry) }
|
|
66
59
|
val statusBarHeight = statusBarHeight ?: getAppStatusBarHeight()
|
|
67
60
|
val navigationBarHeight = getNavigationBarHeight()
|
|
68
61
|
val screenLayout = calculateScreenLayout(statusBarHeight, parent = LocalScreenLayout.current)
|
|
@@ -87,25 +80,16 @@ fun NavigationContainer(
|
|
|
87
80
|
|
|
88
81
|
val resolvedLocalize = localize ?: fallbackLocalize
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
api.getLanguage { result ->
|
|
97
|
-
if (active && result is ApiResult.Success &&
|
|
98
|
-
result.value.trim().lowercase().startsWith(Localize.EN)) {
|
|
99
|
-
target.changeLanguage(Localize.EN)
|
|
83
|
+
LaunchedEffect(maxApi, resolvedLocalize) {
|
|
84
|
+
val api = maxApi ?: return@LaunchedEffect
|
|
85
|
+
runCatching {
|
|
86
|
+
api.getLanguage(callback = { data ->
|
|
87
|
+
if (parseLanguage(data) == Localize.EN) {
|
|
88
|
+
resolvedLocalize.changeLanguage(Localize.EN)
|
|
100
89
|
}
|
|
101
|
-
}
|
|
102
|
-
}.getOrNull()
|
|
103
|
-
onDispose {
|
|
104
|
-
active = false
|
|
105
|
-
handle?.cancel()
|
|
90
|
+
})
|
|
106
91
|
}
|
|
107
92
|
}
|
|
108
|
-
// AI-GENERATED END: cancel language delivery when its composition owner changes
|
|
109
93
|
|
|
110
94
|
val screenId = rememberSaveable { registry.nextId() }
|
|
111
95
|
registry.bind(screenId, initialScreenName, initialScreen, options)
|
|
@@ -213,3 +197,9 @@ fun NavigationContainer(
|
|
|
213
197
|
|
|
214
198
|
val LocalMaxApi = staticCompositionLocalOf<IMaxApi?> { null }
|
|
215
199
|
|
|
200
|
+
private fun parseLanguage(raw: Map<String, Any?>?): String? {
|
|
201
|
+
val code = (raw?.get("response") as? String)?.trim()?.lowercase()
|
|
202
|
+
if (code.isNullOrEmpty()) return null
|
|
203
|
+
return if (code.startsWith(Localize.EN)) Localize.EN else Localize.VI
|
|
204
|
+
}
|
|
205
|
+
|
|
@@ -22,9 +22,6 @@ import vn.momo.kits.components.PopupNotify
|
|
|
22
22
|
import vn.momo.kits.navigation.BottomHeader.*
|
|
23
23
|
import vn.momo.kits.navigation.component.SnackBar
|
|
24
24
|
import vn.momo.maxapi.IMaxApi
|
|
25
|
-
// AI-GENERATED START: dismiss through the typed navigator target
|
|
26
|
-
import vn.momo.maxapi.contract.navigation.model.DismissTarget
|
|
27
|
-
// AI-GENERATED END: dismiss through the typed navigator target
|
|
28
25
|
import kotlin.time.Duration.Companion.milliseconds
|
|
29
26
|
|
|
30
27
|
class Navigator(
|
|
@@ -128,9 +125,7 @@ class Navigator(
|
|
|
128
125
|
delay(STACK_TRANSITION_DURATION.milliseconds)
|
|
129
126
|
dismissing?.let { registry.unregisterScreen(it.id) }
|
|
130
127
|
} else {
|
|
131
|
-
|
|
132
|
-
maxApi?.dismiss(DismissTarget.Current()) {}
|
|
133
|
-
// AI-GENERATED END: preserve root-screen dismissal
|
|
128
|
+
maxApi?.dismiss { }
|
|
134
129
|
}
|
|
135
130
|
}
|
|
136
131
|
|
|
@@ -6,9 +6,6 @@ import androidx.compose.runtime.snapshotFlow
|
|
|
6
6
|
import vn.momo.kits.platform.getScreenDimensions
|
|
7
7
|
import vn.momo.kits.platform.rememberBackGestureProgress
|
|
8
8
|
import vn.momo.maxapi.IMaxApi
|
|
9
|
-
// AI-GENERATED START: use the typed light vibration pattern
|
|
10
|
-
import vn.momo.maxapi.contract.device.vibration.model.VibrateStyle
|
|
11
|
-
// AI-GENERATED END: use the typed light vibration pattern
|
|
12
9
|
|
|
13
10
|
// Unscaled, so the tick lands at the same physical distance on every device.
|
|
14
11
|
private const val SWIPE_BACK_HAPTIC_THRESHOLD_DP = 50f
|
|
@@ -28,9 +25,7 @@ internal fun SwipeBackHaptic(maxApi: IMaxApi?) {
|
|
|
28
25
|
snapshotFlow { progress.value }.collect { fraction ->
|
|
29
26
|
val travelled = fraction ?: 0f
|
|
30
27
|
if (travelled >= threshold && !isSendHaptic) {
|
|
31
|
-
|
|
32
|
-
runCatching { maxApi.vibrate(VibrateStyle.LIGHT) }
|
|
33
|
-
// AI-GENERATED END: keep one light tick per threshold crossing
|
|
28
|
+
runCatching { maxApi.triggerEventVibration("light", callback = null) }
|
|
34
29
|
isSendHaptic = true
|
|
35
30
|
} else if (travelled < threshold && isSendHaptic) {
|
|
36
31
|
isSendHaptic = false
|
|
@@ -39,9 +39,6 @@ import vn.momo.kits.platform.BackHandler
|
|
|
39
39
|
import vn.momo.kits.platform.reportFloatingArrowBack
|
|
40
40
|
import vn.momo.kits.layout.getScreenLayout
|
|
41
41
|
import kotlinx.coroutines.launch
|
|
42
|
-
// AI-GENERATED START: use the typed tab haptic pattern
|
|
43
|
-
import vn.momo.maxapi.contract.device.vibration.model.VibrateStyle
|
|
44
|
-
// AI-GENERATED END: use the typed tab haptic pattern
|
|
45
42
|
|
|
46
43
|
private val inOutQuad = Easing { t -> if (t < 0.5f) 2 * t * t else 1 - 2 * (1 - t) * (1 - t) }
|
|
47
44
|
internal val tabTimingSpec = tween<Float>(durationMillis = 200, easing = inOutQuad)
|
|
@@ -135,9 +132,7 @@ fun BottomTab(
|
|
|
135
132
|
it.bottom + duoRailHeight(items.size, hasQr, compressed = compression.tabBarCompressed) + DUO_GROUP_GAP
|
|
136
133
|
} ?: 0.dp
|
|
137
134
|
val onTabSelected: (Int) -> Unit = { index ->
|
|
138
|
-
|
|
139
|
-
runCatching { maxApi?.vibrate(VibrateStyle.LIGHT) }
|
|
140
|
-
// AI-GENERATED END: preserve the light haptic on tab selection
|
|
135
|
+
runCatching { maxApi?.triggerEventVibration("light", callback = null) }
|
|
141
136
|
val switchTab = {
|
|
142
137
|
if (index != selectedIndex) selectTab(index)
|
|
143
138
|
}
|
package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt
CHANGED
|
@@ -39,8 +39,6 @@ internal val DUO_GROUP_GAP = Spacing.M
|
|
|
39
39
|
private val DEFAULT_TOOLBAR_HEIGHT = DuoMetrics.controlSize + Spacing.M + DuoMetrics.itemPitch * 2
|
|
40
40
|
// Back, then the single button the toolbar folds into
|
|
41
41
|
private val COMPRESSED_TOOLBAR_HEIGHT = DuoMetrics.controlSize * 2 + Spacing.M
|
|
42
|
-
// Back alone, while a rail opened from the compressed state takes the toolbar slot
|
|
43
|
-
private val BACK_ONLY_HEIGHT = DuoMetrics.controlSize
|
|
44
42
|
|
|
45
43
|
// Port of RN DuoBarCompression (HIG, Designing for iPhone Duo: Vertical controls):
|
|
46
44
|
// - both fit: both stay whole
|
|
@@ -78,7 +76,8 @@ internal fun rememberDuoBarCompression(
|
|
|
78
76
|
expandTabBar = { expanded = DuoBar.TAB_BAR },
|
|
79
77
|
setToolbarHeight = setToolbarHeight,
|
|
80
78
|
)
|
|
81
|
-
opened
|
|
79
|
+
// an opened rail always shows, even when only the group gap is short: it closes once a tab is picked
|
|
80
|
+
opened == DuoBar.TAB_BAR -> DuoBarCompression(
|
|
82
81
|
toolbarCompressed = true,
|
|
83
82
|
toolbarHidden = true,
|
|
84
83
|
expandToolbar = { opened = DuoBar.TOOLBAR },
|