Skip to content

Commit 23b2d0c

Browse files
Merge pull request #5 from SimformSolutionsPvtLtd/feature/UNT-T38911_unify_haptics_api
feat(UNT-T38911): Unify haptic API with awaitable playback, cross-platform checks, and doc updates
2 parents 65c4a1b + e20f3b4 commit 23b2d0c

File tree

6 files changed

+128
-58
lines changed

6 files changed

+128
-58
lines changed

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ Import and use the library in your React Native app:
4848
```jsx
4949
import { HapticPatterns } from 'react-native-haptic-patterns';
5050

51-
// Check haptic support (iOS only)
52-
await HapticPatterns.checkHapticSupportForIOS();
51+
// Check haptic support
52+
await HapticPatterns.checkForHapticSupport();
5353

5454
// Play a simple haptic pattern
5555
await HapticPatterns.playHapticPattern(200); // Vibrate for 200ms
@@ -69,17 +69,22 @@ await HapticPatterns.playRecordedPattern(recordedEvents);
6969

7070
## Methods
7171

72-
### checkHapticSupportForIOS
72+
### checkForHapticSupport
7373

7474
```ts
75-
checkHapticSupportForIOS(): Promise<boolean>
75+
checkForHapticSupport(): Promise<boolean>
7676
```
7777

78-
Checks if the device supports haptic feedback (iOS only).
78+
Checks if the device supports haptic feedback.
79+
80+
Platform behavior:
81+
82+
- **Android:** Always returns `true` as Android devices support haptic feedback through the Vibration API.
83+
- **iOS:** Queries the device's Core Haptics capabilities to determine if haptic feedback is supported.
7984

8085
Returns:
8186

82-
- A Promise that returns true if haptics are supported, false otherwise
87+
- A Promise that resolves to `true` if haptics are supported, `false` otherwise.
8388

8489
---
8590

@@ -144,7 +149,7 @@ Platform behavior:
144149

145150
Returns:
146151

147-
- A Promise that resolves when the pattern has started playing.
152+
- A Promise that resolves when the entire pattern has finished playing.
148153

149154
## Examples
150155

example/src/App.tsx

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default function App() {
1515
const [isRecording, setIsRecording] = useState(false);
1616
const [recordedEvents, setRecordedEvents] = useState<RecordedEvent[]>([]);
1717
const [hasRecording, setHasRecording] = useState(false);
18+
const [isPlayingPattern, setIsPlayingPattern] = useState(false);
1819
const recordingStartTime = useRef<number | null>(null);
1920
const recordingTimeout = useRef<any>(null);
2021

@@ -130,45 +131,59 @@ export default function App() {
130131

131132
return (
132133
<View style={styles.container}>
133-
<TouchableOpacity
134-
onPressIn={() => {
135-
HapticPatterns.playHapticPattern(maxPlayBackTime);
136-
if (isRecording) recordPress();
137-
}}
138-
onPressOut={() => {
139-
HapticPatterns.stopHapticPattern();
140-
if (isRecording) recordRelease();
141-
}}
142-
style={[styles.button, isRecording && styles.recordingActive]}>
143-
<Text>
144-
{Strings.pressToVibrate}
145-
{isRecording ? ` (${Strings.recording})` : ''}
146-
</Text>
147-
</TouchableOpacity>
148-
149-
<TouchableOpacity
150-
onPress={isRecording ? stopRecording : startRecording}
151-
style={[styles.button, isRecording && styles.recordingButton]}>
152-
<Text>
153-
{isRecording ? Strings.stopRecording : Strings.recordPattern}
154-
</Text>
155-
</TouchableOpacity>
156-
157-
<TouchableOpacity
158-
onPress={() => {
159-
if (recordedEvents.length) {
160-
HapticPatterns.playRecordedPattern(recordedEvents);
161-
}
162-
}}
163-
style={[styles.button, !hasRecording && styles.disabledButton]}
164-
disabled={!hasRecording}>
165-
<Text>{Strings.playRecordedPattern}</Text>
166-
</TouchableOpacity>
167-
168-
{hasRecording && (
169-
<TouchableOpacity onPress={resetRecording} style={styles.button}>
170-
<Text>{Strings.resetRecording}</Text>
171-
</TouchableOpacity>
134+
{isPlayingPattern ? (
135+
<View style={styles.playingMessage}>
136+
<Text style={styles.playingText}>Playing recorded pattern...</Text>
137+
</View>
138+
) : (
139+
<>
140+
<TouchableOpacity
141+
onPressIn={() => {
142+
HapticPatterns.playHapticPattern(maxPlayBackTime);
143+
if (isRecording) recordPress();
144+
}}
145+
onPressOut={() => {
146+
HapticPatterns.stopHapticPattern();
147+
if (isRecording) recordRelease();
148+
}}
149+
style={[styles.button, isRecording && styles.recordingActive]}>
150+
<Text>
151+
{Strings.pressToVibrate}
152+
{isRecording ? ` (${Strings.recording})` : ''}
153+
</Text>
154+
</TouchableOpacity>
155+
156+
<TouchableOpacity
157+
onPress={isRecording ? stopRecording : startRecording}
158+
style={[styles.button, isRecording && styles.recordingButton]}>
159+
<Text>
160+
{isRecording ? Strings.stopRecording : Strings.recordPattern}
161+
</Text>
162+
</TouchableOpacity>
163+
164+
{hasRecording && (
165+
<TouchableOpacity
166+
onPress={() => {
167+
setIsPlayingPattern(true);
168+
HapticPatterns.playRecordedPattern(recordedEvents).then(() => {
169+
setIsPlayingPattern(false);
170+
});
171+
}}
172+
style={[styles.button, isRecording && styles.disabledButton]}
173+
disabled={isRecording}>
174+
<Text>{Strings.playRecordedPattern}</Text>
175+
</TouchableOpacity>
176+
)}
177+
178+
{hasRecording && (
179+
<TouchableOpacity
180+
onPress={resetRecording}
181+
style={[styles.button, isRecording && styles.disabledButton]}
182+
disabled={isRecording}>
183+
<Text>{Strings.resetRecording}</Text>
184+
</TouchableOpacity>
185+
)}
186+
</>
172187
)}
173188
</View>
174189
);
@@ -193,7 +208,7 @@ const styles = StyleSheet.create({
193208
},
194209
disabledButton: {
195210
backgroundColor: '#cccccc',
196-
opacity: 0.7,
211+
opacity: 0.25,
197212
},
198213
box: {
199214
width: 60,
@@ -204,4 +219,15 @@ const styles = StyleSheet.create({
204219
borderWidth: 2,
205220
borderColor: '#ff6b6b',
206221
},
222+
playingMessage: {
223+
alignItems: 'center',
224+
justifyContent: 'center',
225+
padding: 20,
226+
},
227+
playingText: {
228+
fontSize: 18,
229+
fontWeight: 'bold',
230+
color: '#333',
231+
textAlign: 'center',
232+
},
207233
});

src/haptic-patterns/HapticPatterns.android.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ export const playHapticPattern = (
1919
Vibration.vibrate(vibrationDuration ?? DEFAULT_VIBRATION_DURATION_IN_MS);
2020
};
2121

22+
/**
23+
* Checks if the device supports haptic feedback
24+
* @returns {Promise<boolean>} - Always returns true as Android devices support haptics.
25+
*/
26+
export const checkForHapticSupport = async (): Promise<boolean> => {
27+
// On Android, the haptic support is always available
28+
return true;
29+
};
30+
2231
/**
2332
* Stops any ongoing haptic vibration.
2433
*/
@@ -30,8 +39,9 @@ export const stopHapticPattern = () => {
3039
* Plays a recorded haptic pattern based on an array of recorded events.
3140
*
3241
* @param {RecordedEventType[]} recordedEvents - Array of recorded haptic events to play as a pattern.
42+
* @returns {Promise<void>} Resolves when the entire vibration pattern has finished playing.
3343
*/
34-
export const playRecordedPattern = (recordedEvents: RecordedEventType[]) => {
44+
export const playRecordedPattern = async (recordedEvents: RecordedEventType[]): Promise<void> => {
3545
if (recordedEvents.length === 0) {
3646
return;
3747
}
@@ -66,5 +76,16 @@ export const playRecordedPattern = (recordedEvents: RecordedEventType[]) => {
6676
}
6777
}
6878

79+
// Calculate total pattern duration
80+
const totalDuration = androidPattern.reduce((sum, duration) => sum + duration, 0);
81+
82+
// Start the vibration pattern
6983
Vibration.vibrate(androidPattern);
84+
85+
// Return a promise that resolves after the pattern completes
86+
return new Promise((resolve) => {
87+
setTimeout(() => {
88+
resolve();
89+
}, totalDuration);
90+
});
7091
};

src/haptic-patterns/HapticPatterns.ios.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,17 @@ const HapticEngine: HapticEngineModuleType = NativeModules.HapticEngine
3030
);
3131

3232
/**
33-
* Checks if the device supports haptic feedback (iOS only).
34-
* @async
33+
* Checks if the device supports haptic feedback.
3534
* @returns {Promise<boolean>} returns true if haptics are supported, false otherwise
3635
*/
37-
export const checkHapticSupportForIOS = async (): Promise<boolean> => {
36+
export const checkForHapticSupport = async (): Promise<boolean> => {
3837
const capabilities = await HapticEngine.getDeviceCapabilities();
3938

4039
return capabilities.supportsHaptics;
4140
};
4241

4342
/**
44-
* Helper function to initialize the HapticEngine, create a player, and play the pattern (iOS only).
43+
* Helper function to initialize the HapticEngine, create a player, and play the pattern.
4544
* @async
4645
* @param {HapticPatternType} pattern - The haptic pattern to play.
4746
* @returns {Promise<void>}
@@ -56,7 +55,7 @@ export const playHapticPattern = async (
5655
// vibrationDuration will be in milliseconds
5756
vibrationDuration: number
5857
) => {
59-
const isSupported = await checkHapticSupportForIOS();
58+
const isSupported = await checkForHapticSupport();
6059
if (!isSupported) {
6160
throw new Error('Device does not support haptics');
6261
}
@@ -92,7 +91,7 @@ export const playHapticPattern = async (
9291
* @throws {Error} If the device does not support haptics or stopping fails.
9392
*/
9493
export const stopHapticPattern = async () => {
95-
const isSupported = await checkHapticSupportForIOS();
94+
const isSupported = await checkForHapticSupport();
9695
if (!isSupported) {
9796
throw new Error('Device does not support haptics');
9897
}
@@ -108,13 +107,13 @@ export const stopHapticPattern = async () => {
108107
*
109108
* @async
110109
* @param {RecordedEventType[]} recordedEvents - An array of recorded haptic events, each with startTime, endTime, and isPause properties.
111-
* @returns {Promise<void>} Resolves when the haptic pattern has finished playing or if no events are provided.
110+
* @returns {Promise<void>} Resolves when the entire haptic pattern has finished playing or if no events are provided.
112111
* @throws {Error} If playing the pattern fails.
113112
*/
114113
export const playRecordedPattern = async (
115114
recordedEvents: RecordedEventType[]
116115
) => {
117-
const isSupported = await checkHapticSupportForIOS();
116+
const isSupported = await checkForHapticSupport();
118117
if (!isSupported) {
119118
throw new Error('Device does not support haptics');
120119
}
@@ -171,9 +170,19 @@ export const playRecordedPattern = async (
171170
}
172171
});
173172

173+
// Calculate total pattern duration in milliseconds
174+
const totalDurationMs = time * 1000;
175+
174176
try {
175177
await initializeAndPlayHapticPattern({ hapticEvents });
178+
179+
// Return a promise that resolves after the pattern completes
180+
return new Promise<void>(resolve => {
181+
setTimeout(() => {
182+
resolve();
183+
}, totalDurationMs);
184+
});
176185
} catch (error) {
177-
console.error('Failed to play pattern', error);
186+
throw error;
178187
}
179188
};

src/haptic-patterns/HapticPatternsTypes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,10 @@ export type HapticEngineModuleType = {
9898
): Promise<boolean>;
9999
stop(): Promise<boolean>;
100100
};
101+
102+
export interface HapticPatternsModuleType {
103+
checkForHapticSupport: () => Promise<boolean>;
104+
playHapticPattern: (vibrationDuration: number) => void;
105+
stopHapticPattern: () => Promise<void>;
106+
playRecordedPattern: (recordedEvents: RecordedEventType[]) => Promise<void>;
107+
}

src/haptic-patterns/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const HapticPatterns = require('./HapticPatterns');
1+
import type { HapticPatternsModuleType } from './HapticPatternsTypes';
2+
3+
const HapticPatterns: HapticPatternsModuleType = require('./HapticPatterns');
24

35
export * from './HapticPatternsTypes';
46
export default HapticPatterns;

0 commit comments

Comments
 (0)