Update accessibleNavigation when the state of accessibility changes (flutter/engine#33576)

This commit is contained in:
nbayati 2022-05-23 17:58:05 -07:00 committed by GitHub
parent b907b374ce
commit 53318be751
5 changed files with 201 additions and 68 deletions

View File

@ -120,7 +120,7 @@ abstract class PlatformDispatcher {
class PlatformConfiguration {
const PlatformConfiguration({
this.accessibilityFeatures = const AccessibilityFeatures._(0),
this.accessibilityFeatures = const engine.EngineAccessibilityFeatures(0),
this.alwaysUse24HourFormat = false,
this.semanticsEnabled = false,
this.platformBrightness = Brightness.light,

View File

@ -54,8 +54,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
/// The current platform configuration.
@override
ui.PlatformConfiguration get configuration => _configuration;
ui.PlatformConfiguration _configuration = ui.PlatformConfiguration(
ui.PlatformConfiguration configuration = ui.PlatformConfiguration(
locales: parseBrowserLanguages(),
textScaleFactor: findBrowserTextScaleFactor(),
);
@ -725,12 +724,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
/// The empty list is not a valid value for locales. This is only used for
/// testing locale update logic.
void debugResetLocales() {
_configuration = _configuration.copyWith(locales: const <ui.Locale>[]);
configuration = configuration.copyWith(locales: const <ui.Locale>[]);
}
// Called by FlutterViewEmbedder when browser languages change.
void updateLocales() {
_configuration = _configuration.copyWith(locales: parseBrowserLanguages());
configuration = configuration.copyWith(locales: parseBrowserLanguages());
}
static List<ui.Locale> parseBrowserLanguages() {
@ -788,7 +787,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
/// [onPlatformConfigurationChanged] callbacks if [textScaleFactor] changed.
void _updateTextScaleFactor(double value) {
if (configuration.textScaleFactor != value) {
_configuration = configuration.copyWith(textScaleFactor: value);
configuration = configuration.copyWith(textScaleFactor: value);
invokeOnPlatformConfigurationChanged();
invokeOnTextScaleFactorChanged();
}
@ -859,7 +858,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
void updateSemanticsEnabled(bool semanticsEnabled) {
if (semanticsEnabled != this.semanticsEnabled) {
_configuration = _configuration.copyWith(semanticsEnabled: semanticsEnabled);
configuration = configuration.copyWith(semanticsEnabled: semanticsEnabled);
if (_onSemanticsEnabledChanged != null) {
invokeOnSemanticsEnabledChanged();
}
@ -875,7 +874,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
/// callback if [_platformBrightness] changed.
void _updatePlatformBrightness(ui.Brightness value) {
if (configuration.platformBrightness != value) {
_configuration = configuration.copyWith(platformBrightness: value);
configuration = configuration.copyWith(platformBrightness: value);
invokeOnPlatformConfigurationChanged();
invokeOnPlatformBrightnessChanged();
}

View File

@ -26,6 +26,108 @@ import 'semantics_helper.dart';
import 'tappable.dart';
import 'text_field.dart';
class EngineAccessibilityFeatures implements ui.AccessibilityFeatures {
const EngineAccessibilityFeatures(this._index);
static const int _kAccessibleNavigation = 1 << 0;
static const int _kInvertColorsIndex = 1 << 1;
static const int _kDisableAnimationsIndex = 1 << 2;
static const int _kBoldTextIndex = 1 << 3;
static const int _kReduceMotionIndex = 1 << 4;
static const int _kHighContrastIndex = 1 << 5;
static const int _kOnOffSwitchLabelsIndex = 1 << 6;
// A bitfield which represents each enabled feature.
final int _index;
@override
bool get accessibleNavigation => _kAccessibleNavigation & _index != 0;
@override
bool get invertColors => _kInvertColorsIndex & _index != 0;
@override
bool get disableAnimations => _kDisableAnimationsIndex & _index != 0;
@override
bool get boldText => _kBoldTextIndex & _index != 0;
@override
bool get reduceMotion => _kReduceMotionIndex & _index != 0;
@override
bool get highContrast => _kHighContrastIndex & _index != 0;
@override
bool get onOffSwitchLabels => _kOnOffSwitchLabelsIndex & _index != 0;
@override
String toString() {
final List<String> features = <String>[];
if (accessibleNavigation) {
features.add('accessibleNavigation');
}
if (invertColors) {
features.add('invertColors');
}
if (disableAnimations) {
features.add('disableAnimations');
}
if (boldText) {
features.add('boldText');
}
if (reduceMotion) {
features.add('reduceMotion');
}
if (highContrast) {
features.add('highContrast');
}
if (onOffSwitchLabels) {
features.add('onOffSwitchLabels');
}
return 'AccessibilityFeatures$features';
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is EngineAccessibilityFeatures && other._index == _index;
}
@override
int get hashCode => _index.hashCode;
EngineAccessibilityFeatures copyWith({
bool? accessibleNavigation,
bool? invertColors,
bool? disableAnimations,
bool? boldText,
bool? reduceMotion,
bool? highContrast,
bool? onOffSwitchLabels})
{
int value = 0;
if (accessibleNavigation ?? this.accessibleNavigation) {
value = value | _kAccessibleNavigation;
}
if (invertColors ?? this.invertColors) {
value = value | _kInvertColorsIndex;
}
if (disableAnimations ?? this.disableAnimations) {
value = value | _kDisableAnimationsIndex;
}
if (boldText ?? this.boldText) {
value = value | _kBoldTextIndex;
}
if (reduceMotion ?? this.reduceMotion) {
value = value | _kReduceMotionIndex;
}
if (highContrast ?? this.highContrast) {
value = value | _kHighContrastIndex;
}
if (onOffSwitchLabels ?? this.onOffSwitchLabels) {
value = value | _kOnOffSwitchLabelsIndex;
}
return EngineAccessibilityFeatures(value);
}
}
/// Contains updates for the semantics tree.
///
/// This class provides private engine-side API that's not available in the
@ -1433,6 +1535,15 @@ class EngineSemanticsOwner {
if (value == _semanticsEnabled) {
return;
}
final EngineAccessibilityFeatures original =
EnginePlatformDispatcher.instance.configuration.accessibilityFeatures
as EngineAccessibilityFeatures;
final ui.PlatformConfiguration newConfiguration =
EnginePlatformDispatcher.instance.configuration.copyWith(
accessibilityFeatures:
original.copyWith(accessibleNavigation: value));
EnginePlatformDispatcher.instance.configuration = newConfiguration;
_semanticsEnabled = value;
if (!_semanticsEnabled) {

View File

@ -148,66 +148,14 @@ abstract class SingletonFlutterWindow extends FlutterWindow {
void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name);
}
class AccessibilityFeatures {
const AccessibilityFeatures._(this._index);
static const int _kAccessibleNavigation = 1 << 0;
static const int _kInvertColorsIndex = 1 << 1;
static const int _kDisableAnimationsIndex = 1 << 2;
static const int _kBoldTextIndex = 1 << 3;
static const int _kReduceMotionIndex = 1 << 4;
static const int _kHighContrastIndex = 1 << 5;
static const int _kOnOffSwitchLabelsIndex = 1 << 6;
// A bitfield which represents each enabled feature.
final int _index;
bool get accessibleNavigation => _kAccessibleNavigation & _index != 0;
bool get invertColors => _kInvertColorsIndex & _index != 0;
bool get disableAnimations => _kDisableAnimationsIndex & _index != 0;
bool get boldText => _kBoldTextIndex & _index != 0;
bool get reduceMotion => _kReduceMotionIndex & _index != 0;
bool get highContrast => _kHighContrastIndex & _index != 0;
bool get onOffSwitchLabels => _kOnOffSwitchLabelsIndex & _index != 0;
@override
String toString() {
final List<String> features = <String>[];
if (accessibleNavigation) {
features.add('accessibleNavigation');
}
if (invertColors) {
features.add('invertColors');
}
if (disableAnimations) {
features.add('disableAnimations');
}
if (boldText) {
features.add('boldText');
}
if (reduceMotion) {
features.add('reduceMotion');
}
if (highContrast) {
features.add('highContrast');
}
if (onOffSwitchLabels) {
features.add('onOffSwitchLabels');
}
return 'AccessibilityFeatures$features';
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is AccessibilityFeatures
&& other._index == _index;
}
@override
int get hashCode => _index.hashCode;
abstract class AccessibilityFeatures {
bool get accessibleNavigation;
bool get invertColors;
bool get disableAnimations;
bool get boldText;
bool get reduceMotion;
bool get highContrast;
bool get onOffSwitchLabels;
}
enum Brightness {

View File

@ -118,9 +118,79 @@ void _testEngineSemanticsOwner() {
expect(placeholder.isConnected, isFalse);
});
test('accessibilityFeatures copyWith function works', () {
const EngineAccessibilityFeatures original = EngineAccessibilityFeatures(0);
EngineAccessibilityFeatures copy = original.copyWith(accessibleNavigation: true);
expect(copy.accessibleNavigation, true);
expect(copy.boldText, false);
expect(copy.disableAnimations, false);
expect(copy.highContrast, false);
expect(copy.invertColors, false);
expect(copy.onOffSwitchLabels, false);
expect(copy.reduceMotion, false);
copy = original.copyWith(boldText: true);
expect(copy.accessibleNavigation, false);
expect(copy.boldText, true);
expect(copy.disableAnimations, false);
expect(copy.highContrast, false);
expect(copy.invertColors, false);
expect(copy.onOffSwitchLabels, false);
expect(copy.reduceMotion, false);
copy = original.copyWith(disableAnimations: true);
expect(copy.accessibleNavigation, false);
expect(copy.boldText, false);
expect(copy.disableAnimations, true);
expect(copy.highContrast, false);
expect(copy.invertColors, false);
expect(copy.onOffSwitchLabels, false);
expect(copy.reduceMotion, false);
copy = original.copyWith(highContrast: true);
expect(copy.accessibleNavigation, false);
expect(copy.boldText, false);
expect(copy.disableAnimations, false);
expect(copy.highContrast, true);
expect(copy.invertColors, false);
expect(copy.onOffSwitchLabels, false);
expect(copy.reduceMotion, false);
copy = original.copyWith(invertColors: true);
expect(copy.accessibleNavigation, false);
expect(copy.boldText, false);
expect(copy.disableAnimations, false);
expect(copy.highContrast, false);
expect(copy.invertColors, true);
expect(copy.onOffSwitchLabels, false);
expect(copy.reduceMotion, false);
copy = original.copyWith(onOffSwitchLabels: true);
expect(copy.accessibleNavigation, false);
expect(copy.boldText, false);
expect(copy.disableAnimations, false);
expect(copy.highContrast, false);
expect(copy.invertColors, false);
expect(copy.onOffSwitchLabels, true);
expect(copy.reduceMotion, false);
copy = original.copyWith(reduceMotion: true);
expect(copy.accessibleNavigation, false);
expect(copy.boldText, false);
expect(copy.disableAnimations, false);
expect(copy.highContrast, false);
expect(copy.invertColors, false);
expect(copy.onOffSwitchLabels, false);
expect(copy.reduceMotion, true);
});
test('auto-enables semantics', () async {
flutterViewEmbedder.reset(); // triggers `autoEnableOnTap` to be called
expect(semantics().semanticsEnabled, isFalse);
expect(
EnginePlatformDispatcher
.instance.configuration.accessibilityFeatures.accessibleNavigation,
isFalse);
final html.Element placeholder =
appHostNode.querySelector('flt-semantics-placeholder')!;
@ -133,6 +203,10 @@ void _testEngineSemanticsOwner() {
semantics().updateSemantics(builder.build());
expect(semantics().semanticsEnabled, isTrue);
expect(
EnginePlatformDispatcher
.instance.configuration.accessibilityFeatures.accessibleNavigation,
isTrue);
// The placeholder should be removed
expect(placeholder.isConnected, isFalse);
@ -1753,6 +1827,7 @@ void _testPlatformView() {
rect: const ui.Rect.fromLTRB(0, 0, 20, 60),
childrenInTraversalOrder: Int32List.fromList(<int>[1, 2, 3]),
childrenInHitTestOrder: Int32List.fromList(<int>[1, 2, 3]),
transform: Float64List.fromList(Matrix4.diagonal3Values(ui.window.devicePixelRatio, ui.window.devicePixelRatio, 1).storage)
);
updateNode(
builder,