@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.
Files changed (41) hide show
  1. package/CapawesomeCapacitorScreenReader.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +245 -0
  5. package/android/build.gradle +58 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/ScreenReader.java +93 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/ScreenReaderPlugin.java +122 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/CustomException.java +20 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/CustomExceptions.java +6 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/events/StateChangeEvent.java +22 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/options/AnnounceOptions.java +34 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/classes/results/IsEnabledResult.java +22 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/Callback.java +5 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/EmptyCallback.java +5 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/NonEmptyResultCallback.java +7 -0
  17. package/android/src/main/java/io/capawesome/capacitorjs/plugins/screenreader/interfaces/Result.java +7 -0
  18. package/android/src/main/res/.gitkeep +0 -0
  19. package/dist/docs.json +222 -0
  20. package/dist/esm/definitions.d.ts +92 -0
  21. package/dist/esm/definitions.js +2 -0
  22. package/dist/esm/definitions.js.map +1 -0
  23. package/dist/esm/index.d.ts +4 -0
  24. package/dist/esm/index.js +7 -0
  25. package/dist/esm/index.js.map +1 -0
  26. package/dist/esm/web.d.ts +11 -0
  27. package/dist/esm/web.js +42 -0
  28. package/dist/esm/web.js.map +1 -0
  29. package/dist/plugin.cjs.js +56 -0
  30. package/dist/plugin.cjs.js.map +1 -0
  31. package/dist/plugin.js +59 -0
  32. package/dist/plugin.js.map +1 -0
  33. package/ios/Plugin/Classes/Events/StateChangeEvent.swift +16 -0
  34. package/ios/Plugin/Classes/Options/AnnounceOptions.swift +15 -0
  35. package/ios/Plugin/Classes/Results/IsEnabledResult.swift +16 -0
  36. package/ios/Plugin/Enums/CustomError.swift +21 -0
  37. package/ios/Plugin/Info.plist +24 -0
  38. package/ios/Plugin/Protocols/Result.swift +5 -0
  39. package/ios/Plugin/ScreenReader.swift +71 -0
  40. package/ios/Plugin/ScreenReaderPlugin.swift +86 -0
  41. 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&lt;<a href="#isenabledresult">IsEnabledResult</a>&gt;</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>) =&gt; void</code> |
173
+
174
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</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>() =&gt; Promise&lt;void&gt;</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
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -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
+ }
@@ -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,6 @@
1
+ package io.capawesome.capacitorjs.plugins.screenreader.classes;
2
+
3
+ public class CustomExceptions {
4
+
5
+ public static final CustomException VALUE_MISSING = new CustomException(null, "value must be provided.");
6
+ }
@@ -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
+ }