UNPKG

@neuroequality/neuroadapt-mobile

Version:

Mobile accessibility features for React Native and cross-platform apps

443 lines (442 loc) 14.3 kB
import { Platform } from "react-native"; class MobileAdapter { constructor() { this.adaptations = /* @__PURE__ */ new Map(); this.platform = this.detectPlatform(); this.initializeAdaptations(); } /** * Get current platform information */ getPlatform() { return this.platform; } /** * Check if a feature is supported on current platform */ isFeatureSupported(feature) { const adaptation = this.adaptations.get(feature); if (!adaptation) return false; if (!adaptation.platformSpecific) return true; const implementation = adaptation.implementation; return !!(this.platform.name === "ios" && implementation.ios || this.platform.name === "android" && implementation.android || this.platform.name === "web" && implementation.web || implementation.universal); } /** * Apply accessibility preferences to mobile platform */ async applyPreferences(preferences) { const applicableAdaptations = this.getApplicableAdaptations(preferences); for (const adaptation of applicableAdaptations) { try { await this.executeAdaptation(adaptation); } catch (error) { console.warn(`Failed to apply adaptation ${adaptation.feature}:`, error); } } } /** * Get platform-specific recommendations */ getPlatformRecommendations(preferences) { const recommendations = []; if (this.platform.name === "ios") { if (preferences.sensory?.motionReduction) { recommendations.push({ feature: "reduce_motion", recommendation: "Enable Reduce Motion in iOS Settings > Accessibility > Motion", priority: "high", reason: "Reduces animations system-wide for better comfort" }); } if (preferences.sensory?.highContrast) { recommendations.push({ feature: "increase_contrast", recommendation: "Enable Increase Contrast in iOS Settings > Accessibility > Display & Text Size", priority: "high", reason: "Improves text readability and UI element distinction" }); } if (preferences.motor?.targetSizeIncrease && preferences.motor.targetSizeIncrease > 1.2) { recommendations.push({ feature: "button_shapes", recommendation: "Enable Button Shapes in iOS Settings > Accessibility > Display & Text Size", priority: "medium", reason: "Makes buttons more visually distinct and easier to target" }); } } if (this.platform.name === "android") { if (preferences.cognitive?.processingPace === "relaxed") { recommendations.push({ feature: "remove_animations", recommendation: "Disable animations in Android Developer Options", priority: "medium", reason: "Reduces cognitive load by removing distracting animations" }); } if (preferences.motor?.keyboardNavigation) { recommendations.push({ feature: "talkback_navigation", recommendation: "Configure TalkBack gestures for navigation", priority: "high", reason: "Enables keyboard-like navigation using gestures" }); } if (preferences.sensory?.fontSize && preferences.sensory.fontSize > 18) { recommendations.push({ feature: "large_text", recommendation: "Enable Large Text in Android Settings > Accessibility > Text and display", priority: "high", reason: "Applies text scaling system-wide" }); } } return recommendations; } /** * Test platform capabilities */ async testCapabilities() { const results = []; for (const [feature, adaptation] of this.adaptations.entries()) { try { const supported = this.isFeatureSupported(feature); let tested = false; let error; if (supported) { try { await this.executeAdaptation(adaptation, true); tested = true; } catch (testError) { error = testError instanceof Error ? testError.message : "Unknown error"; } } results.push({ feature, supported, tested, error }); } catch (error) { results.push({ feature, supported: false, tested: false, error: error instanceof Error ? error.message : "Unknown error" }); } } return results; } /** * Get accessibility information for current platform */ async getAccessibilityInfo() { return { screenReader: false, screenReaderType: this.platform.name === "ios" ? "VoiceOver" : "TalkBack", reduceMotion: false, highContrast: false, textScale: 1, isDarkMode: false, isLandscape: false }; } // Private methods detectPlatform() { const platformName = Platform.OS; const version = Platform.Version?.toString() || "0"; return { name: platformName, version, capabilities: this.getPlatformCapabilities(platformName, version) }; } getPlatformCapabilities(platform, version) { const baseCapabilities = { screenReader: true, hapticFeedback: false, voiceControl: false, reduceMotion: false, highContrast: false, boldText: false, largeText: true, grayscale: false, invertColors: false, reduceTransparency: false, buttonShapes: false, onOffLabels: false, systemWideTextScaling: true, magnifier: false, speakSelection: false, speakScreen: false, guidedAccess: false, switchControl: false, assistiveTouch: false, liveRegions: true, semanticLabels: true, customActions: true }; switch (platform) { case "ios": return { ...baseCapabilities, hapticFeedback: true, voiceControl: parseFloat(version) >= 13, reduceMotion: true, highContrast: true, boldText: true, grayscale: true, invertColors: true, reduceTransparency: true, buttonShapes: true, onOffLabels: true, magnifier: true, speakSelection: true, speakScreen: true, guidedAccess: true, switchControl: true, assistiveTouch: true }; case "android": return { ...baseCapabilities, hapticFeedback: true, voiceControl: parseFloat(version) >= 11, reduceMotion: parseFloat(version) >= 10, highContrast: parseFloat(version) >= 8, boldText: parseFloat(version) >= 8, magnifier: parseFloat(version) >= 9 }; case "web": return { ...baseCapabilities, screenReader: true, // Depends on browser and screen reader reduceMotion: true, // CSS media query support highContrast: true // CSS media query support }; default: return baseCapabilities; } } initializeAdaptations() { this.adaptations.set("reduce_motion", { type: "visual", feature: "reduce_motion", enabled: false, platformSpecific: true, implementation: { ios: this.applyReduceMotionIOS, android: this.applyReduceMotionAndroid, web: this.applyReduceMotionWeb, universal: this.applyReduceMotionUniversal } }); this.adaptations.set("high_contrast", { type: "visual", feature: "high_contrast", enabled: false, platformSpecific: true, implementation: { ios: this.applyHighContrastIOS, android: this.applyHighContrastAndroid, web: this.applyHighContrastWeb, universal: this.applyHighContrastUniversal } }); this.adaptations.set("large_text", { type: "visual", feature: "large_text", enabled: false, platformSpecific: false, implementation: { universal: this.applyLargeTextUniversal } }); this.adaptations.set("larger_targets", { type: "motor", feature: "larger_targets", enabled: false, platformSpecific: false, implementation: { universal: this.applyLargerTargetsUniversal } }); this.adaptations.set("haptic_feedback", { type: "motor", feature: "haptic_feedback", enabled: false, platformSpecific: true, implementation: { ios: this.applyHapticFeedbackIOS, android: this.applyHapticFeedbackAndroid } }); this.adaptations.set("simplified_interface", { type: "cognitive", feature: "simplified_interface", enabled: false, platformSpecific: false, implementation: { universal: this.applySimplifiedInterfaceUniversal } }); this.adaptations.set("sound_enhancement", { type: "sensory", feature: "sound_enhancement", enabled: false, platformSpecific: true, implementation: { ios: this.applySoundEnhancementIOS, android: this.applySoundEnhancementAndroid, universal: this.applySoundEnhancementUniversal } }); } getApplicableAdaptations(preferences) { const applicable = []; if (preferences.sensory?.motionReduction) { const adaptation = this.adaptations.get("reduce_motion"); if (adaptation && this.isFeatureSupported("reduce_motion")) { applicable.push({ ...adaptation, enabled: true }); } } if (preferences.sensory?.highContrast) { const adaptation = this.adaptations.get("high_contrast"); if (adaptation && this.isFeatureSupported("high_contrast")) { applicable.push({ ...adaptation, enabled: true }); } } if (preferences.sensory?.fontSize && preferences.sensory.fontSize > 16) { const adaptation = this.adaptations.get("large_text"); if (adaptation && this.isFeatureSupported("large_text")) { applicable.push({ ...adaptation, enabled: true, value: preferences.sensory.fontSize }); } } if (preferences.motor?.targetSizeIncrease && preferences.motor.targetSizeIncrease > 1) { const adaptation = this.adaptations.get("larger_targets"); if (adaptation && this.isFeatureSupported("larger_targets")) { applicable.push({ ...adaptation, enabled: true, value: preferences.motor.targetSizeIncrease }); } } if (preferences.cognitive?.processingPace === "relaxed") { const adaptation = this.adaptations.get("simplified_interface"); if (adaptation && this.isFeatureSupported("simplified_interface")) { applicable.push({ ...adaptation, enabled: true }); } } if (preferences.audio?.enableAudio) { const adaptation = this.adaptations.get("sound_enhancement"); if (adaptation && this.isFeatureSupported("sound_enhancement")) { applicable.push({ ...adaptation, enabled: true, value: preferences.audio.volume }); } } return applicable; } async executeAdaptation(adaptation, dryRun = false) { if (dryRun) { const implementation2 = adaptation.implementation; const hasImplementation = !!(this.platform.name === "ios" && implementation2.ios || this.platform.name === "android" && implementation2.android || this.platform.name === "web" && implementation2.web || implementation2.universal); if (!hasImplementation) { throw new Error(`No implementation for ${adaptation.feature} on ${this.platform.name}`); } return; } const implementation = adaptation.implementation; switch (this.platform.name) { case "ios": if (implementation.ios) { await implementation.ios.call(this); } else if (implementation.universal) { await implementation.universal.call(this); } break; case "android": if (implementation.android) { await implementation.android.call(this); } else if (implementation.universal) { await implementation.universal.call(this); } break; case "web": if (implementation.web) { await implementation.web.call(this); } else if (implementation.universal) { await implementation.universal.call(this); } break; default: if (implementation.universal) { await implementation.universal.call(this); } break; } } // Platform-specific implementation methods async applyReduceMotionIOS() { console.log("Applying iOS reduce motion settings"); } async applyReduceMotionAndroid() { console.log("Applying Android reduce motion settings"); } async applyReduceMotionWeb() { console.log("Applying web reduce motion settings"); } async applyReduceMotionUniversal() { console.log("Applying universal reduce motion settings"); } async applyHighContrastIOS() { console.log("Applying iOS high contrast settings"); } async applyHighContrastAndroid() { console.log("Applying Android high contrast settings"); } async applyHighContrastWeb() { console.log("Applying web high contrast settings"); } async applyHighContrastUniversal() { console.log("Applying universal high contrast settings"); } async applyLargeTextUniversal() { console.log("Applying universal large text settings"); } async applyLargerTargetsUniversal() { console.log("Applying universal larger targets settings"); } async applyHapticFeedbackIOS() { console.log("Applying iOS haptic feedback settings"); } async applyHapticFeedbackAndroid() { console.log("Applying Android haptic feedback settings"); } async applySimplifiedInterfaceUniversal() { console.log("Applying universal simplified interface settings"); } async applySoundEnhancementIOS() { console.log("Applying iOS sound enhancement settings"); } async applySoundEnhancementAndroid() { console.log("Applying Android sound enhancement settings"); } async applySoundEnhancementUniversal() { console.log("Applying universal sound enhancement settings"); } } export { MobileAdapter as M };