@momo-kits/native-kits 0.164.1-beta.4-debug → 0.164.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/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/platform/BackGesture.android.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +49 -30
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +14 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +11 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +39 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +15 -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 +20 -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
|
@@ -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,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) }
|
|
@@ -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,14 @@ 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
|
+
)
|
|
168
|
+
}
|
|
150
169
|
}
|
|
151
170
|
}
|
|
152
171
|
}
|
|
@@ -78,6 +78,10 @@ class Navigator(
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// The swipe-back opt-out predicate in StackScreen needs this: the root screen must keep custom
|
|
82
|
+
// back handling so it still reaches maxApi.dismiss.
|
|
83
|
+
internal fun canPop(): Boolean = navController.previousBackStackEntry != null
|
|
84
|
+
|
|
81
85
|
fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
|
|
82
86
|
scope.launch {
|
|
83
87
|
repeat(count) {
|
|
@@ -104,7 +108,7 @@ class Navigator(
|
|
|
104
108
|
if (navController.previousBackStackEntry != null){
|
|
105
109
|
val dismissing = currentScreen()
|
|
106
110
|
navController.popBackStack()
|
|
107
|
-
delay(
|
|
111
|
+
delay(STACK_TRANSITION_DURATION.milliseconds)
|
|
108
112
|
dismissing?.let { registry.unregisterScreen(it.id) }
|
|
109
113
|
} else {
|
|
110
114
|
maxApi?.dismiss { }
|
|
@@ -222,6 +226,15 @@ class DynamicScreenRegistry {
|
|
|
222
226
|
screens.remove(id)
|
|
223
227
|
}
|
|
224
228
|
|
|
229
|
+
// Gesture pops go through composeNavigator.popBackStack() inside NavHost and never reach
|
|
230
|
+
// Navigator.dismissScreen(), so screens would leak without a stack-driven prune.
|
|
231
|
+
fun retainOnly(ids: Set<Int>) {
|
|
232
|
+
val stale = screens.keys.filter { it !in ids }
|
|
233
|
+
for (id in stale) {
|
|
234
|
+
screens.remove(id)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
225
238
|
fun unregisterAll(){
|
|
226
239
|
val ids = screens.keys.toList()
|
|
227
240
|
for (id in ids) {
|
|
@@ -169,7 +169,17 @@ internal fun StackScreen(
|
|
|
169
169
|
RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
// Port of RN's `hasCustomBackHandling`: this handler is registered inside the NavHost
|
|
173
|
+
// destination, so while it is enabled it beats NavHost's own predictive-back handler and
|
|
174
|
+
// swallows the swipe. Enable it only when a plain pop is the wrong answer.
|
|
175
|
+
val hasCustomBackHandling = !SwipeBackConfig.enabled ||
|
|
176
|
+
isBottomTabChild ||
|
|
177
|
+
options.onBackHandler != null ||
|
|
178
|
+
options.headerBackProps.preventBack != null ||
|
|
179
|
+
OverplayComponentRegistry.isOverplayShowing() ||
|
|
180
|
+
!navigator.canPop()
|
|
181
|
+
|
|
182
|
+
BackHandler(hasCustomBackHandling) { navigator.onBackSafe() }
|
|
173
183
|
|
|
174
184
|
CompositionLocalProvider(
|
|
175
185
|
StackScreenScrollableState provides options.scrollData.scrollState,
|
|
@@ -0,0 +1,157 @@
|
|
|
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.remember
|
|
22
|
+
import androidx.compose.runtime.staticCompositionLocalOf
|
|
23
|
+
import androidx.compose.ui.Modifier
|
|
24
|
+
import androidx.compose.ui.draw.drawBehind
|
|
25
|
+
import androidx.compose.ui.geometry.Offset
|
|
26
|
+
import androidx.compose.ui.geometry.Size
|
|
27
|
+
import androidx.compose.ui.graphics.Brush
|
|
28
|
+
import androidx.compose.ui.graphics.Color
|
|
29
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
30
|
+
import androidx.compose.ui.unit.IntOffset
|
|
31
|
+
import androidx.compose.ui.unit.dp
|
|
32
|
+
import androidx.navigation.NavBackStackEntry
|
|
33
|
+
import androidx.navigation.NavDestination.Companion.hasRoute
|
|
34
|
+
import vn.momo.kits.const.Colors
|
|
35
|
+
import kotlin.math.roundToInt
|
|
36
|
+
|
|
37
|
+
internal const val STACK_TRANSITION_DURATION = 300
|
|
38
|
+
|
|
39
|
+
// Ported 1:1 from the RN kit's `forStackTransitionWithOverlay` so both kits look identical.
|
|
40
|
+
private const val PARALLAX_RATIO = 0.3f
|
|
41
|
+
private const val SCRIM_MAX_ALPHA = 0.6f
|
|
42
|
+
private const val EDGE_SHADOW_MAX_ALPHA = 0.3f
|
|
43
|
+
private val EDGE_SHADOW_WIDTH = 8.dp
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Kill switch for the swipe-back gesture. Set to `false` to restore pre-DS-760 behaviour (kit
|
|
47
|
+
* back handling always wins, iOS edge pan inert) without a kit release. Screen transitions stay
|
|
48
|
+
* on the new parallax curve either way.
|
|
49
|
+
*/
|
|
50
|
+
object SwipeBackConfig {
|
|
51
|
+
var enabled: Boolean = true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// LinearEasing is mandatory, not a style choice: SeekableTransitionState.seekTo() maps the drag
|
|
55
|
+
// fraction straight onto the animation timeline, so any curve would decouple the card from the
|
|
56
|
+
// finger mid-gesture.
|
|
57
|
+
private val stackSpec = tween<IntOffset>(STACK_TRANSITION_DURATION, easing = LinearEasing)
|
|
58
|
+
|
|
59
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackEnter(): EnterTransition =
|
|
60
|
+
slideInHorizontally(stackSpec) { it }
|
|
61
|
+
|
|
62
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackExit(): ExitTransition =
|
|
63
|
+
if (targetState.destination.hasRoute<DynamicDialogRoute>()) ExitTransition.None
|
|
64
|
+
else slideOutHorizontally(stackSpec) { -(it * PARALLAX_RATIO).roundToInt() }
|
|
65
|
+
|
|
66
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackPopEnter(): EnterTransition =
|
|
67
|
+
if (initialState.destination.hasRoute<DynamicDialogRoute>()) EnterTransition.None
|
|
68
|
+
else slideInHorizontally(stackSpec) { -(it * PARALLAX_RATIO).roundToInt() }
|
|
69
|
+
|
|
70
|
+
internal fun AnimatedContentTransitionScope<NavBackStackEntry>.stackPopExit(): ExitTransition =
|
|
71
|
+
slideOutHorizontally(stackSpec) { it }
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tracks which screen ids are currently composed so a screen can tell whether it is the top card
|
|
75
|
+
* or the one underneath.
|
|
76
|
+
*
|
|
77
|
+
* Screen ids come from a single monotonic process-wide counter and NavHost keeps both entries
|
|
78
|
+
* composed for the whole transition segment, so "highest composed stack id" is stable in both
|
|
79
|
+
* directions. `currentBackStackEntry` is not usable here: it flips at frame 0 of a pop, which
|
|
80
|
+
* would make the scrim vanish instantly instead of fading.
|
|
81
|
+
*/
|
|
82
|
+
@Stable
|
|
83
|
+
internal class StackTransitionCoordinator {
|
|
84
|
+
private val composedRoutes = mutableStateMapOf<Int, Boolean>()
|
|
85
|
+
|
|
86
|
+
val topStackId: Int by derivedStateOf {
|
|
87
|
+
composedRoutes.entries.filter { it.value }.maxOfOrNull { it.key } ?: -1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fun attach(id: Int, isStackRoute: Boolean) {
|
|
91
|
+
composedRoutes[id] = isStackRoute
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fun detach(id: Int) {
|
|
95
|
+
composedRoutes.remove(id)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
internal val LocalStackTransition = staticCompositionLocalOf { StackTransitionCoordinator() }
|
|
100
|
+
|
|
101
|
+
/** Draws the covered-screen scrim and the top card's left-edge shadow around a stack destination. */
|
|
102
|
+
@Composable
|
|
103
|
+
internal fun AnimatedVisibilityScope.StackTransitionDecor(
|
|
104
|
+
id: Int,
|
|
105
|
+
isStackRoute: Boolean,
|
|
106
|
+
content: @Composable () -> Unit,
|
|
107
|
+
) {
|
|
108
|
+
val coordinator = LocalStackTransition.current
|
|
109
|
+
coordinator.attach(id, isStackRoute)
|
|
110
|
+
DisposableEffect(coordinator, id) {
|
|
111
|
+
onDispose { coordinator.detach(id) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
val isTopCard = isStackRoute && id == coordinator.topStackId
|
|
115
|
+
val isCovered = isStackRoute && !isTopCard
|
|
116
|
+
|
|
117
|
+
val scrimAlpha = transition.animateFloat(
|
|
118
|
+
transitionSpec = { tween(STACK_TRANSITION_DURATION, easing = LinearEasing) },
|
|
119
|
+
label = "stackScrimAlpha",
|
|
120
|
+
) { state -> if (isCovered && state != EnterExitState.Visible) SCRIM_MAX_ALPHA else 0f }
|
|
121
|
+
|
|
122
|
+
val shadowAlpha = transition.animateFloat(
|
|
123
|
+
transitionSpec = { tween(STACK_TRANSITION_DURATION, easing = LinearEasing) },
|
|
124
|
+
label = "stackEdgeShadowAlpha",
|
|
125
|
+
) { state -> if (isTopCard && state == EnterExitState.Visible) EDGE_SHADOW_MAX_ALPHA else 0f }
|
|
126
|
+
|
|
127
|
+
val shadowWidthPx = with(LocalDensity.current) { EDGE_SHADOW_WIDTH.toPx() }
|
|
128
|
+
val shadowBrush = remember(shadowWidthPx) {
|
|
129
|
+
Brush.horizontalGradient(
|
|
130
|
+
colors = listOf(Color.Transparent, Colors.black_20),
|
|
131
|
+
startX = -shadowWidthPx,
|
|
132
|
+
endX = 0f,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Box(Modifier.fillMaxSize()) {
|
|
137
|
+
content()
|
|
138
|
+
|
|
139
|
+
if (isStackRoute) {
|
|
140
|
+
Box(
|
|
141
|
+
Modifier
|
|
142
|
+
.matchParentSize()
|
|
143
|
+
// Both alphas are read inside drawBehind so a drag only re-draws; reading them
|
|
144
|
+
// in the composable body would recompose the whole screen every gesture frame.
|
|
145
|
+
.drawBehind {
|
|
146
|
+
drawRect(
|
|
147
|
+
brush = shadowBrush,
|
|
148
|
+
topLeft = Offset(-shadowWidthPx, 0f),
|
|
149
|
+
size = Size(shadowWidthPx, size.height),
|
|
150
|
+
alpha = shadowAlpha.value,
|
|
151
|
+
)
|
|
152
|
+
drawRect(Colors.pink_09, alpha = scrimAlpha.value)
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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 = 100f
|
|
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
|
+
if (maxApi == null) return
|
|
21
|
+
|
|
22
|
+
val progress = rememberBackGestureProgress()
|
|
23
|
+
val screenWidth = getScreenDimensions().width.toFloat()
|
|
24
|
+
|
|
25
|
+
LaunchedEffect(maxApi, screenWidth) {
|
|
26
|
+
if (screenWidth <= 0f) return@LaunchedEffect
|
|
27
|
+
val threshold = SWIPE_BACK_HAPTIC_THRESHOLD_DP / screenWidth
|
|
28
|
+
var isSendHaptic = false
|
|
29
|
+
snapshotFlow { progress.value }.collect { fraction ->
|
|
30
|
+
val travelled = fraction ?: 0f
|
|
31
|
+
if (travelled >= threshold && !isSendHaptic) {
|
|
32
|
+
runCatching { maxApi.triggerEventVibration("light", callback = null) }
|
|
33
|
+
isSendHaptic = true
|
|
34
|
+
} else if (travelled < threshold && isSendHaptic) {
|
|
35
|
+
isSendHaptic = false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -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,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.SwipeBackConfig
|
|
41
43
|
|
|
42
44
|
actual fun getPlatformName(): String = "iOS"
|
|
43
45
|
|
|
@@ -60,14 +62,30 @@ actual fun getStatusBarHeight(): Dp {
|
|
|
60
62
|
return WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
// An enabled kit BackHandler sits innermost and therefore consumes the edge-pan gesture — that is
|
|
66
|
+
// how a screen opts out of swipe-back (the port of RN's `hasCustomBackHandling`).
|
|
67
|
+
@Suppress("DEPRECATION")
|
|
68
|
+
@OptIn(ExperimentalComposeUiApi::class)
|
|
63
69
|
@Composable
|
|
64
70
|
actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
|
|
71
|
+
ComposeBackHandler(enabled = enabled, onBack = onBack)
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
@OptIn(InternalComposeUiApi::class)
|
|
68
75
|
@Composable
|
|
69
76
|
actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit) {
|
|
70
|
-
val
|
|
77
|
+
val platformOwner = LocalCompatNavigationEventDispatcherOwner.current
|
|
78
|
+
if (platformOwner != null && SwipeBackConfig.enabled) {
|
|
79
|
+
// UIKitNavigationEventInput is registered on the scene's dispatcher. Providing a fresh
|
|
80
|
+
// NavigationEventDispatcher() here would shadow it with an input-less one that never sees
|
|
81
|
+
// a single edge-pan event, enabled or not — so pass the platform owner straight through.
|
|
82
|
+
content()
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Bare UIView hosts have no scene dispatcher, and BackHandler/PredictiveBackHandler error() on
|
|
87
|
+
// a null owner. This detached fallback is also what the kill switch falls back to.
|
|
88
|
+
val fallbackOwner = remember {
|
|
71
89
|
object : NavigationEventDispatcherOwner {
|
|
72
90
|
override val navigationEventDispatcher = NavigationEventDispatcher().apply {
|
|
73
91
|
isEnabled = false
|
|
@@ -76,7 +94,7 @@ actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit
|
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
CompositionLocalProvider(
|
|
79
|
-
LocalCompatNavigationEventDispatcherOwner provides
|
|
97
|
+
LocalCompatNavigationEventDispatcherOwner provides fallbackOwner,
|
|
80
98
|
content = content
|
|
81
99
|
)
|
|
82
100
|
}
|
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
|