@expo/ui 56.0.15 → 56.0.17
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/CHANGELOG.md +47 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +54 -6
- package/android/src/main/java/expo/modules/ui/HostView.kt +0 -2
- package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +65 -0
- package/android/src/main/java/expo/modules/ui/NavigationBarView.kt +95 -0
- package/android/src/main/java/expo/modules/ui/RNHostView.kt +182 -6
- package/android/src/main/java/expo/modules/ui/textfield/BasicTextField.kt +203 -0
- package/android/src/main/java/expo/modules/ui/{TextFieldView.kt → textfield/TextField.kt} +63 -267
- package/android/src/main/java/expo/modules/ui/textfield/TextFieldShared.kt +299 -0
- package/build/State/useNativeState.d.ts +8 -3
- package/build/State/useNativeState.d.ts.map +1 -1
- package/build/community/pager-view/PagerView.android.d.ts.map +1 -1
- package/build/jetpack-compose/NavigationBar/index.d.ts +101 -0
- package/build/jetpack-compose/NavigationBar/index.d.ts.map +1 -0
- package/build/jetpack-compose/TextField/BasicTextField.d.ts +36 -0
- package/build/jetpack-compose/TextField/BasicTextField.d.ts.map +1 -0
- package/build/jetpack-compose/TextField/TextField.d.ts +131 -0
- package/build/jetpack-compose/TextField/TextField.d.ts.map +1 -0
- package/build/jetpack-compose/TextField/index.d.ts +3 -244
- package/build/jetpack-compose/TextField/index.d.ts.map +1 -1
- package/build/jetpack-compose/TextField/shared.d.ts +171 -0
- package/build/jetpack-compose/TextField/shared.d.ts.map +1 -0
- package/build/jetpack-compose/index.d.ts +2 -1
- package/build/jetpack-compose/index.d.ts.map +1 -1
- package/build/jetpack-compose/modifiers/index.d.ts +42 -0
- package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/DisclosureGroup/index.d.ts +11 -2
- package/build/swift-ui/DisclosureGroup/index.d.ts.map +1 -1
- package/build/swift-ui/Image/index.d.ts +7 -1
- package/build/swift-ui/Image/index.d.ts.map +1 -1
- package/build/swift-ui/Label/index.d.ts +5 -0
- package/build/swift-ui/Label/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +100 -4
- package/build/swift-ui/modifiers/index.d.ts.map +1 -1
- package/build/universal/Collapsible/index.android.d.ts +1 -1
- package/build/universal/Collapsible/index.android.d.ts.map +1 -1
- package/build/universal/Collapsible/index.d.ts +1 -1
- package/build/universal/Collapsible/index.d.ts.map +1 -1
- package/build/universal/Collapsible/index.ios.d.ts +1 -1
- package/build/universal/Collapsible/index.ios.d.ts.map +1 -1
- package/build/universal/Collapsible/types.d.ts +5 -0
- package/build/universal/Collapsible/types.d.ts.map +1 -1
- package/build/universal/TextInput/index.android.d.ts.map +1 -1
- package/build/universal/TextInput/types.d.ts +5 -1
- package/build/universal/TextInput/types.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/BottomSheetView.swift +1 -1
- package/ios/DisclosureGroupView.swift +36 -13
- package/ios/ImageView.swift +20 -14
- package/ios/Label.swift +26 -2
- package/ios/Modifiers/ButtonBorderShapeModifier.swift +46 -0
- package/ios/Modifiers/DynamicTypeSizeModifier.swift +56 -0
- package/ios/Modifiers/FontModifier.swift +4 -1
- package/ios/Modifiers/ImageScaleModifier.swift +29 -0
- package/ios/Modifiers/OnGeometryChangeModifier.swift +8 -16
- package/ios/Modifiers/ViewModifierRegistry.swift +89 -8
- package/ios/SecureFieldView.swift +17 -1
- package/ios/TextFieldView.swift +33 -2
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.15/expo.modules.ui-56.0.15-sources.jar → 56.0.17/expo.modules.ui-56.0.17-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.15/expo.modules.ui-56.0.15.module → 56.0.17/expo.modules.ui-56.0.17.module} +22 -22
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.15/expo.modules.ui-56.0.15.pom → 56.0.17/expo.modules.ui-56.0.17.pom} +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.17/expo.modules.ui-56.0.17.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -4
- package/src/State/index.fx.ts +4 -1
- package/src/State/useNativeState.ts +24 -13
- package/src/community/datetime-picker/DateTimePicker.tsx +1 -1
- package/src/community/menu/MenuView.ios.tsx +1 -1
- package/src/community/pager-view/PagerView.android.tsx +16 -2
- package/src/community/pager-view/PagerView.ios.tsx +1 -1
- package/src/community/picker/Picker.ios.tsx +1 -1
- package/src/community/segmented-control/SegmentedControl.ios.tsx +1 -1
- package/src/community/slider/Slider.ios.tsx +1 -1
- package/src/jetpack-compose/NavigationBar/index.tsx +174 -0
- package/src/jetpack-compose/TextField/BasicTextField.tsx +118 -0
- package/src/jetpack-compose/TextField/TextField.tsx +198 -0
- package/src/jetpack-compose/TextField/index.ts +19 -0
- package/src/jetpack-compose/TextField/{index.tsx → shared.ts} +71 -203
- package/src/jetpack-compose/index.ts +7 -0
- package/src/jetpack-compose/modifiers/index.ts +49 -0
- package/src/swift-ui/BottomSheet/index.tsx +1 -1
- package/src/swift-ui/DisclosureGroup/index.tsx +14 -2
- package/src/swift-ui/Image/index.tsx +16 -3
- package/src/swift-ui/Label/index.tsx +8 -1
- package/src/swift-ui/modifiers/index.ts +143 -5
- package/src/universal/Collapsible/index.android.tsx +10 -2
- package/src/universal/Collapsible/index.ios.tsx +17 -3
- package/src/universal/Collapsible/index.tsx +8 -2
- package/src/universal/Collapsible/types.ts +7 -0
- package/src/universal/TextInput/index.android.tsx +26 -33
- package/src/universal/TextInput/types.ts +5 -1
- package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +0 -28
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.15/expo.modules.ui-56.0.15.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,53 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.17 — 2026-06-10
|
|
14
|
+
|
|
15
|
+
### 🛠 Breaking changes
|
|
16
|
+
|
|
17
|
+
- [universal][android] Use `BasicTextField` component instead of Filled Material TextField. ([#46442](https://github.com/expo/expo/pull/46442) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
18
|
+
|
|
19
|
+
### 🎉 New features
|
|
20
|
+
|
|
21
|
+
- [iOS] Added the SwiftUI `imageScale` modifier to scale SF Symbols within a view relative to the surrounding text (`small`, `medium`, `large`). ([#46774](https://github.com/expo/expo/pull/46774) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
22
|
+
- [jetpack-compose] Added `onGloballyPositioned` modifier, which reports a composable's window position and size. ([#46744](https://github.com/expo/expo/pull/46744) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
23
|
+
- [iOS] Extended the SwiftUI `onGeometryChange` modifier to also report the view's global position (`x`/`y`) alongside its size. ([#46744](https://github.com/expo/expo/pull/46744) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
24
|
+
- [iOS] Added the SwiftUI `minimumScaleFactor` modifier to let text shrink down to a given fraction of its size before truncating. ([#46740](https://github.com/expo/expo/pull/46740) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
25
|
+
- [iOS][android] Added React Compiler-friendly `get()` / `set()` accessors to `useNativeState`, as an alternative to reading and writing `.value`. ([#46690](https://github.com/expo/expo/pull/46692) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
26
|
+
- [jetpack-compose] Added `BasicTextField` component. ([#46442](https://github.com/expo/expo/pull/46442) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
27
|
+
- [iOS] Added the SwiftUI `accessibilityInputLabels` modifier to set alternative spoken phrases Voice Control uses to refer to a view (for example "Hang up" for an "End" button). ([#46661](https://github.com/expo/expo/pull/46661) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
28
|
+
|
|
29
|
+
## 56.0.16 — 2026-06-05
|
|
30
|
+
|
|
31
|
+
### 🎉 New features
|
|
32
|
+
|
|
33
|
+
- [iOS] Added the SwiftUI `accessibilityHidden` modifier to hide decorative views from VoiceOver and other assistive technologies during element traversal. ([#46579](https://github.com/expo/expo/pull/46579) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
34
|
+
- [iOS] Added the SwiftUI `accessibilityIdentifier` modifier to set a stable identifier for UI testing tools such as XCUITest. ([#46556](https://github.com/expo/expo/pull/46556) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
35
|
+
- [iOS] Added the SwiftUI `dynamicTypeSize` modifier to set or constrain the Dynamic Type size within a view. Accepts a single size or a `{ min, max }` range, and cascades from `<Host>` to bound how far text scales at the largest accessibility sizes. ([#46540](https://github.com/expo/expo/pull/46540) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
36
|
+
- [iOS] Added `children` prop to `Label` component, allowing custom title views (e.g. a `VStack` with title and subtitle) while preserving native icon spacing and Dynamic Type icon sizing.
|
|
37
|
+
- [iOS] Add `buttonBorderShape` SwiftUI modifier, which reshapes a styled button (for example a `glass` button) while preserving its native press effect. ([#46348](https://github.com/expo/expo/pull/46348) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
38
|
+
- [jetpack-compose] Added `NavigationBar` and `NavigationBarItem` components.
|
|
39
|
+
- [iOS] Added the SwiftUI `listRowSpacing` modifier. ([#46417](https://github.com/expo/expo/pull/46417) by [@qiyangdev](https://github.com/qiyangdev))
|
|
40
|
+
- [iOS] Added support for custom SF Symbols in the SwiftUI `Image` component. ([#46183](https://github.com/expo/expo/pull/46183) by [@cinques](https://github.com/cinques))
|
|
41
|
+
- [swift-ui] Added `<DisclosureGroup.Label>` for custom label style. ([#46288](https://github.com/expo/expo/pull/46288) by [@kudo](https://github.com/kudo))
|
|
42
|
+
- [universal] Added `<Collapsible.labelStyle>` for custom label style. ([#46288](https://github.com/expo/expo/pull/46288) by [@kudo](https://github.com/kudo))
|
|
43
|
+
- [jetpack-compose] Added `dropShadow` and `innerShadow` modifiers. ([#46364](https://github.com/expo/expo/discussions/46364) by [@duyanhv](https://github.com/duyanhv))
|
|
44
|
+
|
|
45
|
+
### 🐛 Bug fixes
|
|
46
|
+
|
|
47
|
+
- [android] Fix React Native touchables (e.g. `Pressable`) on `community/pager-view` pages not responding, or triggering the wrong page's handler, after navigating between pages. ([#46778](https://github.com/expo/expo/pull/46778) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
48
|
+
- [iOS] Fix `font`, `dynamicTypeSize`, and `resizable` modifiers not applying to the SwiftUI `Image`. SF Symbols scale with Dynamic Type when a `font` modifier sets a `textStyle`. ([#46714](https://github.com/expo/expo/pull/46714) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
49
|
+
- [android] Fix React Native `ScrollView` nested scrolling inside `BottomSheet`. ([#46544](https://github.com/expo/expo/pull/46544) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
50
|
+
- [jetpack-compose] Fix layout shift when `Host` with `matchContents` is used inside React Native Screens. ([#46604](https://github.com/expo/expo/pull/46604) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
51
|
+
- [iOS] Fix `PagerView` offsetting `ScrollView` by safe area insets. ([#46637](https://github.com/expo/expo/pull/46637) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
52
|
+
- [iOS] Fix `SegmentedControl` being overlapped by sibling components inside a `ScrollView`, by disabling the `Host` safe area insets. ([#46575](https://github.com/expo/expo/pull/46575) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
53
|
+
- [iOS] Fix the community `DateTimePicker`, `MenuView`, `Picker`, and `Slider` applying safe area insets (including keyboard avoidance) twice, by disabling the `Host` safe area insets (`ignoreSafeArea="all"`). ([#46721](https://github.com/expo/expo/pull/46721) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
54
|
+
- [jetpack-compose] Fix `TextField` jiggling the surrounding content while its label animates on focus (a Material 3 expressive motion spring overshoot). ([#46568](https://github.com/expo/expo/pull/46568) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
55
|
+
- [iOS] Fix `BottomSheet` animating open from the bottom-left corner in `fitToContents` mode. ([#46546](https://github.com/expo/expo/pull/46546) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
56
|
+
- [iOS] Fix `TextField` and `SecureField` worklet `onTextChange` firing more than once per keystroke (when a change triggers reformatting) and on programmatic text updates. ([#46483](https://github.com/expo/expo/pull/46483) by [@nishan](https://github.com/intergalacticspacehighway))
|
|
57
|
+
- [iOS] Fix the `font` modifier dropping Dynamic Type scaling (`relativeTo`) and `weight` on concatenated `Text` runs. The Text-concatenation path now resolves the same `Font` as the view path. ([#46509](https://github.com/expo/expo/pull/46509) by [@ramonclaudio](https://github.com/ramonclaudio))
|
|
58
|
+
- [android] Fix universal `TextInput` ignoring `style` `backgroundColor` / `borderWidth` and not applying `textAlign` to the placeholder. ([#46441](https://github.com/expo/expo/pull/46441) by [@duyanhv](https://github.com/duyanhv))
|
|
59
|
+
|
|
13
60
|
## 56.0.15 — 2026-05-29
|
|
14
61
|
|
|
15
62
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -12,13 +12,13 @@ apply plugin: 'expo-module-gradle-plugin'
|
|
|
12
12
|
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
|
|
13
13
|
|
|
14
14
|
group = 'expo.modules.ui'
|
|
15
|
-
version = '56.0.
|
|
15
|
+
version = '56.0.17'
|
|
16
16
|
|
|
17
17
|
android {
|
|
18
18
|
namespace "expo.modules.ui"
|
|
19
19
|
defaultConfig {
|
|
20
20
|
versionCode 1
|
|
21
|
-
versionName "56.0.
|
|
21
|
+
versionName "56.0.17"
|
|
22
22
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
23
23
|
}
|
|
24
24
|
buildFeatures {
|
|
@@ -36,6 +36,15 @@ import expo.modules.ui.menu.DropdownMenuItemProps
|
|
|
36
36
|
import expo.modules.kotlin.jni.worklets.Worklet
|
|
37
37
|
import expo.modules.ui.state.ObservableState
|
|
38
38
|
import expo.modules.ui.state.WorkletCallback
|
|
39
|
+
import expo.modules.ui.textfield.BasicTextFieldContent
|
|
40
|
+
import expo.modules.ui.textfield.BasicTextFieldProps
|
|
41
|
+
import expo.modules.ui.textfield.InnerTextFieldView
|
|
42
|
+
import expo.modules.ui.textfield.KeyboardActionEvent
|
|
43
|
+
import expo.modules.ui.textfield.PlaceholderView
|
|
44
|
+
import expo.modules.ui.textfield.TextFieldContent
|
|
45
|
+
import expo.modules.ui.textfield.TextFieldProps
|
|
46
|
+
import expo.modules.ui.textfield.TextFieldSelectionPayload
|
|
47
|
+
import expo.modules.ui.textfield.TextFieldValuePayload
|
|
39
48
|
import expo.modules.ui.menu.ExposedDropdownMenuBoxContent
|
|
40
49
|
import expo.modules.ui.menu.ExposedDropdownMenuBoxProps
|
|
41
50
|
import expo.modules.ui.menu.ExposedDropdownMenuContent
|
|
@@ -109,8 +118,7 @@ class ExpoUIModule : Module() {
|
|
|
109
118
|
//region Views use expo-modules-core DSL for uncommon features
|
|
110
119
|
|
|
111
120
|
View(HostView::class) {
|
|
112
|
-
|
|
113
|
-
Events("onLayoutContent", "onExpoUISyncFlush")
|
|
121
|
+
Events("onLayoutContent")
|
|
114
122
|
|
|
115
123
|
OnViewDidUpdateProps { view ->
|
|
116
124
|
view.onViewDidUpdateProps()
|
|
@@ -147,14 +155,13 @@ class ExpoUIModule : Module() {
|
|
|
147
155
|
colorScheme.toTokenMap()
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
View(RNHostView::class)
|
|
151
|
-
// See ShadowNodeSyncFlush.kt for why this internal phantom event is needed.
|
|
152
|
-
Events("onExpoUISyncFlush")
|
|
153
|
-
}
|
|
158
|
+
View(RNHostView::class)
|
|
154
159
|
|
|
155
160
|
View(SlotView::class) {
|
|
156
161
|
Events("onSlotEvent")
|
|
157
162
|
}
|
|
163
|
+
View(InnerTextFieldView::class)
|
|
164
|
+
View(PlaceholderView::class)
|
|
158
165
|
View(IconView::class)
|
|
159
166
|
View(LazyColumnView::class)
|
|
160
167
|
View(LazyRowView::class)
|
|
@@ -610,6 +617,20 @@ class ExpoUIModule : Module() {
|
|
|
610
617
|
}
|
|
611
618
|
}
|
|
612
619
|
|
|
620
|
+
ExpoUIView<NavigationBarProps>("NavigationBarView") {
|
|
621
|
+
Content { props ->
|
|
622
|
+
NavigationBarContent(props)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
ExpoUIView<NavigationBarItemProps>("NavigationBarItemView") {
|
|
627
|
+
val onButtonPressed by Event<Unit>()
|
|
628
|
+
|
|
629
|
+
Content { props ->
|
|
630
|
+
NavigationBarItemContent(props) { onButtonPressed(Unit) }
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
613
634
|
ExpoUIView<SpacerProps>("SpacerView") {
|
|
614
635
|
Content { props ->
|
|
615
636
|
SpacerContent(props)
|
|
@@ -683,6 +704,33 @@ class ExpoUIModule : Module() {
|
|
|
683
704
|
}
|
|
684
705
|
}
|
|
685
706
|
|
|
707
|
+
ExpoUIView<BasicTextFieldProps>("BasicTextFieldView") {
|
|
708
|
+
val setText by AsyncFunction<String>()
|
|
709
|
+
val setSelection by AsyncFunction<Int, Int>()
|
|
710
|
+
val clear by AsyncFunction()
|
|
711
|
+
val focus by AsyncFunction()
|
|
712
|
+
val blur by AsyncFunction()
|
|
713
|
+
val onValueChange by Event<TextFieldValuePayload>()
|
|
714
|
+
val onFocusChanged by Event<GenericEventPayload1<Boolean>>()
|
|
715
|
+
val onKeyboardAction by Event<KeyboardActionEvent>()
|
|
716
|
+
val onSelectionChange by Event<TextFieldSelectionPayload>()
|
|
717
|
+
|
|
718
|
+
Content { props ->
|
|
719
|
+
BasicTextFieldContent(
|
|
720
|
+
props,
|
|
721
|
+
setText,
|
|
722
|
+
setSelection,
|
|
723
|
+
clear,
|
|
724
|
+
focus,
|
|
725
|
+
blur,
|
|
726
|
+
onValueChanged = { onValueChange(it) },
|
|
727
|
+
onFocusChange = { onFocusChanged(it) },
|
|
728
|
+
onKeyboardActionTriggered = { onKeyboardAction(it) },
|
|
729
|
+
onSelectionChanged = { onSelectionChange(it) }
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
686
734
|
ExpoUIView<RadioButtonProps>("RadioButtonView") {
|
|
687
735
|
val onButtonPressed by Event<Unit>()
|
|
688
736
|
|
|
@@ -193,7 +193,6 @@ internal class HostView(context: Context, appContext: AppContext) :
|
|
|
193
193
|
if (constraints.maxWidth == 0) widthDp else Double.NaN,
|
|
194
194
|
if (constraints.maxHeight == 0) heightDp else Double.NaN
|
|
195
195
|
)
|
|
196
|
-
flushPendingStateUpdates()
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
|
|
@@ -222,7 +221,6 @@ internal class HostView(context: Context, appContext: AppContext) :
|
|
|
222
221
|
val styleWidth = if (matchContentsHorizontal == true && width > 0) width else null
|
|
223
222
|
val styleHeight = if (matchContentsVertical == true && height > 0) height else null
|
|
224
223
|
shadowNodeProxy.setStyleSize(styleWidth?.toDouble(), styleHeight?.toDouble())
|
|
225
|
-
flushPendingStateUpdates()
|
|
226
224
|
}
|
|
227
225
|
|
|
228
226
|
onLayoutContent(LayoutContentEvent(width.toDouble(), height.toDouble()))
|
|
@@ -47,6 +47,8 @@ import androidx.compose.ui.Modifier
|
|
|
47
47
|
import androidx.compose.ui.draw.alpha
|
|
48
48
|
import androidx.compose.ui.draw.blur
|
|
49
49
|
import androidx.compose.ui.draw.clip
|
|
50
|
+
import androidx.compose.ui.draw.dropShadow
|
|
51
|
+
import androidx.compose.ui.draw.innerShadow
|
|
50
52
|
import androidx.compose.ui.draw.rotate
|
|
51
53
|
import androidx.compose.ui.draw.shadow
|
|
52
54
|
import androidx.compose.ui.graphics.CompositingStrategy
|
|
@@ -54,12 +56,16 @@ import androidx.compose.ui.graphics.RectangleShape
|
|
|
54
56
|
import androidx.compose.ui.graphics.Shape
|
|
55
57
|
import androidx.compose.ui.graphics.TransformOrigin
|
|
56
58
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
59
|
+
import androidx.compose.ui.graphics.shadow.Shadow
|
|
60
|
+
import androidx.compose.ui.layout.onGloballyPositioned
|
|
57
61
|
import androidx.compose.ui.layout.onSizeChanged
|
|
58
62
|
import androidx.compose.ui.layout.onVisibilityChanged
|
|
63
|
+
import androidx.compose.ui.layout.positionInWindow
|
|
59
64
|
import androidx.compose.ui.platform.LocalDensity
|
|
60
65
|
import androidx.compose.ui.semantics.Role
|
|
61
66
|
import androidx.compose.ui.semantics.contentType
|
|
62
67
|
import androidx.compose.ui.semantics.semantics
|
|
68
|
+
import androidx.compose.ui.unit.DpOffset
|
|
63
69
|
import androidx.compose.ui.unit.dp
|
|
64
70
|
import androidx.compose.ui.zIndex
|
|
65
71
|
import expo.modules.kotlin.AppContext
|
|
@@ -181,6 +187,17 @@ internal data class ShadowParams(
|
|
|
181
187
|
@Field val elevation: Int = 0
|
|
182
188
|
) : Record
|
|
183
189
|
|
|
190
|
+
@OptimizedRecord
|
|
191
|
+
internal data class ShadowGeometryParams(
|
|
192
|
+
@Field val shape: BuiltinShapeRecord? = null,
|
|
193
|
+
@Field val radius: Float = 0f,
|
|
194
|
+
@Field val spread: Float = 0f,
|
|
195
|
+
@Field val color: Color? = null,
|
|
196
|
+
@Field val offsetX: Float = 0f,
|
|
197
|
+
@Field val offsetY: Float = 0f,
|
|
198
|
+
@Field val alpha: Float = 1f
|
|
199
|
+
) : Record
|
|
200
|
+
|
|
184
201
|
@OptimizedRecord
|
|
185
202
|
internal data class AlphaParams(
|
|
186
203
|
@Field val alpha: Float = 1.0f
|
|
@@ -502,6 +519,36 @@ object ModifierRegistry {
|
|
|
502
519
|
Modifier.shadow(params.elevation.dp)
|
|
503
520
|
}
|
|
504
521
|
|
|
522
|
+
register("dropShadow") { map, _, _, _ ->
|
|
523
|
+
val params = recordFromMap<ShadowGeometryParams>(map)
|
|
524
|
+
val shape = params.shape?.let { resolveShape(it) } ?: RectangleShape
|
|
525
|
+
Modifier.dropShadow(
|
|
526
|
+
shape,
|
|
527
|
+
Shadow(
|
|
528
|
+
radius = params.radius.dp,
|
|
529
|
+
spread = params.spread.dp,
|
|
530
|
+
color = params.color.composeOrNull ?: androidx.compose.ui.graphics.Color.Black,
|
|
531
|
+
offset = DpOffset(params.offsetX.dp, params.offsetY.dp),
|
|
532
|
+
alpha = params.alpha
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
register("innerShadow") { map, _, _, _ ->
|
|
538
|
+
val params = recordFromMap<ShadowGeometryParams>(map)
|
|
539
|
+
val shape = params.shape?.let { resolveShape(it) } ?: RectangleShape
|
|
540
|
+
Modifier.innerShadow(
|
|
541
|
+
shape,
|
|
542
|
+
Shadow(
|
|
543
|
+
radius = params.radius.dp,
|
|
544
|
+
spread = params.spread.dp,
|
|
545
|
+
color = params.color.composeOrNull ?: androidx.compose.ui.graphics.Color.Black,
|
|
546
|
+
offset = DpOffset(params.offsetX.dp, params.offsetY.dp),
|
|
547
|
+
alpha = params.alpha
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
|
|
505
552
|
register("alpha") { map, _, _, _ ->
|
|
506
553
|
val params = recordFromMap<AlphaParams>(map)
|
|
507
554
|
Modifier.alpha(params.alpha)
|
|
@@ -647,6 +694,24 @@ object ModifierRegistry {
|
|
|
647
694
|
}
|
|
648
695
|
}
|
|
649
696
|
|
|
697
|
+
register("onGloballyPositioned") { _, _, _, eventDispatcher ->
|
|
698
|
+
val density = LocalDensity.current
|
|
699
|
+
Modifier.onGloballyPositioned { coordinates ->
|
|
700
|
+
val position = coordinates.positionInWindow()
|
|
701
|
+
with(density) {
|
|
702
|
+
eventDispatcher(
|
|
703
|
+
"onGloballyPositioned",
|
|
704
|
+
mapOf(
|
|
705
|
+
"x" to position.x.toDp().value,
|
|
706
|
+
"y" to position.y.toDp().value,
|
|
707
|
+
"width" to coordinates.size.width.toDp().value,
|
|
708
|
+
"height" to coordinates.size.height.toDp().value
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
650
715
|
register("clickable") { map, _, _, eventDispatcher ->
|
|
651
716
|
val params = recordFromMap<ClickableParams>(map)
|
|
652
717
|
if (params.indication) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package expo.modules.ui
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import androidx.compose.material3.NavigationBar
|
|
5
|
+
import androidx.compose.material3.NavigationBarDefaults
|
|
6
|
+
import androidx.compose.material3.NavigationBarItem
|
|
7
|
+
import androidx.compose.material3.NavigationBarItemDefaults
|
|
8
|
+
import androidx.compose.material3.contentColorFor
|
|
9
|
+
import androidx.compose.runtime.Composable
|
|
10
|
+
import androidx.compose.ui.graphics.Color as ComposeColor
|
|
11
|
+
import androidx.compose.ui.unit.dp
|
|
12
|
+
import expo.modules.kotlin.records.Field
|
|
13
|
+
import expo.modules.kotlin.records.Record
|
|
14
|
+
import expo.modules.kotlin.types.OptimizedRecord
|
|
15
|
+
import expo.modules.kotlin.views.ComposeProps
|
|
16
|
+
import expo.modules.kotlin.views.FunctionalComposableScope
|
|
17
|
+
import expo.modules.kotlin.views.OptimizedComposeProps
|
|
18
|
+
|
|
19
|
+
@OptimizedRecord
|
|
20
|
+
data class NavigationBarItemColors(
|
|
21
|
+
@Field val selectedIconColor: Color? = null,
|
|
22
|
+
@Field val selectedTextColor: Color? = null,
|
|
23
|
+
@Field val selectedIndicatorColor: Color? = null,
|
|
24
|
+
@Field val unselectedIconColor: Color? = null,
|
|
25
|
+
@Field val unselectedTextColor: Color? = null,
|
|
26
|
+
@Field val disabledIconColor: Color? = null,
|
|
27
|
+
@Field val disabledTextColor: Color? = null
|
|
28
|
+
) : Record
|
|
29
|
+
|
|
30
|
+
@OptimizedComposeProps
|
|
31
|
+
data class NavigationBarProps(
|
|
32
|
+
val containerColor: Color? = null,
|
|
33
|
+
val contentColor: Color? = null,
|
|
34
|
+
val tonalElevation: Float? = null,
|
|
35
|
+
val modifiers: ModifierList = emptyList()
|
|
36
|
+
) : ComposeProps
|
|
37
|
+
|
|
38
|
+
@OptimizedComposeProps
|
|
39
|
+
data class NavigationBarItemProps(
|
|
40
|
+
val selected: Boolean = false,
|
|
41
|
+
val enabled: Boolean = true,
|
|
42
|
+
val alwaysShowLabel: Boolean = true,
|
|
43
|
+
val colors: NavigationBarItemColors = NavigationBarItemColors(),
|
|
44
|
+
val modifiers: ModifierList = emptyList()
|
|
45
|
+
) : ComposeProps
|
|
46
|
+
|
|
47
|
+
@Composable
|
|
48
|
+
fun FunctionalComposableScope.NavigationBarContent(props: NavigationBarProps) {
|
|
49
|
+
val resolvedContainerColor = props.containerColor.composeOrNull ?: NavigationBarDefaults.containerColor
|
|
50
|
+
val modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
|
|
51
|
+
|
|
52
|
+
NavigationBar(
|
|
53
|
+
modifier = modifier,
|
|
54
|
+
containerColor = resolvedContainerColor,
|
|
55
|
+
contentColor = props.contentColor.composeOrNull ?: contentColorFor(resolvedContainerColor),
|
|
56
|
+
tonalElevation = props.tonalElevation?.dp ?: NavigationBarDefaults.Elevation
|
|
57
|
+
) {
|
|
58
|
+
Children(UIComposableScope(rowScope = this@NavigationBar), filter = { !isSlotView(it) })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Composable
|
|
63
|
+
fun FunctionalComposableScope.NavigationBarItemContent(
|
|
64
|
+
props: NavigationBarItemProps,
|
|
65
|
+
onClick: () -> Unit
|
|
66
|
+
) {
|
|
67
|
+
val iconSlotView = findChildSlotView(view, "icon")
|
|
68
|
+
val labelSlotView = findChildSlotView(view, "label")
|
|
69
|
+
val modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
|
|
70
|
+
val label: (@Composable () -> Unit)? = labelSlotView?.let { slot -> { slot.renderSlot() } }
|
|
71
|
+
val rowScope = composableScope.rowScope ?: return
|
|
72
|
+
|
|
73
|
+
with(rowScope) {
|
|
74
|
+
NavigationBarItem(
|
|
75
|
+
selected = props.selected,
|
|
76
|
+
onClick = onClick,
|
|
77
|
+
icon = {
|
|
78
|
+
iconSlotView?.renderSlot()
|
|
79
|
+
},
|
|
80
|
+
modifier = modifier,
|
|
81
|
+
enabled = props.enabled,
|
|
82
|
+
label = label,
|
|
83
|
+
alwaysShowLabel = props.alwaysShowLabel,
|
|
84
|
+
colors = NavigationBarItemDefaults.colors(
|
|
85
|
+
selectedIconColor = props.colors.selectedIconColor.composeOrNull ?: ComposeColor.Unspecified,
|
|
86
|
+
selectedTextColor = props.colors.selectedTextColor.composeOrNull ?: ComposeColor.Unspecified,
|
|
87
|
+
indicatorColor = props.colors.selectedIndicatorColor.composeOrNull ?: ComposeColor.Unspecified,
|
|
88
|
+
unselectedIconColor = props.colors.unselectedIconColor.composeOrNull ?: ComposeColor.Unspecified,
|
|
89
|
+
unselectedTextColor = props.colors.unselectedTextColor.composeOrNull ?: ComposeColor.Unspecified,
|
|
90
|
+
disabledIconColor = props.colors.disabledIconColor.composeOrNull ?: ComposeColor.Unspecified,
|
|
91
|
+
disabledTextColor = props.colors.disabledTextColor.composeOrNull ?: ComposeColor.Unspecified
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -19,6 +19,9 @@ import androidx.compose.ui.layout.onSizeChanged
|
|
|
19
19
|
import androidx.compose.ui.platform.LocalDensity
|
|
20
20
|
import androidx.compose.ui.unit.IntSize
|
|
21
21
|
import androidx.compose.ui.viewinterop.AndroidView
|
|
22
|
+
import androidx.core.view.NestedScrollingChild3
|
|
23
|
+
import androidx.core.view.NestedScrollingChildHelper
|
|
24
|
+
import androidx.core.view.ViewCompat
|
|
22
25
|
import com.facebook.react.bridge.ReactContext
|
|
23
26
|
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
24
27
|
import com.facebook.react.config.ReactFeatureFlags
|
|
@@ -158,7 +161,6 @@ internal class RNHostView(context: Context, appContext: AppContext) :
|
|
|
158
161
|
size.width.toDp().value.toDouble(),
|
|
159
162
|
size.height.toDp().value.toDouble()
|
|
160
163
|
)
|
|
161
|
-
flushPendingStateUpdates()
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
}
|
|
@@ -168,13 +170,30 @@ internal class RNHostView(context: Context, appContext: AppContext) :
|
|
|
168
170
|
* A thin FrameLayout that intercepts touch events and dispatches them to JS via
|
|
169
171
|
* JSTouchDispatcher/JSPointerDispatcher, replicating the pattern from React Native's
|
|
170
172
|
* DialogRootViewGroup in ReactModalHostView.
|
|
173
|
+
* Implements NestedScrollingChild3 to forward scroll events up to the parent Compose view, because Compose only listens for NestedScrollingChild3 nested-scroll events.
|
|
171
174
|
*/
|
|
172
175
|
private class TouchDispatchingRootViewGroup(
|
|
173
176
|
context: Context
|
|
174
|
-
) : FrameLayout(context), RootView {
|
|
177
|
+
) : FrameLayout(context), RootView, NestedScrollingChild3 {
|
|
175
178
|
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
176
179
|
private var jsPointerDispatcher: JSPointerDispatcher? = null
|
|
177
180
|
|
|
181
|
+
// The "child face": this helper does the real work of finding the nearest scrolling-aware
|
|
182
|
+
// ancestor (Compose, here) and forwarding our scroll offers to it.
|
|
183
|
+
private val childHelper = NestedScrollingChildHelper(this)
|
|
184
|
+
|
|
185
|
+
// How far the sheet has slid this view on-screen since the gesture began. Used to keep the
|
|
186
|
+
// FlatList's touch coordinates coherent while the view moves under the finger.
|
|
187
|
+
private val gestureStartLocation = IntArray(2)
|
|
188
|
+
private val currentLocation = IntArray(2)
|
|
189
|
+
private var trackingGestureOffset = false
|
|
190
|
+
|
|
191
|
+
// True if the sheet consumed scroll on the most recent drag frame; drives the settle decision.
|
|
192
|
+
private var sheetMovingOnLastDragFrame = false
|
|
193
|
+
|
|
194
|
+
// True once a fling was dispatched this gesture, so the gentle-release settle doesn't double-fire.
|
|
195
|
+
private var flingHandledThisGesture = false
|
|
196
|
+
|
|
178
197
|
var eventDispatcher: EventDispatcher? = null
|
|
179
198
|
|
|
180
199
|
private val reactContext: ThemedReactContext
|
|
@@ -184,6 +203,7 @@ private class TouchDispatchingRootViewGroup(
|
|
|
184
203
|
if (ReactFeatureFlags.dispatchPointerEvents) {
|
|
185
204
|
jsPointerDispatcher = JSPointerDispatcher(this)
|
|
186
205
|
}
|
|
206
|
+
childHelper.isNestedScrollingEnabled = true
|
|
187
207
|
}
|
|
188
208
|
|
|
189
209
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
@@ -200,6 +220,38 @@ private class TouchDispatchingRootViewGroup(
|
|
|
200
220
|
// and we must not override those values.
|
|
201
221
|
}
|
|
202
222
|
|
|
223
|
+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
224
|
+
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
225
|
+
// dispatchTouchEvent is the true start of every gesture, so reset all per-gesture state here.
|
|
226
|
+
getLocationInWindow(gestureStartLocation)
|
|
227
|
+
trackingGestureOffset = true
|
|
228
|
+
sheetMovingOnLastDragFrame = false
|
|
229
|
+
flingHandledThisGesture = false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// While a nested scroll is in flight the sheet may be sliding this whole view up/down. Re-express
|
|
233
|
+
// every event as if the view hadn't moved, so the FlatList's own scroll tracking stays coherent.
|
|
234
|
+
val handled = if (trackingGestureOffset && hasNestedScrollingParent()) {
|
|
235
|
+
getLocationInWindow(currentLocation)
|
|
236
|
+
val dy = currentLocation[1] - gestureStartLocation[1]
|
|
237
|
+
if (dy != 0) {
|
|
238
|
+
ev.offsetLocation(0f, dy.toFloat())
|
|
239
|
+
val result = super.dispatchTouchEvent(ev)
|
|
240
|
+
ev.offsetLocation(0f, -dy.toFloat())
|
|
241
|
+
result
|
|
242
|
+
} else {
|
|
243
|
+
super.dispatchTouchEvent(ev)
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
super.dispatchTouchEvent(ev)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (ev.actionMasked == MotionEvent.ACTION_UP || ev.actionMasked == MotionEvent.ACTION_CANCEL) {
|
|
250
|
+
trackingGestureOffset = false
|
|
251
|
+
}
|
|
252
|
+
return handled
|
|
253
|
+
}
|
|
254
|
+
|
|
203
255
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
204
256
|
eventDispatcher?.let { dispatcher ->
|
|
205
257
|
jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
@@ -241,12 +293,136 @@ private class TouchDispatchingRootViewGroup(
|
|
|
241
293
|
jsPointerDispatcher?.onChildEndedNativeGesture()
|
|
242
294
|
}
|
|
243
295
|
|
|
296
|
+
override fun handleException(t: Throwable) {
|
|
297
|
+
reactContext.reactApplicationContext.handleException(RuntimeException(t))
|
|
298
|
+
}
|
|
299
|
+
|
|
244
300
|
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
|
245
|
-
//
|
|
246
|
-
//
|
|
301
|
+
// Forward the request up so Compose learns the list claimed the gesture and its own sheet-drag
|
|
302
|
+
// yields. But don't call super: setting our own FLAG_DISALLOW_INTERCEPT would skip
|
|
303
|
+
// onInterceptTouchEvent, which must keep firing to dispatch touches to JS (the reason #43716
|
|
304
|
+
// added this override).
|
|
305
|
+
parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
|
|
247
306
|
}
|
|
248
307
|
|
|
249
|
-
|
|
250
|
-
|
|
308
|
+
// --- Parent face: catch the FlatList's scroll offers and relay them up via the child face. ---
|
|
309
|
+
override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean =
|
|
310
|
+
axes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
|
|
311
|
+
|
|
312
|
+
override fun onNestedScrollAccepted(child: View, target: View, axes: Int) {
|
|
313
|
+
super.onNestedScrollAccepted(child, target, axes)
|
|
314
|
+
startNestedScroll(axes)
|
|
251
315
|
}
|
|
316
|
+
|
|
317
|
+
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
|
|
318
|
+
// Expand path: offer the delta to the sheet (above us) before the list scrolls with the rest.
|
|
319
|
+
dispatchNestedPreScroll(dx, dy, consumed, null)
|
|
320
|
+
// Did the sheet move this frame? consumed[1] is what it consumed in y axis.
|
|
321
|
+
sheetMovingOnLastDragFrame = consumed[1] != 0
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) {
|
|
325
|
+
// Collapse path: after the list scrolled what it could, hand the leftover up to the sheet.
|
|
326
|
+
// Use the (…, type, consumed) variant so we can read how much the sheet ate (consumed[1]).
|
|
327
|
+
val consumed = IntArray(2)
|
|
328
|
+
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, ViewCompat.TYPE_TOUCH, consumed)
|
|
329
|
+
if (consumed[1] != 0) sheetMovingOnLastDragFrame = true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
|
|
333
|
+
// A real fling is being dispatched, so its settle is already in motion; note that so the
|
|
334
|
+
// gentle-release fallback in onStopNestedScroll doesn't add a second, redundant settle.
|
|
335
|
+
flingHandledThisGesture = true
|
|
336
|
+
val composeConsumed = dispatchNestedPreFling(velocityX, velocityY)
|
|
337
|
+
// RN's ScrollView wants a synchronous yes/no, but Compose's pre-fling is async (it returns false
|
|
338
|
+
// now and settles later). So decide here: if the sheet was mid-move and this is an up-flick
|
|
339
|
+
// (velocityY > 0), swallow the list's fling so the sheet finishes expanding. A down-flick passes
|
|
340
|
+
// through so it can still settle/collapse.
|
|
341
|
+
return composeConsumed || (sheetMovingOnLastDragFrame && velocityY > 0)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
override fun onNestedFling(target: View, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean =
|
|
345
|
+
// Post-fling: hand the leftover flick up so the sheet can fling to its next anchor.
|
|
346
|
+
dispatchNestedFling(velocityX, velocityY, consumed)
|
|
347
|
+
|
|
348
|
+
override fun onStopNestedScroll(target: View) {
|
|
349
|
+
// Must run BEFORE stopNestedScroll() so Compose is still our parent and can receive the fling.
|
|
350
|
+
settleSheetIfNoFling()
|
|
351
|
+
super.onStopNestedScroll(target)
|
|
352
|
+
stopNestedScroll()
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// A slow (sub-threshold) release never flings, so the sheet would hang where it was dragged. If the
|
|
356
|
+
// sheet moved this gesture and no real fling settled it, dispatch a zero-velocity fling so the
|
|
357
|
+
// holder snaps it to the nearest anchor.
|
|
358
|
+
private fun settleSheetIfNoFling() {
|
|
359
|
+
if (sheetMovingOnLastDragFrame && !flingHandledThisGesture) {
|
|
360
|
+
flingHandledThisGesture = true
|
|
361
|
+
dispatchNestedFling(0f, 0f, false)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// --- Child face: the wrapper's upstream voice. The parent hooks above relay through these. ---
|
|
366
|
+
// So we listen to ViewGroup nested scroll methods and call below methods from them,
|
|
367
|
+
// which essentially converts regular Nested scrolling methods to NestedScrollingChild3.
|
|
368
|
+
override fun setNestedScrollingEnabled(enabled: Boolean) {
|
|
369
|
+
childHelper.isNestedScrollingEnabled = enabled
|
|
370
|
+
}
|
|
371
|
+
override fun isNestedScrollingEnabled(): Boolean = childHelper.isNestedScrollingEnabled
|
|
372
|
+
override fun startNestedScroll(axes: Int): Boolean = childHelper.startNestedScroll(axes)
|
|
373
|
+
override fun startNestedScroll(axes: Int, type: Int): Boolean = childHelper.startNestedScroll(axes, type)
|
|
374
|
+
override fun stopNestedScroll() = childHelper.stopNestedScroll()
|
|
375
|
+
override fun stopNestedScroll(type: Int) = childHelper.stopNestedScroll(type)
|
|
376
|
+
override fun hasNestedScrollingParent(): Boolean = childHelper.hasNestedScrollingParent()
|
|
377
|
+
override fun hasNestedScrollingParent(type: Int): Boolean = childHelper.hasNestedScrollingParent(type)
|
|
378
|
+
|
|
379
|
+
override fun dispatchNestedScroll(
|
|
380
|
+
dxConsumed: Int,
|
|
381
|
+
dyConsumed: Int,
|
|
382
|
+
dxUnconsumed: Int,
|
|
383
|
+
dyUnconsumed: Int,
|
|
384
|
+
offsetInWindow: IntArray?
|
|
385
|
+
): Boolean = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow)
|
|
386
|
+
|
|
387
|
+
override fun dispatchNestedScroll(
|
|
388
|
+
dxConsumed: Int,
|
|
389
|
+
dyConsumed: Int,
|
|
390
|
+
dxUnconsumed: Int,
|
|
391
|
+
dyUnconsumed: Int,
|
|
392
|
+
offsetInWindow: IntArray?,
|
|
393
|
+
type: Int
|
|
394
|
+
): Boolean = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type)
|
|
395
|
+
|
|
396
|
+
override fun dispatchNestedScroll(
|
|
397
|
+
dxConsumed: Int,
|
|
398
|
+
dyConsumed: Int,
|
|
399
|
+
dxUnconsumed: Int,
|
|
400
|
+
dyUnconsumed: Int,
|
|
401
|
+
offsetInWindow: IntArray?,
|
|
402
|
+
type: Int,
|
|
403
|
+
consumed: IntArray
|
|
404
|
+
) {
|
|
405
|
+
childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
override fun dispatchNestedPreScroll(
|
|
409
|
+
dx: Int,
|
|
410
|
+
dy: Int,
|
|
411
|
+
consumed: IntArray?,
|
|
412
|
+
offsetInWindow: IntArray?
|
|
413
|
+
): Boolean = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
|
|
414
|
+
|
|
415
|
+
override fun dispatchNestedPreScroll(
|
|
416
|
+
dx: Int,
|
|
417
|
+
dy: Int,
|
|
418
|
+
consumed: IntArray?,
|
|
419
|
+
offsetInWindow: IntArray?,
|
|
420
|
+
type: Int
|
|
421
|
+
): Boolean = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
|
|
422
|
+
|
|
423
|
+
override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean =
|
|
424
|
+
childHelper.dispatchNestedFling(velocityX, velocityY, consumed)
|
|
425
|
+
|
|
426
|
+
override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean =
|
|
427
|
+
childHelper.dispatchNestedPreFling(velocityX, velocityY)
|
|
252
428
|
}
|