diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index 5cacc802f0..f42d5a7e53 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -56,7 +56,6 @@ "react-native-blob-util": "^0.22.2", "react-native-fast-image": "^8.6.3", "react-native-gesture-handler": "^2.31.0", - "react-native-haptic-feedback": "^2.3.3", "react-native-image-picker": "^8.2.1", "react-native-maps": "1.20.1", "react-native-nitro-modules": "^0.31.3", diff --git a/examples/TypeScriptMessaging/package.json b/examples/TypeScriptMessaging/package.json index 6dba7c5ad9..cea0838c78 100644 --- a/examples/TypeScriptMessaging/package.json +++ b/examples/TypeScriptMessaging/package.json @@ -28,7 +28,6 @@ "react-native-audio-recorder-player": "^3.6.13", "react-native-blob-util": "^0.22.2", "react-native-gesture-handler": "^2.26.0", - "react-native-haptic-feedback": "^2.3.3", "react-native-image-picker": "^8.2.1", "react-native-reanimated": "^4.0.1", "react-native-safe-area-context": "^5.4.1", diff --git a/package/expo-package/android/src/main/java/com/streamchatexpo/StreamChatExpoPackage.java b/package/expo-package/android/src/main/java/com/streamchatexpo/StreamChatExpoPackage.java index 20fa4cab28..c92f5703a3 100644 --- a/package/expo-package/android/src/main/java/com/streamchatexpo/StreamChatExpoPackage.java +++ b/package/expo-package/android/src/main/java/com/streamchatexpo/StreamChatExpoPackage.java @@ -15,11 +15,14 @@ public class StreamChatExpoPackage extends TurboReactPackage { private static final String STREAM_VIDEO_THUMBNAIL_MODULE = "StreamVideoThumbnail"; + private static final String STREAM_HAPTIC_FEEDBACK_MODULE = "StreamHapticFeedback"; @Nullable @Override public NativeModule getModule(String name, ReactApplicationContext reactContext) { - if (name.equals(STREAM_VIDEO_THUMBNAIL_MODULE) && BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + if (name.equals(STREAM_HAPTIC_FEEDBACK_MODULE)) { + return new StreamHapticFeedbackModule(reactContext); + } else if (name.equals(STREAM_VIDEO_THUMBNAIL_MODULE) && BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { return createNewArchModule("com.streamchatexpo.StreamVideoThumbnailModule", reactContext); } @@ -31,6 +34,17 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { return () -> { final Map moduleInfos = new HashMap<>(); boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + moduleInfos.put( + STREAM_HAPTIC_FEEDBACK_MODULE, + new ReactModuleInfo( + STREAM_HAPTIC_FEEDBACK_MODULE, + STREAM_HAPTIC_FEEDBACK_MODULE, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // hasConstants + false, // isCxxModule + false // isTurboModule + )); moduleInfos.put( STREAM_VIDEO_THUMBNAIL_MODULE, new ReactModuleInfo( diff --git a/package/expo-package/android/src/main/java/com/streamchatexpo/StreamHapticFeedbackModule.kt b/package/expo-package/android/src/main/java/com/streamchatexpo/StreamHapticFeedbackModule.kt new file mode 100644 index 0000000000..b11713adae --- /dev/null +++ b/package/expo-package/android/src/main/java/com/streamchatexpo/StreamHapticFeedbackModule.kt @@ -0,0 +1,21 @@ +package com.streamchatexpo + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.streamchatreactnative.shared.StreamHapticFeedback + +class StreamHapticFeedbackModule( + reactContext: ReactApplicationContext, +) : ReactContextBaseJavaModule(reactContext) { + override fun getName(): String = NAME + + @ReactMethod + fun triggerHaptic(type: String) { + StreamHapticFeedback.trigger(currentActivity, type) + } + + companion object { + const val NAME = "StreamHapticFeedback" + } +} diff --git a/package/expo-package/package.json b/package/expo-package/package.json index 03dba3e651..2cc7fd7462 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -36,7 +36,6 @@ "expo-clipboard": "*", "expo-document-picker": "*", "expo-file-system": "*", - "expo-haptics": "*", "expo-image-manipulator": "*", "expo-image-picker": "*", "expo-media-library": "*", @@ -74,9 +73,6 @@ }, "expo-sharing": { "optional": true - }, - "expo-haptics": { - "optional": true } }, "devDependencies": { diff --git a/package/expo-package/src/optionalDependencies/triggerHaptic.ts b/package/expo-package/src/optionalDependencies/triggerHaptic.ts index fbe8cae4cd..880f160a15 100644 --- a/package/expo-package/src/optionalDependencies/triggerHaptic.ts +++ b/package/expo-package/src/optionalDependencies/triggerHaptic.ts @@ -1,48 +1,9 @@ -let Haptics; +import { NativeModules } from 'react-native'; -try { - Haptics = require('expo-haptics'); -} catch (e) { - // do nothing -} +const { StreamHapticFeedback } = NativeModules; -if (!Haptics) { - console.log( - 'expo-haptics is not installed. Installing this package will enable haptic feedback when scaling images in the image gallery if the scaling hits the higher or lower limits for its value.', - ); -} - -type HapticFeedbackTypes = - | 'impactHeavy' - | 'impactLight' - | 'impactMedium' - | 'notificationError' - | 'notificationSuccess' - | 'notificationWarning'; - -export const triggerHaptic = Haptics - ? (method: HapticFeedbackTypes) => { - switch (method) { - case 'impactHeavy': - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); - break; - case 'impactLight': - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - break; - case 'impactMedium': - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); - break; - case 'notificationError': - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - break; - case 'notificationSuccess': - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - break; - case 'notificationWarning': - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); - break; - default: - Haptics.selectionAsync(); - } +export const triggerHaptic = StreamHapticFeedback + ? (method: string) => { + StreamHapticFeedback.triggerHaptic(method); } : () => {}; diff --git a/package/native-package/android/src/main/java/com/streamchatreactnative/StreamChatReactNativePackage.java b/package/native-package/android/src/main/java/com/streamchatreactnative/StreamChatReactNativePackage.java index ec32749c90..140e90d608 100644 --- a/package/native-package/android/src/main/java/com/streamchatreactnative/StreamChatReactNativePackage.java +++ b/package/native-package/android/src/main/java/com/streamchatreactnative/StreamChatReactNativePackage.java @@ -15,12 +15,15 @@ public class StreamChatReactNativePackage extends TurboReactPackage { private static final String STREAM_VIDEO_THUMBNAIL_MODULE = "StreamVideoThumbnail"; + private static final String STREAM_HAPTIC_FEEDBACK_MODULE = "StreamHapticFeedback"; @Nullable @Override public NativeModule getModule(String name, ReactApplicationContext reactContext) { if (name.equals(StreamChatReactNativeModule.NAME)) { return new StreamChatReactNativeModule(reactContext); + } else if (name.equals(STREAM_HAPTIC_FEEDBACK_MODULE)) { + return new StreamHapticFeedbackModule(reactContext); } else if (name.equals(STREAM_VIDEO_THUMBNAIL_MODULE) && BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { return createNewArchModule( "com.streamchatreactnative.StreamVideoThumbnailModule", @@ -47,6 +50,17 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { false, // isCxxModule isTurboModule // isTurboModule )); + moduleInfos.put( + STREAM_HAPTIC_FEEDBACK_MODULE, + new ReactModuleInfo( + STREAM_HAPTIC_FEEDBACK_MODULE, + STREAM_HAPTIC_FEEDBACK_MODULE, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // hasConstants + false, // isCxxModule + false // isTurboModule + )); moduleInfos.put( STREAM_VIDEO_THUMBNAIL_MODULE, new ReactModuleInfo( diff --git a/package/native-package/android/src/main/java/com/streamchatreactnative/StreamHapticFeedbackModule.kt b/package/native-package/android/src/main/java/com/streamchatreactnative/StreamHapticFeedbackModule.kt new file mode 100644 index 0000000000..1785d66074 --- /dev/null +++ b/package/native-package/android/src/main/java/com/streamchatreactnative/StreamHapticFeedbackModule.kt @@ -0,0 +1,21 @@ +package com.streamchatreactnative + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.streamchatreactnative.shared.StreamHapticFeedback + +class StreamHapticFeedbackModule( + reactContext: ReactApplicationContext, +) : ReactContextBaseJavaModule(reactContext) { + override fun getName(): String = NAME + + @ReactMethod + fun triggerHaptic(type: String) { + StreamHapticFeedback.trigger(currentActivity, type) + } + + companion object { + const val NAME = "StreamHapticFeedback" + } +} diff --git a/package/native-package/package.json b/package/native-package/package.json index 6fa36c871b..a8ad84c30a 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -40,7 +40,6 @@ "react-native-audio-recorder-player": ">=3.6.13", "react-native-nitro-sound": ">=0.2.9", "react-native-blob-util": ">=0.22.0", - "react-native-haptic-feedback": ">=2.3.0", "react-native-image-picker": ">=7.1.2", "react-native-share": ">=11.0.0", "react-native-video": ">=6.18.0" @@ -59,9 +58,6 @@ "@react-native-documents/picker": { "optional": true }, - "react-native-haptic-feedback": { - "optional": true - }, "react-native-image-picker": { "optional": true }, diff --git a/package/native-package/src/optionalDependencies/triggerHaptic.ts b/package/native-package/src/optionalDependencies/triggerHaptic.ts index 696717cd84..880f160a15 100644 --- a/package/native-package/src/optionalDependencies/triggerHaptic.ts +++ b/package/native-package/src/optionalDependencies/triggerHaptic.ts @@ -1,46 +1,9 @@ -let ReactNativeHapticFeedback; +import { NativeModules } from 'react-native'; -try { - ReactNativeHapticFeedback = require('react-native-haptic-feedback').default; -} catch (e) { - console.warn('react-native-haptic-feedback is not installed.'); -} +const { StreamHapticFeedback } = NativeModules; -/** - * Since react-native-haptic-feedback isn't installed by default, we've - * copied the types from the package here. - * - * @see https://github.com/junina-de/react-native-haptic-feedback/blob/master/index.d.ts - * */ -export type HapticFeedbackTypes = - | 'selection' - | 'impactLight' - | 'impactMedium' - | 'impactHeavy' - | 'rigid' - | 'soft' - | 'notificationSuccess' - | 'notificationWarning' - | 'notificationError' - | 'clockTick' - | 'contextClick' - | 'keyboardPress' - | 'keyboardRelease' - | 'keyboardTap' - | 'longPress' - | 'textHandleMove' - | 'virtualKey' - | 'virtualKeyRelease' - | 'effectClick' - | 'effectDoubleClick' - | 'effectHeavyClick' - | 'effectTick'; - -export const triggerHaptic = ReactNativeHapticFeedback - ? (method: HapticFeedbackTypes) => { - ReactNativeHapticFeedback.trigger(method, { - enableVibrateFallback: false, - ignoreAndroidSystemSettings: false, - }); +export const triggerHaptic = StreamHapticFeedback + ? (method: string) => { + StreamHapticFeedback.triggerHaptic(method); } : () => {}; diff --git a/package/shared-native/android/StreamHapticFeedback.kt b/package/shared-native/android/StreamHapticFeedback.kt new file mode 100644 index 0000000000..6e2688f0ba --- /dev/null +++ b/package/shared-native/android/StreamHapticFeedback.kt @@ -0,0 +1,29 @@ +package com.streamchatreactnative.shared + +import android.app.Activity +import android.os.Build +import android.view.HapticFeedbackConstants + +object StreamHapticFeedback { + fun trigger(activity: Activity?, type: String) { + val view = activity?.window?.decorView ?: return + val constant = when (type) { + "impactLight" -> HapticFeedbackConstants.KEYBOARD_TAP + "impactMedium" -> HapticFeedbackConstants.VIRTUAL_KEY + "impactHeavy" -> HapticFeedbackConstants.LONG_PRESS + "notificationSuccess" -> if (Build.VERSION.SDK_INT >= 30) { + HapticFeedbackConstants.CONFIRM + } else { + HapticFeedbackConstants.VIRTUAL_KEY + } + "notificationWarning" -> HapticFeedbackConstants.VIRTUAL_KEY + "notificationError" -> if (Build.VERSION.SDK_INT >= 30) { + HapticFeedbackConstants.REJECT + } else { + HapticFeedbackConstants.LONG_PRESS + } + else -> HapticFeedbackConstants.CLOCK_TICK + } + view.performHapticFeedback(constant, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) + } +} diff --git a/package/shared-native/ios/StreamHapticFeedback.swift b/package/shared-native/ios/StreamHapticFeedback.swift new file mode 100644 index 0000000000..5ac769bec9 --- /dev/null +++ b/package/shared-native/ios/StreamHapticFeedback.swift @@ -0,0 +1,23 @@ +import UIKit + +@objcMembers +public final class StreamHapticFeedback: NSObject { + public static func trigger(_ type: String) { + switch type { + case "impactLight": + UIImpactFeedbackGenerator(style: .light).impactOccurred() + case "impactMedium": + UIImpactFeedbackGenerator(style: .medium).impactOccurred() + case "impactHeavy": + UIImpactFeedbackGenerator(style: .heavy).impactOccurred() + case "notificationSuccess": + UINotificationFeedbackGenerator().notificationOccurred(.success) + case "notificationWarning": + UINotificationFeedbackGenerator().notificationOccurred(.warning) + case "notificationError": + UINotificationFeedbackGenerator().notificationOccurred(.error) + default: + UISelectionFeedbackGenerator().selectionChanged() + } + } +} diff --git a/package/shared-native/ios/StreamHapticFeedbackModule.h b/package/shared-native/ios/StreamHapticFeedbackModule.h new file mode 100644 index 0000000000..541ba543f5 --- /dev/null +++ b/package/shared-native/ios/StreamHapticFeedbackModule.h @@ -0,0 +1,4 @@ +#import + +@interface StreamHapticFeedbackModule : NSObject +@end diff --git a/package/shared-native/ios/StreamHapticFeedbackModule.m b/package/shared-native/ios/StreamHapticFeedbackModule.m new file mode 100644 index 0000000000..63fa453b93 --- /dev/null +++ b/package/shared-native/ios/StreamHapticFeedbackModule.m @@ -0,0 +1,31 @@ +#import "StreamHapticFeedbackModule.h" + +#if __has_include() +#import +#elif __has_include() +#import +#elif __has_include("stream_chat_react_native-Swift.h") +#import "stream_chat_react_native-Swift.h" +#elif __has_include("stream_chat_expo-Swift.h") +#import "stream_chat_expo-Swift.h" +#else +#error "Unable to import generated Swift header for StreamHapticFeedback." +#endif + +@implementation StreamHapticFeedbackModule + +RCT_EXPORT_MODULE(StreamHapticFeedback) + +RCT_EXPORT_METHOD(triggerHaptic:(NSString *)type) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [StreamHapticFeedback trigger:type]; + }); +} + ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + +@end