Skip to content

Commit 40c8e3b

Browse files
chrisbobbegnprice
authored andcommitted
offline notice: Announce to screen readers when Internet gained/lost
I tested this on my iPhone (VoiceOver on iOS 15.6, iPhone 13 Pro) and the office Android device (Voice Assistant on Android 9, Samsung Galaxy S9), and it worked as intended. Related: #4394
1 parent ad85b78 commit 40c8e3b

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

src/boot/OfflineNoticeProvider.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow strict-local
22
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
33
import type { Node } from 'react';
4-
import { View, Animated, LayoutAnimation, Platform, Easing } from 'react-native';
4+
import { AccessibilityInfo, View, Animated, LayoutAnimation, Platform, Easing } from 'react-native';
55
import NetInfo from '@react-native-community/netinfo';
66
import { SafeAreaView } from 'react-native-safe-area-context';
77
import type { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes';
@@ -17,6 +17,7 @@ import ZulipTextIntl from '../common/ZulipTextIntl';
1717
import type { ViewStylePropWithout } from '../reactNativeUtils';
1818
import ZulipStatusBar from '../common/ZulipStatusBar';
1919
import type { ThemeName } from '../reduxTypes';
20+
import { TranslationContext } from './TranslationProvider';
2021

2122
function useShouldShowUncertaintyNotice(): boolean {
2223
const isOnline = useGlobalSelector(state => getGlobalSession(state).isOnline);
@@ -87,6 +88,7 @@ const backgroundColorForTheme = (theme: ThemeName): string =>
8788
*/
8889
export function OfflineNoticeProvider(props: ProviderProps): Node {
8990
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
91+
const _ = useContext(TranslationContext);
9092
const isOnline = useGlobalSelector(state => getGlobalSession(state).isOnline);
9193
const shouldShowUncertaintyNotice = useShouldShowUncertaintyNotice();
9294

@@ -120,7 +122,47 @@ export function OfflineNoticeProvider(props: ProviderProps): Node {
120122
}
121123
return newValue;
122124
});
123-
}, [isOnline, shouldShowUncertaintyNotice]);
125+
}, [isOnline, shouldShowUncertaintyNotice, _]);
126+
127+
// Announce connectivity changes to screen-reader users.
128+
const haveAnnouncedOffline = useRef(false);
129+
const haveAnnouncedUncertain = useRef(false);
130+
useEffect(() => {
131+
// When announcing, mention Zulip so this doesn't sound like an
132+
// announcement from the OS. We don't speak for the OS, and the OS might
133+
// disagree about the connectivity state, e.g., because we're wrong
134+
// about Zulip's connectivity or because a connection problem somehow
135+
// affects Zulip but not other apps.
136+
//
137+
// (The banner element shouldn't have to mention Zulip because it's
138+
// already clear that it's from Zulip: it's part of the UI we draw, as
139+
// traversed visually or by a screen reader. The OS should make it clear
140+
// when you're traversing a given app's UI, e.g., so an app can't trick
141+
// you into giving it sensitive data that you meant for the OS or
142+
// another app.)
143+
144+
if (shouldShowUncertaintyNotice && !haveAnnouncedUncertain.current) {
145+
// TODO(react-native-68): Use announceForAccessibilityWithOptions to
146+
// queue this behind any in-progress announcements
147+
AccessibilityInfo.announceForAccessibility(_('Zulip’s Internet connection is uncertain.'));
148+
haveAnnouncedUncertain.current = true;
149+
}
150+
151+
if (isOnline === false && (!haveAnnouncedOffline.current || haveAnnouncedUncertain.current)) {
152+
AccessibilityInfo.announceForAccessibility(_('Zulip is offline.'));
153+
haveAnnouncedOffline.current = true;
154+
haveAnnouncedUncertain.current = false;
155+
} else if (
156+
isOnline === true
157+
&& (haveAnnouncedOffline.current || haveAnnouncedUncertain.current)
158+
) {
159+
// TODO(react-native-68): Use announceForAccessibilityWithOptions to
160+
// queue this behind any in-progress announcements
161+
AccessibilityInfo.announceForAccessibility(_('Zulip is online.'));
162+
haveAnnouncedOffline.current = false;
163+
haveAnnouncedUncertain.current = false;
164+
}
165+
}, [isOnline, shouldShowUncertaintyNotice, _]);
124166

125167
const styles = useMemo(
126168
() =>
@@ -208,6 +250,11 @@ export function OfflineNoticeProvider(props: ProviderProps): Node {
208250
// be present, with explanatory text, or absent. To make it
209251
// "absent" for screen readers, we make this view and its children
210252
// unfocusable.
253+
//
254+
// See also our AccessibilityInfo.announceForAccessibility call,
255+
// where we announce online/offline changes so the user doesn't
256+
// have to poll for the connectivity state by checking for
257+
// presence/absence of this view.
211258
...(isNoticeVisible
212259
? {
213260
// Group descendants into a single selectable component. Its

static/translations/messages_en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@
133133
"Unmute stream": "Unmute stream",
134134
"No Internet connection": "No Internet connection",
135135
"Please check your Internet connection": "Please check your Internet connection",
136+
"Zulip’s Internet connection is uncertain.": "Zulip’s Internet connection is uncertain.",
137+
"Zulip is offline.": "Zulip is offline.",
138+
"Zulip is online.": "Zulip is online.",
136139
"{realm} is running Zulip Server {serverVersion}, which is unsupported. Please upgrade your server as soon as possible.": "{realm} is running Zulip Server {serverVersion}, which is unsupported. Please upgrade your server as soon as possible.",
137140
"{realm} is running Zulip Server {serverVersion}, which is unsupported. Please contact your administrator about upgrading.": "{realm} is running Zulip Server {serverVersion}, which is unsupported. Please contact your administrator about upgrading.",
138141
"Learn more": "Learn more",

0 commit comments

Comments
 (0)