react-native-smooth-clip-view 0.1.0 → 0.2.1
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 +171 -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 +945 -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 +754 -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,78 @@ 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
|
+
- An `animateTo` issued before any host view has registered (for example from
|
|
202
|
+
an effect in the same commit that mounts the host, or inside a modal route
|
|
203
|
+
that attaches late) is held pending and starts with its full duration when
|
|
204
|
+
the first view registers. If no view ever registers, the pending animation
|
|
205
|
+
survives until it is replaced, cancelled, overridden by a take-ownership
|
|
206
|
+
write, or the driver is destroyed — at which point its single
|
|
207
|
+
`finished: false` completion is delivered.
|
|
208
|
+
|
|
209
|
+
Do not call `driver.ui` from React code — it throws on the React runtime; use
|
|
210
|
+
`driver.react` there. During a native transition, `driver.presentation.value`
|
|
211
|
+
is the requested target, not a per-frame mirror of the native presentation
|
|
212
|
+
layer. `beginInteraction()` returns geometry normalized against the host
|
|
213
|
+
bounds, so a clip that extended beyond the host comes back clamped. Start
|
|
214
|
+
gestures with `beginInteraction()`: interactive writes issued while native
|
|
215
|
+
still owns rendering are dropped.
|
|
216
|
+
|
|
217
|
+
### `SmoothClipPresentation`
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
type SmoothClipPresentation = Readonly<{
|
|
221
|
+
clip: ClipGeometry;
|
|
222
|
+
contentTranslateX: number;
|
|
223
|
+
contentTranslateY: number;
|
|
224
|
+
}>;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Use the content translation fields when a clipped viewport must reveal a
|
|
228
|
+
fixed-size child without a separate Reanimated transform. Native transitions
|
|
229
|
+
animate clip position, bounds, radius, and content translation together.
|
|
111
230
|
|
|
112
231
|
### `ClipGeometry`
|
|
113
232
|
|
|
@@ -131,6 +250,15 @@ negative sizes, and clamps `radius` to half of the visible shortest edge.
|
|
|
131
250
|
contract for tests and non-native calculations. Native bounds remain
|
|
132
251
|
authoritative at render time.
|
|
133
252
|
|
|
253
|
+
## Migrating from 0.0.x
|
|
254
|
+
|
|
255
|
+
Version 0.1.0 removes the `initialClip` and `animatedClip` props in favor of
|
|
256
|
+
the driver API: create a driver with `useSmoothClipDriver(initialPresentation)`,
|
|
257
|
+
pass it as the `driver` prop, and move per-frame updates from the
|
|
258
|
+
`animatedClip` SharedValue to `driver.presentation.value` (or
|
|
259
|
+
`driver.ui.setScalars`). Autonomous transitions move from Reanimated
|
|
260
|
+
`withTiming`/`withSpring` to `driver.ui.animateTo` / `driver.react.animateTo`.
|
|
261
|
+
|
|
134
262
|
## Layout and styling contract
|
|
135
263
|
|
|
136
264
|
- Give the host its fixed maximum `width` and `height`; clipping never changes
|
|
@@ -149,20 +277,20 @@ The [`example`](./example) workspace is an Expo SDK 57 app that exercises the
|
|
|
149
277
|
package through its public import on iOS, Android, and web.
|
|
150
278
|
|
|
151
279
|
```sh
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
280
|
+
npm install
|
|
281
|
+
npm run example -- ios
|
|
282
|
+
npm run example -- android
|
|
283
|
+
npm run example -- web
|
|
156
284
|
```
|
|
157
285
|
|
|
158
286
|
Run the repository checks with:
|
|
159
287
|
|
|
160
288
|
```sh
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
289
|
+
npm run lint
|
|
290
|
+
npm run typecheck
|
|
291
|
+
npm test
|
|
292
|
+
npm run prepare
|
|
293
|
+
npm run example -- build:web
|
|
166
294
|
```
|
|
167
295
|
|
|
168
296
|
## 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
|