react-native-smooth-clip-view 0.1.0 → 0.2.0
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/README.md +164 -43
- package/SmoothClipView.podspec +8 -2
- package/android/CMakeLists.txt +39 -0
- package/android/build.gradle +69 -2
- package/android/proguard-rules.pro +6 -0
- package/android/src/main/cpp/SmoothClipAndroid.h +59 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +428 -0
- package/android/src/main/cpp/SmoothClipRegistry.cpp +913 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +25 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +55 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +128 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +164 -30
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +107 -10
- package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +18 -2
- package/cpp/SmoothClipRegistry.h +93 -0
- package/cpp/SmoothClipSharedGeometry.h +50 -0
- package/ios/SmoothClipModuleProvider.h +9 -0
- package/ios/SmoothClipModuleProvider.mm +16 -0
- package/ios/SmoothClipRegistry.mm +717 -0
- package/ios/SmoothClipTurboModule.cpp +290 -0
- package/ios/SmoothClipTurboModule.h +132 -0
- package/ios/SmoothClipView.mm +976 -85
- package/ios/SmoothClipViewRegistry.h +27 -0
- package/lib/module/NativeSmoothClipModule.js +5 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -0
- package/lib/module/SmoothClipView.android.js +4 -0
- package/lib/module/SmoothClipView.android.js.map +1 -0
- package/lib/module/SmoothClipView.ios.js +34 -0
- package/lib/module/SmoothClipView.ios.js.map +1 -0
- package/lib/module/SmoothClipView.js +26 -12
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipView.web.js +28 -12
- package/lib/module/SmoothClipView.web.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +14 -1
- package/lib/module/driverState.js +102 -0
- package/lib/module/driverState.js.map +1 -0
- package/lib/module/driverTypes.js +4 -0
- package/lib/module/driverTypes.js.map +1 -0
- package/lib/module/drivers.android.js +4 -0
- package/lib/module/drivers.android.js.map +1 -0
- package/lib/module/drivers.ios.js +380 -0
- package/lib/module/drivers.ios.js.map +1 -0
- package/lib/module/drivers.js +276 -0
- package/lib/module/drivers.js.map +1 -0
- package/lib/module/geometry.js +19 -0
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/nativeCompletion.js +21 -0
- package/lib/module/nativeCompletion.js.map +1 -0
- package/lib/module/reactRequests.js +50 -0
- package/lib/module/reactRequests.js.map +1 -0
- package/lib/module/smoothClipNative.android.js +15 -0
- package/lib/module/smoothClipNative.android.js.map +1 -0
- package/lib/module/smoothClipNative.js +8 -0
- package/lib/module/smoothClipNative.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +19 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts +3 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.d.ts +5 -6
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +9 -0
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.web.d.ts +5 -7
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/driverState.d.ts +31 -0
- package/lib/typescript/src/driverState.d.ts.map +1 -0
- package/lib/typescript/src/driverTypes.d.ts +61 -0
- package/lib/typescript/src/driverTypes.d.ts.map +1 -0
- package/lib/typescript/src/drivers.android.d.ts +3 -0
- package/lib/typescript/src/drivers.android.d.ts.map +1 -0
- package/lib/typescript/src/drivers.d.ts +5 -0
- package/lib/typescript/src/drivers.d.ts.map +1 -0
- package/lib/typescript/src/drivers.ios.d.ts +5 -0
- package/lib/typescript/src/drivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +8 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +4 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +7 -0
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -0
- package/lib/typescript/src/reactRequests.d.ts +7 -0
- package/lib/typescript/src/reactRequests.d.ts.map +1 -0
- package/lib/typescript/src/smoothClipNative.android.d.ts +4 -0
- package/lib/typescript/src/smoothClipNative.android.d.ts.map +1 -0
- package/lib/typescript/src/smoothClipNative.d.ts +3 -0
- package/lib/typescript/src/smoothClipNative.d.ts.map +1 -0
- package/package.json +12 -7
- package/src/NativeSmoothClipModule.ts +104 -0
- package/src/SmoothClipView.android.tsx +2 -0
- package/src/SmoothClipView.ios.tsx +36 -0
- package/src/SmoothClipView.tsx +31 -27
- package/src/SmoothClipView.web.tsx +26 -22
- package/src/SmoothClipViewNativeComponent.ts +14 -1
- package/src/driverState.ts +150 -0
- package/src/driverTypes.ts +91 -0
- package/src/drivers.android.ts +6 -0
- package/src/drivers.ios.ts +665 -0
- package/src/drivers.ts +416 -0
- package/src/geometry.ts +39 -0
- package/src/index.ts +19 -1
- package/src/index.web.ts +19 -1
- package/src/nativeCompletion.ts +37 -0
- package/src/reactRequests.ts +64 -0
- package/src/smoothClipNative.android.ts +20 -0
- package/src/smoothClipNative.ts +5 -0
package/README.md
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# react-native-smooth-clip-view
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/react-native-smooth-clip-view)
|
|
4
|
+
|
|
3
5
|
Layout-free animated rounded clipping for React Native Fabric.
|
|
4
6
|
|
|
5
|
-
`SmoothClipView` keeps a fixed maximum Yoga footprint while
|
|
6
|
-
|
|
7
|
-
useful for expanding cards, sheets, maps, media, and other reveals where
|
|
8
|
-
animating layout width and height
|
|
7
|
+
`SmoothClipView` keeps a fixed maximum Yoga footprint while a reusable driver
|
|
8
|
+
updates only the native clipping layer. It is
|
|
9
|
+
useful for expanding cards, sheets, maps, media, zoom transitions, and other reveals where
|
|
10
|
+
animating layout width and height cause lag because of yoga/fabric calculating layout every frame.
|
|
11
|
+
|
|
12
|
+
In other words, it makes animating width height on Reanimated a cheap operation.
|
|
9
13
|
|
|
10
14
|
## Requirements
|
|
11
15
|
|
|
12
|
-
- React Native 0.
|
|
13
|
-
- React Native Reanimated 4.
|
|
16
|
+
- React Native 0.86 or newer with the New Architecture enabled
|
|
17
|
+
- React Native Reanimated 4.5 or newer
|
|
18
|
+
- React Native Worklets 0.10 or newer
|
|
14
19
|
- iOS 16.4 or newer
|
|
15
20
|
- Android API 26 or newer
|
|
16
21
|
|
|
@@ -19,6 +24,8 @@ legacy Paper implementation.
|
|
|
19
24
|
|
|
20
25
|
## Installation
|
|
21
26
|
|
|
27
|
+
Available from [npm](https://www.npmjs.com/package/react-native-smooth-clip-view):
|
|
28
|
+
|
|
22
29
|
```sh
|
|
23
30
|
npm install react-native-smooth-clip-view react-native-reanimated react-native-worklets
|
|
24
31
|
```
|
|
@@ -40,13 +47,9 @@ import { Button, StyleSheet, View } from 'react-native';
|
|
|
40
47
|
import {
|
|
41
48
|
type ClipGeometry,
|
|
42
49
|
SmoothClipView,
|
|
50
|
+
createClipPresentation,
|
|
51
|
+
useSmoothClipDriver,
|
|
43
52
|
} from 'react-native-smooth-clip-view';
|
|
44
|
-
import {
|
|
45
|
-
interpolate,
|
|
46
|
-
useDerivedValue,
|
|
47
|
-
useSharedValue,
|
|
48
|
-
withTiming,
|
|
49
|
-
} from 'react-native-reanimated';
|
|
50
53
|
|
|
51
54
|
const initialClip: ClipGeometry = {
|
|
52
55
|
x: 120,
|
|
@@ -58,28 +61,29 @@ const initialClip: ClipGeometry = {
|
|
|
58
61
|
|
|
59
62
|
export function ClipExample() {
|
|
60
63
|
const [expanded, setExpanded] = useState(false);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
y: interpolate(progress.value, [0, 1], [180, 0]),
|
|
65
|
-
width: interpolate(progress.value, [0, 1], [80, 320]),
|
|
66
|
-
height: interpolate(progress.value, [0, 1], [80, 480]),
|
|
67
|
-
radius: interpolate(progress.value, [0, 1], [40, 24]),
|
|
68
|
-
}));
|
|
64
|
+
const driver = useSmoothClipDriver(
|
|
65
|
+
createClipPresentation(initialClip, -initialClip.x, -initialClip.y)
|
|
66
|
+
);
|
|
69
67
|
|
|
70
68
|
const toggle = () => {
|
|
71
69
|
const next = !expanded;
|
|
72
70
|
setExpanded(next);
|
|
73
|
-
|
|
71
|
+
const clip = next
|
|
72
|
+
? { x: 0, y: 0, width: 320, height: 480, radius: 24 }
|
|
73
|
+
: initialClip;
|
|
74
|
+
void driver.react.animateTo(
|
|
75
|
+
createClipPresentation(clip, -clip.x, -clip.y),
|
|
76
|
+
{
|
|
77
|
+
type: 'timing',
|
|
78
|
+
duration: 450,
|
|
79
|
+
controlPoints: [0.42, 0, 0.58, 1],
|
|
80
|
+
}
|
|
81
|
+
);
|
|
74
82
|
};
|
|
75
83
|
|
|
76
84
|
return (
|
|
77
85
|
<View>
|
|
78
|
-
<SmoothClipView
|
|
79
|
-
initialClip={initialClip}
|
|
80
|
-
animatedClip={animatedClip}
|
|
81
|
-
style={styles.host}
|
|
82
|
-
>
|
|
86
|
+
<SmoothClipView driver={driver} style={styles.host}>
|
|
83
87
|
<View style={styles.content} />
|
|
84
88
|
</SmoothClipView>
|
|
85
89
|
<Button title="Toggle clip" onPress={toggle} />
|
|
@@ -93,9 +97,57 @@ const styles = StyleSheet.create({
|
|
|
93
97
|
});
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
The same driver can be passed to multiple hosts. One native registry update
|
|
101
|
+
fans the seven-scalar presentation out to all mounted hosts. Interactive
|
|
102
|
+
updates avoid Yoga and ShadowTree commits.
|
|
103
|
+
|
|
104
|
+
For transitions whose endpoint is known, let Core Animation interpolate on the
|
|
105
|
+
render server:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
const expandFromReact = () =>
|
|
109
|
+
driver.react.animateTo(
|
|
110
|
+
createClipPresentation(
|
|
111
|
+
{ x: 0, y: 0, width: 320, height: 480, radius: 24 },
|
|
112
|
+
0,
|
|
113
|
+
0
|
|
114
|
+
),
|
|
115
|
+
{
|
|
116
|
+
type: 'timing',
|
|
117
|
+
duration: 450,
|
|
118
|
+
controlPoints: [0.42, 0, 0.58, 1],
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Native timing, spring, and keyframed transitions have no app callback between
|
|
124
|
+
setup and completion on iOS. Core Animation interpolates the clip and content
|
|
125
|
+
translation as one presentation. Android runs one C++ frame-loop driven by the
|
|
126
|
+
vsync clock with registry fanout; outline calculation and invalidation still
|
|
127
|
+
run on its UI thread. Springs are physically integrated per geometry channel
|
|
128
|
+
and finish when they settle below sub-pixel thresholds on both platforms.
|
|
129
|
+
Retargeting starts from visible state.
|
|
130
|
+
The same driver can be grabbed by a gesture without a visual jump:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
const gesture = Gesture.Pan()
|
|
134
|
+
.onStart(() => {
|
|
135
|
+
const visible = driver.ui.beginInteraction();
|
|
136
|
+
dragStart.value = visible.clip.height;
|
|
137
|
+
})
|
|
138
|
+
.onUpdate((event) => {
|
|
139
|
+
const clip = geometryForDrag(dragStart.value, event.translationY);
|
|
140
|
+
// Per-frame hot path: writes straight to native without SharedValue
|
|
141
|
+
// bookkeeping. Assigning driver.presentation.value also works.
|
|
142
|
+
driver.ui.setScalars(clip.x, clip.y, clip.width, clip.height, clip.radius, 0, 0);
|
|
143
|
+
})
|
|
144
|
+
.onEnd(() => {
|
|
145
|
+
driver.ui.animateTo(createClipPresentation(expandedClip), {
|
|
146
|
+
type: 'spring',
|
|
147
|
+
initialVelocity: 'inherit',
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
```
|
|
99
151
|
|
|
100
152
|
## API
|
|
101
153
|
|
|
@@ -103,11 +155,71 @@ command is dispatched.
|
|
|
103
155
|
|
|
104
156
|
`SmoothClipViewProps` extends React Native `ViewProps` and adds:
|
|
105
157
|
|
|
106
|
-
| Prop
|
|
107
|
-
|
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
|
|
158
|
+
| Prop | Type | Description |
|
|
159
|
+
| ---------- | ------------------ | --------------------------------------- |
|
|
160
|
+
| `driver` | `SmoothClipDriver` | Reusable hybrid clip driver. |
|
|
161
|
+
| `children` | `ReactNode` | Content rendered inside the fixed host. |
|
|
162
|
+
|
|
163
|
+
### Driver
|
|
164
|
+
|
|
165
|
+
- `useSmoothClipDriver(initialPresentation, options)` returns one hybrid driver
|
|
166
|
+
whose writable `presentation` SharedValue contains clip geometry and content
|
|
167
|
+
translation. Passing a `ClipGeometry` remains supported and initializes both
|
|
168
|
+
translations to zero.
|
|
169
|
+
- `driver.ui` is the synchronous UI-worklet interface. `beginInteraction()`
|
|
170
|
+
freezes a transition at visible presentation state; `set()`, `animateTo()`,
|
|
171
|
+
and `cancel()` change ownership atomically.
|
|
172
|
+
- `driver.ui.setScalars(x, y, width, height, radius, tx, ty)` is the per-frame
|
|
173
|
+
hot path: it writes geometry straight to native without touching
|
|
174
|
+
`driver.presentation`, skipping SharedValue bookkeeping for high-frequency
|
|
175
|
+
streams such as gestures. `driver.presentation.value` is stale after hot
|
|
176
|
+
writes by design; `beginInteraction()` remains the source of truth for
|
|
177
|
+
visible geometry, and `animateTo()` after hot writes starts from the native
|
|
178
|
+
registry's latest value rather than the stale SharedValue. Do not interleave
|
|
179
|
+
`setScalars` with `presentation.value` writes on the same driver.
|
|
180
|
+
- Spring `initialVelocity` is one normalized scalar along the current-to-target
|
|
181
|
+
trajectory, in units of the remaining distance per second (`1` covers the
|
|
182
|
+
remaining distance in one second). Every geometry channel continues with the
|
|
183
|
+
same normalized rate, so grab/release preserves the felt direction and
|
|
184
|
+
speed. `'inherit'` (the default) estimates the scalar from the last two
|
|
185
|
+
interactive samples on iOS and Android; samples older than 100 ms — and the
|
|
186
|
+
web fallback — fall back to zero.
|
|
187
|
+
- `driver.react` exposes `beginInteraction`, `set`, `animateTo`, and `cancel`
|
|
188
|
+
as Promises (`setScalars` is UI-worklet-only). React code never blocks
|
|
189
|
+
waiting for main/UI-thread work. An immediate animation request resolves
|
|
190
|
+
before its completion callback is delivered.
|
|
191
|
+
- `animateTo()` transfers ownership to native animation. Timing uses
|
|
192
|
+
cubic Bézier control points; springs accept mass, stiffness, damping, and an
|
|
193
|
+
explicit normalized velocity or `'inherit'` (the default). Keyframes accept
|
|
194
|
+
validated, monotonically increasing offsets from zero through one.
|
|
195
|
+
- `cancel()` freezes visible presentation by default. Pass `'target'` as its
|
|
196
|
+
behavior to jump to the requested endpoint.
|
|
197
|
+
- `options.onAnimationComplete` fires once per animation with its ID and
|
|
198
|
+
`finished` state, including cancellation, replacement, participant unmount,
|
|
199
|
+
and native-side rejection (`animateTo` then returns `0` and one
|
|
200
|
+
`finished: false` completion is delivered).
|
|
201
|
+
|
|
202
|
+
Do not call `driver.ui` from React code — it throws on the React runtime; use
|
|
203
|
+
`driver.react` there. During a native transition, `driver.presentation.value`
|
|
204
|
+
is the requested target, not a per-frame mirror of the native presentation
|
|
205
|
+
layer. `beginInteraction()` returns geometry normalized against the host
|
|
206
|
+
bounds, so a clip that extended beyond the host comes back clamped. Start
|
|
207
|
+
gestures with `beginInteraction()`: interactive writes issued while native
|
|
208
|
+
still owns rendering are dropped.
|
|
209
|
+
|
|
210
|
+
### `SmoothClipPresentation`
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
type SmoothClipPresentation = Readonly<{
|
|
214
|
+
clip: ClipGeometry;
|
|
215
|
+
contentTranslateX: number;
|
|
216
|
+
contentTranslateY: number;
|
|
217
|
+
}>;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Use the content translation fields when a clipped viewport must reveal a
|
|
221
|
+
fixed-size child without a separate Reanimated transform. Native transitions
|
|
222
|
+
animate clip position, bounds, radius, and content translation together.
|
|
111
223
|
|
|
112
224
|
### `ClipGeometry`
|
|
113
225
|
|
|
@@ -131,6 +243,15 @@ negative sizes, and clamps `radius` to half of the visible shortest edge.
|
|
|
131
243
|
contract for tests and non-native calculations. Native bounds remain
|
|
132
244
|
authoritative at render time.
|
|
133
245
|
|
|
246
|
+
## Migrating from 0.0.x
|
|
247
|
+
|
|
248
|
+
Version 0.1.0 removes the `initialClip` and `animatedClip` props in favor of
|
|
249
|
+
the driver API: create a driver with `useSmoothClipDriver(initialPresentation)`,
|
|
250
|
+
pass it as the `driver` prop, and move per-frame updates from the
|
|
251
|
+
`animatedClip` SharedValue to `driver.presentation.value` (or
|
|
252
|
+
`driver.ui.setScalars`). Autonomous transitions move from Reanimated
|
|
253
|
+
`withTiming`/`withSpring` to `driver.ui.animateTo` / `driver.react.animateTo`.
|
|
254
|
+
|
|
134
255
|
## Layout and styling contract
|
|
135
256
|
|
|
136
257
|
- Give the host its fixed maximum `width` and `height`; clipping never changes
|
|
@@ -149,20 +270,20 @@ The [`example`](./example) workspace is an Expo SDK 57 app that exercises the
|
|
|
149
270
|
package through its public import on iOS, Android, and web.
|
|
150
271
|
|
|
151
272
|
```sh
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
273
|
+
npm install
|
|
274
|
+
npm run example -- ios
|
|
275
|
+
npm run example -- android
|
|
276
|
+
npm run example -- web
|
|
156
277
|
```
|
|
157
278
|
|
|
158
279
|
Run the repository checks with:
|
|
159
280
|
|
|
160
281
|
```sh
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
282
|
+
npm run lint
|
|
283
|
+
npm run typecheck
|
|
284
|
+
npm test
|
|
285
|
+
npm run prepare
|
|
286
|
+
npm run example -- build:web
|
|
166
287
|
```
|
|
167
288
|
|
|
168
289
|
## License
|
package/SmoothClipView.podspec
CHANGED
|
@@ -12,8 +12,14 @@ Pod::Spec.new do |s|
|
|
|
12
12
|
|
|
13
13
|
s.platforms = { :ios => "16.4" }
|
|
14
14
|
s.source = { :git => "https://github.com/kbrattli/react-native-smooth-clip-view.git", :tag => "v#{s.version}" }
|
|
15
|
-
s.source_files = "ios/*.{h,m,mm,swift,cpp}"
|
|
16
|
-
s.private_header_files = "ios/*.h"
|
|
15
|
+
s.source_files = ["ios/*.{h,m,mm,swift,cpp}", "cpp/*.{h,cpp}"]
|
|
16
|
+
s.private_header_files = ["ios/*.h", "cpp/*.h"]
|
|
17
|
+
s.pod_target_xcconfig = {
|
|
18
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\" \"$(PODS_TARGET_SRCROOT)/ios\"",
|
|
19
|
+
# Signpost instrumentation is compiled into Debug only; Release interactive
|
|
20
|
+
# frames must not generate signpost IDs or intervals.
|
|
21
|
+
"GCC_PREPROCESSOR_DEFINITIONS[config=Debug]" => "$(inherited) SMOOTH_CLIP_ENABLE_SIGNPOSTS=1"
|
|
22
|
+
}
|
|
17
23
|
|
|
18
24
|
install_modules_dependencies(s)
|
|
19
25
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.16)
|
|
2
|
+
project(smoothclipview)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
|
|
7
|
+
find_package(fbjni REQUIRED CONFIG)
|
|
8
|
+
find_package(ReactAndroid REQUIRED CONFIG)
|
|
9
|
+
|
|
10
|
+
# Folly compile flags used across the React Native C++ surface.
|
|
11
|
+
include("${REACT_NATIVE_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake")
|
|
12
|
+
add_compile_options(${folly_FLAGS})
|
|
13
|
+
|
|
14
|
+
file(GLOB smoothclip_SOURCES CONFIGURE_DEPENDS
|
|
15
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/*.cpp")
|
|
16
|
+
|
|
17
|
+
add_library(smoothclipview SHARED ${smoothclip_SOURCES})
|
|
18
|
+
|
|
19
|
+
# C++20, RTTI, exceptions and React Native macros.
|
|
20
|
+
include("${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake")
|
|
21
|
+
target_compile_reactnative_options(smoothclipview PUBLIC)
|
|
22
|
+
|
|
23
|
+
target_include_directories(
|
|
24
|
+
smoothclipview
|
|
25
|
+
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp"
|
|
26
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/../cpp"
|
|
27
|
+
"${REACT_NATIVE_DIR}/ReactCommon"
|
|
28
|
+
"${REACT_NATIVE_DIR}/ReactCommon/jsi"
|
|
29
|
+
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
|
|
30
|
+
"${REACT_NATIVE_DIR}/ReactCommon/react/nativemodule/core/ReactCommon"
|
|
31
|
+
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/react/turbomodule")
|
|
32
|
+
|
|
33
|
+
target_link_libraries(
|
|
34
|
+
smoothclipview
|
|
35
|
+
android
|
|
36
|
+
log
|
|
37
|
+
fbjni::fbjni
|
|
38
|
+
ReactAndroid::jsi
|
|
39
|
+
ReactAndroid::reactnative)
|
package/android/build.gradle
CHANGED
|
@@ -28,17 +28,74 @@ apply plugin: "com.android.library"
|
|
|
28
28
|
apply plugin: "kotlin-android"
|
|
29
29
|
apply plugin: "com.facebook.react"
|
|
30
30
|
|
|
31
|
+
def resolveReactNativeDirectory() {
|
|
32
|
+
def reactNativeLocation = rootProject.ext.has("REACT_NATIVE_NODE_MODULES_DIR")
|
|
33
|
+
? rootProject.ext.get("REACT_NATIVE_NODE_MODULES_DIR")
|
|
34
|
+
: null
|
|
35
|
+
if (reactNativeLocation != null) {
|
|
36
|
+
return file(reactNativeLocation)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fallback to node resolver for custom directory structures like monorepos.
|
|
40
|
+
def reactNativePackage = file(
|
|
41
|
+
providers.exec {
|
|
42
|
+
workingDir(rootDir)
|
|
43
|
+
commandLine("node", "--print", "require.resolve('react-native/package.json')")
|
|
44
|
+
}.standardOutput.asText.get().trim()
|
|
45
|
+
)
|
|
46
|
+
if (reactNativePackage.exists()) {
|
|
47
|
+
return reactNativePackage.parentFile
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new GradleException("[react-native-smooth-clip-view] Unable to resolve react-native in node_modules.")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def reactNativeRootDir = resolveReactNativeDirectory()
|
|
54
|
+
|
|
55
|
+
def reactNativeArchitectures() {
|
|
56
|
+
def value = rootProject.findProperty("reactNativeArchitectures")
|
|
57
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
android {
|
|
32
61
|
namespace "com.smoothclipview"
|
|
33
62
|
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
34
63
|
|
|
64
|
+
if (rootProject.hasProperty("ndkPath")) {
|
|
65
|
+
ndkPath rootProject.ext.ndkPath
|
|
66
|
+
}
|
|
67
|
+
if (rootProject.hasProperty("ndkVersion")) {
|
|
68
|
+
ndkVersion rootProject.ext.ndkVersion
|
|
69
|
+
} else {
|
|
70
|
+
ndkVersion "27.1.12297006"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
buildFeatures {
|
|
74
|
+
buildConfig true
|
|
75
|
+
prefab true
|
|
76
|
+
}
|
|
77
|
+
|
|
35
78
|
defaultConfig {
|
|
36
79
|
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
37
80
|
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
81
|
+
// C++ resolves SmoothClipView by name and calls setClipPresentationDip via
|
|
82
|
+
// a cached jmethodID; consumer minification must not rename or strip them.
|
|
83
|
+
consumerProguardFiles "proguard-rules.pro"
|
|
84
|
+
|
|
85
|
+
externalNativeBuild {
|
|
86
|
+
cmake {
|
|
87
|
+
cppFlags "-O2", "-frtti", "-fexceptions", "-Wall", "-std=c++20"
|
|
88
|
+
arguments "-DANDROID_STL=c++_shared",
|
|
89
|
+
"-DREACT_NATIVE_DIR=${reactNativeRootDir}"
|
|
90
|
+
abiFilters(*reactNativeArchitectures())
|
|
91
|
+
}
|
|
92
|
+
}
|
|
38
93
|
}
|
|
39
94
|
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
externalNativeBuild {
|
|
96
|
+
cmake {
|
|
97
|
+
path "CMakeLists.txt"
|
|
98
|
+
}
|
|
42
99
|
}
|
|
43
100
|
|
|
44
101
|
buildTypes {
|
|
@@ -55,6 +112,16 @@ android {
|
|
|
55
112
|
sourceCompatibility JavaVersion.VERSION_1_8
|
|
56
113
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
57
114
|
}
|
|
115
|
+
|
|
116
|
+
packagingOptions {
|
|
117
|
+
// Provided by the host application at runtime.
|
|
118
|
+
excludes += [
|
|
119
|
+
"**/libc++_shared.so",
|
|
120
|
+
"**/libfbjni.so",
|
|
121
|
+
"**/libjsi.so",
|
|
122
|
+
"**/libreactnative.so",
|
|
123
|
+
]
|
|
124
|
+
}
|
|
58
125
|
}
|
|
59
126
|
|
|
60
127
|
dependencies {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# SmoothClipView is resolved from C++ by name (fbjni findClassStatic) and its
|
|
2
|
+
# setClipPresentationDip / setClipPresentationPx are invoked through cached
|
|
3
|
+
# jmethodIDs; SmoothClipBindings registers native methods by name. None of them
|
|
4
|
+
# may be renamed or stripped in consumer release builds.
|
|
5
|
+
-keep class com.smoothclipview.SmoothClipView { *; }
|
|
6
|
+
-keep class com.smoothclipview.SmoothClipBindings { *; }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <ReactCommon/CallInvoker.h>
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
#include <jsi/jsi.h>
|
|
6
|
+
|
|
7
|
+
#include <memory>
|
|
8
|
+
|
|
9
|
+
#include "SmoothClipRegistry.h"
|
|
10
|
+
#include "SmoothClipSharedGeometry.h"
|
|
11
|
+
|
|
12
|
+
namespace smoothclip {
|
|
13
|
+
|
|
14
|
+
// fbjni wrapper around com.smoothclipview.SmoothClipView. Only the primitive
|
|
15
|
+
// clip setter is invoked from native, so the Kotlin view stays a thin outline
|
|
16
|
+
// clipper while all driver state lives in the shared C++ registry.
|
|
17
|
+
struct JSmoothClipView : facebook::jni::JavaClass<JSmoothClipView> {
|
|
18
|
+
static constexpr auto kJavaDescriptor = "Lcom/smoothclipview/SmoothClipView;";
|
|
19
|
+
|
|
20
|
+
void applyClip(const Presentation &presentation) const;
|
|
21
|
+
void applyClipPx(
|
|
22
|
+
const NormalizedClip &clip,
|
|
23
|
+
double contentTranslateXPx,
|
|
24
|
+
double contentTranslateYPx) const;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Called from Kotlin (SmoothClipViewManager) on the UI thread when a Fabric
|
|
28
|
+
// view mounts/unmounts. The registry retains a global ref for the view.
|
|
29
|
+
void registerViewAndroid(
|
|
30
|
+
uint64_t driverId,
|
|
31
|
+
facebook::jni::alias_ref<JSmoothClipView> view,
|
|
32
|
+
Presentation initialPresentation,
|
|
33
|
+
double density,
|
|
34
|
+
double hostWidthPx,
|
|
35
|
+
double hostHeightPx);
|
|
36
|
+
// Refreshes a registered view's density / host size (px) and synchronously
|
|
37
|
+
// redelivers the driver's visible value pre-normalized against them.
|
|
38
|
+
void setViewHostGeometryAndroid(
|
|
39
|
+
uint64_t driverId,
|
|
40
|
+
facebook::jni::alias_ref<JSmoothClipView> view,
|
|
41
|
+
double density,
|
|
42
|
+
double hostWidthPx,
|
|
43
|
+
double hostHeightPx);
|
|
44
|
+
void unregisterViewAndroid(
|
|
45
|
+
uint64_t driverId,
|
|
46
|
+
facebook::jni::alias_ref<JSmoothClipView> view);
|
|
47
|
+
|
|
48
|
+
// Installs `global.__SmoothClipView` (worklet-callable host functions) into the
|
|
49
|
+
// JS runtime and wires native animation completion delivery through the
|
|
50
|
+
// CallInvoker. Invoked by the TurboModule BindingsInstaller.
|
|
51
|
+
void installBindings(
|
|
52
|
+
facebook::jsi::Runtime &runtime,
|
|
53
|
+
const std::shared_ptr<facebook::react::CallInvoker> &callInvoker);
|
|
54
|
+
|
|
55
|
+
// Releases the listener functions and completion sink while the JS runtime is
|
|
56
|
+
// still alive. Called from SmoothClipModule.invalidate() during host teardown.
|
|
57
|
+
void invalidateBindings();
|
|
58
|
+
|
|
59
|
+
} // namespace smoothclip
|