@neuroequality/neuroadapt-mobile
Version:
Mobile accessibility features for React Native and cross-platform apps
443 lines (442 loc) • 14.3 kB
JavaScript
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
};