@momo-kits/native-kits 0.164.1-beta.1-debug → 0.164.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/.claude/settings.local.json +185 -0
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +19 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +27 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +1 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +16 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +28 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +50 -30
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +35 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +24 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +164 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +15 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +40 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ComposeBackGesture.ios.kt +65 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +35 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +23 -2
- package/gradle.properties +1 -1
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/local.properties +2 -2
- package/package.json +1 -1
- package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
- package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
|
|
5
|
+
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/maven-metadata.xml\")",
|
|
6
|
+
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/maven-metadata.xml\")",
|
|
7
|
+
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/0.144.6/skiko-0.144.6.module\")",
|
|
8
|
+
"Bash(curl -s -o /dev/null -w '%{http_code}' https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.__TRACKED_VAR__)",
|
|
9
|
+
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/\")",
|
|
10
|
+
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/\")",
|
|
11
|
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.148.1/skiko-android-0.148.1.pom\")",
|
|
12
|
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.5/skiko-android-0.144.5.pom\")",
|
|
13
|
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://dl.google.com/dl/android/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
|
|
14
|
+
"Bash(curl -s \"https://search.maven.org/solrsearch/select?q=a:skiko-android&rows=5&wt=json\")",
|
|
15
|
+
"Bash(curl -sL -o /dev/null -w \"%{http_code} %{url_effective}\\\\n\" \"https://repo1.maven.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
|
|
16
|
+
"Bash(curl *)",
|
|
17
|
+
"Bash(./gradlew :sample:androidApp:dependencies --configuration debugRuntimeClasspath)",
|
|
18
|
+
"Bash(./gradlew :sample:shared:compileDebugKotlinAndroid)",
|
|
19
|
+
"Bash(./gradlew :sample:androidApp:compileDebugKotlin)",
|
|
20
|
+
"Bash(ls -d *.xcodeproj)",
|
|
21
|
+
"Bash(grep -c ChipDemo.swift __TRACKED_VAR__/project.pbxproj)",
|
|
22
|
+
"Bash(grep -c PBXFileSystemSynchronizedRootGroup __TRACKED_VAR__/project.pbxproj)",
|
|
23
|
+
"Bash(grep -c InformationDemo.swift __TRACKED_VAR__/project.pbxproj)",
|
|
24
|
+
"Bash(plutil -lint project.pbxproj)",
|
|
25
|
+
"Bash(xcode-select -p)",
|
|
26
|
+
"Bash(/usr/bin/xcrun xcodebuild *)",
|
|
27
|
+
"Bash(echo \"exit: $?\")",
|
|
28
|
+
"Bash(/Applications/Xcode26.app/Contents/Developer/usr/bin/xcodebuild -version)",
|
|
29
|
+
"Bash(./gradlew :compose:tasks --all)",
|
|
30
|
+
"Bash(grep -n \"vanniktech\\\\|mavenPublishing\\\\|^publishing\\\\|version = gitlabVersion\\\\|namespace =\\\\|compileSdk =\\\\|minSdk =\\\\|GitLabPackages\\\\|name = \\\\\"\\\\${gitlabRepo}Packages\\\\\"\")",
|
|
31
|
+
"Bash(git checkout *)",
|
|
32
|
+
"Bash(./gradlew :compose:publishAllPublicationsToGitLabPackagesRepository --dry-run)",
|
|
33
|
+
"Bash(git fetch *)",
|
|
34
|
+
"Bash(git pull *)",
|
|
35
|
+
"Bash(git push *)",
|
|
36
|
+
"Bash(git branch *)",
|
|
37
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -list)",
|
|
38
|
+
"Bash(xcrun simctl *)",
|
|
39
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
40
|
+
"Bash(pod install *)",
|
|
41
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
42
|
+
"Bash(xcrun swiftc *)",
|
|
43
|
+
"Bash(echo \"=== exit: $? \\(1=none found\\) ===\")",
|
|
44
|
+
"Bash(cat Radius.swift)",
|
|
45
|
+
"Bash(cat Spacing.swift)",
|
|
46
|
+
"Bash(echo \"exit=$?\")",
|
|
47
|
+
"Bash(ruby *)",
|
|
48
|
+
"Bash(xcodebuild -list -workspace iosApp.xcworkspace)",
|
|
49
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' build)",
|
|
50
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
|
|
51
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
|
|
52
|
+
"Bash(xcodebuild build *)",
|
|
53
|
+
"Bash(git stash *)",
|
|
54
|
+
"Bash(plutil -lint sample/iosApp/iosApp.xcodeproj/project.pbxproj)",
|
|
55
|
+
"Bash(git add *)",
|
|
56
|
+
"Bash(git commit -m 'feat: DS-646 [ci build] *)",
|
|
57
|
+
"Bash(git commit -q -m 'fix: DS-656 [ci build] *)",
|
|
58
|
+
"Bash(xargs git log --oneline -1)",
|
|
59
|
+
"Bash(git rev-list *)",
|
|
60
|
+
"Bash(git status *)",
|
|
61
|
+
"Bash(git rebase *)",
|
|
62
|
+
"Bash(git merge *)",
|
|
63
|
+
"Bash(git reset *)",
|
|
64
|
+
"Bash(awk '/^ *)",
|
|
65
|
+
"Bash(git ls-tree *)",
|
|
66
|
+
"Bash(awk *)",
|
|
67
|
+
"Read(//Users/sophia/Workspace/momo/momo-app/**)",
|
|
68
|
+
"Bash(git log *)",
|
|
69
|
+
"Bash(xcodebuild -version)",
|
|
70
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
71
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
72
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug clean build CODE_SIGNING_ALLOWED=NO)",
|
|
73
|
+
"Bash(git commit *)",
|
|
74
|
+
"Bash(npm view *)",
|
|
75
|
+
"Bash(echo \"EXIT=$? -> not resolvable\")",
|
|
76
|
+
"Bash(yarn install *)",
|
|
77
|
+
"Bash(RCT_NEW_ARCH_ENABLED=1 pod install)",
|
|
78
|
+
"Bash(xcodebuild -workspace MoMoPlatform.xcworkspace -list)",
|
|
79
|
+
"Bash(git --no-pager diff --stat ios/Application/Localize.swift ios/Application/Navigation/NavigationContainer.swift ios/Popup/PopupDisplay.swift ios/Popup/PopupNotify.swift ios/Template/TrustBanner/TrustBanner.swift)",
|
|
80
|
+
"Bash(git rm *)",
|
|
81
|
+
"Bash(echo \"EXIT=$?\")",
|
|
82
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff sample/iosApp/iosApp/Demo/ButtonDemo.swift)",
|
|
83
|
+
"Bash(python3 *)",
|
|
84
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits status --short)",
|
|
85
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits log --oneline -3)",
|
|
86
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits log --oneline -5 -- ios/Button/Button.swift)",
|
|
87
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff ios/native-kits.podspec compose/compose.podspec gradle.properties package.json)",
|
|
88
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits show HEAD:ios/Button/Button.swift)",
|
|
89
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff package.json ios/native-kits.podspec)",
|
|
90
|
+
"Bash(grep -n \"must be a \\\\`.background\\\\` of the label\" /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Button/Button.swift)",
|
|
91
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits show -s --format=\"%h %ci %s\" 953b234d)",
|
|
92
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-app show -s --format=\"%h %ci %s\" f8757a14437)",
|
|
93
|
+
"Bash(git -C /Users/sophia/Workspace/momo/momo-app log -S '\"@momo-kits/native-kits\": \"0.163.1-beta.18\"' --format=\"%h %ci %s\" -- package.json)",
|
|
94
|
+
"Bash(swiftc -O repro_isfull.swift -o repro_isfull)",
|
|
95
|
+
"Bash(./repro_isfull)",
|
|
96
|
+
"Bash(grep -n '^\\\\[32;1m$\\\\|section_start\\\\|xcodebuild.*-workspace\\\\|xcodebuild.*archive\\\\|Prebuilt\\\\|prebuilt\\\\|framework.*download\\\\|rugby\\\\|Rugby' /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/job_trace.log)",
|
|
97
|
+
"Bash(grep -n '\\\\[32;1m\\\\$' /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/job_trace.log)",
|
|
98
|
+
"Bash(npm pack *)",
|
|
99
|
+
"Bash(mkdir -p b13 b18)",
|
|
100
|
+
"Bash(tar xzf *)",
|
|
101
|
+
"Bash(git *)",
|
|
102
|
+
"Bash(tar -tzf momo-kits-native-kits-0.163.1-beta.18.tgz)",
|
|
103
|
+
"Bash(tar -xzf momo-kits-native-kits-0.163.1-beta.18.tgz package/ios/Button/Button.swift)",
|
|
104
|
+
"Bash(tar -xzf momo-kits-native-kits-0.162.28.tgz package/ios/Button/Button.swift -C .)",
|
|
105
|
+
"Bash(mkdir -p old)",
|
|
106
|
+
"Bash(tar -xzf momo-kits-native-kits-0.162.28.tgz -C old package/ios/Button/Button.swift)",
|
|
107
|
+
"Bash(nm '/private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/ipa_extract/Payload/MoMoPlatform UAT.app/MoMoPlatform UAT')",
|
|
108
|
+
"Bash(xcodebuild -workspace /Users/sophia/Workspace/momo/momo-native-kits/sample/iosApp/iosApp.xcworkspace -list)",
|
|
109
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' build)",
|
|
110
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build-verify build)",
|
|
111
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build-verify build)",
|
|
112
|
+
"Bash(./gradlew :sample:shared:syncComposeResourcesForIos)",
|
|
113
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -derivedDataPath build-verify build)",
|
|
114
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -quiet build)",
|
|
115
|
+
"Bash(xcodebuild -workspace sample/iosApp/iosApp.xcworkspace -list)",
|
|
116
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -destination 'id=0228A4B2-57E0-4E56-BFAD-DB9E95044AD9' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/dd build)",
|
|
117
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/dd build)",
|
|
118
|
+
"Bash([ -s $S/kb.png ])",
|
|
119
|
+
"Bash(sips -g pixelWidth -g pixelHeight /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/kb.png)",
|
|
120
|
+
"Bash(defaults read *)",
|
|
121
|
+
"Bash(defaults write *)",
|
|
122
|
+
"Bash(xcrun xcodebuild *)",
|
|
123
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -quiet build)",
|
|
124
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -quiet build)",
|
|
125
|
+
"Read(//tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/3e8adc08-a7e7-4169-aee6-8b22a6996f38/scratchpad/**)",
|
|
126
|
+
"Bash(echo \"parse-exit=$?\")",
|
|
127
|
+
"Bash(echo \"=== exit $?\")",
|
|
128
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build)",
|
|
129
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -configuration Debug build)",
|
|
130
|
+
"Bash(./gradlew :sample:shared:compileCommonMainKotlinMetadata -q)",
|
|
131
|
+
"Bash(echo \"gradle=$?\")",
|
|
132
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=0228A4B2-57E0-4E56-BFAD-DB9E95044AD9' -derivedDataPath build/dd build)",
|
|
133
|
+
"Bash(osascript *)",
|
|
134
|
+
"Bash(screencapture -x -R253,53,452,950 win.png)",
|
|
135
|
+
"Bash(sips -g pixelWidth -g pixelHeight win.png)",
|
|
136
|
+
"Bash(screencapture -x -R\"253,53,452,950\" win.png)",
|
|
137
|
+
"Bash(screencapture -x full.png)",
|
|
138
|
+
"Bash(sips -g pixelWidth -g pixelHeight full.png)",
|
|
139
|
+
"Bash(sips -c 1900 904 --cropOffset 106 506 full.png --out crop.png)",
|
|
140
|
+
"Bash(swiftc -O mouse.swift -o mouse)",
|
|
141
|
+
"Bash(./mouse click *)",
|
|
142
|
+
"Bash(./mouse drag *)",
|
|
143
|
+
"Bash(sips -c 900 924 --cropOffset 0 0 s12.png --out s12crop.png)",
|
|
144
|
+
"Bash(sips -c 300 1170 --cropOffset 300 0 s12.png --out s12top.png)",
|
|
145
|
+
"Bash(sips -c 700 1170 --cropOffset 250 0 t1.png --out t1c.png)",
|
|
146
|
+
"Bash(sips -c 700 1170 --cropOffset 250 0 t2.png --out t2c.png)",
|
|
147
|
+
"Bash(sips -c 700 1170 --cropOffset 150 0 t3.png --out t3c.png)",
|
|
148
|
+
"Bash(sips -c 1200 1170 --cropOffset 600 0 t4.png --out t4c.png)",
|
|
149
|
+
"Bash(sips -c 600 1170 --cropOffset 150 0 t5.png --out t5c.png)",
|
|
150
|
+
"Bash(sips -c 500 1170 --cropOffset 150 0 u1.png --out u1c.png)",
|
|
151
|
+
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios/Application/Navigation/NavigationContainer.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Application/Navigation/NavigationContainer.swift)",
|
|
152
|
+
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios/Application/Navigation/Navigator.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Application/Navigation/Navigator.swift)",
|
|
153
|
+
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios/Application/Navigation/NavigatorManager.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Application/Navigation/NavigatorManager.swift)",
|
|
154
|
+
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift)",
|
|
155
|
+
"Bash(xcodebuild -workspace MoMoPlatform.xcworkspace -scheme MoMoPlatform-UAT -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build/DerivedData build)",
|
|
156
|
+
"Bash(cp android/gradle/libs.versions.toml /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/bfb380ee-54cb-4a02-bc5f-d152cce4b727/scratchpad/libs.versions.toml.bak)",
|
|
157
|
+
"Bash(sed -i '' 's/kits = \"0.163.1-sp.8\"/kits = \"0.163.1-sp.7\"/' android/gradle/libs.versions.toml)",
|
|
158
|
+
"Bash(xcrun --sdk iphonesimulator --show-sdk-path)",
|
|
159
|
+
"Bash(chmod +x /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/tc.sh)",
|
|
160
|
+
"Bash(/private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/tc.sh)",
|
|
161
|
+
"Bash(perl -0pi -e 's{^ static let \\(\\\\w+\\) = \\(appFont\\\\\\(size: [\\\\d.]+\\\\\\)\\(?:\\\\.weight\\\\\\(\\\\.\\\\w+\\\\\\)\\)?\\)\\(\\\\s*//.*\\)?$}{ static var $1: Font { $2 }$3}mg' ios/Typography/Typography.swift)",
|
|
162
|
+
"Bash(perl -0pi -e 's{\\(\\\\s*\\)// AI-GENERATED START: route Localize injection through the observing modifier\\\\n\\(\\\\s*\\)\\\\.momoLocalize\\\\\\(localize\\\\\\)\\\\n\\(\\\\s*\\)// AI-GENERATED END: route Localize injection through the observing modifier}{$1// AI-GENERATED START: route Localize + font-scale injection through the observing modifiers\\\\n$2.momoLocalize\\(localize\\)\\\\n$2.momoFontScale\\(\\)\\\\n$3// AI-GENERATED END: route Localize + font-scale injection through the observing modifiers}g' ios/Application/Navigation/NavigationContainer.swift)",
|
|
163
|
+
"Bash(perl -pi -e 's/Mirrors Compose.s `fontScale\\\\\\(\\\\.\\\\.\\\\.\\\\\\)`/Mirrors Compose'\"'\"'s `scaleSize\\(...\\)`/' ios/Chip/Chip.swift)",
|
|
164
|
+
"Bash(perl -pi -e 's/fontScale\\\\\\(\\(size == \\\\.small \\\\? 48 : 56\\), 1\\\\.1\\\\\\)/fontScale\\($1\\)/' ios/Input/Input.swift ios/Input/InputPhoneNumber.swift)",
|
|
165
|
+
"Bash(perl -pi -e 's/^\\(\\\\s*\\)\\\\.momoFontScale\\\\\\(\\\\\\)$/$1.momoFontScale\\(context: applicationEnvironment.applicationContext\\)/' ios/Application/Navigation/NavigationContainer.swift)",
|
|
166
|
+
"Bash(chmod +x /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/tcdemo.sh)",
|
|
167
|
+
"Bash(/private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/tcdemo.sh)",
|
|
168
|
+
"Bash(\"/Users/sophia/Workspace/momo-glab/momo-agent-flows/momo-agent/scripts/repo.sh\" find *)",
|
|
169
|
+
"Read(//Users/sophia/.momo-agent/**)",
|
|
170
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/dd build)",
|
|
171
|
+
"Bash(\"/Users/sophia/Workspace/momo-glab/momo-agent-flows/momo-agent/scripts/repo.sh\" prep *)",
|
|
172
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -configuration Debug -destination id=1E6F9A79-5E64-4590-BEA2-828D75D99393 -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/dd build)",
|
|
173
|
+
"Bash(find /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/62b6dcd2-247f-4111-98ef-3847c8914676/scratchpad/dd/Build/Products -maxdepth 2 -name *.app)",
|
|
174
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build/dd build)",
|
|
175
|
+
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build/dd ARCHS=arm64 ONLY_ACTIVE_ARCH=YES build)",
|
|
176
|
+
"Bash(./gradlew :compose:generateDummyFramework)",
|
|
177
|
+
"Bash(break)",
|
|
178
|
+
"Bash(\"/Users/sophia/Workspace/momo-glab/momo-agent-flows/momo-agent/scripts/gitlab.sh\" mr-view *)",
|
|
179
|
+
"Bash(\"/Users/sophia/Workspace/momo-glab/momo-agent-flows/momo-agent/scripts/gitlab.sh\" mr-discussions *)",
|
|
180
|
+
"Bash(command -v glab)",
|
|
181
|
+
"Bash(glab --version)",
|
|
182
|
+
"Bash(/Users/sophia/Workspace/momo-glab/momo-agent-flows/momo-agent/scripts/jira.sh transitions *)"
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
}
|
package/compose/build.gradle.kts
CHANGED
package/compose/compose.podspec
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package vn.momo.kits.const
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.remember
|
|
5
|
+
import androidx.compose.ui.platform.LocalContext
|
|
6
|
+
import androidx.compose.ui.text.font.Font
|
|
7
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
8
|
+
|
|
9
|
+
@Composable
|
|
10
|
+
internal actual fun hostFontFamily(fileName: String): FontFamily? {
|
|
11
|
+
val assets = LocalContext.current.applicationContext.assets
|
|
12
|
+
return remember(fileName) {
|
|
13
|
+
val path = "fonts/$fileName"
|
|
14
|
+
// AssetManager.open ném IOException nếu thiếu file; Font(path, assetManager) thì lỗi
|
|
15
|
+
// muộn lúc render nên phải kiểm tra tồn tại trước.
|
|
16
|
+
val exists = runCatching { assets.open(path).close() }.isSuccess
|
|
17
|
+
if (exists) FontFamily(Font(path = path, assetManager = assets)) else null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -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 exists at androidx.activity 1.10.x — a second enabled callback would steal
|
|
9
|
+
// the gesture from NavHost. Android's system back gesture emits its own platform haptic on commit.
|
|
10
|
+
// Revisit if androidx.activity >= 1.12 is adopted; this then becomes a copy of the iOS actual.
|
|
11
|
+
@Composable
|
|
12
|
+
actual fun rememberBackGestureProgress(): State<Float?> = remember { mutableStateOf(null) }
|
|
@@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
|
|
|
6
6
|
import androidx.compose.foundation.layout.*
|
|
7
7
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
8
8
|
import androidx.compose.runtime.Composable
|
|
9
|
+
import androidx.compose.runtime.remember
|
|
9
10
|
import androidx.compose.ui.Modifier
|
|
10
11
|
import androidx.compose.ui.draw.clip
|
|
11
12
|
import androidx.compose.ui.text.style.TextAlign
|
|
@@ -16,6 +17,7 @@ import vn.momo.kits.const.Radius
|
|
|
16
17
|
import vn.momo.kits.const.Spacing
|
|
17
18
|
import vn.momo.kits.const.Typography
|
|
18
19
|
import vn.momo.kits.modifier.conditional
|
|
20
|
+
import vn.momo.kits.modifier.setAutomationId
|
|
19
21
|
|
|
20
22
|
enum class InformationState {
|
|
21
23
|
DEFAULT,
|
|
@@ -34,7 +36,12 @@ fun Information(
|
|
|
34
36
|
action: String? = null,
|
|
35
37
|
showIconClose: Boolean = true,
|
|
36
38
|
onPressClose: (() -> Unit)? = null,
|
|
39
|
+
accessibilityLabel: String? = null,
|
|
37
40
|
) {
|
|
41
|
+
val automationId = remember(accessibilityLabel, title) {
|
|
42
|
+
accessibilityLabel ?: "Information${if (title != null) "/$title" else ""}"
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
var borderColor = Colors.blue_07
|
|
39
46
|
var backgroundColor = Colors.blue_10
|
|
40
47
|
var color = Colors.blue_03
|
|
@@ -64,6 +71,7 @@ fun Information(
|
|
|
64
71
|
border(1.dp, Colors.blue_03)
|
|
65
72
|
}
|
|
66
73
|
.padding(Spacing.M)
|
|
74
|
+
.setAutomationId(automationId)
|
|
67
75
|
) {
|
|
68
76
|
Row {
|
|
69
77
|
if (image != null) {
|
|
@@ -71,7 +79,7 @@ fun Information(
|
|
|
71
79
|
source = image,
|
|
72
80
|
modifier = Modifier.padding(end = Spacing.S).width(40.dp).height(40.dp).clip(
|
|
73
81
|
RoundedCornerShape(Radius.S)
|
|
74
|
-
)
|
|
82
|
+
).setAutomationId("$automationId|img")
|
|
75
83
|
)
|
|
76
84
|
}
|
|
77
85
|
if (image == null && showIcon) {
|
|
@@ -80,30 +88,44 @@ fun Information(
|
|
|
80
88
|
size = 16.dp,
|
|
81
89
|
color = color,
|
|
82
90
|
modifier = Modifier.padding(end = Spacing.S)
|
|
91
|
+
.setAutomationId("$automationId|icon")
|
|
83
92
|
)
|
|
84
93
|
}
|
|
85
94
|
Column(modifier = Modifier.weight(1f)) {
|
|
86
95
|
if (title != null) {
|
|
87
|
-
Text(
|
|
96
|
+
Text(
|
|
97
|
+
title,
|
|
98
|
+
style = Typography.labelDefaultMedium,
|
|
99
|
+
accessibilityId = "$automationId|title-text"
|
|
100
|
+
)
|
|
88
101
|
}
|
|
89
|
-
Text(
|
|
102
|
+
Text(
|
|
103
|
+
description,
|
|
104
|
+
style = Typography.descriptionDefaultRegular,
|
|
105
|
+
accessibilityId = "$automationId|description-text"
|
|
106
|
+
)
|
|
90
107
|
}
|
|
91
108
|
if (onPressClose != null && showIconClose) {
|
|
92
109
|
Icon(
|
|
93
110
|
source = "navigation_close",
|
|
94
111
|
size = 16.dp,
|
|
95
112
|
modifier = Modifier.clickable(onClick = onPressClose)
|
|
113
|
+
.setAutomationId("$automationId|close-icon-touch")
|
|
96
114
|
)
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
if (action != null && onPressAction != null) {
|
|
100
|
-
Row(
|
|
118
|
+
Row(
|
|
119
|
+
modifier = Modifier.padding(top = Spacing.S)
|
|
120
|
+
.setAutomationId("$automationId|cta-touch")
|
|
121
|
+
) {
|
|
101
122
|
Text(
|
|
102
123
|
action,
|
|
103
124
|
modifier = Modifier.weight(1f).clickable(onClick = onPressAction),
|
|
104
125
|
textAlign = TextAlign.End,
|
|
105
126
|
style = Typography.actionXsBold,
|
|
106
|
-
color = color
|
|
127
|
+
color = color,
|
|
128
|
+
accessibilityId = "$automationId|cta-text"
|
|
107
129
|
)
|
|
108
130
|
}
|
|
109
131
|
}
|
|
@@ -35,8 +35,6 @@ import vn.momo.kits.application.IsShowBaseLineDebug
|
|
|
35
35
|
import vn.momo.kits.const.*
|
|
36
36
|
import vn.momo.kits.modifier.conditional
|
|
37
37
|
import vn.momo.kits.modifier.setAutomationId
|
|
38
|
-
import vn.momo.uikits.resources.Res
|
|
39
|
-
import vn.momo.uikits.resources.sfprotext_regular
|
|
40
38
|
|
|
41
39
|
data class InputSizeDetail(
|
|
42
40
|
val borderWidth: Dp,
|
|
@@ -268,7 +266,7 @@ fun Input(
|
|
|
268
266
|
)
|
|
269
267
|
}
|
|
270
268
|
|
|
271
|
-
val passwordFontFamily =
|
|
269
|
+
val passwordFontFamily = getFontFamily("SFProText", FontWeight.Normal)
|
|
272
270
|
val passwordFontSize = scaleSize(24.sp)
|
|
273
271
|
val visualTransformation = remember(secureTextEntry, inputState.passHidden, passwordFontFamily) {
|
|
274
272
|
if (secureTextEntry && !inputState.passHidden)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package vn.momo.kits.const
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Nạp font từ tài nguyên native của app host thay vì đóng gói bản copy riêng trong SDK.
|
|
8
|
+
*
|
|
9
|
+
* App host (MoMo Platform) đã bundle sẵn toàn bộ font từ `font-source`:
|
|
10
|
+
* - Android: `assets/fonts/<fileName>` (gradle task `syncMomoFonts`)
|
|
11
|
+
* - iOS: file rời ở bundle root (pbxproj fileRef được `post_install.rb` trỏ về font-source)
|
|
12
|
+
*
|
|
13
|
+
* Trả về `null` nếu host không có font đó — caller tự fallback [FontFamily.Default].
|
|
14
|
+
*/
|
|
15
|
+
@Composable
|
|
16
|
+
internal expect fun hostFontFamily(fileName: String): FontFamily?
|
|
@@ -8,12 +8,8 @@ import androidx.compose.ui.text.font.FontFamily
|
|
|
8
8
|
import androidx.compose.ui.text.font.FontWeight
|
|
9
9
|
import androidx.compose.ui.text.style.TextDecoration
|
|
10
10
|
import androidx.compose.ui.unit.*
|
|
11
|
-
import org.jetbrains.compose.resources.Font
|
|
12
|
-
import org.jetbrains.compose.resources.FontResource
|
|
13
|
-
import org.jetbrains.compose.resources.InternalResourceApi
|
|
14
11
|
import vn.momo.kits.application.LocalContext
|
|
15
12
|
import vn.momo.kits.platform.getScreenDimensions
|
|
16
|
-
import vn.momo.uikits.resources.*
|
|
17
13
|
import kotlin.math.max
|
|
18
14
|
import kotlin.math.min
|
|
19
15
|
|
|
@@ -87,48 +83,39 @@ fun scaleSize(textStyle: TextStyle): TextStyle {
|
|
|
87
83
|
}
|
|
88
84
|
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Tên file font trong tài nguyên native của app host (xem [hostFontFamily]).
|
|
88
|
+
*
|
|
89
|
+
* SDK không đóng gói font riêng nữa: app host đã bundle sẵn toàn bộ font từ `font-source`
|
|
90
|
+
* cho cả React Native lẫn native, nên bản copy trong `composeResources` chỉ làm phình app.
|
|
91
|
+
*/
|
|
92
|
+
private const val DEFAULT_FONT_FILE = "SFProText-Regular.ttf"
|
|
93
|
+
|
|
94
|
+
private val hostFontFiles: Map<String, String> = mapOf(
|
|
95
|
+
"SFProText-100" to "SFProText-Thin.ttf",
|
|
96
|
+
"SFProText-200" to "SFProText-Ultralight.ttf",
|
|
97
|
+
"SFProText-300" to "SFProText-Light.ttf",
|
|
98
|
+
"SFProText-400" to DEFAULT_FONT_FILE,
|
|
99
|
+
"SFProText-500" to "SFProText-Medium.ttf",
|
|
100
|
+
"SFProText-600" to "SFProText-Semibold.ttf",
|
|
101
|
+
"SFProText-700" to "SFProText-Bold.ttf",
|
|
102
|
+
"SFProText-800" to "SFProText-Heavy.ttf",
|
|
103
|
+
"SFProText-900" to "SFProText-Black.ttf",
|
|
104
|
+
) + (1..9).flatMap { step ->
|
|
105
|
+
val weight = step * 100
|
|
106
|
+
listOf(
|
|
107
|
+
"MoMoSignature-$weight" to "MoMoSignature.otf",
|
|
108
|
+
"MoMoTrustDisplay-$weight" to "MoMoTrustDisplay.otf",
|
|
109
|
+
)
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
@Composable
|
|
97
113
|
fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
|
|
98
114
|
val key = "$fontFamily-${fontWeight?.weight}"
|
|
99
|
-
val
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"SFProText-400" to Res.font.sfprotext_regular,
|
|
104
|
-
"SFProText-500" to Res.font.sfprotext_medium,
|
|
105
|
-
"SFProText-600" to Res.font.sfprotext_semibold,
|
|
106
|
-
"SFProText-700" to Res.font.sfprotext_bold,
|
|
107
|
-
"SFProText-800" to Res.font.sfprotext_heavy,
|
|
108
|
-
"SFProText-900" to Res.font.sfprotext_black,
|
|
109
|
-
|
|
110
|
-
"MoMoSignature-100" to Res.font.momosignature,
|
|
111
|
-
"MoMoSignature-200" to Res.font.momosignature,
|
|
112
|
-
"MoMoSignature-300" to Res.font.momosignature,
|
|
113
|
-
"MoMoSignature-400" to Res.font.momosignature,
|
|
114
|
-
"MoMoSignature-500" to Res.font.momosignature,
|
|
115
|
-
"MoMoSignature-600" to Res.font.momosignature,
|
|
116
|
-
"MoMoSignature-700" to Res.font.momosignature,
|
|
117
|
-
"MoMoSignature-800" to Res.font.momosignature,
|
|
118
|
-
"MoMoSignature-900" to Res.font.momosignature,
|
|
119
|
-
|
|
120
|
-
"MoMoTrustDisplay-100" to Res.font.momotrustdisplay,
|
|
121
|
-
"MoMoTrustDisplay-200" to Res.font.momotrustdisplay,
|
|
122
|
-
"MoMoTrustDisplay-300" to Res.font.momotrustdisplay,
|
|
123
|
-
"MoMoTrustDisplay-400" to Res.font.momotrustdisplay,
|
|
124
|
-
"MoMoTrustDisplay-500" to Res.font.momotrustdisplay,
|
|
125
|
-
"MoMoTrustDisplay-600" to Res.font.momotrustdisplay,
|
|
126
|
-
"MoMoTrustDisplay-700" to Res.font.momotrustdisplay,
|
|
127
|
-
"MoMoTrustDisplay-800" to Res.font.momotrustdisplay,
|
|
128
|
-
"MoMoTrustDisplay-900" to Res.font.momotrustdisplay,
|
|
129
|
-
)
|
|
130
|
-
val font = fontMap[key] ?: Res.font.sfprotext_regular
|
|
131
|
-
return getFont(font)
|
|
115
|
+
val fileName = hostFontFiles[key] ?: DEFAULT_FONT_FILE
|
|
116
|
+
return hostFontFamily(fileName)
|
|
117
|
+
?: hostFontFamily(DEFAULT_FONT_FILE)
|
|
118
|
+
?: FontFamily.Default
|
|
132
119
|
}
|
|
133
120
|
|
|
134
121
|
@Immutable
|
|
@@ -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,6 +17,8 @@ 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
22
|
import vn.momo.kits.application.LocalContext
|
|
23
23
|
import vn.momo.kits.application.LocalLocalize
|
|
24
24
|
import vn.momo.kits.application.Localize
|
|
@@ -33,6 +33,7 @@ import vn.momo.kits.platform.ProvideOsFontScale
|
|
|
33
33
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
34
34
|
import vn.momo.kits.utils.getNavigationBarHeight
|
|
35
35
|
import vn.momo.maxapi.IMaxApi
|
|
36
|
+
import kotlin.time.Duration.Companion.milliseconds
|
|
36
37
|
|
|
37
38
|
@Composable
|
|
38
39
|
fun NavigationContainer(
|
|
@@ -55,6 +56,7 @@ fun NavigationContainer(
|
|
|
55
56
|
val parentContext = LocalContext.current
|
|
56
57
|
val mergedContext = MiniAppContext.merge(parentContext, miniAppContext)
|
|
57
58
|
|
|
59
|
+
val stackTransition = remember { StackTransitionCoordinator() }
|
|
58
60
|
val theme = remember { mutableStateOf(initialTheme) }
|
|
59
61
|
val fallbackLocalize = remember { Localize() }
|
|
60
62
|
|
|
@@ -86,38 +88,53 @@ fun NavigationContainer(
|
|
|
86
88
|
AppNavigationBar provides navigationBarHeight,
|
|
87
89
|
LocalContext provides mergedContext,
|
|
88
90
|
LocalLocalize provides resolvedLocalize,
|
|
91
|
+
LocalStackTransition provides stackTransition,
|
|
89
92
|
) {
|
|
90
93
|
LaunchedEffect(Unit) {
|
|
91
94
|
setNavigator?.invoke(navigator)
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
popExitTransition = {
|
|
105
|
-
slideOutHorizontally(
|
|
106
|
-
animationSpec = tween(300),
|
|
107
|
-
targetOffsetX = { it }
|
|
108
|
-
)
|
|
97
|
+
SwipeBackHaptic(maxApi)
|
|
98
|
+
|
|
99
|
+
// Gesture pops never reach Navigator.dismissScreen(), so prune the registry from
|
|
100
|
+
// the back stack itself. collectLatest lets a rapid re-push cancel a pending prune.
|
|
101
|
+
LaunchedEffect(navController, registry) {
|
|
102
|
+
navController.currentBackStack.collectLatest { entries ->
|
|
103
|
+
delay(STACK_TRANSITION_DURATION.milliseconds)
|
|
104
|
+
val aliveIds = entries.mapNotNullTo(mutableSetOf()) { entry ->
|
|
105
|
+
runCatching { entry.toRoute<DynamicScreenRoute>().id }.getOrNull()
|
|
106
|
+
?: runCatching { entry.toRoute<DynamicDialogRoute>().id }.getOrNull()
|
|
109
107
|
}
|
|
110
|
-
|
|
108
|
+
aliveIds.add(screenId)
|
|
109
|
+
registry.retainOnly(aliveIds)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The transitions must live on the NavHost call site, not on the destinations:
|
|
114
|
+
// NavHost.uikit computes isDefaultTransition from these arguments alone, and while
|
|
115
|
+
// it is true the library paints its own hard-coded black iOS blackout that would
|
|
116
|
+
// sit over the pink scrim.
|
|
117
|
+
NavHost(
|
|
118
|
+
navController,
|
|
119
|
+
startDestination = startDestination,
|
|
120
|
+
enterTransition = { stackEnter() },
|
|
121
|
+
exitTransition = { stackExit() },
|
|
122
|
+
popEnterTransition = { stackPopEnter() },
|
|
123
|
+
popExitTransition = { stackPopExit() },
|
|
124
|
+
) {
|
|
125
|
+
composable<DynamicScreenRoute> { backStackEntry ->
|
|
111
126
|
val route = backStackEntry.toRoute<DynamicScreenRoute>()
|
|
112
127
|
val screen = registry.getScreen(route.id)
|
|
113
128
|
|
|
114
129
|
if (screen != null) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
StackTransitionDecor(id = route.id, isStackRoute = true) {
|
|
131
|
+
StackScreen(
|
|
132
|
+
id = route.id,
|
|
133
|
+
name = screen.name,
|
|
134
|
+
content = screen.content,
|
|
135
|
+
navigationOptions = screen.options
|
|
136
|
+
)
|
|
137
|
+
}
|
|
121
138
|
}
|
|
122
139
|
}
|
|
123
140
|
|
|
@@ -141,12 +158,15 @@ fun NavigationContainer(
|
|
|
141
158
|
val screen = registry.getScreen(route.id)
|
|
142
159
|
|
|
143
160
|
if (screen != null) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
StackTransitionDecor(id = route.id, isStackRoute = false) {
|
|
162
|
+
StackScreen(
|
|
163
|
+
id = route.id,
|
|
164
|
+
name = screen.name,
|
|
165
|
+
content = screen.content,
|
|
166
|
+
navigationOptions = screen.options,
|
|
167
|
+
isDialogRoute = true
|
|
168
|
+
)
|
|
169
|
+
}
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
172
|
}
|
|
@@ -4,11 +4,15 @@ import androidx.compose.foundation.layout.Box
|
|
|
4
4
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
5
5
|
import androidx.compose.runtime.Composable
|
|
6
6
|
import androidx.compose.runtime.MutableState
|
|
7
|
+
import androidx.compose.runtime.collectAsState
|
|
8
|
+
import androidx.compose.runtime.getValue
|
|
7
9
|
import androidx.compose.runtime.mutableStateOf
|
|
8
10
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
9
11
|
import androidx.compose.ui.Alignment
|
|
10
12
|
import androidx.compose.ui.Modifier
|
|
13
|
+
import androidx.navigation.NavBackStackEntry
|
|
11
14
|
import androidx.navigation.NavController
|
|
15
|
+
import androidx.navigation.NavGraph
|
|
12
16
|
import androidx.navigation.toRoute
|
|
13
17
|
import kotlinx.coroutines.CoroutineScope
|
|
14
18
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -78,6 +82,23 @@ class Navigator(
|
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
// The swipe-back opt-out predicate in StackScreen needs this: the root screen must keep custom
|
|
86
|
+
// back handling so it still reaches maxApi.dismiss. It has to read currentBackStack rather than
|
|
87
|
+
// previousBackStackEntry — the latter walks a plain ArrayDeque, so a composable reading it never
|
|
88
|
+
// recomposes and the predicate would freeze at the value it had on first composition.
|
|
89
|
+
@Composable
|
|
90
|
+
internal fun rememberCanPop(): Boolean {
|
|
91
|
+
val backStack by navController.currentBackStack.collectAsState()
|
|
92
|
+
return backStack.hasPoppableEntry()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Non-composable twin of [rememberCanPop] for the iOS host: its own back check is mount-based
|
|
97
|
+
* and so answers `true` even on the mini-app root, where a swipe must dismiss rather than pop.
|
|
98
|
+
* Reads the StateFlow value directly — correct here precisely because nothing recomposes on it.
|
|
99
|
+
*/
|
|
100
|
+
fun canPop(): Boolean = navController.currentBackStack.value.hasPoppableEntry()
|
|
101
|
+
|
|
81
102
|
fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
|
|
82
103
|
scope.launch {
|
|
83
104
|
repeat(count) {
|
|
@@ -104,7 +125,7 @@ class Navigator(
|
|
|
104
125
|
if (navController.previousBackStackEntry != null){
|
|
105
126
|
val dismissing = currentScreen()
|
|
106
127
|
navController.popBackStack()
|
|
107
|
-
delay(
|
|
128
|
+
delay(STACK_TRANSITION_DURATION.milliseconds)
|
|
108
129
|
dismissing?.let { registry.unregisterScreen(it.id) }
|
|
109
130
|
} else {
|
|
110
131
|
maxApi?.dismiss { }
|
|
@@ -175,6 +196,10 @@ class Navigator(
|
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
|
|
199
|
+
// The NavGraph itself sits in the back stack as an entry; only real destinations can be popped.
|
|
200
|
+
private fun List<NavBackStackEntry>.hasPoppableEntry(): Boolean =
|
|
201
|
+
count { it.destination !is NavGraph } > 1
|
|
202
|
+
|
|
178
203
|
val LocalNavigator = staticCompositionLocalOf<Navigator> {
|
|
179
204
|
error("No NavigationStack provided")
|
|
180
205
|
}
|
|
@@ -222,6 +247,15 @@ class DynamicScreenRegistry {
|
|
|
222
247
|
screens.remove(id)
|
|
223
248
|
}
|
|
224
249
|
|
|
250
|
+
// Gesture pops go through composeNavigator.popBackStack() inside NavHost and never reach
|
|
251
|
+
// Navigator.dismissScreen(), so screens would leak without a stack-driven prune.
|
|
252
|
+
fun retainOnly(ids: Set<Int>) {
|
|
253
|
+
val stale = screens.keys.filter { it !in ids }
|
|
254
|
+
for (id in stale) {
|
|
255
|
+
screens.remove(id)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
225
259
|
fun unregisterAll(){
|
|
226
260
|
val ids = screens.keys.toList()
|
|
227
261
|
for (id in ids) {
|
|
@@ -93,6 +93,7 @@ internal fun StackScreen(
|
|
|
93
93
|
id: Int = -1,
|
|
94
94
|
name: String = "",
|
|
95
95
|
bottomTabIndex: Int = -1,
|
|
96
|
+
isDialogRoute: Boolean = false,
|
|
96
97
|
onBackHandler: (() -> Unit)? = null,
|
|
97
98
|
) {
|
|
98
99
|
val navigator = LocalNavigator.current
|
|
@@ -169,7 +170,29 @@ internal fun StackScreen(
|
|
|
169
170
|
RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
// Hoisted out of the `||` chain below: short-circuiting would drop the back-stack subscription
|
|
174
|
+
// whenever an earlier term is true.
|
|
175
|
+
val canPop = navigator.rememberCanPop()
|
|
176
|
+
|
|
177
|
+
// Port of RN's `hasCustomBackHandling`: this handler is registered inside the NavHost
|
|
178
|
+
// destination, so while it is enabled it beats NavHost's own predictive-back handler and
|
|
179
|
+
// swallows the swipe. Enable it only when a plain pop is the wrong answer.
|
|
180
|
+
//
|
|
181
|
+
// A presented route is one of those cases: NavHost arms predictive back on back-stack size
|
|
182
|
+
// alone, so a horizontal edge drag over a dialog would seek its *vertical* slide.
|
|
183
|
+
val hasCustomBackHandling = !SwipeBackConfig.enabled ||
|
|
184
|
+
isBottomTabChild ||
|
|
185
|
+
isDialogRoute ||
|
|
186
|
+
options.onBackHandler != null ||
|
|
187
|
+
options.headerBackProps.preventBack != null ||
|
|
188
|
+
OverplayComponentRegistry.isOverplayShowing() ||
|
|
189
|
+
!canPop
|
|
190
|
+
|
|
191
|
+
// BottomTab supplies a tab-aware handler; falling through to onBackSafe() there would dismiss
|
|
192
|
+
// the whole mini-app instead of returning to tab 0.
|
|
193
|
+
val onBack = onBackHandler ?: { navigator.onBackSafe() }
|
|
194
|
+
|
|
195
|
+
BackHandler(hasCustomBackHandling, onBack)
|
|
173
196
|
|
|
174
197
|
CompositionLocalProvider(
|
|
175
198
|
StackScreenScrollableState provides options.scrollData.scrollState,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
package vn.momo.kits.navigation
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.AnimatedContentTransitionScope
|
|
4
|
+
import androidx.compose.animation.AnimatedVisibilityScope
|
|
5
|
+
import androidx.compose.animation.EnterExitState
|
|
6
|
+
import androidx.compose.animation.EnterTransition
|
|
7
|
+
import androidx.compose.animation.ExitTransition
|
|
8
|
+
import androidx.compose.animation.core.LinearEasing
|
|
9
|
+
import androidx.compose.animation.core.animateFloat
|
|
10
|
+
import androidx.compose.animation.core.tween
|
|
11
|
+
import androidx.compose.animation.slideInHorizontally
|
|
12
|
+
import androidx.compose.animation.slideOutHorizontally
|
|
13
|
+
import androidx.compose.foundation.layout.Box
|
|
14
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
15
|
+
import androidx.compose.runtime.Composable
|
|
16
|
+
import androidx.compose.runtime.DisposableEffect
|
|
17
|
+
import androidx.compose.runtime.Stable
|
|
18
|
+
import androidx.compose.runtime.derivedStateOf
|
|
19
|
+
import androidx.compose.runtime.getValue
|
|
20
|
+
import androidx.compose.runtime.mutableStateMapOf
|
|
21
|
+
import androidx.compose.runtime.mutableStateOf
|
|
22
|
+
import androidx.compose.runtime.remember
|
|
23
|
+
import androidx.compose.runtime.setValue
|
|
24
|
+
import androidx.compose.runtime.staticCompositionLocalOf
|
|
25
|
+
import androidx.compose.ui.Modifier
|
|
26
|
+
import androidx.compose.ui.draw.drawBehind
|
|
27
|
+
import androidx.compose.ui.geometry.Offset
|
|
28
|
+
import androidx.compose.ui.geometry.Size
|
|
29
|
+
import androidx.compose.ui.graphics.Brush
|
|
30
|
+
import androidx.compose.ui.graphics.Color
|
|
31
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
32
|
+
import androidx.compose.ui.unit.IntOffset
|
|
33
|
+
import androidx.compose.ui.unit.dp
|
|
34
|
+
import androidx.navigation.NavBackStackEntry
|
|
35
|
+
import androidx.navigation.NavDestination.Companion.hasRoute
|
|
36
|
+
import vn.momo.kits.const.Colors
|
|
37
|
+
import kotlin.math.roundToInt
|
|
38
|
+
|
|
39
|
+
internal const val STACK_TRANSITION_DURATION = 300
|
|
40
|
+
|
|
41
|
+
// Ported 1:1 from the RN kit's `forStackTransitionWithOverlay` so both kits look identical.
|
|
42
|
+
private const val PARALLAX_RATIO = 0.3f
|
|
43
|
+
private const val SCRIM_MAX_ALPHA = 0.6f
|
|
44
|
+
private const val EDGE_SHADOW_MAX_ALPHA = 0.3f
|
|
45
|
+
private val EDGE_SHADOW_WIDTH = 8.dp
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Kill switch for the swipe-back gesture. Set to `false` to make kit back handling always win,
|
|
49
|
+
* which disables the predictive-back drag on both platforms — the gesture then pops without
|
|
50
|
+
* following the finger, and fires no haptic. Screen transitions stay on the parallax curve either
|
|
51
|
+
* way.
|
|
52
|
+
*/
|
|
53
|
+
object SwipeBackConfig {
|
|
54
|
+
// Snapshot-backed so a host toggle takes effect on the next frame instead of waiting for an
|
|
55
|
+
// unrelated recomposition of StackScreen.
|
|
56
|
+
var enabled: Boolean by mutableStateOf(true)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// LinearEasing is mandatory, not a style choice: SeekableTransitionState.seekTo() maps the drag
|
|
60
|
+
// fraction straight onto the animation timeline, so any curve would decouple the card from the
|
|
61
|
+
// finger mid-gesture.
|
|
62
|
+
private val stackSpec = tween<IntOffset>(STACK_TRANSITION_DURATION, easing = LinearEasing)
|
|
63
|
+
|
|
64
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackEnter(): EnterTransition =
|
|
65
|
+
slideInHorizontally(stackSpec) { it }
|
|
66
|
+
|
|
67
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackExit(): ExitTransition =
|
|
68
|
+
if (targetState.destination.hasRoute<DynamicDialogRoute>()) ExitTransition.None
|
|
69
|
+
else slideOutHorizontally(stackSpec) { -(it * PARALLAX_RATIO).roundToInt() }
|
|
70
|
+
|
|
71
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackPopEnter(): EnterTransition =
|
|
72
|
+
if (initialState.destination.hasRoute<DynamicDialogRoute>()) EnterTransition.None
|
|
73
|
+
else slideInHorizontally(stackSpec) { -(it * PARALLAX_RATIO).roundToInt() }
|
|
74
|
+
|
|
75
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackPopExit(): ExitTransition =
|
|
76
|
+
slideOutHorizontally(stackSpec) { it }
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Tracks which screen ids are currently composed so a screen can tell whether it is the top card
|
|
80
|
+
* or the one underneath.
|
|
81
|
+
*
|
|
82
|
+
* Screen ids come from a single monotonic process-wide counter and NavHost keeps both entries
|
|
83
|
+
* composed for the whole transition segment, so "highest composed stack id" is stable in both
|
|
84
|
+
* directions. `currentBackStackEntry` is not usable here: it flips at frame 0 of a pop, which
|
|
85
|
+
* would make the scrim vanish instantly instead of fading.
|
|
86
|
+
*/
|
|
87
|
+
@Stable
|
|
88
|
+
internal class StackTransitionCoordinator {
|
|
89
|
+
private val composedRoutes = mutableStateMapOf<Int, Boolean>()
|
|
90
|
+
|
|
91
|
+
val topStackId: Int by derivedStateOf {
|
|
92
|
+
composedRoutes.entries.filter { it.value }.maxOfOrNull { it.key } ?: -1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fun attach(id: Int, isStackRoute: Boolean) {
|
|
96
|
+
composedRoutes[id] = isStackRoute
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fun detach(id: Int) {
|
|
100
|
+
composedRoutes.remove(id)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
internal val LocalStackTransition = staticCompositionLocalOf { StackTransitionCoordinator() }
|
|
105
|
+
|
|
106
|
+
/** Draws the covered-screen scrim and the top card's left-edge shadow around a stack destination. */
|
|
107
|
+
@Composable
|
|
108
|
+
internal fun AnimatedVisibilityScope.StackTransitionDecor(
|
|
109
|
+
id: Int,
|
|
110
|
+
isStackRoute: Boolean,
|
|
111
|
+
content: @Composable () -> Unit,
|
|
112
|
+
) {
|
|
113
|
+
val coordinator = LocalStackTransition.current
|
|
114
|
+
// attach() writes state that derivedStateOf { topStackId } reads in the same pass, so it has to
|
|
115
|
+
// stay out of the composable body.
|
|
116
|
+
DisposableEffect(coordinator, id, isStackRoute) {
|
|
117
|
+
coordinator.attach(id, isStackRoute)
|
|
118
|
+
onDispose { coordinator.detach(id) }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
val isTopCard = isStackRoute && id == coordinator.topStackId
|
|
122
|
+
val isCovered = isStackRoute && !isTopCard
|
|
123
|
+
|
|
124
|
+
val scrimAlpha = transition.animateFloat(
|
|
125
|
+
transitionSpec = { tween(STACK_TRANSITION_DURATION, easing = LinearEasing) },
|
|
126
|
+
label = "stackScrimAlpha",
|
|
127
|
+
) { state -> if (isCovered && state != EnterExitState.Visible) SCRIM_MAX_ALPHA else 0f }
|
|
128
|
+
|
|
129
|
+
val shadowAlpha = transition.animateFloat(
|
|
130
|
+
transitionSpec = { tween(STACK_TRANSITION_DURATION, easing = LinearEasing) },
|
|
131
|
+
label = "stackEdgeShadowAlpha",
|
|
132
|
+
) { state -> if (isTopCard && state == EnterExitState.Visible) EDGE_SHADOW_MAX_ALPHA else 0f }
|
|
133
|
+
|
|
134
|
+
val shadowWidthPx = with(LocalDensity.current) { EDGE_SHADOW_WIDTH.toPx() }
|
|
135
|
+
val shadowBrush = remember(shadowWidthPx) {
|
|
136
|
+
Brush.horizontalGradient(
|
|
137
|
+
colors = listOf(Color.Transparent, Colors.black_20),
|
|
138
|
+
startX = -shadowWidthPx,
|
|
139
|
+
endX = 0f,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Box(Modifier.fillMaxSize()) {
|
|
144
|
+
content()
|
|
145
|
+
|
|
146
|
+
if (isStackRoute) {
|
|
147
|
+
Box(
|
|
148
|
+
Modifier
|
|
149
|
+
.matchParentSize()
|
|
150
|
+
// Both alphas are read inside drawBehind so a drag only re-draws; reading them
|
|
151
|
+
// in the composable body would recompose the whole screen every gesture frame.
|
|
152
|
+
.drawBehind {
|
|
153
|
+
drawRect(
|
|
154
|
+
brush = shadowBrush,
|
|
155
|
+
topLeft = Offset(-shadowWidthPx, 0f),
|
|
156
|
+
size = Size(shadowWidthPx, size.height),
|
|
157
|
+
alpha = shadowAlpha.value,
|
|
158
|
+
)
|
|
159
|
+
drawRect(Colors.pink_09, alpha = scrimAlpha.value)
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package vn.momo.kits.navigation
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
5
|
+
import androidx.compose.runtime.snapshotFlow
|
|
6
|
+
import vn.momo.kits.platform.getScreenDimensions
|
|
7
|
+
import vn.momo.kits.platform.rememberBackGestureProgress
|
|
8
|
+
import vn.momo.maxapi.IMaxApi
|
|
9
|
+
|
|
10
|
+
// RN fires the tick at a flat 100 px of thumb travel; keep it unscaled so the physical distance is
|
|
11
|
+
// the same on every device.
|
|
12
|
+
private const val SWIPE_BACK_HAPTIC_THRESHOLD_DP = 50f
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fires one light tick once the back gesture has travelled ~100dp, re-arming when the finger drops
|
|
16
|
+
* back below the threshold. iOS only — [rememberBackGestureProgress] is always `null` on Android.
|
|
17
|
+
*/
|
|
18
|
+
@Composable
|
|
19
|
+
internal fun SwipeBackHaptic(maxApi: IMaxApi?) {
|
|
20
|
+
// With the kill switch off the kit's own BackHandler swallows the gesture, so the card never
|
|
21
|
+
// moves — a tick there would be a phantom.
|
|
22
|
+
if (maxApi == null || !SwipeBackConfig.enabled) return
|
|
23
|
+
|
|
24
|
+
val progress = rememberBackGestureProgress()
|
|
25
|
+
val screenWidth = getScreenDimensions().width.toFloat()
|
|
26
|
+
|
|
27
|
+
LaunchedEffect(maxApi, screenWidth) {
|
|
28
|
+
if (screenWidth <= 0f) return@LaunchedEffect
|
|
29
|
+
val threshold = SWIPE_BACK_HAPTIC_THRESHOLD_DP / screenWidth
|
|
30
|
+
var isSendHaptic = false
|
|
31
|
+
snapshotFlow { progress.value }.collect { fraction ->
|
|
32
|
+
val travelled = fraction ?: 0f
|
|
33
|
+
if (travelled >= threshold && !isSendHaptic) {
|
|
34
|
+
runCatching { maxApi.triggerEventVibration("light", callback = null) }
|
|
35
|
+
isSendHaptic = true
|
|
36
|
+
} else if (travelled < threshold && isSendHaptic) {
|
|
37
|
+
isSendHaptic = false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package vn.momo.kits.platform
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.State
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Progress of an in-flight back gesture as a fraction of the container width, or `null` while no
|
|
8
|
+
* gesture is running. Observing only — it must never consume the gesture, otherwise it would steal
|
|
9
|
+
* it from NavHost.
|
|
10
|
+
*
|
|
11
|
+
* Android always returns `null`: its NavHost runs on `androidx.activity`'s OnBackPressedDispatcher,
|
|
12
|
+
* which exposes progress only to the top-most enabled callback, and that has to stay NavHost's.
|
|
13
|
+
*/
|
|
14
|
+
@Composable
|
|
15
|
+
expect fun rememberBackGestureProgress(): State<Float?>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
|
2
|
+
|
|
3
|
+
package vn.momo.kits.const
|
|
4
|
+
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.runtime.remember
|
|
7
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
8
|
+
import androidx.compose.ui.text.platform.Font
|
|
9
|
+
import kotlinx.cinterop.ExperimentalForeignApi
|
|
10
|
+
import kotlinx.cinterop.addressOf
|
|
11
|
+
import kotlinx.cinterop.usePinned
|
|
12
|
+
import platform.Foundation.NSBundle
|
|
13
|
+
import platform.Foundation.NSData
|
|
14
|
+
import platform.Foundation.dataWithContentsOfFile
|
|
15
|
+
import platform.posix.memcpy
|
|
16
|
+
|
|
17
|
+
@Composable
|
|
18
|
+
internal actual fun hostFontFamily(fileName: String): FontFamily? = remember(fileName) {
|
|
19
|
+
val name = fileName.substringBeforeLast('.')
|
|
20
|
+
val ext = fileName.substringAfterLast('.', "")
|
|
21
|
+
// Font từ font-source nằm rời ở bundle root, không nằm trong compose-resources.
|
|
22
|
+
val path = NSBundle.mainBundle.pathForResource(name, ext) ?: return@remember null
|
|
23
|
+
FontFamily(
|
|
24
|
+
Font(
|
|
25
|
+
identity = fileName,
|
|
26
|
+
getData = {
|
|
27
|
+
NSData.dataWithContentsOfFile(path)?.toByteArray() ?: ByteArray(0)
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
34
|
+
private fun NSData.toByteArray(): ByteArray {
|
|
35
|
+
val size = length.toInt()
|
|
36
|
+
if (size == 0) return ByteArray(0)
|
|
37
|
+
return ByteArray(size).apply {
|
|
38
|
+
usePinned { pinned -> memcpy(pinned.addressOf(0), bytes, length) }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
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.NavigationEventDispatcher
|
|
7
|
+
import androidx.navigationevent.NavigationEventInput
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read-only answer to "will Compose consume this back?", for the iOS host.
|
|
11
|
+
*
|
|
12
|
+
* Compose owns the edge-pan gesture through its own `UIKitBackGestureRecognizer`; the host reads
|
|
13
|
+
* this to decide whether to stand down or keep driving its own recogniser, instead of guessing.
|
|
14
|
+
* Nothing here dispatches — [ObserveBackGestureOwnership] only listens.
|
|
15
|
+
*
|
|
16
|
+
* Pair it with [Navigator.canPop]: the mini-app root reports an enabled handler too (it swallows
|
|
17
|
+
* back to reach `maxApi.dismiss`), yet there the host still owns the gesture.
|
|
18
|
+
*/
|
|
19
|
+
object ComposeBackGesture {
|
|
20
|
+
private val observers = mutableListOf<BackHandlerObserver>()
|
|
21
|
+
|
|
22
|
+
/** State of the top-most mounted container; `false` when none is mounted. */
|
|
23
|
+
val hasEnabledHandlers: Boolean
|
|
24
|
+
get() = observers.lastOrNull()?.hasEnabledHandlers == true
|
|
25
|
+
|
|
26
|
+
internal fun attach(observer: BackHandlerObserver) {
|
|
27
|
+
observers += observer
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
internal fun detach(observer: BackHandlerObserver) {
|
|
31
|
+
observers -= observer
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Publishes one [NavigationContainer]'s back-handling state to [ComposeBackGesture] while composed.
|
|
37
|
+
*
|
|
38
|
+
* Only called for a container whose dispatcher is the scene's — a bare-`UIView` host gets the
|
|
39
|
+
* detached fallback dispatcher, which can never receive an edge pan, so reporting its handlers
|
|
40
|
+
* would tell the host to stand down in favour of a gesture that does not exist.
|
|
41
|
+
*/
|
|
42
|
+
@Composable
|
|
43
|
+
internal fun ObserveBackGestureOwnership(dispatcher: NavigationEventDispatcher) {
|
|
44
|
+
val observer = remember(dispatcher) { BackHandlerObserver() }
|
|
45
|
+
|
|
46
|
+
DisposableEffect(dispatcher, observer) {
|
|
47
|
+
dispatcher.addInput(observer)
|
|
48
|
+
ComposeBackGesture.attach(observer)
|
|
49
|
+
onDispose {
|
|
50
|
+
ComposeBackGesture.detach(observer)
|
|
51
|
+
dispatcher.removeInput(observer)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A NavigationEventInput that never dispatches: adding an input is the only way to observe the
|
|
57
|
+
// dispatcher's enabled-handler state without registering a handler, which would consume the gesture.
|
|
58
|
+
internal class BackHandlerObserver : NavigationEventInput() {
|
|
59
|
+
var hasEnabledHandlers: Boolean = false
|
|
60
|
+
private set
|
|
61
|
+
|
|
62
|
+
override fun onHasEnabledHandlersChanged(hasEnabledHandlers: Boolean) {
|
|
63
|
+
this.hasEnabledHandlers = hasEnabledHandlers
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
|
|
12
|
+
// NavigationEventDispatcher.transitionState is a passive observer: UIKitNavigationEventInput
|
|
13
|
+
// publishes every edge-pan event to it without any handler consuming the gesture. Its `progress`
|
|
14
|
+
// is touch.x / view width, i.e. a plain fraction of the screen width.
|
|
15
|
+
@OptIn(InternalComposeUiApi::class)
|
|
16
|
+
@Composable
|
|
17
|
+
actual fun rememberBackGestureProgress(): State<Float?> {
|
|
18
|
+
val dispatcher = LocalCompatNavigationEventDispatcherOwner.current?.navigationEventDispatcher
|
|
19
|
+
val progress = remember { mutableStateOf<Float?>(null) }
|
|
20
|
+
|
|
21
|
+
LaunchedEffect(dispatcher) {
|
|
22
|
+
if (dispatcher == null) {
|
|
23
|
+
progress.value = null
|
|
24
|
+
return@LaunchedEffect
|
|
25
|
+
}
|
|
26
|
+
dispatcher.transitionState.collect { state ->
|
|
27
|
+
progress.value = (state as? NavigationEventTransitionState.InProgress)
|
|
28
|
+
?.takeIf { it.direction == NavigationEventTransitionState.TRANSITIONING_BACK }
|
|
29
|
+
?.latestEvent
|
|
30
|
+
?.progress
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return progress
|
|
35
|
+
}
|
|
@@ -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,7 @@ 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.ObserveBackGestureOwnership
|
|
41
43
|
|
|
42
44
|
actual fun getPlatformName(): String = "iOS"
|
|
43
45
|
|
|
@@ -60,14 +62,33 @@ actual fun getStatusBarHeight(): Dp {
|
|
|
60
62
|
return WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
// An enabled kit BackHandler is registered inside the NavHost destination, so it out-ranks
|
|
66
|
+
// NavHost's own predictive-back handler and swallows the gesture — that is how a screen opts out
|
|
67
|
+
// of swipe-back (the port of RN's `hasCustomBackHandling`).
|
|
68
|
+
@Suppress("DEPRECATION")
|
|
69
|
+
@OptIn(ExperimentalComposeUiApi::class)
|
|
63
70
|
@Composable
|
|
64
71
|
actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
|
|
72
|
+
ComposeBackHandler(enabled = enabled, onBack = onBack)
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
@OptIn(InternalComposeUiApi::class)
|
|
68
76
|
@Composable
|
|
69
77
|
actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit) {
|
|
70
|
-
val
|
|
78
|
+
val platformOwner = LocalCompatNavigationEventDispatcherOwner.current
|
|
79
|
+
if (platformOwner != null) {
|
|
80
|
+
// UIKitNavigationEventInput is registered on the scene's dispatcher, and that dispatcher
|
|
81
|
+
// having enabled handlers is what arms Compose's UIKitBackGestureRecognizer. Providing a
|
|
82
|
+
// fresh NavigationEventDispatcher() here would shadow it with an input-less one that never
|
|
83
|
+
// sees a single edge-pan event — no recogniser, and no finger-following seek.
|
|
84
|
+
ObserveBackGestureOwnership(platformOwner.navigationEventDispatcher)
|
|
85
|
+
content()
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Bare UIView hosts have no scene dispatcher, and BackHandler/PredictiveBackHandler error() on
|
|
90
|
+
// a null owner. Disabled: nothing can feed it, so it exists only to keep those from throwing.
|
|
91
|
+
val fallbackOwner = remember {
|
|
71
92
|
object : NavigationEventDispatcherOwner {
|
|
72
93
|
override val navigationEventDispatcher = NavigationEventDispatcher().apply {
|
|
73
94
|
isEnabled = false
|
|
@@ -76,7 +97,7 @@ actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit
|
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
CompositionLocalProvider(
|
|
79
|
-
LocalCompatNavigationEventDispatcherOwner provides
|
|
100
|
+
LocalCompatNavigationEventDispatcherOwner provides fallbackOwner,
|
|
80
101
|
content = content
|
|
81
102
|
)
|
|
82
103
|
}
|
package/gradle.properties
CHANGED
package/ios/native-kits.podspec
CHANGED
package/local.properties
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
# Location of the SDK. This is only used by Gradle.
|
|
5
5
|
# For customization when using a Version Control System, please read the
|
|
6
6
|
# header note.
|
|
7
|
-
#
|
|
8
|
-
sdk.dir=/Users/
|
|
7
|
+
#Tue Jun 02 17:05:32 ICT 2026
|
|
8
|
+
sdk.dir=/Users/sophia/Library/Android/sdk
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|