@capawesome/capacitor-screen-reader 0.0.1
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/CapawesomeCapacitorScreenReader.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +245 -0
- package/android/build.gradle +58 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/ScreenReader.java +93 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/ScreenReaderPlugin.java +122 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/CustomExceptions.java +6 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/events/StateChangeEvent.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/options/AnnounceOptions.java +34 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/results/IsEnabledResult.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/EmptyCallback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/NonEmptyResultCallback.java +7 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/Result.java +7 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +222 -0
- package/dist/esm/definitions.d.ts +92 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +11 -0
- package/dist/esm/web.js +42 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +56 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +59 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Classes/Events/StateChangeEvent.swift +16 -0
- package/ios/Plugin/Classes/Options/AnnounceOptions.swift +15 -0
- package/ios/Plugin/Classes/Results/IsEnabledResult.swift +16 -0
- package/ios/Plugin/Enums/CustomError.swift +21 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Protocols/Result.swift +5 -0
- package/ios/Plugin/ScreenReader.swift +71 -0
- package/ios/Plugin/ScreenReaderPlugin.swift +86 -0
- package/package.json +95 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapawesomeCapacitorScreenReader'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robin Genz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapawesomeCapacitorScreenReader",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapawesomeCapacitorScreenReader",
|
|
10
|
+
targets: ["ScreenReaderPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "ScreenReaderPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Plugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "ScreenReaderPluginTests",
|
|
25
|
+
dependencies: ["ScreenReaderPlugin"],
|
|
26
|
+
path: "ios/PluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Capacitor Screen Reader Plugin
|
|
2
|
+
|
|
3
|
+
Capacitor plugin to interact with screen readers.
|
|
4
|
+
|
|
5
|
+
<div class="capawesome-z29o10a">
|
|
6
|
+
<a href="https://cloud.capawesome.io/" target="_blank">
|
|
7
|
+
<img alt="Deliver Live Updates to your Capacitor app with Capawesome Cloud" src="https://cloud.capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
|
|
8
|
+
</a>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🔎 **Detection**: Check whether a screen reader (VoiceOver/TalkBack) is currently enabled.
|
|
14
|
+
- 📣 **Announcements**: Post accessibility announcements that are read out by the active screen reader.
|
|
15
|
+
- 🔔 **State changes**: Listen for changes to the enabled state of the screen reader.
|
|
16
|
+
- 🖥️ **Cross-platform**: Supports Android, iOS and the web.
|
|
17
|
+
- 🔁 **Up-to-date**: Always supports the latest Capacitor version.
|
|
18
|
+
- 🤝 **Compatibility**: Works alongside the [Accessibility Preferences](https://capawesome.io/docs/sdks/capacitor/accessibility-preferences/) and [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/) plugins.
|
|
19
|
+
|
|
20
|
+
Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look!
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> This plugin does **not** perform text-to-speech. The `announce(...)` method posts an accessibility announcement that is only read out when a screen reader is active. If you are looking for real text-to-speech, use the [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/) plugin instead.
|
|
24
|
+
|
|
25
|
+
## Newsletter
|
|
26
|
+
|
|
27
|
+
Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/).
|
|
28
|
+
|
|
29
|
+
## Compatibility
|
|
30
|
+
|
|
31
|
+
| Plugin Version | Capacitor Version | Status |
|
|
32
|
+
| -------------- | ----------------- | -------------- |
|
|
33
|
+
| 0.x.x | >=8.x.x | Active support |
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
You can use our **AI-Assisted Setup** to install the plugin.
|
|
38
|
+
Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx skills add capawesome-team/skills --skill capacitor-plugins
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then use the following prompt:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-screen-reader` plugin in my project.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @capawesome/capacitor-screen-reader
|
|
54
|
+
npx cap sync
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Android
|
|
58
|
+
|
|
59
|
+
No additional configuration is required for this plugin.
|
|
60
|
+
|
|
61
|
+
### iOS
|
|
62
|
+
|
|
63
|
+
No additional configuration is required for this plugin.
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
No configuration required for this plugin.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { ScreenReader } from '@capawesome/capacitor-screen-reader';
|
|
73
|
+
|
|
74
|
+
const announce = async () => {
|
|
75
|
+
await ScreenReader.announce({
|
|
76
|
+
value: 'The item was added to your cart.',
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const isEnabled = async () => {
|
|
81
|
+
const { enabled } = await ScreenReader.isEnabled();
|
|
82
|
+
return enabled;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const addStateChangeListener = async () => {
|
|
86
|
+
await ScreenReader.addListener('stateChange', event => {
|
|
87
|
+
console.log('Screen reader enabled:', event.enabled);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const removeAllListeners = async () => {
|
|
92
|
+
await ScreenReader.removeAllListeners();
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API
|
|
97
|
+
|
|
98
|
+
<docgen-index>
|
|
99
|
+
|
|
100
|
+
* [`announce(...)`](#announce)
|
|
101
|
+
* [`isEnabled()`](#isenabled)
|
|
102
|
+
* [`addListener('stateChange', ...)`](#addlistenerstatechange-)
|
|
103
|
+
* [`removeAllListeners()`](#removealllisteners)
|
|
104
|
+
* [Interfaces](#interfaces)
|
|
105
|
+
|
|
106
|
+
</docgen-index>
|
|
107
|
+
|
|
108
|
+
<docgen-api>
|
|
109
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
110
|
+
|
|
111
|
+
### announce(...)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
announce(options: AnnounceOptions) => Promise<void>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Post an accessibility announcement to the active screen reader.
|
|
118
|
+
|
|
119
|
+
This does **not** perform text-to-speech. It posts an announcement that is
|
|
120
|
+
read out by the screen reader (VoiceOver/TalkBack) if one is active. For
|
|
121
|
+
real text-to-speech, use the [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/)
|
|
122
|
+
plugin instead.
|
|
123
|
+
|
|
124
|
+
On the web, the announcement is made through a visually hidden
|
|
125
|
+
`aria-live` region, so it is only read out if the user has a screen reader
|
|
126
|
+
running.
|
|
127
|
+
|
|
128
|
+
| Param | Type |
|
|
129
|
+
| ------------- | ----------------------------------------------------------- |
|
|
130
|
+
| **`options`** | <code><a href="#announceoptions">AnnounceOptions</a></code> |
|
|
131
|
+
|
|
132
|
+
**Since:** 0.1.0
|
|
133
|
+
|
|
134
|
+
--------------------
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### isEnabled()
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
isEnabled() => Promise<IsEnabledResult>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Check whether a screen reader is currently enabled.
|
|
144
|
+
|
|
145
|
+
On Android, this refers to whether touch exploration (TalkBack) is enabled.
|
|
146
|
+
On iOS, this refers to whether VoiceOver is running.
|
|
147
|
+
|
|
148
|
+
Only available on Android and iOS.
|
|
149
|
+
|
|
150
|
+
**Returns:** <code>Promise<<a href="#isenabledresult">IsEnabledResult</a>></code>
|
|
151
|
+
|
|
152
|
+
**Since:** 0.1.0
|
|
153
|
+
|
|
154
|
+
--------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
### addListener('stateChange', ...)
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
addListener(eventName: 'stateChange', listenerFunc: (event: StateChangeEvent) => void) => Promise<PluginListenerHandle>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Listen for changes to the enabled state of the screen reader.
|
|
164
|
+
|
|
165
|
+
The device is only observed while at least one listener is attached.
|
|
166
|
+
|
|
167
|
+
Only available on Android and iOS.
|
|
168
|
+
|
|
169
|
+
| Param | Type |
|
|
170
|
+
| ------------------ | --------------------------------------------------------------------------------- |
|
|
171
|
+
| **`eventName`** | <code>'stateChange'</code> |
|
|
172
|
+
| **`listenerFunc`** | <code>(event: <a href="#statechangeevent">StateChangeEvent</a>) => void</code> |
|
|
173
|
+
|
|
174
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
175
|
+
|
|
176
|
+
**Since:** 0.1.0
|
|
177
|
+
|
|
178
|
+
--------------------
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
### removeAllListeners()
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
removeAllListeners() => Promise<void>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Remove all listeners for this plugin.
|
|
188
|
+
|
|
189
|
+
**Since:** 0.1.0
|
|
190
|
+
|
|
191
|
+
--------------------
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
### Interfaces
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
#### AnnounceOptions
|
|
198
|
+
|
|
199
|
+
| Prop | Type | Description | Since |
|
|
200
|
+
| -------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
201
|
+
| **`value`** | <code>string</code> | The message to announce. | 0.1.0 |
|
|
202
|
+
| **`language`** | <code>string</code> | The language of the message as a BCP 47 language tag. This helps the screen reader pronounce the message correctly. Only available on Android. | 0.1.0 |
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
#### IsEnabledResult
|
|
206
|
+
|
|
207
|
+
| Prop | Type | Description | Since |
|
|
208
|
+
| ------------- | -------------------- | --------------------------------------------- | ----- |
|
|
209
|
+
| **`enabled`** | <code>boolean</code> | Whether a screen reader is currently enabled. | 0.1.0 |
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
#### PluginListenerHandle
|
|
213
|
+
|
|
214
|
+
| Prop | Type |
|
|
215
|
+
| ------------ | ----------------------------------------- |
|
|
216
|
+
| **`remove`** | <code>() => Promise<void></code> |
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
#### StateChangeEvent
|
|
220
|
+
|
|
221
|
+
| Prop | Type | Description | Since |
|
|
222
|
+
| ------------- | -------------------- | --------------------------------------------- | ----- |
|
|
223
|
+
| **`enabled`** | <code>boolean</code> | Whether a screen reader is currently enabled. | 0.1.0 |
|
|
224
|
+
|
|
225
|
+
</docgen-api>
|
|
226
|
+
|
|
227
|
+
## Migrating from `@capacitor/screen-reader`
|
|
228
|
+
|
|
229
|
+
This plugin is a drop-in replacement for the official `@capacitor/screen-reader` plugin with a few naming changes:
|
|
230
|
+
|
|
231
|
+
| `@capacitor/screen-reader` | Screen Reader |
|
|
232
|
+
| --------------------------------- | --------------------------------- |
|
|
233
|
+
| `speak({ value, language })` | `announce({ value, language })` |
|
|
234
|
+
| `isEnabled()` | `isEnabled()` |
|
|
235
|
+
| `addListener('stateChange', ...)` | `addListener('stateChange', ...)` |
|
|
236
|
+
|
|
237
|
+
The `speak(...)` method has been renamed to `announce(...)` to make it clear that it posts an accessibility announcement to the active screen reader and does **not** perform text-to-speech. For real text-to-speech, use the [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/) plugin instead.
|
|
238
|
+
|
|
239
|
+
## Changelog
|
|
240
|
+
|
|
241
|
+
See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/screen-reader/CHANGELOG.md).
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/screen-reader/LICENSE).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace = "io.capawesome.capacitorjs.plugins.screenreader"
|
|
22
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError = false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
mavenCentral()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
dependencies {
|
|
52
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
53
|
+
implementation project(':capacitor-android')
|
|
54
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
55
|
+
testImplementation "junit:junit:$junitVersion"
|
|
56
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
58
|
+
}
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/ScreenReader.java
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.screenreader;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.text.SpannableString;
|
|
5
|
+
import android.text.Spanned;
|
|
6
|
+
import android.text.style.LocaleSpan;
|
|
7
|
+
import android.view.View;
|
|
8
|
+
import android.view.accessibility.AccessibilityManager;
|
|
9
|
+
import androidx.annotation.NonNull;
|
|
10
|
+
import androidx.annotation.Nullable;
|
|
11
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.events.StateChangeEvent;
|
|
12
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.options.AnnounceOptions;
|
|
13
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.results.IsEnabledResult;
|
|
14
|
+
import io.capawesome.capacitorjs.plugins.screenreader.interfaces.EmptyCallback;
|
|
15
|
+
import io.capawesome.capacitorjs.plugins.screenreader.interfaces.NonEmptyResultCallback;
|
|
16
|
+
import java.util.Locale;
|
|
17
|
+
|
|
18
|
+
public class ScreenReader {
|
|
19
|
+
|
|
20
|
+
@NonNull
|
|
21
|
+
private final ScreenReaderPlugin plugin;
|
|
22
|
+
|
|
23
|
+
@NonNull
|
|
24
|
+
private final AccessibilityManager accessibilityManager;
|
|
25
|
+
|
|
26
|
+
private final AccessibilityManager.TouchExplorationStateChangeListener touchExplorationStateChangeListener = enabled ->
|
|
27
|
+
handleTouchExplorationStateChanged(enabled);
|
|
28
|
+
|
|
29
|
+
private boolean isObserving = false;
|
|
30
|
+
|
|
31
|
+
private boolean lastEnabled = false;
|
|
32
|
+
|
|
33
|
+
public ScreenReader(@NonNull ScreenReaderPlugin plugin) {
|
|
34
|
+
this.plugin = plugin;
|
|
35
|
+
this.accessibilityManager = (AccessibilityManager) plugin.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public void announce(@NonNull AnnounceOptions options, @NonNull EmptyCallback callback) {
|
|
39
|
+
View webView = plugin.getBridge().getWebView();
|
|
40
|
+
if (webView == null) {
|
|
41
|
+
callback.success();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
CharSequence text = createAnnouncementText(options.getValue(), options.getLanguage());
|
|
45
|
+
webView.post(() -> {
|
|
46
|
+
webView.announceForAccessibility(text);
|
|
47
|
+
callback.success();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public void isEnabled(@NonNull NonEmptyResultCallback<IsEnabledResult> callback) {
|
|
52
|
+
callback.success(new IsEnabledResult(isTouchExplorationEnabled()));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public void startObserving() {
|
|
56
|
+
if (isObserving) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
lastEnabled = isTouchExplorationEnabled();
|
|
60
|
+
accessibilityManager.addTouchExplorationStateChangeListener(touchExplorationStateChangeListener);
|
|
61
|
+
isObserving = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public void stopObserving() {
|
|
65
|
+
if (!isObserving) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
accessibilityManager.removeTouchExplorationStateChangeListener(touchExplorationStateChangeListener);
|
|
69
|
+
isObserving = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@NonNull
|
|
73
|
+
private CharSequence createAnnouncementText(@NonNull String value, @Nullable String language) {
|
|
74
|
+
if (language == null) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
SpannableString spannable = new SpannableString(value);
|
|
78
|
+
spannable.setSpan(new LocaleSpan(Locale.forLanguageTag(language)), 0, spannable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
|
79
|
+
return spannable;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private void handleTouchExplorationStateChanged(boolean enabled) {
|
|
83
|
+
if (enabled == lastEnabled) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
lastEnabled = enabled;
|
|
87
|
+
plugin.notifyStateChangeListeners(new StateChangeEvent(enabled));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private boolean isTouchExplorationEnabled() {
|
|
91
|
+
return accessibilityManager.isTouchExplorationEnabled();
|
|
92
|
+
}
|
|
93
|
+
}
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/ScreenReaderPlugin.java
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.screenreader;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.getcapacitor.Logger;
|
|
5
|
+
import com.getcapacitor.Plugin;
|
|
6
|
+
import com.getcapacitor.PluginCall;
|
|
7
|
+
import com.getcapacitor.PluginMethod;
|
|
8
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
9
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.CustomException;
|
|
10
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.events.StateChangeEvent;
|
|
11
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.options.AnnounceOptions;
|
|
12
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.results.IsEnabledResult;
|
|
13
|
+
import io.capawesome.capacitorjs.plugins.screenreader.interfaces.EmptyCallback;
|
|
14
|
+
import io.capawesome.capacitorjs.plugins.screenreader.interfaces.NonEmptyResultCallback;
|
|
15
|
+
import io.capawesome.capacitorjs.plugins.screenreader.interfaces.Result;
|
|
16
|
+
|
|
17
|
+
@CapacitorPlugin(name = "ScreenReader")
|
|
18
|
+
public class ScreenReaderPlugin extends Plugin {
|
|
19
|
+
|
|
20
|
+
public static final String EVENT_STATE_CHANGE = "stateChange";
|
|
21
|
+
|
|
22
|
+
public static final String TAG = "ScreenReaderPlugin";
|
|
23
|
+
|
|
24
|
+
private static final String ERROR_UNKNOWN_ERROR = "An unknown error has occurred.";
|
|
25
|
+
|
|
26
|
+
private ScreenReader implementation;
|
|
27
|
+
|
|
28
|
+
@Override
|
|
29
|
+
public void load() {
|
|
30
|
+
super.load();
|
|
31
|
+
this.implementation = new ScreenReader(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Override
|
|
35
|
+
@PluginMethod(returnType = PluginMethod.RETURN_NONE)
|
|
36
|
+
public void addListener(PluginCall call) {
|
|
37
|
+
super.addListener(call);
|
|
38
|
+
implementation.startObserving();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@PluginMethod
|
|
42
|
+
public void announce(PluginCall call) {
|
|
43
|
+
try {
|
|
44
|
+
AnnounceOptions options = new AnnounceOptions(call);
|
|
45
|
+
EmptyCallback callback = new EmptyCallback() {
|
|
46
|
+
@Override
|
|
47
|
+
public void success() {
|
|
48
|
+
resolveCall(call);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Override
|
|
52
|
+
public void error(Exception exception) {
|
|
53
|
+
rejectCall(call, exception);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
implementation.announce(options, callback);
|
|
57
|
+
} catch (Exception exception) {
|
|
58
|
+
rejectCall(call, exception);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@PluginMethod
|
|
63
|
+
public void isEnabled(PluginCall call) {
|
|
64
|
+
try {
|
|
65
|
+
NonEmptyResultCallback<IsEnabledResult> callback = new NonEmptyResultCallback<>() {
|
|
66
|
+
@Override
|
|
67
|
+
public void success(@NonNull IsEnabledResult result) {
|
|
68
|
+
resolveCall(call, result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Override
|
|
72
|
+
public void error(@NonNull Exception exception) {
|
|
73
|
+
rejectCall(call, exception);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
implementation.isEnabled(callback);
|
|
77
|
+
} catch (Exception exception) {
|
|
78
|
+
rejectCall(call, exception);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public void notifyStateChangeListeners(@NonNull StateChangeEvent event) {
|
|
83
|
+
notifyListeners(EVENT_STATE_CHANGE, event.toJSObject());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Override
|
|
87
|
+
@PluginMethod
|
|
88
|
+
public void removeAllListeners(PluginCall call) {
|
|
89
|
+
super.removeAllListeners(call);
|
|
90
|
+
implementation.stopObserving();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Override
|
|
94
|
+
@PluginMethod(returnType = PluginMethod.RETURN_NONE)
|
|
95
|
+
public void removeListener(PluginCall call) {
|
|
96
|
+
super.removeListener(call);
|
|
97
|
+
if (!hasListeners(EVENT_STATE_CHANGE)) {
|
|
98
|
+
implementation.stopObserving();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private void rejectCall(@NonNull PluginCall call, @NonNull Exception exception) {
|
|
103
|
+
String message = exception.getMessage();
|
|
104
|
+
if (message == null) {
|
|
105
|
+
message = ERROR_UNKNOWN_ERROR;
|
|
106
|
+
}
|
|
107
|
+
String code = null;
|
|
108
|
+
if (exception instanceof CustomException) {
|
|
109
|
+
code = ((CustomException) exception).getCode();
|
|
110
|
+
}
|
|
111
|
+
Logger.error(TAG, message, exception);
|
|
112
|
+
call.reject(message, code);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private void resolveCall(@NonNull PluginCall call) {
|
|
116
|
+
call.resolve();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private void resolveCall(@NonNull PluginCall call, @NonNull Result result) {
|
|
120
|
+
call.resolve(result.toJSObject());
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.screenreader.classes;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
public class CustomException extends Exception {
|
|
7
|
+
|
|
8
|
+
@Nullable
|
|
9
|
+
private final String code;
|
|
10
|
+
|
|
11
|
+
public CustomException(@Nullable String code, @NonNull String message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Nullable
|
|
17
|
+
public String getCode() {
|
|
18
|
+
return code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.screenreader.classes.events;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.getcapacitor.JSObject;
|
|
5
|
+
import io.capawesome.capacitorjs.plugins.screenreader.interfaces.Result;
|
|
6
|
+
|
|
7
|
+
public class StateChangeEvent implements Result {
|
|
8
|
+
|
|
9
|
+
private final boolean enabled;
|
|
10
|
+
|
|
11
|
+
public StateChangeEvent(boolean enabled) {
|
|
12
|
+
this.enabled = enabled;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Override
|
|
16
|
+
@NonNull
|
|
17
|
+
public JSObject toJSObject() {
|
|
18
|
+
JSObject result = new JSObject();
|
|
19
|
+
result.put("enabled", enabled);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.screenreader.classes.options;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
import com.getcapacitor.PluginCall;
|
|
6
|
+
import io.capawesome.capacitorjs.plugins.screenreader.classes.CustomExceptions;
|
|
7
|
+
|
|
8
|
+
public class AnnounceOptions {
|
|
9
|
+
|
|
10
|
+
@NonNull
|
|
11
|
+
private final String value;
|
|
12
|
+
|
|
13
|
+
@Nullable
|
|
14
|
+
private final String language;
|
|
15
|
+
|
|
16
|
+
public AnnounceOptions(@NonNull PluginCall call) throws Exception {
|
|
17
|
+
String value = call.getString("value");
|
|
18
|
+
if (value == null) {
|
|
19
|
+
throw CustomExceptions.VALUE_MISSING;
|
|
20
|
+
}
|
|
21
|
+
this.value = value;
|
|
22
|
+
this.language = call.getString("language");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Nullable
|
|
26
|
+
public String getLanguage() {
|
|
27
|
+
return language;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@NonNull
|
|
31
|
+
public String getValue() {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
}
|