mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Update accessibleNavigation when the state of accessibility changes (flutter/engine#33576)
This commit is contained in:
parent
b907b374ce
commit
53318be751
@ -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,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user