mirror of
https://github.com/flutter/flutter.git
synced 2026-01-09 07:51:35 +08:00
This reverts commit ef29db350f0951ab976e2fdb5d092e65578329e5. <!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> reland https://github.com/flutter/flutter/pull/165173 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
c00f050f11
commit
423a30323c
@ -298,6 +298,8 @@ void sendSemanticsUpdate() {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
@ -359,6 +361,8 @@ void sendSemanticsUpdateWithRole() {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
@ -420,6 +424,8 @@ void sendSemanticsUpdateWithLocale() {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: Locale('es', 'MX'),
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
@ -476,6 +482,8 @@ void sendSemanticsUpdateWithIsLink() {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: Locale('es', 'MX'),
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
|
||||
@ -1975,6 +1975,8 @@ abstract class SemanticsUpdateBuilder {
|
||||
SemanticsHitTestBehavior hitTestBehavior = SemanticsHitTestBehavior.defer,
|
||||
required SemanticsInputType inputType,
|
||||
required Locale? locale,
|
||||
required String minValue,
|
||||
required String maxValue,
|
||||
});
|
||||
|
||||
/// Update the custom semantics action associated with the given `id`.
|
||||
@ -2056,6 +2058,8 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
SemanticsHitTestBehavior hitTestBehavior = SemanticsHitTestBehavior.defer,
|
||||
required SemanticsInputType inputType,
|
||||
required Locale? locale,
|
||||
required String minValue,
|
||||
required String maxValue,
|
||||
}) {
|
||||
assert(_matrix4IsValid(transform));
|
||||
assert(
|
||||
@ -2107,6 +2111,8 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
hitTestBehavior.index,
|
||||
inputType.index,
|
||||
locale?.toLanguageTag() ?? '',
|
||||
minValue,
|
||||
maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2157,6 +2163,8 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
Int32,
|
||||
Int32,
|
||||
Handle,
|
||||
Handle,
|
||||
Handle,
|
||||
)
|
||||
>(symbol: 'SemanticsUpdateBuilder::updateNode')
|
||||
external void _updateNode(
|
||||
@ -2204,6 +2212,8 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
int hitTestBehaviorIndex,
|
||||
int inputType,
|
||||
String locale,
|
||||
String minValue,
|
||||
String maxValue,
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@ -145,6 +145,8 @@ struct SemanticsNode {
|
||||
double scrollPosition = std::nan("");
|
||||
double scrollExtentMax = std::nan("");
|
||||
double scrollExtentMin = std::nan("");
|
||||
std::string minValue;
|
||||
std::string maxValue;
|
||||
std::string identifier;
|
||||
std::string label;
|
||||
StringAttributes labelAttributes;
|
||||
|
||||
@ -74,7 +74,9 @@ void SemanticsUpdateBuilder::updateNode(
|
||||
int validationResult,
|
||||
int hitTestBehavior,
|
||||
int inputType,
|
||||
std::string locale) {
|
||||
std::string locale,
|
||||
std::string minValue,
|
||||
std::string maxValue) {
|
||||
FML_CHECK(scrollChildren == 0 ||
|
||||
(scrollChildren > 0 && childrenInHitTestOrder.data()))
|
||||
<< "Semantics update contained scrollChildren but did not have "
|
||||
@ -96,6 +98,8 @@ void SemanticsUpdateBuilder::updateNode(
|
||||
node.scrollPosition = scrollPosition;
|
||||
node.scrollExtentMax = scrollExtentMax;
|
||||
node.scrollExtentMin = scrollExtentMin;
|
||||
node.minValue = std::move(minValue);
|
||||
node.maxValue = std::move(maxValue);
|
||||
node.rect = SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
|
||||
SafeNarrow(right), SafeNarrow(bottom));
|
||||
node.identifier = std::move(identifier);
|
||||
|
||||
@ -73,7 +73,9 @@ class SemanticsUpdateBuilder
|
||||
int validationResult,
|
||||
int hitTestBehavior,
|
||||
int inputType,
|
||||
std::string locale);
|
||||
std::string locale,
|
||||
std::string minValue,
|
||||
std::string maxValue);
|
||||
|
||||
void updateCustomAction(int id,
|
||||
std::string label,
|
||||
|
||||
@ -753,6 +753,8 @@ class SemanticsUpdateBuilder {
|
||||
SemanticsHitTestBehavior hitTestBehavior = SemanticsHitTestBehavior.defer,
|
||||
required SemanticsInputType inputType,
|
||||
required Locale? locale,
|
||||
required String minValue,
|
||||
required String maxValue,
|
||||
}) {
|
||||
if (transform.length != 16) {
|
||||
throw ArgumentError('transform argument must have 16 entries.');
|
||||
@ -800,6 +802,8 @@ class SemanticsUpdateBuilder {
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
inputType: inputType,
|
||||
locale: locale,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -116,6 +116,7 @@ export 'engine/semantics/list.dart';
|
||||
export 'engine/semantics/live_region.dart';
|
||||
export 'engine/semantics/menus.dart';
|
||||
export 'engine/semantics/platform_view.dart';
|
||||
export 'engine/semantics/progress_bar.dart';
|
||||
export 'engine/semantics/requirable.dart';
|
||||
export 'engine/semantics/route.dart';
|
||||
export 'engine/semantics/scrollable.dart';
|
||||
|
||||
@ -20,6 +20,7 @@ export 'semantics/list.dart';
|
||||
export 'semantics/live_region.dart';
|
||||
export 'semantics/menus.dart';
|
||||
export 'semantics/platform_view.dart';
|
||||
export 'semantics/progress_bar.dart';
|
||||
export 'semantics/requirable.dart';
|
||||
export 'semantics/scrollable.dart';
|
||||
export 'semantics/semantics.dart';
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import 'label_and_value.dart';
|
||||
import 'semantics.dart';
|
||||
|
||||
/// Indicates a progress bar element.
|
||||
///
|
||||
/// Uses aria progressbar role to convey this semantic information to the element.
|
||||
///
|
||||
/// Screen-readers take advantage of "aria-label" to describe the visual.
|
||||
class SemanticsProgressBar extends SemanticRole {
|
||||
SemanticsProgressBar(SemanticsObject semanticsObject)
|
||||
: super.withBasics(
|
||||
EngineSemanticsRole.progressBar,
|
||||
semanticsObject,
|
||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||
) {
|
||||
setAriaRole('progressbar');
|
||||
_updateAriaAttributes();
|
||||
}
|
||||
|
||||
void _updateAriaAttributes() {
|
||||
// Set ARIA attributes for min, max and current value.
|
||||
if (semanticsObject.minValue?.isNotEmpty ?? false) {
|
||||
setAttribute('aria-valuemin', semanticsObject.minValue!);
|
||||
}
|
||||
if (semanticsObject.maxValue?.isNotEmpty ?? false) {
|
||||
setAttribute('aria-valuemax', semanticsObject.maxValue!);
|
||||
}
|
||||
if (semanticsObject.value?.isNotEmpty ?? false) {
|
||||
setAttribute('aria-valuenow', semanticsObject.value!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void update() {
|
||||
super.update();
|
||||
_updateAriaAttributes();
|
||||
}
|
||||
|
||||
@override
|
||||
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||
}
|
||||
|
||||
/// Indicates a loading spinner element.
|
||||
class SemanticsLoadingSpinner extends SemanticRole {
|
||||
SemanticsLoadingSpinner(SemanticsObject semanticsObject)
|
||||
: super.withBasics(
|
||||
EngineSemanticsRole.loadingSpinner,
|
||||
semanticsObject,
|
||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||
);
|
||||
|
||||
@override
|
||||
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||
}
|
||||
@ -37,6 +37,7 @@ import 'list.dart';
|
||||
import 'live_region.dart';
|
||||
import 'menus.dart';
|
||||
import 'platform_view.dart';
|
||||
import 'progress_bar.dart';
|
||||
import 'requirable.dart';
|
||||
import 'route.dart';
|
||||
import 'scrollable.dart';
|
||||
@ -275,6 +276,8 @@ class SemanticsNodeUpdate {
|
||||
this.hitTestBehavior = ui.SemanticsHitTestBehavior.defer,
|
||||
required this.inputType,
|
||||
required this.locale,
|
||||
required this.minValue,
|
||||
required this.maxValue,
|
||||
});
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
@ -399,6 +402,12 @@ class SemanticsNodeUpdate {
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final ui.Locale? locale;
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final String minValue;
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final String maxValue;
|
||||
}
|
||||
|
||||
/// Identifies [SemanticRole] implementations.
|
||||
@ -503,6 +512,12 @@ enum EngineSemanticsRole {
|
||||
/// An item in a [list].
|
||||
listItem,
|
||||
|
||||
/// A graphic object that shows progress with a numeric number.
|
||||
progressBar,
|
||||
|
||||
/// A graphic object that spins to indicate the application is busy.
|
||||
loadingSpinner,
|
||||
|
||||
/// A role used when a more specific role cannot be assigend to
|
||||
/// a [SemanticsObject].
|
||||
///
|
||||
@ -1550,6 +1565,31 @@ class SemanticsObject {
|
||||
_dirtyFields |= _hitTestBehaviorIndex;
|
||||
}
|
||||
|
||||
String? get minValue => _minValue;
|
||||
String? _minValue;
|
||||
|
||||
static const int _minValueIndex = 1 << 29;
|
||||
|
||||
/// Whether the [minValue] field has been updated but has not been
|
||||
/// applied to the DOM yet.
|
||||
bool get isMinValueDirty => _isDirty(_minValueIndex);
|
||||
void _markMinValueDirty() {
|
||||
_dirtyFields |= _minValueIndex;
|
||||
}
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
String? get maxValue => _maxValue;
|
||||
String? _maxValue;
|
||||
|
||||
static const int _maxValueIndex = 1 << 30;
|
||||
|
||||
/// Whether the [maxValue] field has been updated but has not been
|
||||
/// applied to the DOM yet.
|
||||
bool get isMaxValueDirty => _isDirty(_maxValueIndex);
|
||||
void _markMaxValueDirty() {
|
||||
_dirtyFields |= _maxValueIndex;
|
||||
}
|
||||
|
||||
/// A unique permanent identifier of the semantics node in the tree.
|
||||
final int id;
|
||||
|
||||
@ -1887,6 +1927,16 @@ class SemanticsObject {
|
||||
_markHitTestBehaviorDirty();
|
||||
}
|
||||
|
||||
if (_minValue != update.minValue) {
|
||||
_minValue = update.minValue;
|
||||
_markMinValueDirty();
|
||||
}
|
||||
|
||||
if (_maxValue != update.maxValue) {
|
||||
_maxValue = update.maxValue;
|
||||
_markMaxValueDirty();
|
||||
}
|
||||
|
||||
role = update.role;
|
||||
|
||||
inputType = update.inputType;
|
||||
@ -2139,14 +2189,16 @@ class SemanticsObject {
|
||||
return EngineSemanticsRole.region;
|
||||
case ui.SemanticsRole.form:
|
||||
return EngineSemanticsRole.form;
|
||||
case ui.SemanticsRole.loadingSpinner:
|
||||
return EngineSemanticsRole.loadingSpinner;
|
||||
case ui.SemanticsRole.progressBar:
|
||||
return EngineSemanticsRole.progressBar;
|
||||
// TODO(chunhtai): implement these roles.
|
||||
// https://github.com/flutter/flutter/issues/159741.
|
||||
case ui.SemanticsRole.dragHandle:
|
||||
case ui.SemanticsRole.spinButton:
|
||||
case ui.SemanticsRole.comboBox:
|
||||
case ui.SemanticsRole.tooltip:
|
||||
case ui.SemanticsRole.loadingSpinner:
|
||||
case ui.SemanticsRole.progressBar:
|
||||
case ui.SemanticsRole.hotKey:
|
||||
case ui.SemanticsRole.none:
|
||||
// fallback to checking semantics properties.
|
||||
@ -2213,6 +2265,8 @@ class SemanticsObject {
|
||||
EngineSemanticsRole.menuItemRadio => SemanticMenuItemRadio(this),
|
||||
EngineSemanticsRole.alert => SemanticAlert(this),
|
||||
EngineSemanticsRole.status => SemanticStatus(this),
|
||||
EngineSemanticsRole.progressBar => SemanticsProgressBar(this),
|
||||
EngineSemanticsRole.loadingSpinner => SemanticsLoadingSpinner(this),
|
||||
EngineSemanticsRole.generic => GenericRole(this),
|
||||
EngineSemanticsRole.complementary => SemanticComplementary(this),
|
||||
EngineSemanticsRole.contentInfo => SemanticContentInfo(this),
|
||||
|
||||
@ -160,6 +160,12 @@ void runSemanticsTests() {
|
||||
group('forms', () {
|
||||
_testForms();
|
||||
});
|
||||
group('progressBar', () {
|
||||
_testProgressBar();
|
||||
});
|
||||
group('loadingSpinner', () {
|
||||
_testLoadingSpinner();
|
||||
});
|
||||
}
|
||||
|
||||
void _testSemanticRole() {
|
||||
@ -6151,6 +6157,55 @@ void _testForms() {
|
||||
semantics().semanticsEnabled = false;
|
||||
}
|
||||
|
||||
void _testProgressBar() {
|
||||
test('nodes with progress bar role', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
SemanticsObject pumpSemantics() {
|
||||
final tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
role: ui.SemanticsRole.progressBar,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
}
|
||||
|
||||
final SemanticsObject object = pumpSemantics();
|
||||
expect(object.semanticRole?.kind, EngineSemanticsRole.progressBar);
|
||||
expect(object.element.getAttribute('role'), 'progressbar');
|
||||
});
|
||||
|
||||
semantics().semanticsEnabled = false;
|
||||
}
|
||||
|
||||
void _testLoadingSpinner() {
|
||||
test('nodes with loading spinner role', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
SemanticsObject pumpSemantics() {
|
||||
final tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
role: ui.SemanticsRole.loadingSpinner,
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
}
|
||||
|
||||
final SemanticsObject object = pumpSemantics();
|
||||
expect(object.semanticRole?.kind, EngineSemanticsRole.loadingSpinner);
|
||||
});
|
||||
|
||||
semantics().semanticsEnabled = false;
|
||||
}
|
||||
|
||||
/// A facade in front of [ui.SemanticsUpdateBuilder.updateNode] that
|
||||
/// supplies default values for semantics attributes.
|
||||
void updateNode(
|
||||
@ -6195,6 +6250,8 @@ void updateNode(
|
||||
ui.SemanticsHitTestBehavior hitTestBehavior = ui.SemanticsHitTestBehavior.defer,
|
||||
ui.SemanticsInputType inputType = ui.SemanticsInputType.none,
|
||||
ui.Locale? locale,
|
||||
String minValue = '0',
|
||||
String maxValue = '0',
|
||||
}) {
|
||||
transform ??= Float64List.fromList(Matrix4.identity().storage);
|
||||
hitTestTransform ??= Float64List.fromList(Matrix4.identity().storage);
|
||||
@ -6242,6 +6299,8 @@ void updateNode(
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
inputType: inputType,
|
||||
locale: locale,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -92,6 +92,8 @@ class SemanticsTester {
|
||||
ui.SemanticsHitTestBehavior hitTestBehavior = ui.SemanticsHitTestBehavior.defer,
|
||||
ui.SemanticsInputType inputType = ui.SemanticsInputType.none,
|
||||
ui.Locale? locale,
|
||||
String? minValue,
|
||||
String? maxValue,
|
||||
}) {
|
||||
// Actions
|
||||
if (hasTap ?? false) {
|
||||
@ -228,6 +230,8 @@ class SemanticsTester {
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
inputType: inputType,
|
||||
locale: locale,
|
||||
minValue: minValue ?? '0',
|
||||
maxValue: maxValue ?? '0',
|
||||
);
|
||||
_nodeUpdates.add(update);
|
||||
return update;
|
||||
|
||||
@ -316,6 +316,8 @@ void sendSemanticsUpdate() {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
|
||||
@ -200,6 +200,8 @@ Future<void> a11y_main() async {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
)
|
||||
..updateNode(
|
||||
id: 84,
|
||||
@ -238,6 +240,8 @@ Future<void> a11y_main() async {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
)
|
||||
..updateNode(
|
||||
id: 96,
|
||||
@ -276,6 +280,8 @@ Future<void> a11y_main() async {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
)
|
||||
..updateNode(
|
||||
id: 128,
|
||||
@ -314,6 +320,8 @@ Future<void> a11y_main() async {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
)
|
||||
..updateCustomAction(id: 21, label: 'Archive', hint: 'archive message');
|
||||
|
||||
@ -399,6 +407,8 @@ Future<void> a11y_string_attributes() async {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
|
||||
PlatformDispatcher.instance.setSemanticsTreeEnabled(true);
|
||||
@ -1689,6 +1699,8 @@ Future<void> a11y_main_multi_view() async {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -477,6 +477,8 @@ Future<void> sendSemanticsTreeInfo() async {
|
||||
controlsNodes: null,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ -77,6 +77,8 @@ class LocaleInitialization extends Scenario {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
|
||||
final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build();
|
||||
@ -139,6 +141,8 @@ class LocaleInitialization extends Scenario {
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
|
||||
final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build();
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -143,11 +144,20 @@ abstract class ProgressIndicator extends StatefulWidget {
|
||||
}
|
||||
|
||||
Widget _buildSemanticsWrapper({required BuildContext context, required Widget child}) {
|
||||
var isProgressBar = false;
|
||||
String? expandedSemanticsValue = semanticsValue;
|
||||
if (value != null) {
|
||||
expandedSemanticsValue ??= '${(_effectiveValue! * 100).round()}%';
|
||||
expandedSemanticsValue ??= '${(_effectiveValue! * 100).round()}';
|
||||
isProgressBar = true;
|
||||
}
|
||||
return Semantics(label: semanticsLabel, value: expandedSemanticsValue, child: child);
|
||||
return Semantics(
|
||||
label: semanticsLabel,
|
||||
role: isProgressBar ? SemanticsRole.progressBar : SemanticsRole.loadingSpinner,
|
||||
minValue: isProgressBar ? '0' : null,
|
||||
maxValue: isProgressBar ? '100' : null,
|
||||
value: expandedSemanticsValue,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1194,28 +1204,32 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (widget._indicatorType) {
|
||||
case _ActivityIndicatorType.material:
|
||||
if (widget._effectiveValue != null) {
|
||||
return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
|
||||
}
|
||||
return _buildAnimation();
|
||||
case _ActivityIndicatorType.adaptive:
|
||||
final ThemeData theme = Theme.of(context);
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
return _buildCupertinoIndicator(context);
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return Builder(
|
||||
builder: (BuildContext context) {
|
||||
switch (widget._indicatorType) {
|
||||
case _ActivityIndicatorType.material:
|
||||
if (widget._effectiveValue != null) {
|
||||
return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
|
||||
}
|
||||
return _buildAnimation();
|
||||
case _ActivityIndicatorType.adaptive:
|
||||
final ThemeData theme = Theme.of(context);
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
return _buildCupertinoIndicator(context);
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
if (widget._effectiveValue != null) {
|
||||
return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
|
||||
}
|
||||
return _buildAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1049,6 +1049,12 @@ class RenderCustomPaint extends RenderProxyBox {
|
||||
if (properties.inputType != null) {
|
||||
config.inputType = properties.inputType!;
|
||||
}
|
||||
if (properties.minValue != null) {
|
||||
config.minValue = properties.minValue;
|
||||
}
|
||||
if (properties.maxValue != null) {
|
||||
config.maxValue = properties.maxValue;
|
||||
}
|
||||
if (properties.onTap != null) {
|
||||
config.onTap = properties.onTap;
|
||||
}
|
||||
|
||||
@ -4945,6 +4945,12 @@ mixin SemanticsAnnotationsMixin on RenderObject {
|
||||
if (_properties.inputType != null) {
|
||||
config.inputType = _properties.inputType!;
|
||||
}
|
||||
if (_properties.minValue != null) {
|
||||
config.minValue = _properties.minValue;
|
||||
}
|
||||
if (_properties.maxValue != null) {
|
||||
config.maxValue = _properties.maxValue;
|
||||
}
|
||||
|
||||
// Registering _perform* as action handlers instead of the user provided
|
||||
// ones to ensure that changing a user provided handler from a non-null to
|
||||
|
||||
@ -183,14 +183,14 @@ sealed class _DebugSemanticsRoleChecks {
|
||||
SemanticsRole.navigation => _semanticsNavigation,
|
||||
SemanticsRole.region => _semanticsRegion,
|
||||
SemanticsRole.form => _noCheckRequired,
|
||||
SemanticsRole.loadingSpinner => _noCheckRequired,
|
||||
SemanticsRole.progressBar => _semanticsProgressBar,
|
||||
// TODO(chunhtai): add checks when the roles are used in framework.
|
||||
// https://github.com/flutter/flutter/issues/159741.
|
||||
SemanticsRole.dragHandle => _unimplemented,
|
||||
SemanticsRole.spinButton => _unimplemented,
|
||||
SemanticsRole.comboBox => _unimplemented,
|
||||
SemanticsRole.tooltip => _unimplemented,
|
||||
SemanticsRole.loadingSpinner => _unimplemented,
|
||||
SemanticsRole.progressBar => _unimplemented,
|
||||
SemanticsRole.hotKey => _unimplemented,
|
||||
}(node);
|
||||
|
||||
@ -206,6 +206,48 @@ sealed class _DebugSemanticsRoleChecks {
|
||||
|
||||
static FlutterError? _noCheckRequired(SemanticsNode node) => null;
|
||||
|
||||
static FlutterError? _semanticsProgressBar(SemanticsNode node) {
|
||||
final SemanticsData data = node.getSemanticsData();
|
||||
|
||||
// Check if value is present
|
||||
if (data.value.isEmpty) {
|
||||
return FlutterError('A progress bar must have a value');
|
||||
}
|
||||
|
||||
// Check if minValue and maxValue are present
|
||||
if (data.minValue?.isEmpty ?? true) {
|
||||
return FlutterError('A progress bar must have a minValue');
|
||||
}
|
||||
|
||||
if (data.maxValue?.isEmpty ?? true) {
|
||||
return FlutterError('A progress bar must have a maxValue');
|
||||
}
|
||||
|
||||
// Validate that value is within min and max range
|
||||
try {
|
||||
final double currentValue = double.parse(data.value);
|
||||
final double minVal = double.parse(data.minValue!);
|
||||
final double maxVal = double.parse(data.maxValue!);
|
||||
|
||||
if (currentValue < minVal || currentValue > maxVal) {
|
||||
return FlutterError(
|
||||
'Progress bar value ($currentValue) must be between minValue ($minVal) and maxValue ($maxVal)',
|
||||
);
|
||||
}
|
||||
|
||||
if (minVal >= maxVal) {
|
||||
return FlutterError('Progress bar minValue ($minVal) must be less than maxValue ($maxVal)');
|
||||
}
|
||||
} catch (e) {
|
||||
return FlutterError(
|
||||
'Progress bar value, minValue, and maxValue must be valid numbers. '
|
||||
'value: "${data.value}", minValue: "${data.minValue}", maxValue: "${data.maxValue}"',
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static FlutterError? _semanticsTab(SemanticsNode node) {
|
||||
final SemanticsData data = node.getSemanticsData();
|
||||
if (data.flagsCollection.isSelected == Tristate.none) {
|
||||
@ -1024,6 +1066,8 @@ class SemanticsData with Diagnosticable {
|
||||
required this.hitTestBehavior,
|
||||
required this.inputType,
|
||||
required this.locale,
|
||||
required this.minValue,
|
||||
required this.maxValue,
|
||||
this.tags,
|
||||
this.transform,
|
||||
this.customSemanticsActionIds,
|
||||
@ -1302,6 +1346,12 @@ class SemanticsData with Diagnosticable {
|
||||
/// content of this semantics node.
|
||||
final Locale? locale;
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.maxValue}
|
||||
final String? maxValue;
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.minValue}
|
||||
final String? minValue;
|
||||
|
||||
/// Whether [flags] contains the given flag.
|
||||
@Deprecated(
|
||||
'Use flagsCollection instead. '
|
||||
@ -1386,6 +1436,8 @@ class SemanticsData with Diagnosticable {
|
||||
),
|
||||
);
|
||||
}
|
||||
properties.add(StringProperty('minValue', minValue, defaultValue: null));
|
||||
properties.add(StringProperty('maxValue', maxValue, defaultValue: null));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1422,7 +1474,11 @@ class SemanticsData with Diagnosticable {
|
||||
other.inputType == inputType &&
|
||||
other.hitTestBehavior == hitTestBehavior &&
|
||||
_sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds) &&
|
||||
setEquals<String>(controlsNodes, other.controlsNodes);
|
||||
setEquals<String>(controlsNodes, other.controlsNodes) &&
|
||||
other.traversalParentIdentifier == traversalParentIdentifier &&
|
||||
other.traversalChildIdentifier == traversalChildIdentifier &&
|
||||
other.minValue == minValue &&
|
||||
other.maxValue == maxValue;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1458,6 +1514,10 @@ class SemanticsData with Diagnosticable {
|
||||
controlsNodes == null ? null : Object.hashAll(controlsNodes!),
|
||||
inputType,
|
||||
hitTestBehavior,
|
||||
traversalParentIdentifier,
|
||||
traversalChildIdentifier,
|
||||
minValue,
|
||||
maxValue,
|
||||
),
|
||||
);
|
||||
|
||||
@ -1641,6 +1701,8 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
this.onExpand,
|
||||
this.onCollapse,
|
||||
this.customSemanticsActions,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
}) : assert(
|
||||
label == null || attributedLabel == null,
|
||||
'Only one of label or attributedLabel should be provided',
|
||||
@ -2579,6 +2641,28 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
/// {@endtemplate}
|
||||
final SemanticsInputType? inputType;
|
||||
|
||||
/// {@template flutter.semantics.SemanticsProperties.maxValue}
|
||||
/// The maximum value of the node.
|
||||
///
|
||||
/// Used in conjunction with [value] to define the current value and range
|
||||
/// of a node. A typical usage is for progress indicators, where [value]
|
||||
/// represents the current progress and [maxValue] defines the maximum
|
||||
/// possible value.
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final String? maxValue;
|
||||
|
||||
/// {@template flutter.semantics.SemanticsProperties.minValue}
|
||||
/// The minimum value of the node.
|
||||
///
|
||||
/// Used in conjunction with [value] to define the current value and range
|
||||
/// of a node. A typical usage is for progress indicators, where [value]
|
||||
/// represents the current progress and [minValue] defines the minimum
|
||||
/// possible value.
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final String? minValue;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
@ -3249,7 +3333,11 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
_linkUrl != config._linkUrl ||
|
||||
_role != config.role ||
|
||||
_validationResult != config.validationResult ||
|
||||
_hitTestBehavior != config.hitTestBehavior;
|
||||
_hitTestBehavior != config.hitTestBehavior ||
|
||||
_traversalChildIdentifier != config._traversalChildIdentifier ||
|
||||
_traversalParentIdentifier != config._traversalParentIdentifier ||
|
||||
_minValue != config._minValue ||
|
||||
_maxValue != config._maxValue;
|
||||
}
|
||||
|
||||
// TAGS, LABELS, ACTIONS
|
||||
@ -3539,6 +3627,14 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
Set<String>? get controlsNodes => _controlsNodes;
|
||||
Set<String>? _controlsNodes = _kEmptyConfig.controlsNodes;
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.minValue}
|
||||
String? get minValue => _minValue;
|
||||
String? _minValue;
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.maxValue}
|
||||
String? get maxValue => _maxValue;
|
||||
String? _maxValue;
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.validationResult}
|
||||
SemanticsValidationResult get validationResult => _validationResult;
|
||||
SemanticsValidationResult _validationResult = _kEmptyConfig.validationResult;
|
||||
@ -3631,6 +3727,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
_inputType = config._inputType;
|
||||
_locale = config.locale;
|
||||
|
||||
_minValue = config.minValue;
|
||||
_maxValue = config.maxValue;
|
||||
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
||||
|
||||
if (mergeAllDescendantsIntoThisNodeValueChanged) {
|
||||
@ -3686,6 +3784,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
SemanticsInputType inputType = _inputType;
|
||||
final Locale? locale = _locale;
|
||||
final customSemanticsActionIds = <int>{};
|
||||
String? minValue = _minValue;
|
||||
String? maxValue = _maxValue;
|
||||
for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
|
||||
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
|
||||
}
|
||||
@ -3795,6 +3895,9 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
controlsNodes = <String>{...controlsNodes!, ...node._controlsNodes!};
|
||||
}
|
||||
|
||||
minValue ??= node._minValue;
|
||||
maxValue ??= node._maxValue;
|
||||
|
||||
if (validationResult == SemanticsValidationResult.none) {
|
||||
validationResult = node._validationResult;
|
||||
} else if (validationResult == SemanticsValidationResult.valid) {
|
||||
@ -3844,6 +3947,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
inputType: inputType,
|
||||
locale: locale,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
@ -4016,6 +4121,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
hitTestBehavior: data.hitTestBehavior,
|
||||
inputType: data.inputType,
|
||||
locale: data.locale,
|
||||
minValue: data.minValue ?? '',
|
||||
maxValue: data.maxValue ?? '',
|
||||
);
|
||||
_dirty = false;
|
||||
}
|
||||
@ -4308,6 +4415,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
),
|
||||
);
|
||||
}
|
||||
properties.add(StringProperty('minValue', _minValue, defaultValue: null));
|
||||
properties.add(StringProperty('maxValue', _maxValue, defaultValue: null));
|
||||
}
|
||||
|
||||
/// Returns a string representation of this node and its descendants.
|
||||
@ -6453,6 +6562,22 @@ class SemanticsConfiguration {
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.maxValue}
|
||||
String? get maxValue => _maxValue;
|
||||
String? _maxValue;
|
||||
set maxValue(String? value) {
|
||||
_maxValue = value;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.minValue}
|
||||
String? get minValue => _minValue;
|
||||
String? _minValue;
|
||||
set minValue(String? value) {
|
||||
_minValue = value;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
// TAGS
|
||||
|
||||
/// The set of tags that this configuration wants to add to all child
|
||||
@ -6560,6 +6685,12 @@ class SemanticsConfiguration {
|
||||
other._hitTestBehavior != ui.SemanticsHitTestBehavior.defer) {
|
||||
return false;
|
||||
}
|
||||
if (_minValue != null && other._minValue != null) {
|
||||
return false;
|
||||
}
|
||||
if (_maxValue != null && other._maxValue != null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -6669,6 +6800,8 @@ class SemanticsConfiguration {
|
||||
_accessiblityFocusBlockType = _accessiblityFocusBlockType._merge(
|
||||
child._accessiblityFocusBlockType,
|
||||
);
|
||||
_minValue ??= child._minValue;
|
||||
_maxValue ??= child._maxValue;
|
||||
|
||||
if (_hitTestBehavior == ui.SemanticsHitTestBehavior.defer &&
|
||||
child._hitTestBehavior != ui.SemanticsHitTestBehavior.defer) {
|
||||
@ -6721,7 +6854,11 @@ class SemanticsConfiguration {
|
||||
.._controlsNodes = _controlsNodes
|
||||
.._validationResult = _validationResult
|
||||
.._inputType = _inputType
|
||||
.._hitTestBehavior = _hitTestBehavior;
|
||||
.._hitTestBehavior = _hitTestBehavior
|
||||
.._traversalChildIdentifier = _traversalChildIdentifier
|
||||
.._traversalParentIdentifier = _traversalParentIdentifier
|
||||
.._minValue = _minValue
|
||||
.._maxValue = _maxValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4108,6 +4108,8 @@ sealed class _SemanticsBase extends SingleChildRenderObjectWidget {
|
||||
required ui.SemanticsHitTestBehavior? hitTestBehavior,
|
||||
required ui.SemanticsInputType? inputType,
|
||||
required Locale? localeForSubtree,
|
||||
required String? minValue,
|
||||
required String? maxValue,
|
||||
}) : this.fromProperties(
|
||||
key: key,
|
||||
child: child,
|
||||
@ -4193,6 +4195,8 @@ sealed class _SemanticsBase extends SingleChildRenderObjectWidget {
|
||||
validationResult: validationResult,
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
inputType: inputType,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
),
|
||||
);
|
||||
|
||||
@ -4440,6 +4444,8 @@ class SliverSemantics extends _SemanticsBase {
|
||||
super.hitTestBehavior,
|
||||
super.inputType,
|
||||
super.localeForSubtree,
|
||||
super.minValue,
|
||||
super.maxValue,
|
||||
}) : super(child: sliver);
|
||||
|
||||
/// {@macro flutter.widgets.SemanticsBase.fromProperties}
|
||||
@ -8023,6 +8029,8 @@ class Semantics extends _SemanticsBase {
|
||||
super.hitTestBehavior,
|
||||
super.inputType,
|
||||
super.localeForSubtree,
|
||||
super.minValue,
|
||||
super.maxValue,
|
||||
});
|
||||
|
||||
/// {@macro flutter.widgets.SemanticsBase.fromProperties}
|
||||
|
||||
@ -378,7 +378,12 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(CircularProgressIndicator)),
|
||||
matchesSemantics(value: '0%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(
|
||||
value: '0',
|
||||
textDirection: TextDirection.ltr,
|
||||
minValue: '0',
|
||||
maxValue: '100',
|
||||
),
|
||||
);
|
||||
handle.dispose();
|
||||
},
|
||||
@ -936,7 +941,8 @@ void main() {
|
||||
final SemanticsHandle handle = tester.ensureSemantics();
|
||||
final GlobalKey key = GlobalKey();
|
||||
const label = 'Label';
|
||||
const value = '25%';
|
||||
const value = '25';
|
||||
|
||||
await tester.pumpWidget(
|
||||
Theme(
|
||||
data: theme,
|
||||
@ -978,7 +984,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byKey(key)),
|
||||
matchesSemantics(textDirection: TextDirection.ltr, label: label, value: '25%'),
|
||||
matchesSemantics(textDirection: TextDirection.ltr, label: label, value: '25'),
|
||||
);
|
||||
|
||||
handle.dispose();
|
||||
@ -1033,7 +1039,8 @@ void main() {
|
||||
final SemanticsHandle handle = tester.ensureSemantics();
|
||||
final GlobalKey key = GlobalKey();
|
||||
const label = 'Label';
|
||||
const value = '25%';
|
||||
const value = '25';
|
||||
|
||||
await tester.pumpWidget(
|
||||
Theme(
|
||||
data: theme,
|
||||
@ -1061,7 +1068,7 @@ void main() {
|
||||
final SemanticsHandle handle = tester.ensureSemantics();
|
||||
final GlobalKey key = GlobalKey();
|
||||
const label = 'Label';
|
||||
const value = '25%';
|
||||
const value = '25';
|
||||
await tester.pumpWidget(
|
||||
Theme(
|
||||
data: theme,
|
||||
@ -1995,7 +2002,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(LinearProgressIndicator)),
|
||||
matchesSemantics(value: '100%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(value: '100', textDirection: TextDirection.ltr),
|
||||
);
|
||||
|
||||
// Test value < 0.0
|
||||
@ -2005,7 +2012,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(LinearProgressIndicator)),
|
||||
matchesSemantics(value: '0%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(value: '0', textDirection: TextDirection.ltr),
|
||||
);
|
||||
|
||||
handle.dispose();
|
||||
@ -2021,7 +2028,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(CircularProgressIndicator)),
|
||||
matchesSemantics(value: '100%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(value: '100', textDirection: TextDirection.ltr),
|
||||
);
|
||||
|
||||
// Test value < 0.0
|
||||
@ -2031,7 +2038,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(CircularProgressIndicator)),
|
||||
matchesSemantics(value: '0%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(value: '0', textDirection: TextDirection.ltr),
|
||||
);
|
||||
|
||||
handle.dispose();
|
||||
@ -2047,7 +2054,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(RefreshProgressIndicator)),
|
||||
matchesSemantics(value: '100%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(value: '100', textDirection: TextDirection.ltr),
|
||||
);
|
||||
|
||||
// Test value < 0.0
|
||||
@ -2057,7 +2064,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSemantics(find.byType(RefreshProgressIndicator)),
|
||||
matchesSemantics(value: '0%', textDirection: TextDirection.ltr),
|
||||
matchesSemantics(value: '0', textDirection: TextDirection.ltr),
|
||||
);
|
||||
|
||||
handle.dispose();
|
||||
|
||||
@ -720,7 +720,9 @@ void main() {
|
||||
' scrollPosition: null\n'
|
||||
' scrollExtentMax: null\n'
|
||||
' indexInParent: null\n'
|
||||
' headingLevel: 0\n',
|
||||
' headingLevel: 0\n'
|
||||
' minValue: null\n'
|
||||
' maxValue: null\n',
|
||||
);
|
||||
|
||||
final config = SemanticsConfiguration()
|
||||
@ -868,7 +870,9 @@ void main() {
|
||||
' scrollPosition: null\n'
|
||||
' scrollExtentMax: null\n'
|
||||
' indexInParent: null\n'
|
||||
' headingLevel: 0\n',
|
||||
' headingLevel: 0\n'
|
||||
' minValue: null\n'
|
||||
' maxValue: null\n',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -322,6 +322,8 @@ class SemanticsUpdateBuilderSpy extends Fake implements ui.SemanticsUpdateBuilde
|
||||
ui.SemanticsHitTestBehavior hitTestBehavior = ui.SemanticsHitTestBehavior.defer,
|
||||
required ui.SemanticsInputType inputType,
|
||||
required ui.Locale? locale,
|
||||
required String minValue,
|
||||
required String maxValue,
|
||||
}) {
|
||||
// Makes sure we don't send the same id twice.
|
||||
assert(!observations.containsKey(id));
|
||||
|
||||
@ -726,6 +726,8 @@ class SemanticsTester {
|
||||
double? scrollExtentMin,
|
||||
int? currentValueLength,
|
||||
int? maxValueLength,
|
||||
String? maxValue,
|
||||
String? minValue,
|
||||
SemanticsNode? ancestor,
|
||||
SemanticsInputType? inputType,
|
||||
}) {
|
||||
@ -823,6 +825,12 @@ class SemanticsTester {
|
||||
if (inputType != null && node.inputType != inputType) {
|
||||
return false;
|
||||
}
|
||||
if (maxValue != null && node.maxValue != maxValue) {
|
||||
return false;
|
||||
}
|
||||
if (minValue != null && node.minValue != minValue) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1133,6 +1141,8 @@ class _IncludesNodeWith extends Matcher {
|
||||
this.maxValueLength,
|
||||
this.currentValueLength,
|
||||
this.inputType,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
}) : assert(
|
||||
label != null ||
|
||||
value != null ||
|
||||
@ -1148,6 +1158,7 @@ class _IncludesNodeWith extends Matcher {
|
||||
maxValueLength != null ||
|
||||
currentValueLength != null ||
|
||||
inputType != null,
|
||||
minValue != null || maxValue != null,
|
||||
);
|
||||
final AttributedString? attributedLabel;
|
||||
final AttributedString? attributedValue;
|
||||
@ -1168,6 +1179,8 @@ class _IncludesNodeWith extends Matcher {
|
||||
final int? currentValueLength;
|
||||
final int? maxValueLength;
|
||||
final SemanticsInputType? inputType;
|
||||
final String? minValue;
|
||||
final String? maxValue;
|
||||
|
||||
@override
|
||||
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||
@ -1192,6 +1205,8 @@ class _IncludesNodeWith extends Matcher {
|
||||
currentValueLength: currentValueLength,
|
||||
maxValueLength: maxValueLength,
|
||||
inputType: inputType,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
)
|
||||
.isNotEmpty;
|
||||
}
|
||||
@ -1228,6 +1243,8 @@ class _IncludesNodeWith extends Matcher {
|
||||
if (currentValueLength != null) 'currentValueLength "$currentValueLength"',
|
||||
if (maxValueLength != null) 'maxValueLength "$maxValueLength"',
|
||||
if (inputType != null) 'inputType $inputType',
|
||||
if (minValue != null) 'minValue "$minValue"',
|
||||
if (maxValue != null) 'maxValue "$maxValue"',
|
||||
];
|
||||
return strings.join(', ');
|
||||
}
|
||||
@ -1257,6 +1274,8 @@ Matcher includesNodeWith({
|
||||
int? maxValueLength,
|
||||
int? currentValueLength,
|
||||
SemanticsInputType? inputType,
|
||||
String? minValue,
|
||||
String? maxValue,
|
||||
}) {
|
||||
return _IncludesNodeWith(
|
||||
label: label,
|
||||
@ -1278,5 +1297,7 @@ Matcher includesNodeWith({
|
||||
maxValueLength: maxValueLength,
|
||||
currentValueLength: currentValueLength,
|
||||
inputType: inputType,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
@ -687,6 +687,8 @@ Matcher matchesSemantics({
|
||||
int? currentValueLength,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
ui.SemanticsInputType? inputType,
|
||||
String? maxValue,
|
||||
String? minValue,
|
||||
// Flags //
|
||||
bool hasCheckedState = false,
|
||||
bool isChecked = false,
|
||||
@ -772,6 +774,8 @@ Matcher matchesSemantics({
|
||||
currentValueLength: currentValueLength,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
// Flags
|
||||
hasCheckedState: hasCheckedState,
|
||||
isChecked: isChecked,
|
||||
@ -887,6 +891,8 @@ Matcher containsSemantics({
|
||||
int? currentValueLength,
|
||||
SemanticsValidationResult? validationResult,
|
||||
ui.SemanticsInputType? inputType,
|
||||
String? maxValue,
|
||||
String? minValue,
|
||||
// Flags
|
||||
bool? hasCheckedState,
|
||||
bool? isChecked,
|
||||
@ -972,6 +978,8 @@ Matcher containsSemantics({
|
||||
currentValueLength: currentValueLength,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
minValue: minValue,
|
||||
maxValue: maxValue,
|
||||
// Flags
|
||||
hasCheckedState: hasCheckedState,
|
||||
isChecked: isChecked,
|
||||
@ -2404,6 +2412,8 @@ class _MatchesSemanticsData extends Matcher {
|
||||
required this.currentValueLength,
|
||||
required this.validationResult,
|
||||
required this.inputType,
|
||||
required this.minValue,
|
||||
required this.maxValue,
|
||||
// Flags
|
||||
required bool? hasCheckedState,
|
||||
required bool? isChecked,
|
||||
@ -2551,6 +2561,8 @@ class _MatchesSemanticsData extends Matcher {
|
||||
final ui.SemanticsInputType? inputType;
|
||||
final List<Matcher>? children;
|
||||
final SemanticsValidationResult? validationResult;
|
||||
final String? maxValue;
|
||||
final String? minValue;
|
||||
|
||||
/// There are three possible states for these two maps:
|
||||
///
|
||||
@ -2664,6 +2676,12 @@ class _MatchesSemanticsData extends Matcher {
|
||||
if (validationResult != null) {
|
||||
description.add(' with validation result: $validationResult');
|
||||
}
|
||||
if (minValue != null) {
|
||||
description.add(' with minValue: $minValue');
|
||||
}
|
||||
if (maxValue != null) {
|
||||
description.add(' with maxValue: $maxValue');
|
||||
}
|
||||
if (children != null) {
|
||||
description.add(' with children:\n ');
|
||||
final List<_MatchesSemanticsData> childMatches = children!.cast<_MatchesSemanticsData>();
|
||||
@ -2823,6 +2841,12 @@ class _MatchesSemanticsData extends Matcher {
|
||||
if (inputType != null && inputType != data.inputType) {
|
||||
return failWithDescription(matchState, 'inputType was: ${data.inputType}');
|
||||
}
|
||||
if (minValue != null && minValue != data.minValue) {
|
||||
return failWithDescription(matchState, 'minValue was: ${data.minValue}');
|
||||
}
|
||||
if (maxValue != null && maxValue != data.maxValue) {
|
||||
return failWithDescription(matchState, 'maxValue was: ${data.maxValue}');
|
||||
}
|
||||
if (actions.isNotEmpty) {
|
||||
final unexpectedActions = <SemanticsAction>[];
|
||||
final missingActions = <SemanticsAction>[];
|
||||
|
||||
@ -756,6 +756,8 @@ void main() {
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -948,6 +950,8 @@ void main() {
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
maxValue: '',
|
||||
minValue: '',
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1114,6 +1118,8 @@ void main() {
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
minValue: '',
|
||||
maxValue: '',
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1417,6 +1423,8 @@ void main() {
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1520,6 +1528,8 @@ void main() {
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1628,6 +1638,8 @@ void main() {
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
final emptyNode = _FakeSemanticsNode(emptyData);
|
||||
|
||||
@ -1664,6 +1676,8 @@ void main() {
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
final fullNode = _FakeSemanticsNode(fullData);
|
||||
|
||||
@ -1799,6 +1813,8 @@ void main() {
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
minValue: '0',
|
||||
maxValue: '0',
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user