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.
Files changed (113) hide show
  1. package/README.md +164 -43
  2. package/SmoothClipView.podspec +8 -2
  3. package/android/CMakeLists.txt +39 -0
  4. package/android/build.gradle +69 -2
  5. package/android/proguard-rules.pro +6 -0
  6. package/android/src/main/cpp/SmoothClipAndroid.h +59 -0
  7. package/android/src/main/cpp/SmoothClipBindings.cpp +428 -0
  8. package/android/src/main/cpp/SmoothClipRegistry.cpp +913 -0
  9. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +25 -0
  10. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +55 -0
  11. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +128 -0
  12. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +164 -30
  13. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +107 -10
  14. package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +18 -2
  15. package/cpp/SmoothClipRegistry.h +93 -0
  16. package/cpp/SmoothClipSharedGeometry.h +50 -0
  17. package/ios/SmoothClipModuleProvider.h +9 -0
  18. package/ios/SmoothClipModuleProvider.mm +16 -0
  19. package/ios/SmoothClipRegistry.mm +717 -0
  20. package/ios/SmoothClipTurboModule.cpp +290 -0
  21. package/ios/SmoothClipTurboModule.h +132 -0
  22. package/ios/SmoothClipView.mm +976 -85
  23. package/ios/SmoothClipViewRegistry.h +27 -0
  24. package/lib/module/NativeSmoothClipModule.js +5 -0
  25. package/lib/module/NativeSmoothClipModule.js.map +1 -0
  26. package/lib/module/SmoothClipView.android.js +4 -0
  27. package/lib/module/SmoothClipView.android.js.map +1 -0
  28. package/lib/module/SmoothClipView.ios.js +34 -0
  29. package/lib/module/SmoothClipView.ios.js.map +1 -0
  30. package/lib/module/SmoothClipView.js +26 -12
  31. package/lib/module/SmoothClipView.js.map +1 -1
  32. package/lib/module/SmoothClipView.web.js +28 -12
  33. package/lib/module/SmoothClipView.web.js.map +1 -1
  34. package/lib/module/SmoothClipViewNativeComponent.ts +14 -1
  35. package/lib/module/driverState.js +102 -0
  36. package/lib/module/driverState.js.map +1 -0
  37. package/lib/module/driverTypes.js +4 -0
  38. package/lib/module/driverTypes.js.map +1 -0
  39. package/lib/module/drivers.android.js +4 -0
  40. package/lib/module/drivers.android.js.map +1 -0
  41. package/lib/module/drivers.ios.js +380 -0
  42. package/lib/module/drivers.ios.js.map +1 -0
  43. package/lib/module/drivers.js +276 -0
  44. package/lib/module/drivers.js.map +1 -0
  45. package/lib/module/geometry.js +19 -0
  46. package/lib/module/geometry.js.map +1 -1
  47. package/lib/module/index.js +2 -0
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/index.web.js +2 -0
  50. package/lib/module/index.web.js.map +1 -1
  51. package/lib/module/nativeCompletion.js +21 -0
  52. package/lib/module/nativeCompletion.js.map +1 -0
  53. package/lib/module/reactRequests.js +50 -0
  54. package/lib/module/reactRequests.js.map +1 -0
  55. package/lib/module/smoothClipNative.android.js +15 -0
  56. package/lib/module/smoothClipNative.android.js.map +1 -0
  57. package/lib/module/smoothClipNative.js +8 -0
  58. package/lib/module/smoothClipNative.js.map +1 -0
  59. package/lib/typescript/src/NativeSmoothClipModule.d.ts +19 -0
  60. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -0
  61. package/lib/typescript/src/SmoothClipView.android.d.ts +3 -0
  62. package/lib/typescript/src/SmoothClipView.android.d.ts.map +1 -0
  63. package/lib/typescript/src/SmoothClipView.d.ts +5 -6
  64. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  65. package/lib/typescript/src/SmoothClipView.ios.d.ts +9 -0
  66. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -0
  67. package/lib/typescript/src/SmoothClipView.web.d.ts +5 -7
  68. package/lib/typescript/src/SmoothClipView.web.d.ts.map +1 -1
  69. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +4 -0
  70. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  71. package/lib/typescript/src/driverState.d.ts +31 -0
  72. package/lib/typescript/src/driverState.d.ts.map +1 -0
  73. package/lib/typescript/src/driverTypes.d.ts +61 -0
  74. package/lib/typescript/src/driverTypes.d.ts.map +1 -0
  75. package/lib/typescript/src/drivers.android.d.ts +3 -0
  76. package/lib/typescript/src/drivers.android.d.ts.map +1 -0
  77. package/lib/typescript/src/drivers.d.ts +5 -0
  78. package/lib/typescript/src/drivers.d.ts.map +1 -0
  79. package/lib/typescript/src/drivers.ios.d.ts +5 -0
  80. package/lib/typescript/src/drivers.ios.d.ts.map +1 -0
  81. package/lib/typescript/src/geometry.d.ts +8 -0
  82. package/lib/typescript/src/geometry.d.ts.map +1 -1
  83. package/lib/typescript/src/index.d.ts +4 -1
  84. package/lib/typescript/src/index.d.ts.map +1 -1
  85. package/lib/typescript/src/index.web.d.ts +4 -1
  86. package/lib/typescript/src/index.web.d.ts.map +1 -1
  87. package/lib/typescript/src/nativeCompletion.d.ts +7 -0
  88. package/lib/typescript/src/nativeCompletion.d.ts.map +1 -0
  89. package/lib/typescript/src/reactRequests.d.ts +7 -0
  90. package/lib/typescript/src/reactRequests.d.ts.map +1 -0
  91. package/lib/typescript/src/smoothClipNative.android.d.ts +4 -0
  92. package/lib/typescript/src/smoothClipNative.android.d.ts.map +1 -0
  93. package/lib/typescript/src/smoothClipNative.d.ts +3 -0
  94. package/lib/typescript/src/smoothClipNative.d.ts.map +1 -0
  95. package/package.json +12 -7
  96. package/src/NativeSmoothClipModule.ts +104 -0
  97. package/src/SmoothClipView.android.tsx +2 -0
  98. package/src/SmoothClipView.ios.tsx +36 -0
  99. package/src/SmoothClipView.tsx +31 -27
  100. package/src/SmoothClipView.web.tsx +26 -22
  101. package/src/SmoothClipViewNativeComponent.ts +14 -1
  102. package/src/driverState.ts +150 -0
  103. package/src/driverTypes.ts +91 -0
  104. package/src/drivers.android.ts +6 -0
  105. package/src/drivers.ios.ts +665 -0
  106. package/src/drivers.ts +416 -0
  107. package/src/geometry.ts +39 -0
  108. package/src/index.ts +19 -1
  109. package/src/index.web.ts +19 -1
  110. package/src/nativeCompletion.ts +37 -0
  111. package/src/reactRequests.ts +64 -0
  112. package/src/smoothClipNative.android.ts +20 -0
  113. package/src/smoothClipNative.ts +5 -0
package/README.md CHANGED
@@ -1,16 +1,21 @@
1
1
  # react-native-smooth-clip-view
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/react-native-smooth-clip-view.svg)](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 Reanimated sends
6
- one atomic clip geometry update to the native view on the UI runtime. It is
7
- useful for expanding cards, sheets, maps, media, and other reveals where
8
- animating layout width and height would reflow descendants.
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.85 or newer with the New Architecture enabled
13
- - React Native Reanimated 4.3 or newer
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 progress = useSharedValue(0);
62
- const animatedClip = useDerivedValue<ClipGeometry>(() => ({
63
- x: interpolate(progress.value, [0, 1], [120, 0]),
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
- progress.value = withTiming(next ? 1 : 0);
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
- `initialClip` must equal `animatedClip.value` when the component mounts. This
97
- lets native rendering start with the correct clip before the first UI-runtime
98
- command is dispatched.
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 | Type | Description |
107
- | --- | --- | --- |
108
- | `initialClip` | `ClipGeometry` | Clip rendered synchronously at mount. |
109
- | `animatedClip` | `SharedValue<ClipGeometry>` | Geometry observed on the Reanimated UI runtime. |
110
- | `children` | `ReactNode` | Content rendered inside the fixed host. |
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
- yarn
153
- yarn example ios
154
- yarn example android
155
- yarn example web
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
- yarn lint
162
- yarn typecheck
163
- yarn test
164
- yarn prepare
165
- yarn example build:web
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
@@ -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)
@@ -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
- buildFeatures {
41
- buildConfig true
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