@momo-kits/native-kits 0.167.1-duo.2-debug → 0.167.1-duo.3-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.
Files changed (48) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/.claude/settings.local.json +185 -0
  3. package/CLAUDE.md +8 -3
  4. package/compose/build.gradle.kts +15 -1
  5. package/compose/build.gradle.kts.backup +14 -0
  6. package/compose/compose.podspec +1 -1
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +52 -25
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +45 -15
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +34 -8
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +81 -62
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +281 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +31 -4
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +27 -2
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +17 -10
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  20. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  21. package/gradle.properties +1 -1
  22. package/ios/Application/HeaderRight.swift +25 -12
  23. package/ios/Application/Layout/ScreenLayout.swift +196 -0
  24. package/ios/Application/Navigation/BottomTab/BottomTab.swift +30 -9
  25. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +5 -2
  26. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +275 -0
  27. package/ios/Application/Navigation/HeaderUser.swift +4 -1
  28. package/ios/Application/Navigation/NavigationContainer.swift +3 -0
  29. package/ios/Application/Navigation/Overplay/BottomSheet.swift +41 -4
  30. package/ios/Application/Navigation/Overplay/ModalScreen.swift +5 -1
  31. package/ios/Application/Navigation/component/DuoScrollGradient.swift +55 -0
  32. package/ios/Application/Navigation/component/Header.swift +9 -3
  33. package/ios/Application/Navigation/component/HeaderBackground.swift +3 -2
  34. package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
  35. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +61 -9
  36. package/ios/Application/Navigation/component/SideBar.swift +155 -0
  37. package/ios/Application/StackScreen.swift +135 -18
  38. package/ios/Popup/PopupDisplay.swift +84 -68
  39. package/ios/Popup/PopupLayout.swift +45 -0
  40. package/ios/Popup/PopupNotify.swift +43 -32
  41. package/ios/Popup/PopupPromotion.swift +17 -5
  42. package/ios/native-kits.podspec +1 -1
  43. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  44. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +76 -0
  45. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  46. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +170 -0
  47. package/local.properties +8 -0
  48. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"sessionId":"611d4121-c353-42bf-a58b-2fe5f02b02bb","pid":99784,"procStart":"Sun Oct 4 12:12:05 2026","acquiredAt":1791116444172}
@@ -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/CLAUDE.md CHANGED
@@ -47,11 +47,16 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
47
47
  - Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
48
48
  - **iOS swipe-back:** the host owns the only edge-pan recogniser and replays it through `HostBackGestureBridge` (`navigation/HostBackGestureBridge.kt`; the kit framework is never exported, so the host reaches it through its own Koin-provided facade): `checkCanPop` / `canPopInner` / `pop` route the back into the kit stack, `started` / `progressed` / `completed` / `cancelled` seek the NavHost transition, `shouldUseFloatingArrowBack` is set by bottom tabs. `NavigationContainer` attaches itself automatically (`AttachHostBackGesture`, iOS only, detached by identity on dispose) — hosts need no wrapper and no `setNavigator`. `maxApi` is a required parameter of `NavigationContainer` — every container, nested or root, is handed its host bridge explicitly.
49
49
 
50
- ## Screen layout / iPhone Duo (port of RN `useScreenLayout`)
50
+ ## Screen layout / iPhone Duo (port of RN `useScreenLayout`, momo-kits !664)
51
51
 
52
- - `layout/ScreenLayout.kt`: `getScreenLayout()` returns window size, side safe-area insets, `contentWidth`, header geometry and the iPhone Duo `sideBar`. `NavigationContainer` computes it once and provides `LocalScreenLayout`; reactive to fold/unfold and rotation (window size + `WindowInsets`). Metrics (`DuoMetrics`, 72pt iPhone header floor, 20pt status-bar threshold) mirror RN `Layout/utils.ts` — change both together.
52
+ - `layout/ScreenLayout.kt`: `getScreenLayout()` returns window size, raw top/bottom + side insets, `contentWidth`, header geometry, the iPhone Duo `sideBar` and `duoTopInset`. `NavigationContainer` computes it once and provides `LocalScreenLayout`; reactive to fold/unfold and rotation (window size + `WindowInsets`). Metrics (`DuoMetrics`, 72pt iPhone header floor, 20pt status-bar threshold) mirror RN `Layout/utils.ts` — change both together.
53
+ - iPhone-only: everything gates on `isPhoneIdiom()` (`platform/`), never on "iOS", so iPad windows keep the regular layout. Two detectors, as in RN: `isIPhoneDuo` (window aspect < 1.6 or asymmetric side insets) drives layout; `isIPhoneDuoDevice` (short side of screen or window > 440, RN `device.ts`) drives popups, modal and bottom sheet.
53
54
  - Header height is `headerHeight` (not `AppStatusBar + HEADER_HEIGHT`); the bar row is `headerBarHeight`. When the status bar is off the top edge (iPhone landscape, iPhone Duo) `headerStatusBarHeight` is 0 so the title stays centered.
54
- - iPhone Duo with a side strip ≥ 60pt: back + toolkit leave the bar for `HeaderSideBar` (`navigation/component/SideBar.kt`, 48pt glass controls); custom header-right stays in the bar. The column starts below the camera / status-bar cluster: 170pt portrait, 120pt in any landscape (folded too — RN still uses 170 there), below the header when the status bar is on top; UIKit's `statusBarFrame` is useless on Duo. Unlike RN it is clamped 24pt above footer / tab bar, so a long toolkit (shortcut + group) never covers the footer.
55
+ - iPhone Duo with a side strip ≥ 60pt: back + toolkit leave the bar for `HeaderSideBar` (`navigation/component/SideBar.kt`, 48pt glass controls); custom header-right stays in the bar. The column starts at 120pt when the inner display is landscape (height ≥ 600), else 170pt, or below the header when the status bar is on top; UIKit's `statusBarFrame` is useless on Duo. Unlike RN it is clamped 24pt above footer / tab bar / rail.
56
+ - Tab bar on Duo is `bottomtab/DuoTabBar.kt`: a rail in the side strip (anchored 24pt from the bottom, QR below it) or, without a side bar, a floating bar spanning 6/8 grid columns. Both overlay the content; `duoFloatingTabBarInset` pads tab content, footer and FAB.
57
+ - Glass header: on Duo with a side bar, Default/Extended headers without search and with a `ScrollState` let content scroll under the bar (`usesDuoGlassHeader`); the bar fades in a translucent surface + hairline over 16pt (Compose has no backdrop blur — RN's fallback look), the gradient scrolls away / collapses.
58
+ - Overlays on Duo: popups cap at 440pt wide and the room between `duoTopInset` and the bottom inset (body scrolls, actions stay); modal centers inside that room; bottom sheet uses the window height, centers at width ≥ 600 and pads by side insets in landscape.
59
+ - Font scale deliberately uses the short side (`min(w, h)`), unlike RN which reads the window width — landscape must not inflate every font.
55
60
  - `StackScreen` pads content/footer/search/FAB by the side insets and provides an inset-consumed layout to its content; a bottom-tab root stays full width (tab bar + each tab's StackScreen handle insets). Grid columns use `contentWidth`.
56
61
 
57
62
  ## i18n / Localize (port of RN `Localize`)
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.167.1-duo.2-debug"
43
+ version = "0.167.1-duo.3-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -188,3 +188,17 @@ compose {
188
188
  // }
189
189
  // }
190
190
  // }
191
+
192
+ // Xcode 27 rejects pods below iOS 15 (lottie-ios 4.4.3 declares 13.0), and the synthetic Podfile has no post_install hook.
193
+ val syntheticPodsProject = layout.buildDirectory.file("cocoapods/synthetic/ios/Pods/Pods.xcodeproj/project.pbxproj")
194
+ tasks.matching { it.name.startsWith("podInstallSynthetic") }.configureEach {
195
+ doLast {
196
+ val pbxproj = syntheticPodsProject.get().asFile
197
+ if (!pbxproj.exists()) return@doLast
198
+ val target = Regex("IPHONEOS_DEPLOYMENT_TARGET = ([0-9.]+);")
199
+ pbxproj.writeText(pbxproj.readText().replace(target) { match ->
200
+ val major = match.groupValues[1].substringBefore('.').toIntOrNull() ?: 0
201
+ if (major < 15) "IPHONEOS_DEPLOYMENT_TARGET = 15.0;" else match.value
202
+ })
203
+ }
204
+ }
@@ -188,3 +188,17 @@ mavenPublishing {
188
188
  }
189
189
  }
190
190
  }
191
+
192
+ // Xcode 27 rejects pods below iOS 15 (lottie-ios 4.4.3 declares 13.0), and the synthetic Podfile has no post_install hook.
193
+ val syntheticPodsProject = layout.buildDirectory.file("cocoapods/synthetic/ios/Pods/Pods.xcodeproj/project.pbxproj")
194
+ tasks.matching { it.name.startsWith("podInstallSynthetic") }.configureEach {
195
+ doLast {
196
+ val pbxproj = syntheticPodsProject.get().asFile
197
+ if (!pbxproj.exists()) return@doLast
198
+ val target = Regex("IPHONEOS_DEPLOYMENT_TARGET = ([0-9.]+);")
199
+ pbxproj.writeText(pbxproj.readText().replace(target) { match ->
200
+ val major = match.groupValues[1].substringBefore('.').toIntOrNull() ?: 0
201
+ if (major < 15) "IPHONEOS_DEPLOYMENT_TARGET = 15.0;" else match.value
202
+ })
203
+ }
204
+ }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.167.1-beta.4-debug'
3
+ spec.version = '0.167.1-duo.3-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -36,6 +36,8 @@ import androidx.activity.compose.BackHandler as AndroidBackHandler
36
36
 
37
37
  actual fun getPlatformName(): String = "Android"
38
38
 
39
+ actual fun isPhoneIdiom(): Boolean = false
40
+
39
41
  @Composable
40
42
  actual fun getScreenDimensions(): ScreenDimension {
41
43
  val config = LocalConfiguration.current
@@ -4,6 +4,8 @@ import androidx.compose.foundation.background
4
4
  import androidx.compose.foundation.border
5
5
  import androidx.compose.foundation.clickable
6
6
  import androidx.compose.foundation.interaction.MutableInteractionSource
7
+ import androidx.compose.foundation.rememberScrollState
8
+ import androidx.compose.foundation.verticalScroll
7
9
  import androidx.compose.foundation.layout.*
8
10
  import androidx.compose.foundation.lazy.LazyColumn
9
11
  import androidx.compose.foundation.shape.RoundedCornerShape
@@ -20,7 +22,7 @@ import vn.momo.kits.application.LocalLocalize
20
22
  import vn.momo.kits.const.*
21
23
  import vn.momo.kits.modifier.conditional
22
24
  import vn.momo.kits.modifier.setAutomationId
23
- import vn.momo.kits.platform.getScreenDimensions
25
+ import vn.momo.kits.layout.getScreenLayout
24
26
  import kotlin.math.min
25
27
 
26
28
  data class PopupAction(
@@ -34,9 +36,21 @@ enum class PopupActionDirection {
34
36
  AUTO
35
37
  }
36
38
 
37
- // Portrait width in landscape too, so aspect-ratio images don't push popups past the screen height.
39
+ // Width of a system alert on the iPhone Duo inner display, in both orientations (RN Popup/utils.ts).
40
+ private val DUO_POPUP_MAX_WIDTH = 440.dp
41
+
42
+ // Card width cap plus the popup's own side padding; unbounded off iPhone Duo.
43
+ @Composable
44
+ internal fun popupMaxWidth(horizontalPadding: Dp): Dp =
45
+ if (getScreenLayout().isIPhoneDuoDevice) DUO_POPUP_MAX_WIDTH + horizontalPadding * 2 else Dp.Unspecified
46
+
47
+ // Room left for a popup on iPhone Duo, where landscape is too short for it; null elsewhere.
38
48
  @Composable
39
- internal fun popupMaxWidth(): Dp = getScreenDimensions().let { minOf(it.width, it.height) }.dp
49
+ internal fun popupMaxHeight(): Dp? {
50
+ val layout = getScreenLayout()
51
+ if (!layout.isIPhoneDuoDevice) return null
52
+ return layout.height - layout.duoTopInset - layout.insetBottom - Spacing.XL * 2
53
+ }
40
54
 
41
55
  private fun heightForLines(
42
56
  layout: TextLayoutResult,
@@ -129,12 +143,15 @@ fun PopupNotify(
129
143
  callback?.invoke()
130
144
  }
131
145
 
146
+ val maxHeight = popupMaxHeight()
147
+ val bodyScrollState = rememberScrollState()
148
+
132
149
  Box(
133
150
  Modifier.setAutomationId("popup_notify"), contentAlignment = Alignment.TopEnd
134
151
  ) {
135
152
  Column(
136
153
  modifier = Modifier
137
- .widthIn(max = popupMaxWidth())
154
+ .widthIn(max = popupMaxWidth(horizontalPadding = 12.dp))
138
155
  .fillMaxWidth()
139
156
  .padding(horizontal = 12.dp)
140
157
  .clip(RoundedCornerShape(Radius.L))
@@ -145,31 +162,41 @@ fun PopupNotify(
145
162
  .conditional(IsShowBaseLineDebug) {
146
163
  border(1.dp, Colors.blue_03)
147
164
  }
165
+ .conditional(maxHeight != null) {
166
+ heightIn(max = maxHeight ?: Dp.Unspecified)
167
+ }
148
168
  ) {
149
- if(props.image.isNotEmpty()) {
150
- Image(
151
- source = props.image,
152
- modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
153
- options = Options(alignment = Alignment.Center)
154
- )
155
- }
169
+ // scrolls when iPhone Duo landscape is too short; actions stay visible
170
+ Column(
171
+ Modifier.conditional(maxHeight != null) {
172
+ weight(1f, fill = false).verticalScroll(bodyScrollState)
173
+ }
174
+ ) {
175
+ if(props.image.isNotEmpty()) {
176
+ Image(
177
+ source = props.image,
178
+ modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
179
+ options = Options(alignment = Alignment.Center)
180
+ )
181
+ }
156
182
 
157
- Column(modifier = Modifier.padding(Spacing.XL)) {
158
- Text(
159
- style = Typography.headerDefaultBold,
160
- maxLines = 2,
161
- text = props.title,
162
- modifier = Modifier.setAutomationId("title_popup_permission")
163
- )
164
- content(Modifier.padding(top = Spacing.S))
165
- if (props.error.isNotEmpty()) {
183
+ Column(modifier = Modifier.padding(Spacing.XL)) {
166
184
  Text(
167
- text = LocalLocalize.current.translate("errorCode") + props.error,
168
- style = Typography.descriptionXsRegular,
169
- color = AppTheme.current.colors.text.hint,
170
- maxLines = 1,
171
- modifier = Modifier.padding(top = Spacing.S)
185
+ style = Typography.headerDefaultBold,
186
+ maxLines = 2,
187
+ text = props.title,
188
+ modifier = Modifier.setAutomationId("title_popup_permission")
172
189
  )
190
+ content(Modifier.padding(top = Spacing.S))
191
+ if (props.error.isNotEmpty()) {
192
+ Text(
193
+ text = LocalLocalize.current.translate("errorCode") + props.error,
194
+ style = Typography.descriptionXsRegular,
195
+ color = AppTheme.current.colors.text.hint,
196
+ maxLines = 1,
197
+ modifier = Modifier.padding(top = Spacing.S)
198
+ )
199
+ }
173
200
  }
174
201
  }
175
202
 
@@ -22,14 +22,24 @@ import vn.momo.kits.modifier.conditional
22
22
  import vn.momo.kits.modifier.setAutomationId
23
23
  import vn.momo.kits.utils.ifNotNull
24
24
 
25
+ private const val ASPECT_RATIO = 0.72f
26
+ private val CLOSE_ROW_HEIGHT = Spacing.S + 40.dp
27
+
25
28
  @Composable
26
29
  fun PopupPromotion(
27
30
  source: String = "",
28
31
  onPress: (() -> Unit) = {},
29
32
  onClose: (() -> Unit) = {},
30
33
  ) {
34
+ val maxHeight = popupMaxHeight()
35
+ var maxWidth = popupMaxWidth(horizontalPadding = Spacing.M)
36
+ if (maxHeight != null) {
37
+ // fit the portrait image by height when iPhone Duo leaves too little room
38
+ maxWidth = minOf(maxWidth, (maxHeight - CLOSE_ROW_HEIGHT) * ASPECT_RATIO + Spacing.M * 2)
39
+ }
40
+
31
41
  Column(Modifier
32
- .widthIn(max = popupMaxWidth())
42
+ .widthIn(max = maxWidth)
33
43
  .padding(horizontal = Spacing.M)
34
44
  .conditional(IsShowBaseLineDebug) {
35
45
  border(1.dp, Colors.blue_03)
@@ -39,7 +49,7 @@ fun PopupPromotion(
39
49
  Image(
40
50
  source = source,
41
51
  modifier = Modifier.fillMaxWidth()
42
- .aspectRatio(0.72f)
52
+ .aspectRatio(ASPECT_RATIO)
43
53
  .clickable(
44
54
  interactionSource = remember { MutableInteractionSource() },
45
55
  indication = null,
@@ -1,5 +1,7 @@
1
1
  package vn.momo.kits.layout
2
2
 
3
+ import androidx.compose.foundation.layout.WindowInsets
4
+ import androidx.compose.foundation.layout.systemBars
3
5
  import androidx.compose.runtime.Composable
4
6
  import androidx.compose.runtime.Immutable
5
7
  import androidx.compose.runtime.compositionLocalOf
@@ -11,7 +13,8 @@ import androidx.compose.ui.unit.Dp
11
13
  import androidx.compose.ui.unit.dp
12
14
  import vn.momo.kits.const.Spacing
13
15
  import vn.momo.kits.navigation.component.HEADER_HEIGHT
14
- import vn.momo.kits.platform.getPlatformName
16
+ import vn.momo.kits.platform.getScreenDimensions
17
+ import vn.momo.kits.platform.isPhoneIdiom
15
18
  import vn.momo.kits.utils.getAppStatusBarHeight
16
19
  import vn.momo.kits.utils.horizontalSafeAreaInsets
17
20
 
@@ -30,6 +33,9 @@ data class ScreenLayout(
30
33
  val width: Dp,
31
34
  val height: Dp,
32
35
  val isIPhoneDuo: Boolean,
36
+ // Raw top / bottom safe area; overlays and the Duo floating tab bar keep clear of them.
37
+ val insetTop: Dp,
38
+ val insetBottom: Dp,
33
39
  val insetLeft: Dp,
34
40
  val insetRight: Dp,
35
41
  val contentWidth: Dp,
@@ -38,6 +44,11 @@ data class ScreenLayout(
38
44
  val headerStatusBarHeight: Dp,
39
45
  // iPhone Duo moves back and toolkit into a column in the side safe area; null while they stay in the bar.
40
46
  val sideBar: SideBarLayout? = null,
47
+ // Port of RN `device.ts` isIPhoneDuo: the screen stays on the outer display, so Split View still matches.
48
+ val isIPhoneDuoDevice: Boolean = isIPhoneDuo,
49
+ // Portrait iPhone Duo puts the status bar in the side strip, about twice as tall as the strip is wide.
50
+ // Stored so it survives consumeHorizontalInsets().
51
+ val duoTopInset: Dp = insetTop,
41
52
  ) {
42
53
  val headerBarHeight: Dp get() = headerHeight - headerStatusBarHeight
43
54
  }
@@ -50,11 +61,15 @@ internal object DuoMetrics {
50
61
  val itemPitch = 50.dp
51
62
  // Portrait: below the camera/clock cluster in the strip (ends near y 151).
52
63
  val firstItemTop = 170.dp
53
- // Landscape: below the inner display's status cluster (ends near y 98); folded, it clears the camera
54
- // (ends near y 60) and leaves the longest toolkit ~36pt above the footer.
64
+ // Inner display in landscape: below its status cluster (ends near y 98).
55
65
  val firstItemTopLandscape = 120.dp
66
+ // Window height from which the inner display is in use (outer is 466pt tall in landscape, inner 669pt).
67
+ val innerMinHeight = 600.dp
56
68
  }
57
69
 
70
+ // Widest short side of any single-display iPhone.
71
+ private val IPHONE_MAX_SHORT_SIDE = 440.dp
72
+
58
73
  private val IPHONE_HEADER_MIN_HEIGHT = 72.dp
59
74
 
60
75
  // A smaller top inset means the status bar is not on the top edge.
@@ -69,9 +84,9 @@ fun getScreenLayout(): ScreenLayout =
69
84
 
70
85
  @Composable
71
86
  internal fun calculateScreenLayout(topInset: Dp, parent: ScreenLayout? = null): ScreenLayout {
72
- val isIOS = getPlatformName() == "iOS"
87
+ val isPhone = isPhoneIdiom()
73
88
  if (parent != null) {
74
- val (headerHeight, headerStatusBarHeight) = headerGeometry(topInset, isIOS)
89
+ val (headerHeight, headerStatusBarHeight) = headerGeometry(topInset, isPhone)
75
90
  return parent.copy(headerHeight = headerHeight, headerStatusBarHeight = headerStatusBarHeight)
76
91
  }
77
92
 
@@ -81,17 +96,26 @@ internal fun calculateScreenLayout(topInset: Dp, parent: ScreenLayout? = null):
81
96
  val insets = horizontalSafeAreaInsets
82
97
  val leftPx = insets.getLeft(density, layoutDirection)
83
98
  val rightPx = insets.getRight(density, layoutDirection)
99
+ val bottomPx = WindowInsets.systemBars.getBottom(density)
100
+ val screen = getScreenDimensions()
84
101
 
85
- return remember(size, leftPx, rightPx, topInset, density) {
102
+ return remember(size, leftPx, rightPx, bottomPx, topInset, screen, density) {
86
103
  with(density) {
104
+ val width = size.width.toDp()
105
+ val height = size.height.toDp()
87
106
  screenLayoutOf(
88
- width = size.width.toDp(),
89
- height = size.height.toDp(),
107
+ width = width,
108
+ height = height,
90
109
  insetLeft = leftPx.toDp(),
91
110
  insetRight = rightPx.toDp(),
92
111
  topInset = topInset,
93
- isIOS = isIOS,
112
+ bottomInset = bottomPx.toDp(),
113
+ isPhone = isPhone,
94
114
  asymmetricInsets = leftPx != rightPx,
115
+ isDuoDevice = isPhone && maxOf(
116
+ minOf(screen.width, screen.height).dp,
117
+ minOf(width, height),
118
+ ) > IPHONE_MAX_SHORT_SIDE,
95
119
  )
96
120
  }
97
121
  }
@@ -101,9 +125,9 @@ internal fun calculateScreenLayout(topInset: Dp, parent: ScreenLayout? = null):
101
125
  internal fun ScreenLayout.consumeHorizontalInsets(): ScreenLayout =
102
126
  copy(width = contentWidth, insetLeft = 0.dp, insetRight = 0.dp, sideBar = null)
103
127
 
104
- private fun headerGeometry(topInset: Dp, isIOS: Boolean): Pair<Dp, Dp> {
128
+ private fun headerGeometry(topInset: Dp, isPhone: Boolean): Pair<Dp, Dp> {
105
129
  val height = topInset + HEADER_HEIGHT.dp
106
- if (!isIOS) return height to topInset
130
+ if (!isPhone) return height to topInset
107
131
  return maxOf(height, IPHONE_HEADER_MIN_HEIGHT) to if (topInset < STATUS_BAR_MIN_INSET) 0.dp else topInset
108
132
  }
109
133
 
@@ -120,11 +144,13 @@ private fun screenLayoutOf(
120
144
  insetLeft: Dp,
121
145
  insetRight: Dp,
122
146
  topInset: Dp,
123
- isIOS: Boolean,
147
+ bottomInset: Dp,
148
+ isPhone: Boolean,
124
149
  asymmetricInsets: Boolean,
150
+ isDuoDevice: Boolean,
125
151
  ): ScreenLayout {
126
- val (headerHeight, headerStatusBarHeight) = headerGeometry(topInset, isIOS)
127
- val isIPhoneDuo = isIOS && isIPhoneDuoWindow(width, height, asymmetricInsets)
152
+ val (headerHeight, headerStatusBarHeight) = headerGeometry(topInset, isPhone)
153
+ val isIPhoneDuo = isPhone && isIPhoneDuoWindow(width, height, asymmetricInsets)
128
154
 
129
155
  var sideBar: SideBarLayout? = null
130
156
  val sideInset = maxOf(insetLeft, insetRight)
@@ -133,7 +159,7 @@ private fun screenLayoutOf(
133
159
  // UIKit reports no usable status bar frame here (a 2pt top strip), so the spot is inferred like RN
134
160
  val top = when {
135
161
  topInset >= STATUS_BAR_MIN_INSET -> topInset + HEADER_HEIGHT.dp + Spacing.M
136
- width > height -> DuoMetrics.firstItemTopLandscape
162
+ width > height && height >= DuoMetrics.innerMinHeight -> DuoMetrics.firstItemTopLandscape
137
163
  else -> DuoMetrics.firstItemTop
138
164
  }
139
165
  sideBar = SideBarLayout(
@@ -147,11 +173,15 @@ private fun screenLayoutOf(
147
173
  width = width,
148
174
  height = height,
149
175
  isIPhoneDuo = isIPhoneDuo,
176
+ insetTop = topInset,
177
+ insetBottom = bottomInset,
150
178
  insetLeft = insetLeft,
151
179
  insetRight = insetRight,
152
180
  contentWidth = width - insetLeft - insetRight,
153
181
  headerHeight = headerHeight,
154
182
  headerStatusBarHeight = headerStatusBarHeight,
155
183
  sideBar = sideBar,
184
+ isIPhoneDuoDevice = isDuoDevice,
185
+ duoTopInset = maxOf(topInset, if (height > width) sideInset * 2 else 0.dp),
156
186
  )
157
187
  }