Skip to content

Commit fec60a6

Browse files
committed
feat: adding required types, and the iOS haptic module
1 parent 00ac827 commit fec60a6

4 files changed

Lines changed: 182 additions & 2 deletions

File tree

ios/Haptics.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
#import <HapticsSpec/HapticsSpec.h>
2+
#import <UIKit/UIKit.h>
3+
4+
NS_ASSUME_NONNULL_BEGIN
25

36
@interface Haptics : NSObject <NativeHapticsSpec>
47

8+
@property (nonatomic, strong) UISelectionFeedbackGenerator *selectionGenerator;
9+
@property (nonatomic, strong) UINotificationFeedbackGenerator *notificationGenerator;
10+
11+
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *impactStyles;
12+
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *notificationTypes;
13+
514
@end
15+
16+
NS_ASSUME_NONNULL_END

ios/Haptics.mm

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,87 @@
11
#import "Haptics.h"
2+
#import <React/RCTLog.h>
23

34
@implementation Haptics
5+
46
RCT_EXPORT_MODULE()
57

8+
+ (BOOL)requiresMainQueueSetup
9+
{
10+
return YES;
11+
}
12+
13+
- (instancetype)init
14+
{
15+
self = [super init];
16+
if (self) {
17+
_notificationGenerator = [UINotificationFeedbackGenerator new];
18+
[_notificationGenerator prepare];
19+
20+
_selectionGenerator = [UISelectionFeedbackGenerator new];
21+
[_selectionGenerator prepare];
22+
23+
_impactStyles = @{
24+
@"light": @(UIImpactFeedbackStyleLight),
25+
@"medium": @(UIImpactFeedbackStyleMedium),
26+
@"heavy": @(UIImpactFeedbackStyleHeavy),
27+
@"soft": @(UIImpactFeedbackStyleSoft),
28+
@"rigid": @(UIImpactFeedbackStyleRigid)
29+
};
30+
31+
_notificationTypes = @{
32+
@"success": @(UINotificationFeedbackTypeSuccess),
33+
@"warning": @(UINotificationFeedbackTypeWarning),
34+
@"error": @(UINotificationFeedbackTypeError)
35+
};
36+
}
37+
return self;
38+
}
39+
40+
- (void)impact:(NSString *)style
41+
resolve:(nonnull RCTPromiseResolveBlock)resolve
42+
reject:(nonnull RCTPromiseRejectBlock)reject
43+
{
44+
NSNumber *styleValue = self.impactStyles[style];
45+
46+
if (!styleValue) {
47+
reject(@"E_INVALID_STYLE", [NSString stringWithFormat:@"Invalid impact style '%@'", style], nil);
48+
return;
49+
}
50+
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc]
51+
initWithStyle:(UIImpactFeedbackStyle)styleValue.integerValue];
52+
[generator prepare];
53+
[generator impactOccurred];
54+
55+
resolve(nil);
56+
}
57+
58+
- (void)notification:(NSString *)type
59+
resolve:(nonnull RCTPromiseResolveBlock)resolve
60+
reject:(nonnull RCTPromiseRejectBlock)reject
61+
{
62+
NSNumber *typeValue = self.notificationTypes[type];
63+
64+
if (!typeValue) {
65+
reject(@"E_INVALID_TYPE", [NSString stringWithFormat:@"Invalid notification type '%@'", type], nil);
66+
return;
67+
}
68+
[self.notificationGenerator notificationOccurred:(UINotificationFeedbackType)typeValue.integerValue];
69+
resolve(nil);
70+
}
71+
72+
- (void)selection:(nonnull RCTPromiseResolveBlock)resolve
73+
reject:(nonnull RCTPromiseRejectBlock)reject {
74+
[self.selectionGenerator prepare];
75+
[self.selectionGenerator selectionChanged];
76+
resolve(nil);
77+
}
78+
79+
- (void)androidHaptics:(nonnull NSString *)type
80+
resolve:(nonnull RCTPromiseResolveBlock)resolve
81+
reject:(nonnull RCTPromiseRejectBlock)reject {
82+
resolve(nil);
83+
}
84+
685
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
786
(const facebook::react::ObjCTurboModule::InitParams &)params
887
{

src/NativeHaptics.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,53 @@
11
import type {TurboModule} from 'react-native';
22
import {TurboModuleRegistry} from 'react-native';
33

4-
export interface Spec extends TurboModule {}
4+
export type NotificationFeedback = 'error' | 'success' | 'warning';
5+
export type ImpactFeedback = 'soft' | 'light' | 'rigid' | 'heavy' | 'medium';
6+
export type AndroidHapticsFeedback =
7+
| 'reject'
8+
| 'confirm'
9+
| 'toggle-on'
10+
| 'no-haptics'
11+
| 'long-press'
12+
| 'drag-start'
13+
| 'clock-tick'
14+
| 'toggle-off'
15+
| 'virtual-key'
16+
| 'gesture-end'
17+
| 'keyboard-tap'
18+
| 'segment-tick'
19+
| 'gesture-start'
20+
| 'context-click'
21+
| 'keyboard-press'
22+
| 'text-handle-move'
23+
| 'keyboard-release'
24+
| 'virtual-key-release'
25+
| 'segment-frequent-tick';
26+
27+
export interface Spec extends TurboModule {
28+
/**
29+
* Triggers haptic feedback based on the provided selection feedback type.
30+
* @returns A promise that resolves when the haptic feedback is completed.
31+
*/
32+
selection(): Promise<void>;
33+
/**
34+
* Triggers haptic feedback based on the provided impact style.
35+
* @param style The type of impact feedback to trigger.
36+
* @returns A promise that resolves when the haptic feedback is completed.
37+
*/
38+
impact(style: ImpactFeedback): Promise<void>;
39+
/**
40+
* Triggers haptic feedback based on the provided selection feedback type.
41+
* @platform android
42+
* @returns A promise that resolves when the haptic feedback is completed.
43+
*/
44+
androidHaptics(type: AndroidHapticsFeedback): Promise<void>;
45+
/**
46+
* Triggers haptic feedback based on the provided notification type.
47+
* @param type The type of notification feedback to trigger.
48+
* @returns A promise that resolves when the haptic feedback is completed.
49+
*/
50+
notification(type: NotificationFeedback): Promise<void>;
51+
}
552

653
export default TurboModuleRegistry.getEnforcing<Spec>('Haptics');

src/index.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,44 @@
1-
export {default as Haptics} from './NativeHaptics';
1+
import NativeHaptics, {
2+
type ImpactFeedback,
3+
type NotificationFeedback,
4+
type AndroidHapticsFeedback,
5+
} from './NativeHaptics';
6+
import {Platform} from 'react-native';
7+
8+
/**
9+
* Triggers haptic feedback based on the provided impact style.
10+
* @param style The type of impact feedback to trigger.
11+
* @returns A promise that resolves when the haptic feedback is completed.
12+
*/
13+
const impact = async (style: ImpactFeedback) => {
14+
await NativeHaptics.impact(style);
15+
};
16+
/**
17+
* Triggers haptic feedback based on the provided notification type.
18+
* @param type The type of notification feedback to trigger.
19+
* @returns A promise that resolves when the haptic feedback is completed.
20+
*/
21+
const notification = async (type: NotificationFeedback) => {
22+
await NativeHaptics.notification(type);
23+
};
24+
/**
25+
* Triggers haptic feedback based on the provided selection feedback type.
26+
* @platform android
27+
* @returns A promise that resolves when the haptic feedback is completed.
28+
*/
29+
const androidHaptics = async (type: AndroidHapticsFeedback) => {
30+
if (Platform.OS !== 'android') {
31+
return;
32+
}
33+
await NativeHaptics.androidHaptics(type);
34+
};
35+
/**
36+
* Triggers haptic feedback based on the provided selection feedback type.
37+
* @returns A promise that resolves when the haptic feedback is completed.
38+
*/
39+
const selection = async () => {
40+
await NativeHaptics.selection();
41+
};
42+
43+
const Haptics = {impact, notification, androidHaptics, selection};
44+
export default Haptics;

0 commit comments

Comments
 (0)