@accessibility-rn-js/react-native-accessibility-toolkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHECKLIST.md +228 -0
- package/INSTALLATION_GUIDE.md +270 -0
- package/PLUGIN_STATUS.md +206 -0
- package/README.md +251 -0
- package/package.json +49 -0
- package/setup.sh +40 -0
- package/src/components/AccessibilityButton.js +56 -0
- package/src/components/AccessibleText.js +50 -0
- package/src/context/AccessibilityContext.js +125 -0
- package/src/hooks/useDynamicColors.js +32 -0
- package/src/hooks/usePageRead.js +75 -0
- package/src/hooks/useThemeColors.js +169 -0
- package/src/index.js +49 -0
- package/src/services/AccessibilityStorage.js +40 -0
- package/src/services/NativeAccessibilityBridge.js +171 -0
- package/src/services/TTSService.js +117 -0
- package/src/utils/AccessibilityUtils.js +223 -0
- package/src/utils/Colors.js +203 -0
- package/src/utils/Fonts.js +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# React Native Accessibility Toolkit
|
|
2
|
+
|
|
3
|
+
A comprehensive, production-ready accessibility plugin for React Native applications. This toolkit provides features like text-to-speech, screen reader support, dynamic color adjustments, text scaling, and more to make your React Native apps accessible to all users.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **🔊 Text-to-Speech (TTS)** - Read content aloud with customizable voice controls
|
|
8
|
+
- **📱 Native Accessibility** - TalkBack (Android) & VoiceOver (iOS) integration
|
|
9
|
+
- **🎨 Dynamic Colors** - High contrast, grayscale, color inversion, saturation controls
|
|
10
|
+
- **📏 Text Controls** - Font scaling, line height, letter spacing adjustments
|
|
11
|
+
- **👁️ Visual Aids** - Hide images, enlarge buttons, reduced motion
|
|
12
|
+
- **📖 Reading Features** - Text alignment, reading guides, text magnification
|
|
13
|
+
- **🎯 Accessibility Profiles** - Pre-configured settings for specific needs
|
|
14
|
+
- **💾 Persistent Storage** - Automatically saves user preferences
|
|
15
|
+
- **🌐 Cross-Platform** - Works on both iOS and Android
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @yourorg/react-native-accessibility-toolkit
|
|
21
|
+
# or
|
|
22
|
+
yarn add @yourorg/react-native-accessibility-toolkit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Install Peer Dependencies
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install react-native-tts @react-native-async-storage/async-storage react-native-vector-icons
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### iOS Setup
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd ios && pod install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Add to your `Info.plist`:
|
|
38
|
+
```xml
|
|
39
|
+
<key>NSSpeechRecognitionUsageDescription</key>
|
|
40
|
+
<string>This app uses speech recognition for accessibility features</string>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Android Setup
|
|
44
|
+
|
|
45
|
+
For react-native-vector-icons, add to `android/app/build.gradle`:
|
|
46
|
+
```gradle
|
|
47
|
+
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 🚀 Quick Start
|
|
51
|
+
|
|
52
|
+
### 1. Wrap Your App
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
import { AccessibilityProvider } from '@yourorg/react-native-accessibility-toolkit';
|
|
56
|
+
|
|
57
|
+
export default function App() {
|
|
58
|
+
return (
|
|
59
|
+
<AccessibilityProvider>
|
|
60
|
+
{/* Your app content */}
|
|
61
|
+
</AccessibilityProvider>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Add Accessibility Button
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
import { AccessibilityButton } from '@yourorg/react-native-accessibility-toolkit';
|
|
70
|
+
|
|
71
|
+
function MyScreen() {
|
|
72
|
+
return (
|
|
73
|
+
<View>
|
|
74
|
+
{/* Your content */}
|
|
75
|
+
<AccessibilityButton />
|
|
76
|
+
</View>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Use Accessible Components
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
import { AccessibleText, useAccessibility } from '@yourorg/react-native-accessibility-toolkit';
|
|
85
|
+
|
|
86
|
+
function MyComponent() {
|
|
87
|
+
const { fontScale, textColor } = useAccessibility();
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<AccessibleText baseFontSize={16}>
|
|
91
|
+
This text respects accessibility settings!
|
|
92
|
+
</AccessibleText>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 📖 API Documentation
|
|
98
|
+
|
|
99
|
+
### Core Hooks
|
|
100
|
+
|
|
101
|
+
#### `useAccessibility()`
|
|
102
|
+
```javascript
|
|
103
|
+
const {
|
|
104
|
+
fontScale, // Current font scale (1.0 - 2.0)
|
|
105
|
+
textColor, // Custom text color
|
|
106
|
+
backgroundColor, // Custom background color
|
|
107
|
+
updateSetting, // Update single setting
|
|
108
|
+
setProfile, // Apply accessibility profile
|
|
109
|
+
openModal, // Open accessibility settings
|
|
110
|
+
} = useAccessibility();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### `useDynamicColors()`
|
|
114
|
+
```javascript
|
|
115
|
+
const colors = useDynamicColors();
|
|
116
|
+
// Returns: { primaryTextColor, defaultBackground, primaryButtonColor, etc. }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### `usePageRead(textContent)`
|
|
120
|
+
```javascript
|
|
121
|
+
const { start, pause, resume, stop, isSpeaking } = usePageRead("Text to read");
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Components
|
|
125
|
+
|
|
126
|
+
- **`<AccessibleText>`** - Auto-scales and styles text
|
|
127
|
+
- **`<AccessibilityButton>`** - Floating accessibility menu button
|
|
128
|
+
|
|
129
|
+
### Services
|
|
130
|
+
|
|
131
|
+
- **`TTSService`** - Text-to-speech functionality
|
|
132
|
+
- **`NativeAccessibilityBridge`** - Native platform integration
|
|
133
|
+
|
|
134
|
+
### Profiles
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
import { ACCESSIBILITY_PROFILES } from '@yourorg/react-native-accessibility-toolkit';
|
|
138
|
+
|
|
139
|
+
// Available profiles:
|
|
140
|
+
ACCESSIBILITY_PROFILES.BLIND // Screen reader optimized
|
|
141
|
+
ACCESSIBILITY_PROFILES.DYSLEXIA // Increased spacing, reading aids
|
|
142
|
+
ACCESSIBILITY_PROFILES.LOW_VISION // Maximum text, high contrast
|
|
143
|
+
ACCESSIBILITY_PROFILES.COGNITIVE // Simplified, clear layout
|
|
144
|
+
ACCESSIBILITY_PROFILES.EPILEPSY_SAFE // No animations, reduced saturation
|
|
145
|
+
ACCESSIBILITY_PROFILES.ADHD_FOCUS // Reading mask, minimal distractions
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 🎨 Customization
|
|
149
|
+
|
|
150
|
+
### Update Individual Settings
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const { updateSetting } = useAccessibility();
|
|
154
|
+
|
|
155
|
+
updateSetting('fontScale', 1.5);
|
|
156
|
+
updateSetting('colorTheme', 'dark');
|
|
157
|
+
updateSetting('textColor', '#FF0000');
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Apply Complete Profile
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const { setProfile } = useAccessibility();
|
|
164
|
+
|
|
165
|
+
setProfile(ACCESSIBILITY_PROFILES.DYSLEXIA);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 🛠️ Advanced Usage
|
|
169
|
+
|
|
170
|
+
### Custom Styling with Accessibility
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
import { useAccessibility, getAdjustedFontSize } from '@yourorg/react-native-accessibility-toolkit';
|
|
174
|
+
|
|
175
|
+
function CustomComponent() {
|
|
176
|
+
const { fontScale, backgroundColor, textColor } = useAccessibility();
|
|
177
|
+
|
|
178
|
+
const styles = StyleSheet.create({
|
|
179
|
+
container: {
|
|
180
|
+
backgroundColor: backgroundColor || '#FFFFFF',
|
|
181
|
+
},
|
|
182
|
+
text: {
|
|
183
|
+
fontSize: getAdjustedFontSize(16, fontScale),
|
|
184
|
+
color: textColor || '#000000',
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return <View style={styles.container}>...</View>;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Screen Reader Announcements
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
import { NativeAccessibilityBridge } from '@yourorg/react-native-accessibility-toolkit';
|
|
196
|
+
|
|
197
|
+
// Announce important messages
|
|
198
|
+
NativeAccessibilityBridge.announce("Form submitted successfully");
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 📱 Platform Support
|
|
202
|
+
|
|
203
|
+
- **iOS**: 12.4+
|
|
204
|
+
- **Android**: API 21+
|
|
205
|
+
- **React Native**: 0.60.0+
|
|
206
|
+
|
|
207
|
+
## 🤝 Before Publishing
|
|
208
|
+
|
|
209
|
+
### Update Package Name
|
|
210
|
+
|
|
211
|
+
Replace `@yourorg/react-native-accessibility-toolkit` with your actual npm organization/package name in:
|
|
212
|
+
- `package.json`
|
|
213
|
+
- This README
|
|
214
|
+
- Example code
|
|
215
|
+
|
|
216
|
+
### Update Author & Repository
|
|
217
|
+
|
|
218
|
+
Edit `package.json`:
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"author": "Your Name <your.email@example.com>",
|
|
222
|
+
"repository": {
|
|
223
|
+
"url": "https://github.com/yourusername/your-repo.git"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Publishing to NPM
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
cd PLUGIN_SETUP
|
|
232
|
+
npm login
|
|
233
|
+
npm publish --access public
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 📄 License
|
|
237
|
+
|
|
238
|
+
MIT
|
|
239
|
+
|
|
240
|
+
## 🙏 Acknowledgments
|
|
241
|
+
|
|
242
|
+
Built following WCAG 2.1 accessibility guidelines to ensure apps work for everyone.
|
|
243
|
+
|
|
244
|
+
## 📞 Support
|
|
245
|
+
|
|
246
|
+
- Issues: https://github.com/yourorg/react-native-accessibility-toolkit/issues
|
|
247
|
+
- Documentation: Full API docs available in the repository
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
Made with ❤️ for accessibility
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@accessibility-rn-js/react-native-accessibility-toolkit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A comprehensive accessibility toolkit for React Native applications with TTS, screen reader, color adjustments, and more",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"lint": "eslint ."
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"react-native",
|
|
12
|
+
"accessibility",
|
|
13
|
+
"a11y",
|
|
14
|
+
"screen-reader",
|
|
15
|
+
"tts",
|
|
16
|
+
"text-to-speech",
|
|
17
|
+
"wcag",
|
|
18
|
+
"inclusive-design"
|
|
19
|
+
],
|
|
20
|
+
"author": "Jayesh Gupta <jayeshgupta3502@gmail.com>",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/accessibility-rn-js/react-native-accessibility-toolkit.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/accessibility-rn-js/react-native-accessibility-toolkit/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/accessibility-rn-js/react-native-accessibility-toolkit#readme",
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": ">=16.8.0",
|
|
32
|
+
"react-native": ">=0.60.0",
|
|
33
|
+
"react-native-vector-icons": ">=9.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"react-native-tts": "^4.1.0",
|
|
37
|
+
"@react-native-async-storage/async-storage": "^1.19.0",
|
|
38
|
+
"react-native-draggable": "^3.3.0",
|
|
39
|
+
"@miblanchard/react-native-slider": "^2.3.1",
|
|
40
|
+
"react-native-element-dropdown": "^2.10.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/core": "^7.20.0",
|
|
44
|
+
"@babel/preset-env": "^7.20.0",
|
|
45
|
+
"@babel/runtime": "^7.20.0",
|
|
46
|
+
"eslint": "^8.19.0",
|
|
47
|
+
"jest": "^29.2.1"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/setup.sh
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Quick setup script for converting to plugin
|
|
4
|
+
|
|
5
|
+
echo "🚀 Setting up React Native Accessibility Toolkit Plugin..."
|
|
6
|
+
|
|
7
|
+
# Create directory structure
|
|
8
|
+
echo "📁 Creating directory structure..."
|
|
9
|
+
mkdir -p src/components src/context src/hooks src/services src/utils
|
|
10
|
+
|
|
11
|
+
# Copy files from existing project
|
|
12
|
+
echo "📋 Copying files..."
|
|
13
|
+
echo "Please manually copy these files:"
|
|
14
|
+
echo " - src/accessibility/*.js → src/components/"
|
|
15
|
+
echo " - src/accessibility/AccessibilityContext.js → src/context/"
|
|
16
|
+
echo " - src/accessibility/use*.js → src/hooks/"
|
|
17
|
+
echo " - src/accessibility/TTSService.js → src/services/"
|
|
18
|
+
echo " - src/accessibility/NativeAccessibilityBridge.js → src/services/"
|
|
19
|
+
echo " - src/accessibility/AccessibilityStorage.js → src/services/"
|
|
20
|
+
echo " - src/accessibility/AccessibilityUtils.js → src/utils/"
|
|
21
|
+
|
|
22
|
+
# Initialize npm if not already done
|
|
23
|
+
if [ ! -f "package.json" ]; then
|
|
24
|
+
echo "📦 Initializing npm..."
|
|
25
|
+
npm init -y
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Install dev dependencies
|
|
29
|
+
echo "📥 Installing dev dependencies..."
|
|
30
|
+
npm install --save-dev @babel/core @babel/preset-env eslint jest
|
|
31
|
+
|
|
32
|
+
echo "✅ Setup complete!"
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Next steps:"
|
|
35
|
+
echo "1. Copy your accessibility files to the new structure"
|
|
36
|
+
echo "2. Remove project-specific dependencies (Global, Colors, etc.)"
|
|
37
|
+
echo "3. Test the package locally with 'npm link'"
|
|
38
|
+
echo "4. Publish to npm with 'npm publish'"
|
|
39
|
+
echo ""
|
|
40
|
+
echo "📖 Read INSTALLATION_GUIDE.md for detailed instructions"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
Text,
|
|
6
|
+
Platform,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import { useAccessibility } from '../context/AccessibilityContext';
|
|
9
|
+
import { MIN_TOUCH_TARGET } from '../utils/AccessibilityUtils';
|
|
10
|
+
import Icon from 'react-native-vector-icons/Ionicons';
|
|
11
|
+
|
|
12
|
+
const AccessibilityButton = () => {
|
|
13
|
+
const { openModal, enlargeButtons } = useAccessibility();
|
|
14
|
+
|
|
15
|
+
const buttonSize = enlargeButtons ? MIN_TOUCH_TARGET * 1.2 : MIN_TOUCH_TARGET;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<TouchableOpacity
|
|
19
|
+
style={[
|
|
20
|
+
styles.floatingButton,
|
|
21
|
+
{
|
|
22
|
+
width: buttonSize,
|
|
23
|
+
height: buttonSize,
|
|
24
|
+
borderRadius: buttonSize / 2,
|
|
25
|
+
},
|
|
26
|
+
]}
|
|
27
|
+
onPress={openModal}
|
|
28
|
+
accessible={true}
|
|
29
|
+
accessibilityLabel="Open Accessibility Menu"
|
|
30
|
+
accessibilityHint="Opens a menu with accessibility options like text size, contrast, and display settings"
|
|
31
|
+
accessibilityRole="button"
|
|
32
|
+
activeOpacity={0.8}
|
|
33
|
+
>
|
|
34
|
+
<Icon name="accessibility" size={enlargeButtons ? 28 : 24} color="#ffffff" />
|
|
35
|
+
</TouchableOpacity>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const styles = StyleSheet.create({
|
|
40
|
+
floatingButton: {
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
bottom: 20,
|
|
43
|
+
right: 20,
|
|
44
|
+
backgroundColor: '#8B63FF',
|
|
45
|
+
justifyContent: 'center',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
elevation: 5,
|
|
48
|
+
shadowColor: '#000',
|
|
49
|
+
shadowOffset: { width: 0, height: 2 },
|
|
50
|
+
shadowOpacity: 0.3,
|
|
51
|
+
shadowRadius: 4,
|
|
52
|
+
zIndex: 1000,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export default AccessibilityButton;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text as RNText } from 'react-native';
|
|
3
|
+
import { useAccessibility } from '../context/AccessibilityContext';
|
|
4
|
+
import { getAdjustedFontSize, getAdjustedLineHeight } from '../utils/AccessibilityUtils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Accessibility-aware Text component
|
|
8
|
+
* Automatically applies font scaling, line height, letter spacing, and text alignment
|
|
9
|
+
*/
|
|
10
|
+
const AccessibleText = ({
|
|
11
|
+
style,
|
|
12
|
+
children,
|
|
13
|
+
baseFontSize = 14,
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
const {
|
|
17
|
+
fontScale,
|
|
18
|
+
lineHeight: lineHeightMultiplier,
|
|
19
|
+
letterSpacing: letterSpacingValue,
|
|
20
|
+
textAlignment,
|
|
21
|
+
textColor,
|
|
22
|
+
nativeBoldText,
|
|
23
|
+
} = useAccessibility();
|
|
24
|
+
|
|
25
|
+
const adjustedFontSize = getAdjustedFontSize(baseFontSize, fontScale);
|
|
26
|
+
const adjustedLineHeight = getAdjustedLineHeight(baseFontSize, lineHeightMultiplier, fontScale);
|
|
27
|
+
|
|
28
|
+
const accessibilityStyles = {
|
|
29
|
+
fontSize: adjustedFontSize,
|
|
30
|
+
lineHeight: adjustedLineHeight,
|
|
31
|
+
letterSpacing: letterSpacingValue,
|
|
32
|
+
textAlign: textAlignment,
|
|
33
|
+
...(textColor && { color: textColor }),
|
|
34
|
+
...(nativeBoldText && { fontWeight: 'bold' }),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<RNText
|
|
39
|
+
style={[style, accessibilityStyles]}
|
|
40
|
+
{...props}
|
|
41
|
+
accessible={true}
|
|
42
|
+
accessibilityRole="text"
|
|
43
|
+
allowFontScaling={true}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</RNText>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default AccessibleText;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { DEFAULT_ACCESSIBILITY_STATE, applyProfile } from '../utils/AccessibilityUtils';
|
|
3
|
+
import { loadAccessibilityPreferences, saveAccessibilityPreferences } from '../services/AccessibilityStorage';
|
|
4
|
+
import NativeAccessibilityBridge from '../services/NativeAccessibilityBridge';
|
|
5
|
+
|
|
6
|
+
const AccessibilityContext = createContext();
|
|
7
|
+
|
|
8
|
+
export const useAccessibility = () => {
|
|
9
|
+
const context = useContext(AccessibilityContext);
|
|
10
|
+
if (!context) {
|
|
11
|
+
throw new Error('useAccessibility must be used within AccessibilityProvider');
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const AccessibilityProvider = ({ children }) => {
|
|
17
|
+
const [state, setState] = useState(DEFAULT_ACCESSIBILITY_STATE);
|
|
18
|
+
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
19
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
20
|
+
|
|
21
|
+
// Initialize native accessibility detection
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const initNative = async () => {
|
|
24
|
+
await NativeAccessibilityBridge.initialize((nativeSettings) => {
|
|
25
|
+
setState(prevState => ({
|
|
26
|
+
...prevState,
|
|
27
|
+
...nativeSettings,
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
initNative();
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
NativeAccessibilityBridge.cleanup();
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
// Load preferences on mount
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const loadPreferences = async () => {
|
|
42
|
+
const preferences = await loadAccessibilityPreferences();
|
|
43
|
+
setState(preferences);
|
|
44
|
+
setIsLoading(false);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
loadPreferences();
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
// Save preferences whenever state changes
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isLoading) {
|
|
53
|
+
saveAccessibilityPreferences(state);
|
|
54
|
+
}
|
|
55
|
+
}, [state, isLoading]);
|
|
56
|
+
|
|
57
|
+
// Update individual setting
|
|
58
|
+
const updateSetting = useCallback((key, value) => {
|
|
59
|
+
setState(prevState => ({
|
|
60
|
+
...prevState,
|
|
61
|
+
[key]: value,
|
|
62
|
+
activeProfile: 'none',
|
|
63
|
+
}));
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
// Update multiple settings
|
|
67
|
+
const updateSettings = useCallback((updates) => {
|
|
68
|
+
setState(prevState => ({
|
|
69
|
+
...prevState,
|
|
70
|
+
...updates,
|
|
71
|
+
}));
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
// Apply profile
|
|
75
|
+
const setProfile = useCallback((profile) => {
|
|
76
|
+
const profileSettings = applyProfile(profile);
|
|
77
|
+
setState(profileSettings);
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
// Reset defaults
|
|
81
|
+
const resetToDefault = useCallback(() => {
|
|
82
|
+
setState(DEFAULT_ACCESSIBILITY_STATE);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
// Modal controls
|
|
86
|
+
const toggleModal = useCallback(() => {
|
|
87
|
+
setIsModalVisible(prev => !prev);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const openModal = useCallback(() => {
|
|
91
|
+
setIsModalVisible(true);
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const closeModal = useCallback(() => {
|
|
95
|
+
setIsModalVisible(false);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
// Screen reader announce
|
|
99
|
+
const announce = useCallback((message, options) => {
|
|
100
|
+
NativeAccessibilityBridge.announce(message, options);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const value = {
|
|
104
|
+
...state,
|
|
105
|
+
isModalVisible,
|
|
106
|
+
isLoading,
|
|
107
|
+
|
|
108
|
+
updateSetting,
|
|
109
|
+
updateSettings,
|
|
110
|
+
setProfile,
|
|
111
|
+
resetToDefault,
|
|
112
|
+
toggleModal,
|
|
113
|
+
openModal,
|
|
114
|
+
closeModal,
|
|
115
|
+
announce,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<AccessibilityContext.Provider value={value}>
|
|
120
|
+
{children}
|
|
121
|
+
</AccessibilityContext.Provider>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default AccessibilityContext;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDynamicColors.js
|
|
3
|
+
* Hook to get colors that respect accessibility settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { useAccessibility } from '../context/AccessibilityContext';
|
|
8
|
+
import { getColors } from '../utils/Colors';
|
|
9
|
+
|
|
10
|
+
export const useDynamicColors = () => {
|
|
11
|
+
const accessibilitySettings = useAccessibility();
|
|
12
|
+
|
|
13
|
+
const colors = useMemo(() => {
|
|
14
|
+
return getColors(accessibilitySettings);
|
|
15
|
+
}, [
|
|
16
|
+
accessibilitySettings.colorTheme,
|
|
17
|
+
accessibilitySettings.darkMode,
|
|
18
|
+
accessibilitySettings.highContrast,
|
|
19
|
+
accessibilitySettings.darkHighContrast,
|
|
20
|
+
accessibilitySettings.whiteHighContrast,
|
|
21
|
+
accessibilitySettings.colorInversion,
|
|
22
|
+
accessibilitySettings.greyscale,
|
|
23
|
+
accessibilitySettings.lowSaturation,
|
|
24
|
+
accessibilitySettings.highSaturation,
|
|
25
|
+
accessibilitySettings.textColor,
|
|
26
|
+
accessibilitySettings.backgroundColor,
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
return colors;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default useDynamicColors;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import Tts from 'react-native-tts';
|
|
3
|
+
|
|
4
|
+
export function usePageRead(textToRead) {
|
|
5
|
+
const [isSpeaking, setIsSpeaking] = useState(false);
|
|
6
|
+
const [currentWordIndex, setCurrentWordIndex] = useState(null);
|
|
7
|
+
|
|
8
|
+
// Memoize words
|
|
9
|
+
const words = useMemo(
|
|
10
|
+
() => textToRead.trim().split(/\s+/),
|
|
11
|
+
[textToRead]
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const indexRef = useRef(0);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const finishSub = Tts.addEventListener('tts-finish', () => {
|
|
18
|
+
indexRef.current += 1;
|
|
19
|
+
|
|
20
|
+
if (indexRef.current < words.length) {
|
|
21
|
+
setCurrentWordIndex(indexRef.current);
|
|
22
|
+
Tts.speak(words[indexRef.current]);
|
|
23
|
+
} else {
|
|
24
|
+
setIsSpeaking(false);
|
|
25
|
+
setCurrentWordIndex(null);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const cancelSub = Tts.addEventListener('tts-cancel', () => {
|
|
30
|
+
setIsSpeaking(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
finishSub?.remove?.();
|
|
35
|
+
cancelSub?.remove?.();
|
|
36
|
+
};
|
|
37
|
+
}, [words]);
|
|
38
|
+
|
|
39
|
+
return useMemo(() => ({
|
|
40
|
+
isSpeaking,
|
|
41
|
+
currentWordIndex,
|
|
42
|
+
|
|
43
|
+
start: async () => {
|
|
44
|
+
if (!words.length) return;
|
|
45
|
+
|
|
46
|
+
indexRef.current = 0;
|
|
47
|
+
setCurrentWordIndex(0);
|
|
48
|
+
setIsSpeaking(true);
|
|
49
|
+
|
|
50
|
+
await Tts.stop();
|
|
51
|
+
await Tts.speak(words[0]);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
pause: async () => {
|
|
55
|
+
await Tts.stop();
|
|
56
|
+
setIsSpeaking(false);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
resume: async () => {
|
|
60
|
+
if (indexRef.current < words.length) {
|
|
61
|
+
setIsSpeaking(true);
|
|
62
|
+
await Tts.speak(words[indexRef.current]);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
stop: async () => {
|
|
67
|
+
await Tts.stop();
|
|
68
|
+
indexRef.current = 0;
|
|
69
|
+
setIsSpeaking(false);
|
|
70
|
+
setCurrentWordIndex(null);
|
|
71
|
+
},
|
|
72
|
+
}), [isSpeaking, currentWordIndex, words]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default usePageRead;
|