@renegades/react-native-tickle 0.1.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/LICENSE +21 -0
- package/README.md +352 -0
- package/Tickle.podspec +29 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +126 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/tickle/Tickle.kt +71 -0
- package/android/src/main/java/com/margelo/nitro/tickle/TicklePackage.kt +22 -0
- package/ios/Tickle.swift +185 -0
- package/ios/TickleUtils.swift +404 -0
- package/lib/module/Tickle.nitro.js +4 -0
- package/lib/module/Tickle.nitro.js.map +1 -0
- package/lib/module/index.js +254 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Tickle.nitro.d.ts +63 -0
- package/lib/typescript/src/Tickle.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +148 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitro.json +17 -0
- package/nitrogen/generated/android/c++/JHapticCurve.hpp +87 -0
- package/nitrogen/generated/android/c++/JHapticCurveControlPoint.hpp +61 -0
- package/nitrogen/generated/android/c++/JHapticEvent.hpp +94 -0
- package/nitrogen/generated/android/c++/JHapticEventParameter.hpp +62 -0
- package/nitrogen/generated/android/c++/JHapticEventType.hpp +58 -0
- package/nitrogen/generated/android/c++/JHapticImpactStyle.hpp +67 -0
- package/nitrogen/generated/android/c++/JHapticNotificationType.hpp +61 -0
- package/nitrogen/generated/android/c++/JHapticParameterType.hpp +58 -0
- package/nitrogen/generated/android/c++/JHybridTickleSpec.cpp +162 -0
- package/nitrogen/generated/android/c++/JHybridTickleSpec.hpp +79 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticCurve.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticCurveControlPoint.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticEvent.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticEventParameter.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticEventType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticImpactStyle.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticNotificationType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticParameterType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HybridTickleSpec.kt +109 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/tickleOnLoad.kt +35 -0
- package/nitrogen/generated/android/tickle+autolinking.cmake +81 -0
- package/nitrogen/generated/android/tickle+autolinking.gradle +27 -0
- package/nitrogen/generated/android/tickleOnLoad.cpp +44 -0
- package/nitrogen/generated/android/tickleOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/Tickle+autolinking.rb +60 -0
- package/nitrogen/generated/ios/Tickle-Swift-Cxx-Bridge.cpp +33 -0
- package/nitrogen/generated/ios/Tickle-Swift-Cxx-Bridge.hpp +139 -0
- package/nitrogen/generated/ios/Tickle-Swift-Cxx-Umbrella.hpp +70 -0
- package/nitrogen/generated/ios/TickleAutolinking.mm +33 -0
- package/nitrogen/generated/ios/TickleAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridTickleSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridTickleSpecSwift.hpp +185 -0
- package/nitrogen/generated/ios/swift/HapticCurve.swift +46 -0
- package/nitrogen/generated/ios/swift/HapticCurveControlPoint.swift +35 -0
- package/nitrogen/generated/ios/swift/HapticEvent.swift +57 -0
- package/nitrogen/generated/ios/swift/HapticEventParameter.swift +35 -0
- package/nitrogen/generated/ios/swift/HapticEventType.swift +40 -0
- package/nitrogen/generated/ios/swift/HapticImpactStyle.swift +52 -0
- package/nitrogen/generated/ios/swift/HapticNotificationType.swift +44 -0
- package/nitrogen/generated/ios/swift/HapticParameterType.swift +40 -0
- package/nitrogen/generated/ios/swift/HybridTickleSpec.swift +69 -0
- package/nitrogen/generated/ios/swift/HybridTickleSpec_cxx.swift +282 -0
- package/nitrogen/generated/shared/c++/HapticCurve.hpp +96 -0
- package/nitrogen/generated/shared/c++/HapticCurveControlPoint.hpp +87 -0
- package/nitrogen/generated/shared/c++/HapticEvent.hpp +101 -0
- package/nitrogen/generated/shared/c++/HapticEventParameter.hpp +88 -0
- package/nitrogen/generated/shared/c++/HapticEventType.hpp +76 -0
- package/nitrogen/generated/shared/c++/HapticImpactStyle.hpp +88 -0
- package/nitrogen/generated/shared/c++/HapticNotificationType.hpp +80 -0
- package/nitrogen/generated/shared/c++/HapticParameterType.hpp +76 -0
- package/nitrogen/generated/shared/c++/HybridTickleSpec.cpp +34 -0
- package/nitrogen/generated/shared/c++/HybridTickleSpec.hpp +87 -0
- package/package.json +179 -0
- package/react-native.config.js +8 -0
- package/src/Tickle.nitro.ts +84 -0
- package/src/index.tsx +306 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Renegades
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# react-native-tickle
|
|
2
|
+
|
|
3
|
+
AHAP-style haptics (transient + continuous) on top of [Nitro Modules](https://nitro.margelo.com/) — the core functions are **UI-thread friendly** (`'worklet'`).
|
|
4
|
+
|
|
5
|
+
## Why this package?
|
|
6
|
+
|
|
7
|
+
Complete flexibility over iOS haptics with synchronous UI-thread execution.
|
|
8
|
+
|
|
9
|
+
> iOS only (Core Haptics). Android support could be added in the future, but it’s currently unplanned.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm i react-native-tickle react-native-nitro-modules
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Concepts (what to use when)
|
|
18
|
+
|
|
19
|
+
- **Transient**: Instant “click/tap” events. No duration — you trigger them at a point in time.
|
|
20
|
+
- **Continuous (pattern)**: Time-based patterns you _can_ define ahead of time. You provide **events** (with `duration`) and optionally **curves** (automation over time).
|
|
21
|
+
- **Continuous player (real-time)**: For _unpredictable_ input (gesture position, scroll velocity, real-time data). You create a player once, then **start → update (many times) → stop**.
|
|
22
|
+
|
|
23
|
+
### Why `events[]` and `curves[]` are separate
|
|
24
|
+
|
|
25
|
+
On iOS Core Haptics, a pattern is made of two different building blocks:
|
|
26
|
+
|
|
27
|
+
- **Events**: things that happen (transient “ticks” or continuous segments) at a `relativeTime`, with base `intensity`/`sharpness`.
|
|
28
|
+
- **Curves**: how parameters (intensity/sharpness) evolve over time via control points, independent of “what event” is currently playing.
|
|
29
|
+
|
|
30
|
+
They’re separate because they’re different object types in Core Haptics (events vs parameter curves) and they serve different jobs: **events define the structure**, **curves define the modulation**. You often combine both in one pattern.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Haptic "provider" (recommended)
|
|
35
|
+
|
|
36
|
+
Wrap your app inside `HapticProvider`. This initializes the engine and automatically destroys it when the app goes to background.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { HapticProvider } from 'react-native-tickle';
|
|
40
|
+
|
|
41
|
+
export function App() {
|
|
42
|
+
return <HapticProvider>{/* {Rest of your app} */}</HapticProvider>;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `startHaptic(events, curves)` (transient + continuous patterns)
|
|
47
|
+
|
|
48
|
+
Play a transient:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { startHaptic } from 'react-native-tickle';
|
|
52
|
+
|
|
53
|
+
startHaptic(
|
|
54
|
+
[
|
|
55
|
+
{
|
|
56
|
+
type: 'transient',
|
|
57
|
+
relativeTime: 0,
|
|
58
|
+
parameters: [
|
|
59
|
+
{ type: 'intensity', value: 1 },
|
|
60
|
+
{ type: 'sharpness', value: 0.5 },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
[]
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Play a continuous pattern (events + curves together):
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { startHaptic } from 'react-native-tickle';
|
|
72
|
+
|
|
73
|
+
startHaptic(
|
|
74
|
+
[
|
|
75
|
+
{
|
|
76
|
+
type: 'continuous',
|
|
77
|
+
relativeTime: 0,
|
|
78
|
+
duration: 1200,
|
|
79
|
+
parameters: [
|
|
80
|
+
{ type: 'intensity', value: 0.2 },
|
|
81
|
+
{ type: 'sharpness', value: 0.5 },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
{
|
|
87
|
+
type: 'intensity',
|
|
88
|
+
relativeTime: 0,
|
|
89
|
+
controlPoints: [
|
|
90
|
+
{ relativeTime: 0, value: 0.2 },
|
|
91
|
+
{ relativeTime: 600, value: 1.0 },
|
|
92
|
+
{ relativeTime: 1200, value: 0.2 },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Combine transient + continuous in one pattern:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { startHaptic } from 'react-native-tickle';
|
|
103
|
+
|
|
104
|
+
startHaptic(
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
type: 'transient',
|
|
108
|
+
relativeTime: 0,
|
|
109
|
+
parameters: [
|
|
110
|
+
{ type: 'intensity', value: 1 },
|
|
111
|
+
{ type: 'sharpness', value: 0.8 },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'continuous',
|
|
116
|
+
relativeTime: 50,
|
|
117
|
+
duration: 800,
|
|
118
|
+
parameters: [
|
|
119
|
+
{ type: 'intensity', value: 0.2 },
|
|
120
|
+
{ type: 'sharpness', value: 0.4 },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
[]
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Real-time continuous mode (continuous player)
|
|
129
|
+
|
|
130
|
+
Use this when you _can't_ predefine a pattern. You start the player, update it in real time, then stop it.
|
|
131
|
+
|
|
132
|
+
**Using the hook (recommended):**
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { useContinuousPlayer } from 'react-native-tickle';
|
|
136
|
+
|
|
137
|
+
function MyComponent() {
|
|
138
|
+
const { start, stop, update } = useContinuousPlayer('my-player', 1.0, 0.5);
|
|
139
|
+
|
|
140
|
+
const gesture = Gesture.Pan()
|
|
141
|
+
.onBegin(() => {
|
|
142
|
+
start();
|
|
143
|
+
})
|
|
144
|
+
.onUpdate((e) => {
|
|
145
|
+
update(e.translationY / 100, 0.5);
|
|
146
|
+
})
|
|
147
|
+
.onEnd(() => {
|
|
148
|
+
stop();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Manual control:**
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import {
|
|
157
|
+
createContinuousPlayer,
|
|
158
|
+
startContinuousPlayer,
|
|
159
|
+
updateContinuousPlayer,
|
|
160
|
+
stopContinuousPlayer,
|
|
161
|
+
destroyContinuousPlayer,
|
|
162
|
+
} from 'react-native-tickle';
|
|
163
|
+
|
|
164
|
+
const PLAYER_ID = 'my-player';
|
|
165
|
+
|
|
166
|
+
createContinuousPlayer(PLAYER_ID, 1.0, 0.5);
|
|
167
|
+
startContinuousPlayer(PLAYER_ID);
|
|
168
|
+
updateContinuousPlayer(PLAYER_ID, 0.8, 0.1);
|
|
169
|
+
stopContinuousPlayer(PLAYER_ID);
|
|
170
|
+
destroyContinuousPlayer(PLAYER_ID);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Opt out of the provider (manual engine control)
|
|
174
|
+
|
|
175
|
+
If you don’t want the provider behavior:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { initializeEngine, destroyEngine } from 'react-native-tickle';
|
|
179
|
+
|
|
180
|
+
initializeEngine();
|
|
181
|
+
// ...
|
|
182
|
+
destroyEngine();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Stop everything (recommended in screen cleanups)
|
|
186
|
+
|
|
187
|
+
Call `stopAllHaptics()` in your cleanup functions to terminate any ongoing continuous haptics. This prevents haptics from bleeding through to the next screen when navigating.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { stopAllHaptics } from 'react-native-tickle';
|
|
191
|
+
import { useEffect } from 'react';
|
|
192
|
+
|
|
193
|
+
export function SomeScreen() {
|
|
194
|
+
useEffect(() => () => stopAllHaptics(), []);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Global enable/disable (settings toggle)
|
|
200
|
+
|
|
201
|
+
Disable haptics globally for users who prefer no haptic feedback. The setting is **persisted** across app restarts. When disabled, all haptic calls become no-ops.
|
|
202
|
+
|
|
203
|
+
**Using the hook (recommended):**
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { useHapticsEnabled } from 'react-native-tickle';
|
|
207
|
+
|
|
208
|
+
function SettingsScreen() {
|
|
209
|
+
const [hapticsEnabled, setHapticsEnabled] = useHapticsEnabled();
|
|
210
|
+
|
|
211
|
+
return <Switch value={hapticsEnabled} onValueChange={setHapticsEnabled} />;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Manual control:**
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { setHapticsEnabled, getHapticsEnabled } from 'react-native-tickle';
|
|
219
|
+
|
|
220
|
+
const isEnabled = getHapticsEnabled(); // true by default
|
|
221
|
+
setHapticsEnabled(false); // Disable all haptics
|
|
222
|
+
setHapticsEnabled(true); // Re-enable haptics
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### System haptics (predefined OS-level feedback)
|
|
226
|
+
|
|
227
|
+
While the main purpose of this package is to support AHAP-style patterns (transient + continuous haptics with curves), system haptics are also available for completeness. These are simple, predefined OS-level feedback types that don't require pattern definitions. They can be called from the UI thread and don't require the haptic engine to be initialized.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import {
|
|
231
|
+
triggerImpact,
|
|
232
|
+
triggerNotification,
|
|
233
|
+
triggerSelection,
|
|
234
|
+
} from 'react-native-tickle';
|
|
235
|
+
|
|
236
|
+
// Impact feedback - simulates a physical collision
|
|
237
|
+
triggerImpact('light'); // 'light' | 'medium' | 'heavy' | 'soft' | 'rigid'
|
|
238
|
+
|
|
239
|
+
// Notification feedback - for alerts and status updates
|
|
240
|
+
triggerNotification('success'); // 'success' | 'warning' | 'error'
|
|
241
|
+
|
|
242
|
+
// Selection feedback - for picker wheels and toggles
|
|
243
|
+
triggerSelection();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## API
|
|
247
|
+
|
|
248
|
+
| Function | Purpose |
|
|
249
|
+
| ---------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
250
|
+
| `HapticProvider` | Component that initializes engine; destroys on background |
|
|
251
|
+
| `useHapticEngine()` | Hook to manage engine lifecycle (used internally by provider) |
|
|
252
|
+
| `initializeEngine()` / `destroyEngine()` | Manual engine lifecycle |
|
|
253
|
+
| `startHaptic(events, curves)` | Play a pattern (transient + continuous events, optional curves) |
|
|
254
|
+
| `stopAllHaptics()` | Stop any running haptics (useful on unmount/navigation) |
|
|
255
|
+
| `useHapticsEnabled()` | Hook for reactive haptics enabled state |
|
|
256
|
+
| `setHapticsEnabled(enabled)` / `getHapticsEnabled()` | Manual enable/disable toggle (persisted) |
|
|
257
|
+
| `useContinuousPlayer(playerId, initialIntensity, initialSharpness)` | Hook to manage a continuous player lifecycle |
|
|
258
|
+
| `createContinuousPlayer(playerId, initialIntensity, initialSharpness)` | Create a continuous player with given ID |
|
|
259
|
+
| `startContinuousPlayer(playerId)` / `stopContinuousPlayer(playerId)` | Start/stop continuous playback for player |
|
|
260
|
+
| `updateContinuousPlayer(playerId, intensityControl, sharpnessControl)` | Update intensity/sharpness for player |
|
|
261
|
+
| `destroyContinuousPlayer(playerId)` | Destroy player and release resources |
|
|
262
|
+
| `triggerImpact(style)` | Trigger impact feedback (collision simulation) |
|
|
263
|
+
| `triggerNotification(type)` | Trigger notification feedback (alerts/status) |
|
|
264
|
+
| `triggerSelection()` | Trigger selection feedback (pickers/toggles) |
|
|
265
|
+
|
|
266
|
+
## Types (inputs)
|
|
267
|
+
|
|
268
|
+
| Type | Values |
|
|
269
|
+
| ------------------------ | ----------------------------------------------------- |
|
|
270
|
+
| `HapticParameterType` | `'intensity' \| 'sharpness'` |
|
|
271
|
+
| `HapticImpactStyle` | `'light' \| 'medium' \| 'heavy' \| 'soft' \| 'rigid'` |
|
|
272
|
+
| `HapticNotificationType` | `'success' \| 'warning' \| 'error'` |
|
|
273
|
+
|
|
274
|
+
| `HapticEventParameter` | Type |
|
|
275
|
+
| ---------------------- | --------------------- |
|
|
276
|
+
| `type` | `HapticParameterType` |
|
|
277
|
+
| `value` | `number` |
|
|
278
|
+
|
|
279
|
+
**`HapticEvent`** (discriminated union)
|
|
280
|
+
|
|
281
|
+
- **Transient:**
|
|
282
|
+
|
|
283
|
+
- `type`: `'transient'`
|
|
284
|
+
- `relativeTime`: `number`
|
|
285
|
+
- `parameters`: `HapticEventParameter[]`
|
|
286
|
+
|
|
287
|
+
- **Continuous:**
|
|
288
|
+
- `type`: `'continuous'`
|
|
289
|
+
- `relativeTime`: `number`
|
|
290
|
+
- `duration`: `number`
|
|
291
|
+
- `parameters`: `HapticEventParameter[]`
|
|
292
|
+
|
|
293
|
+
| `HapticCurveControlPoint` | Type |
|
|
294
|
+
| ------------------------- | -------- |
|
|
295
|
+
| `relativeTime` | `number` |
|
|
296
|
+
| `value` | `number` |
|
|
297
|
+
|
|
298
|
+
| `HapticCurve` | Type |
|
|
299
|
+
| --------------- | --------------------------- |
|
|
300
|
+
| `type` | `HapticCurveType` |
|
|
301
|
+
| `relativeTime` | `number` |
|
|
302
|
+
| `controlPoints` | `HapticCurveControlPoint[]` |
|
|
303
|
+
|
|
304
|
+
## Limitations
|
|
305
|
+
|
|
306
|
+
### Parameter curves affect all events in a pattern
|
|
307
|
+
|
|
308
|
+
In CoreHaptics, `CHHapticParameterCurve` uses `hapticIntensityControl` and `hapticSharpnessControl` — these are **pattern-level multipliers**, not per-event modifiers. Any curve you define will multiply the intensity/sharpness of **all events** playing at that moment, including transients.
|
|
309
|
+
|
|
310
|
+
**Example problem:**
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
startHaptic(
|
|
314
|
+
[
|
|
315
|
+
{ type: 'continuous', relativeTime: 0, duration: 2000, parameters: [...] },
|
|
316
|
+
{ type: 'transient', relativeTime: 1000, parameters: [{ type: 'intensity', value: 1.0 }, ...] },
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
{ type: 'intensity', relativeTime: 0, controlPoints: [
|
|
320
|
+
{ relativeTime: 0, value: 1.0 },
|
|
321
|
+
{ relativeTime: 1000, value: 0.3 }, // At t=1000ms, intensity control = 0.3
|
|
322
|
+
{ relativeTime: 2000, value: 0.3 },
|
|
323
|
+
]},
|
|
324
|
+
]
|
|
325
|
+
);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
The transient at `t=1000ms` has base intensity `1.0`, but the curve sets `intensityControl=0.3` at that moment. **Effective intensity: 1.0 × 0.3 = 0.3**. The transient feels weaker than expected.
|
|
329
|
+
|
|
330
|
+
**Workaround:** Play continuous and transient events in separate `startHaptic()` calls:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
// Continuous with curves
|
|
334
|
+
startHaptic(continuousEvents, curves);
|
|
335
|
+
|
|
336
|
+
// Transients without curves (separate pattern)
|
|
337
|
+
startHaptic(transientEvents, []);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Each call creates an isolated pattern/player — curves from one won't affect events in the other.
|
|
341
|
+
|
|
342
|
+
> **Note:** The library automatically resets control values to `1.0` at the end of each continuous event, so transients **after** a continuous event finishes are not affected. This limitation only applies to transients **during** a continuous event with active curves.
|
|
343
|
+
|
|
344
|
+
## Contributing
|
|
345
|
+
|
|
346
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
347
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
348
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
MIT
|
package/Tickle.podspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "Tickle"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://google.com.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"ios/**/*.{swift}",
|
|
18
|
+
"ios/**/*.{m,mm}",
|
|
19
|
+
"cpp/**/*.{hpp,cpp}",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
s.dependency 'React-jsi'
|
|
23
|
+
s.dependency 'React-callinvoker'
|
|
24
|
+
|
|
25
|
+
load 'nitrogen/generated/ios/Tickle+autolinking.rb'
|
|
26
|
+
add_nitrogen_files(s)
|
|
27
|
+
|
|
28
|
+
install_modules_dependencies(s)
|
|
29
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
project(tickle)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME tickle)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
|
|
10
|
+
|
|
11
|
+
# Add Nitrogen specs :)
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/tickle+autolinking.cmake)
|
|
13
|
+
|
|
14
|
+
# Set up local includes
|
|
15
|
+
include_directories("src/main/cpp" "../cpp")
|
|
16
|
+
|
|
17
|
+
find_library(LOG_LIB log)
|
|
18
|
+
|
|
19
|
+
# Link all libraries together
|
|
20
|
+
target_link_libraries(
|
|
21
|
+
${PACKAGE_NAME}
|
|
22
|
+
${LOG_LIB}
|
|
23
|
+
android # <-- Android core
|
|
24
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['Tickle_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def reactNativeArchitectures() {
|
|
19
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
20
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
apply plugin: "com.android.library"
|
|
24
|
+
apply plugin: "kotlin-android"
|
|
25
|
+
apply from: '../nitrogen/generated/android/tickle+autolinking.gradle'
|
|
26
|
+
|
|
27
|
+
def getExtOrIntegerDefault(name) {
|
|
28
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Tickle_" + name]).toInteger()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
android {
|
|
32
|
+
namespace "com.margelo.nitro.tickle"
|
|
33
|
+
|
|
34
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
35
|
+
|
|
36
|
+
defaultConfig {
|
|
37
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
38
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
39
|
+
|
|
40
|
+
externalNativeBuild {
|
|
41
|
+
cmake {
|
|
42
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
43
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
44
|
+
abiFilters (*reactNativeArchitectures())
|
|
45
|
+
|
|
46
|
+
buildTypes {
|
|
47
|
+
debug {
|
|
48
|
+
cppFlags "-O1 -g"
|
|
49
|
+
}
|
|
50
|
+
release {
|
|
51
|
+
cppFlags "-O2"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
externalNativeBuild {
|
|
59
|
+
cmake {
|
|
60
|
+
path "CMakeLists.txt"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
packagingOptions {
|
|
65
|
+
excludes = [
|
|
66
|
+
"META-INF",
|
|
67
|
+
"META-INF/**",
|
|
68
|
+
"**/libc++_shared.so",
|
|
69
|
+
"**/libfbjni.so",
|
|
70
|
+
"**/libjsi.so",
|
|
71
|
+
"**/libfolly_json.so",
|
|
72
|
+
"**/libfolly_runtime.so",
|
|
73
|
+
"**/libglog.so",
|
|
74
|
+
"**/libhermes.so",
|
|
75
|
+
"**/libhermes-executor-debug.so",
|
|
76
|
+
"**/libhermes_executor.so",
|
|
77
|
+
"**/libreactnative.so",
|
|
78
|
+
"**/libreactnativejni.so",
|
|
79
|
+
"**/libturbomodulejsijni.so",
|
|
80
|
+
"**/libreact_nativemodule_core.so",
|
|
81
|
+
"**/libjscexecutor.so"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
buildFeatures {
|
|
86
|
+
buildConfig true
|
|
87
|
+
prefab true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
buildTypes {
|
|
91
|
+
release {
|
|
92
|
+
minifyEnabled false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
lintOptions {
|
|
97
|
+
disable "GradleCompatible"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
compileOptions {
|
|
101
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
102
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sourceSets {
|
|
106
|
+
main {
|
|
107
|
+
java.srcDirs += [
|
|
108
|
+
"generated/java",
|
|
109
|
+
"generated/jni"
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
repositories {
|
|
116
|
+
mavenCentral()
|
|
117
|
+
google()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
121
|
+
|
|
122
|
+
dependencies {
|
|
123
|
+
implementation "com.facebook.react:react-android"
|
|
124
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
125
|
+
implementation project(":react-native-nitro-modules")
|
|
126
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package com.margelo.nitro.tickle
|
|
2
|
+
|
|
3
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
4
|
+
|
|
5
|
+
@DoNotStrip
|
|
6
|
+
class Tickle : HybridTickleSpec() {
|
|
7
|
+
override val memorySize: Long
|
|
8
|
+
get() = 0L
|
|
9
|
+
|
|
10
|
+
private var hapticsEnabled: Boolean = true
|
|
11
|
+
|
|
12
|
+
override fun startHaptic(events: Array<HapticEvent>, curves: Array<HapticCurve>) {
|
|
13
|
+
// TODO: Android haptics implementation not yet supported
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun stopAllHaptics() {
|
|
17
|
+
// TODO: Android haptics implementation not yet supported
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun initializeEngine() {
|
|
21
|
+
// TODO: Android haptics implementation not yet supported
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun destroyEngine() {
|
|
25
|
+
// TODO: Android haptics implementation not yet supported
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun createContinuousPlayer(playerId: String, initialIntensity: Double, initialSharpness: Double) {
|
|
29
|
+
// TODO: Android haptics implementation not yet supported
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun startContinuousPlayer(playerId: String) {
|
|
33
|
+
// TODO: Android haptics implementation not yet supported
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun updateContinuousPlayer(playerId: String, intensityControl: Double, sharpnessControl: Double) {
|
|
37
|
+
// TODO: Android haptics implementation not yet supported
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun stopContinuousPlayer(playerId: String) {
|
|
41
|
+
// TODO: Android haptics implementation not yet supported
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun destroyContinuousPlayer(playerId: String) {
|
|
45
|
+
// TODO: Android haptics implementation not yet supported
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: - Global Haptics Enable/Disable
|
|
49
|
+
|
|
50
|
+
override fun setHapticsEnabled(enabled: Boolean) {
|
|
51
|
+
hapticsEnabled = enabled
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun getHapticsEnabled(): Boolean {
|
|
55
|
+
return hapticsEnabled
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// MARK: - System Haptics (Predefined OS-level feedback)
|
|
59
|
+
|
|
60
|
+
override fun triggerImpact(style: HapticImpactStyle) {
|
|
61
|
+
// TODO: Android haptics implementation not yet supported
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override fun triggerNotification(type: HapticNotificationType) {
|
|
65
|
+
// TODO: Android haptics implementation not yet supported
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun triggerSelection() {
|
|
69
|
+
// TODO: Android haptics implementation not yet supported
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.margelo.nitro.tickle
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
|
|
8
|
+
class TicklePackage : BaseReactPackage() {
|
|
9
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
14
|
+
return ReactModuleInfoProvider { HashMap() }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
init {
|
|
19
|
+
System.loadLibrary("tickle")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|