/** * Haptic feedback utilities for AR object interactions * Provides tactile responses when interacting with AR objects */ import { Platform, Vibration } from 'react-native'; // Optional: For iOS-specific haptic feedback patterns let ReactNativeHapticFeedback: any; try { ReactNativeHapticFeedback = require('react-native-haptic-feedback').default; } catch (e) { // Haptic feedback module not available, will fall back to basic vibration } /** * Haptic feedback type/intensity */ export enum ARHapticFeedbackType { LIGHT = 'light', // Light impact feedback, best for UI element selection MEDIUM = 'medium', // Medium impact feedback, best for confirming actions HEAVY = 'heavy', // Heavy impact feedback, best for major state changes SUCCESS = 'success', // Success feedback pattern WARNING = 'warning', // Warning feedback pattern ERROR = 'error', // Error feedback pattern SELECTION = 'selection', // Selection feedback (light tap sensation) } /** * Options for configuring haptic feedback */ export interface ARHapticOptions { /** * Whether haptic feedback is enabled * @default true */ enabled: boolean; /** * Intensity multiplier for the haptic feedback (0.0 - 1.0) * @default 1.0 */ intensity?: number; /** * Whether to ignore the device's system settings for haptics * When false, haptics won't trigger if device has haptics disabled * @default false */ ignoreSystemSettings?: boolean; } /** * Default haptic options */ const defaultOptions: ARHapticOptions = { enabled: true, intensity: 1.0, ignoreSystemSettings: false, }; /** * Haptic feedback manager for AR interactions */ export class ARHapticFeedback { /** * Whether haptic feedback is supported by the device/platform */ private static isSupported: boolean = Platform.OS === 'ios' || (Platform.OS === 'android' && Platform.Version >= 26); /** * Current haptic options */ private static options: ARHapticOptions = { ...defaultOptions }; /** * Sets global haptic feedback options */ static setOptions(options: Partial): void { this.options = { ...this.options, ...options, }; } /** * Triggers haptic feedback based on the provided type * @param type Type of haptic feedback to trigger * @param customOptions Optional override options for this specific feedback */ static trigger( type: ARHapticFeedbackType = ARHapticFeedbackType.MEDIUM, customOptions?: Partial ): void { // Merge global options with custom options for this call const options = { ...this.options, ...customOptions, }; // Skip if haptics are disabled if (!options.enabled || !this.isSupported) { return; } // Apply intensity (modifies duration on Android, intensity on iOS) const intensity = options.intensity || 1.0; // iOS implementation with ReactNativeHapticFeedback if (Platform.OS === 'ios' && ReactNativeHapticFeedback) { let iosType: string; switch (type) { case ARHapticFeedbackType.LIGHT: iosType = 'impactLight'; break; case ARHapticFeedbackType.MEDIUM: iosType = 'impactMedium'; break; case ARHapticFeedbackType.HEAVY: iosType = 'impactHeavy'; break; case ARHapticFeedbackType.SUCCESS: iosType = 'notificationSuccess'; break; case ARHapticFeedbackType.WARNING: iosType = 'notificationWarning'; break; case ARHapticFeedbackType.ERROR: iosType = 'notificationError'; break; case ARHapticFeedbackType.SELECTION: iosType = 'selection'; break; default: iosType = 'impactMedium'; } ReactNativeHapticFeedback.trigger(iosType, { enableVibrateFallback: true, ignoreAndroidSystemSettings: options.ignoreSystemSettings, }); return; } // Android implementation with Vibration if (Platform.OS === 'android') { // Different vibration patterns based on feedback type let duration: number; switch (type) { case ARHapticFeedbackType.LIGHT: duration = 20; break; case ARHapticFeedbackType.MEDIUM: duration = 40; break; case ARHapticFeedbackType.HEAVY: duration = 60; break; case ARHapticFeedbackType.SUCCESS: Vibration.vibrate([0, 30, 20, 50], false); return; case ARHapticFeedbackType.WARNING: Vibration.vibrate([0, 30, 30, 60], false); return; case ARHapticFeedbackType.ERROR: Vibration.vibrate([0, 50, 30, 70], false); return; case ARHapticFeedbackType.SELECTION: duration = 10; break; default: duration = 40; } // Apply intensity by modifying duration duration = Math.round(duration * intensity); Vibration.vibrate(duration); } } /** * Triggers feedback when an object is selected */ static objectSelected(): void { this.trigger(ARHapticFeedbackType.MEDIUM); } /** * Triggers feedback when an object is placed */ static objectPlaced(): void { this.trigger(ARHapticFeedbackType.SUCCESS); } /** * Triggers feedback when an object is moved */ static objectMoved(): void { this.trigger(ARHapticFeedbackType.LIGHT); } /** * Triggers feedback when an object is scaled */ static objectScaled(): void { this.trigger(ARHapticFeedbackType.LIGHT); } /** * Triggers feedback when an object is rotated */ static objectRotated(): void { this.trigger(ARHapticFeedbackType.LIGHT); } /** * Triggers feedback when an object is removed */ static objectRemoved(): void { this.trigger(ARHapticFeedbackType.MEDIUM); } /** * Triggers feedback for plane detection */ static planeDetected(): void { this.trigger(ARHapticFeedbackType.SELECTION); } /** * Triggers feedback for error conditions */ static error(): void { this.trigger(ARHapticFeedbackType.ERROR); } /** * Triggers a custom pattern of haptic feedback * @param pattern Array of durations in ms, alternating between vibration and pause * @param repeat Whether to repeat the pattern (-1 for no repeat, or index to start repeat from) */ static customPattern(pattern: number[], repeat: boolean = false): void { if (!this.options.enabled || !this.isSupported) { return; } Vibration.vibrate(pattern, repeat); } /** * Cancels any ongoing haptic feedback */ static cancel(): void { Vibration.cancel(); } }