From 349a77867dcf6827bf2e558d5d62038bfc12d9ba Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 12:50:32 -0700 Subject: [PATCH 1/7] AccessibilityOverridesPane UI --- .../accessibility/accessibility_screen.dart | 215 +++++++++++++++++- .../accessibility_screen_test.dart | 97 ++++++++ 2 files changed, 306 insertions(+), 6 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart b/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart index 6c3efd2a224..b416ffcd6b2 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart @@ -82,13 +82,216 @@ class AccessibilityOverridesPane extends StatelessWidget { @override Widget build(BuildContext context) { - return const DevToolsAreaPane( - header: AreaPaneHeader(title: Text('Accessibility Overrides')), - child: CenteredMessage( - message: - 'Accessibility overrides placeholder.\n' - '// TODO(hannah-hyj): Implement setting overrides (brightness, text scale, bold text, screen reader, high contrast).', + final theme = Theme.of(context); + final controller = screenControllers.lookup(); + return DevToolsAreaPane( + header: const AreaPaneHeader(title: Text('Accessibility Overrides')), + child: Scrollbar( + child: SingleChildScrollView( + padding: const EdgeInsets.all(defaultSpacing), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Simulate and test accessibility settings on the connected device in real-time.', + style: theme.subtleTextStyle, + ), + const SizedBox(height: defaultSpacing), + const Divider(), + const SizedBox(height: denseSpacing), + _BrightnessOverride(controller: controller), + const SizedBox(height: defaultSpacing), + _TextScaleOverride(controller: controller), + const SizedBox(height: defaultSpacing), + _SwitchOverride( + label: 'Bold Text', + description: 'Forces all text in the application to be bold.', + notifier: controller.boldText, + ), + const SizedBox(height: defaultSpacing), + _SwitchOverride( + label: 'Screen Reader Debugger', + description: + 'Enables interactive screen reader simulation semantics.', + notifier: controller.screenReader, + ), + const SizedBox(height: defaultSpacing), + _SwitchOverride( + label: 'High Contrast', + description: 'Increases the contrast of text and icons.', + notifier: controller.highContrast, + ), + ], + ), + ), ), ); } } + +class _BrightnessOverride extends StatelessWidget { + const _BrightnessOverride({required this.controller}); + + final AccessibilityController controller; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Brightness', + style: theme.boldTextStyle, + ), + const SizedBox(height: densePadding), + Text( + 'Override the color scheme mode of the app.', + style: theme.subtleTextStyle, + ), + const SizedBox(height: denseSpacing), + ValueListenableBuilder( + valueListenable: controller.brightness, + builder: (context, value, _) { + return RoundedDropDownButton( + isExpanded: true, + value: value, + items: const [ + DropdownMenuItem( + value: 'System', + child: Text('System Default'), + ), + DropdownMenuItem( + value: 'Light', + child: Text('Light Mode'), + ), + DropdownMenuItem( + value: 'Dark', + child: Text('Dark Mode'), + ), + ], + onChanged: (newValue) { + if (newValue != null) { + controller.brightness.value = newValue; + } + }, + ); + }, + ), + ], + ); + } +} + +class _TextScaleOverride extends StatelessWidget { + const _TextScaleOverride({required this.controller}); + + final AccessibilityController controller; + + static const _minTextScale = 0.5; + static const _maxTextScale = 3.0; + static const _textScaleDivisions = 25; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ValueListenableBuilder( + valueListenable: controller.textScale, + builder: (context, value, _) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Text Scale', + style: theme.boldTextStyle, + ), + const SizedBox(height: densePadding), + Text( + 'Scale the system font size.', + style: theme.subtleTextStyle, + ), + ], + ), + Text( + '${value.toStringAsFixed(2)}x', + style: theme.boldTextStyle, + ), + ], + ); + }, + ), + const SizedBox(height: densePadding), + ValueListenableBuilder( + valueListenable: controller.textScale, + builder: (context, value, _) { + return Slider( + value: value, + min: _minTextScale, + max: _maxTextScale, + divisions: _textScaleDivisions, + onChanged: (newValue) { + controller.textScale.value = newValue; + }, + ); + }, + ), + ], + ); + } +} + +class _SwitchOverride extends StatelessWidget { + const _SwitchOverride({ + required this.label, + required this.description, + required this.notifier, + }); + + final String label; + final String description; + final ValueNotifier notifier; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.boldTextStyle, + ), + const SizedBox(height: densePadding), + Text( + description, + style: theme.subtleTextStyle, + ), + ], + ), + ), + ValueListenableBuilder( + valueListenable: notifier, + builder: (context, enabled, _) { + return Switch( + value: enabled, + onChanged: (newValue) { + notifier.value = newValue; + }, + ); + }, + ), + ], + ); + } +} + + diff --git a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart index 595cf4a4a7b..0251e9fafee 100644 --- a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart +++ b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart @@ -76,5 +76,102 @@ void main() { // Semantics Tree pane should be visible expect(find.byType(AccessibilitySemanticsTreePane), findsOneWidget); }); + + testWidgetsWithWindowSize( + 'renders all override controls in AccessibilityOverridesPane', + windowSize, + (WidgetTester tester) async { + await pumpAccessibilityScreen(tester); + await tester.pumpAndSettle(); + + expect(find.text('Accessibility Overrides'), findsOneWidget); + expect( + find.text( + 'Simulate and test accessibility settings on the connected device in real-time.', + ), + findsOneWidget, + ); + + // Brightness controls + expect(find.text('Brightness'), findsOneWidget); + expect( + find.text('Override the color scheme mode of the app.'), + findsOneWidget, + ); + expect( + find.byType(RoundedDropDownButton), + findsOneWidget, + ); + + // Text Scale controls + expect(find.text('Text Scale'), findsOneWidget); + expect(find.text('Scale the system font size.'), findsOneWidget); + expect(find.text('1.00x'), findsOneWidget); + expect(find.byType(Slider), findsOneWidget); + + // Switches + expect(find.text('Bold Text'), findsOneWidget); + expect( + find.text('Forces all text in the application to be bold.'), + findsOneWidget, + ); + expect(find.text('Screen Reader Debugger'), findsOneWidget); + expect( + find.text('Enables interactive screen reader simulation semantics.'), + findsOneWidget, + ); + expect(find.text('High Contrast'), findsOneWidget); + expect( + find.text('Increases the contrast of text and icons.'), + findsOneWidget, + ); + expect(find.byType(Switch), findsNWidgets(3)); + }, + ); + + testWidgetsWithWindowSize( + 'interacting with override controls updates controller state', + windowSize, + (WidgetTester tester) async { + await pumpAccessibilityScreen(tester); + await tester.pumpAndSettle(); + + // 1. Test Brightness Dropdown + expect(controller.brightness.value, 'System'); + await tester.tap(find.byType(RoundedDropDownButton)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Light Mode').last); + await tester.pumpAndSettle(); + expect(controller.brightness.value, 'Light'); + + // 2. Test Bold Text Switch + expect(controller.boldText.value, isFalse); + final boldTextSwitch = find.byType(Switch).at(0); + await tester.ensureVisible(boldTextSwitch); + await tester.pumpAndSettle(); + await tester.tap(boldTextSwitch); + await tester.pumpAndSettle(); + expect(controller.boldText.value, isTrue); + + // 3. Test Screen Reader Debugger Switch + expect(controller.screenReader.value, isFalse); + final screenReaderSwitch = find.byType(Switch).at(1); + await tester.ensureVisible(screenReaderSwitch); + await tester.pumpAndSettle(); + await tester.tap(screenReaderSwitch); + await tester.pumpAndSettle(); + expect(controller.screenReader.value, isTrue); + + // 4. Test High Contrast Switch + expect(controller.highContrast.value, isFalse); + final highContrastSwitch = find.byType(Switch).at(2); + await tester.ensureVisible(highContrastSwitch); + await tester.pumpAndSettle(); + await tester.tap(highContrastSwitch); + await tester.pumpAndSettle(); + expect(controller.highContrast.value, isTrue); + }, + ); }); } + From 562125be9f8b74f3148b106b991faeb06624f82c Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 13:20:05 -0700 Subject: [PATCH 2/7] 1 --- .../accessibility/accessibility_screen.dart | 33 ++++++++----------- .../accessibility_screen_test.dart | 16 +++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart b/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart index b416ffcd6b2..baa22660ab4 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart @@ -195,13 +195,13 @@ class _TextScaleOverride extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ValueListenableBuilder( - valueListenable: controller.textScale, - builder: (context, value, _) { - return Row( + return ValueListenableBuilder( + valueListenable: controller.textScale, + builder: (context, value, _) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( @@ -223,14 +223,9 @@ class _TextScaleOverride extends StatelessWidget { style: theme.boldTextStyle, ), ], - ); - }, - ), - const SizedBox(height: densePadding), - ValueListenableBuilder( - valueListenable: controller.textScale, - builder: (context, value, _) { - return Slider( + ), + const SizedBox(height: densePadding), + Slider( value: value, min: _minTextScale, max: _maxTextScale, @@ -238,10 +233,10 @@ class _TextScaleOverride extends StatelessWidget { onChanged: (newValue) { controller.textScale.value = newValue; }, - ); - }, - ), - ], + ), + ], + ); + }, ); } } diff --git a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart index 0251e9fafee..61a25e6bdfe 100644 --- a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart +++ b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart @@ -136,6 +136,16 @@ void main() { await pumpAccessibilityScreen(tester); await tester.pumpAndSettle(); + Finder findSwitchFor(String label) { + return find.descendant( + of: find.ancestor( + of: find.text(label), + matching: find.byType(Row), + ), + matching: find.byType(Switch), + ); + } + // 1. Test Brightness Dropdown expect(controller.brightness.value, 'System'); await tester.tap(find.byType(RoundedDropDownButton)); @@ -146,7 +156,7 @@ void main() { // 2. Test Bold Text Switch expect(controller.boldText.value, isFalse); - final boldTextSwitch = find.byType(Switch).at(0); + final boldTextSwitch = findSwitchFor('Bold Text'); await tester.ensureVisible(boldTextSwitch); await tester.pumpAndSettle(); await tester.tap(boldTextSwitch); @@ -155,7 +165,7 @@ void main() { // 3. Test Screen Reader Debugger Switch expect(controller.screenReader.value, isFalse); - final screenReaderSwitch = find.byType(Switch).at(1); + final screenReaderSwitch = findSwitchFor('Screen Reader Debugger'); await tester.ensureVisible(screenReaderSwitch); await tester.pumpAndSettle(); await tester.tap(screenReaderSwitch); @@ -164,7 +174,7 @@ void main() { // 4. Test High Contrast Switch expect(controller.highContrast.value, isFalse); - final highContrastSwitch = find.byType(Switch).at(2); + final highContrastSwitch = findSwitchFor('High Contrast'); await tester.ensureVisible(highContrastSwitch); await tester.pumpAndSettle(); await tester.tap(highContrastSwitch); From 7c9ade103041b7e0342530e0495d45419c2a2be9 Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 14:59:10 -0700 Subject: [PATCH 3/7] resolve comments --- .../accessibility_controller.dart | 15 +- .../accessibility/accessibility_screen.dart | 238 +----------------- .../screens/accessibility/overrides_pane.dart | 219 ++++++++++++++++ .../accessibility/semantics_tree_pane.dart | 28 +++ .../accessibility_screen_test.dart | 10 +- 5 files changed, 272 insertions(+), 238 deletions(-) create mode 100644 packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart create mode 100644 packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart diff --git a/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart b/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart index 69f6dd12e0d..887ddf5b9fb 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart @@ -8,6 +8,17 @@ import 'package:flutter/foundation.dart'; import '../../shared/framework/screen.dart'; import '../../shared/framework/screen_controllers.dart'; +/// Modes for brightness override in the accessibility controls. +enum BrightnessOverride { + system('System Default'), + light('Light Mode'), + dark('Dark Mode'); + + const BrightnessOverride(this.display); + + final String display; +} + /// Controller for the Accessibility screen. class AccessibilityController extends DevToolsScreenController with AutoDisposeControllerMixin { @@ -49,7 +60,9 @@ class AccessibilityController extends DevToolsScreenController final screenId = ScreenMetaData.accessibility.id; // --- Accessibility Overrides State --- - final brightness = ValueNotifier('System'); + final brightness = ValueNotifier( + BrightnessOverride.system, + ); final textScale = ValueNotifier(1.0); final boldText = ValueNotifier(false); final screenReader = ValueNotifier(false); diff --git a/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart b/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart index baa22660ab4..fc387547b58 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart @@ -8,8 +8,12 @@ import 'package:flutter/material.dart'; import '../../shared/framework/screen.dart'; import '../../shared/globals.dart'; -import '../../shared/ui/common_widgets.dart'; import 'accessibility_controller.dart'; +import 'overrides_pane.dart'; +import 'semantics_tree_pane.dart'; + +export 'overrides_pane.dart'; +export 'semantics_tree_pane.dart'; /// A screen that displays accessibility information. class AccessibilityScreen extends Screen { @@ -58,235 +62,3 @@ class _AccessibilityScreenBodyState extends State return screenSize.width > 1000 ? Axis.horizontal : Axis.vertical; } } - -/// A pane that displays the semantics tree of the connected app. -class AccessibilitySemanticsTreePane extends StatelessWidget { - const AccessibilitySemanticsTreePane({super.key}); - - @override - Widget build(BuildContext context) { - return const DevToolsAreaPane( - header: AreaPaneHeader(title: Text('Semantics Tree')), - child: CenteredMessage( - message: - 'Accessibility semantics tree placeholder.\n' - '// TODO(hannah-hyj): Implement semantics tree view and details explorer.', - ), - ); - } -} - -/// A pane that displays the accessibility overrides controls. -class AccessibilityOverridesPane extends StatelessWidget { - const AccessibilityOverridesPane({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final controller = screenControllers.lookup(); - return DevToolsAreaPane( - header: const AreaPaneHeader(title: Text('Accessibility Overrides')), - child: Scrollbar( - child: SingleChildScrollView( - padding: const EdgeInsets.all(defaultSpacing), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Simulate and test accessibility settings on the connected device in real-time.', - style: theme.subtleTextStyle, - ), - const SizedBox(height: defaultSpacing), - const Divider(), - const SizedBox(height: denseSpacing), - _BrightnessOverride(controller: controller), - const SizedBox(height: defaultSpacing), - _TextScaleOverride(controller: controller), - const SizedBox(height: defaultSpacing), - _SwitchOverride( - label: 'Bold Text', - description: 'Forces all text in the application to be bold.', - notifier: controller.boldText, - ), - const SizedBox(height: defaultSpacing), - _SwitchOverride( - label: 'Screen Reader Debugger', - description: - 'Enables interactive screen reader simulation semantics.', - notifier: controller.screenReader, - ), - const SizedBox(height: defaultSpacing), - _SwitchOverride( - label: 'High Contrast', - description: 'Increases the contrast of text and icons.', - notifier: controller.highContrast, - ), - ], - ), - ), - ), - ); - } -} - -class _BrightnessOverride extends StatelessWidget { - const _BrightnessOverride({required this.controller}); - - final AccessibilityController controller; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Brightness', - style: theme.boldTextStyle, - ), - const SizedBox(height: densePadding), - Text( - 'Override the color scheme mode of the app.', - style: theme.subtleTextStyle, - ), - const SizedBox(height: denseSpacing), - ValueListenableBuilder( - valueListenable: controller.brightness, - builder: (context, value, _) { - return RoundedDropDownButton( - isExpanded: true, - value: value, - items: const [ - DropdownMenuItem( - value: 'System', - child: Text('System Default'), - ), - DropdownMenuItem( - value: 'Light', - child: Text('Light Mode'), - ), - DropdownMenuItem( - value: 'Dark', - child: Text('Dark Mode'), - ), - ], - onChanged: (newValue) { - if (newValue != null) { - controller.brightness.value = newValue; - } - }, - ); - }, - ), - ], - ); - } -} - -class _TextScaleOverride extends StatelessWidget { - const _TextScaleOverride({required this.controller}); - - final AccessibilityController controller; - - static const _minTextScale = 0.5; - static const _maxTextScale = 3.0; - static const _textScaleDivisions = 25; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return ValueListenableBuilder( - valueListenable: controller.textScale, - builder: (context, value, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Text Scale', - style: theme.boldTextStyle, - ), - const SizedBox(height: densePadding), - Text( - 'Scale the system font size.', - style: theme.subtleTextStyle, - ), - ], - ), - Text( - '${value.toStringAsFixed(2)}x', - style: theme.boldTextStyle, - ), - ], - ), - const SizedBox(height: densePadding), - Slider( - value: value, - min: _minTextScale, - max: _maxTextScale, - divisions: _textScaleDivisions, - onChanged: (newValue) { - controller.textScale.value = newValue; - }, - ), - ], - ); - }, - ); - } -} - -class _SwitchOverride extends StatelessWidget { - const _SwitchOverride({ - required this.label, - required this.description, - required this.notifier, - }); - - final String label; - final String description; - final ValueNotifier notifier; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: theme.boldTextStyle, - ), - const SizedBox(height: densePadding), - Text( - description, - style: theme.subtleTextStyle, - ), - ], - ), - ), - ValueListenableBuilder( - valueListenable: notifier, - builder: (context, enabled, _) { - return Switch( - value: enabled, - onChanged: (newValue) { - notifier.value = newValue; - }, - ); - }, - ), - ], - ); - } -} - - diff --git a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart new file mode 100644 index 00000000000..61a33abfea4 --- /dev/null +++ b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart @@ -0,0 +1,219 @@ +// Copyright 2026 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/material.dart'; + +import '../../shared/globals.dart'; +import 'accessibility_controller.dart'; + +/// A pane that displays the accessibility overrides controls. +class AccessibilityOverridesPane extends StatelessWidget { + const AccessibilityOverridesPane({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final controller = screenControllers.lookup(); + return DevToolsAreaPane( + header: const AreaPaneHeader( + title: Text('Accessibility Overrides'), + includeTopBorder: false, + ), + child: Scrollbar( + child: SingleChildScrollView( + padding: const EdgeInsets.all(defaultSpacing), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Simulate and test accessibility settings on the connected device in real-time.', + style: theme.subtleTextStyle, + ), + const SizedBox(height: defaultSpacing), + const Divider(), + const SizedBox(height: denseSpacing), + _BrightnessOverride(controller: controller), + const SizedBox(height: defaultSpacing), + _TextScaleOverride(controller: controller), + const SizedBox(height: defaultSpacing), + _SwitchOverride( + label: 'Bold Text', + description: 'Forces all text in the application to be bold.', + notifier: controller.boldText, + ), + const SizedBox(height: defaultSpacing), + _SwitchOverride( + label: 'Screen Reader Debugger', + description: + 'Enables interactive screen reader simulation semantics.', + notifier: controller.screenReader, + ), + const SizedBox(height: defaultSpacing), + _SwitchOverride( + label: 'High Contrast', + description: 'Increases the contrast of text and icons.', + notifier: controller.highContrast, + ), + ], + ), + ), + ), + ); + } +} + +class _AccessibilityPanelLabel extends StatelessWidget { + const _AccessibilityPanelLabel({ + required this.label, + required this.description, + }); + + final String label; + final String description; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.boldTextStyle, + ), + const SizedBox(height: densePadding), + Text( + description, + style: theme.subtleTextStyle, + ), + ], + ); + } +} + +class _BrightnessOverride extends StatelessWidget { + const _BrightnessOverride({required this.controller}); + + final AccessibilityController controller; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const _AccessibilityPanelLabel( + label: 'Brightness', + description: 'Override the color scheme mode of the app.', + ), + const SizedBox(height: denseSpacing), + ValueListenableBuilder( + valueListenable: controller.brightness, + builder: (context, value, _) { + return RoundedDropDownButton( + isExpanded: true, + value: value, + items: BrightnessOverride.values.map((option) { + return DropdownMenuItem( + value: option, + child: Text(option.display), + ); + }).toList(), + onChanged: (newValue) { + if (newValue != null) { + controller.brightness.value = newValue; + } + }, + ); + }, + ), + ], + ); + } +} + +class _TextScaleOverride extends StatelessWidget { + const _TextScaleOverride({required this.controller}); + + final AccessibilityController controller; + + static const _minTextScale = 0.5; + static const _maxTextScale = 3.0; + static const _textScaleDivisions = 25; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return ValueListenableBuilder( + valueListenable: controller.textScale, + builder: (context, value, _) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const _AccessibilityPanelLabel( + label: 'Text Scale', + description: 'Scale the system font size.', + ), + Text( + '${value.toStringAsFixed(2)}x', + style: theme.boldTextStyle, + ), + ], + ), + const SizedBox(height: densePadding), + Slider( + value: value, + min: _minTextScale, + max: _maxTextScale, + divisions: _textScaleDivisions, + onChanged: (newValue) { + controller.textScale.value = newValue; + }, + ), + ], + ); + }, + ); + } +} + +class _SwitchOverride extends StatelessWidget { + const _SwitchOverride({ + required this.label, + required this.description, + required this.notifier, + }); + + final String label; + final String description; + final ValueNotifier notifier; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _AccessibilityPanelLabel( + label: label, + description: description, + ), + ), + ValueListenableBuilder( + valueListenable: notifier, + builder: (context, enabled, _) { + return Switch( + value: enabled, + onChanged: (newValue) { + notifier.value = newValue; + }, + ); + }, + ), + ], + ); + } +} diff --git a/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart b/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart new file mode 100644 index 00000000000..d75be31c802 --- /dev/null +++ b/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart @@ -0,0 +1,28 @@ +// Copyright 2026 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/material.dart'; + +import '../../shared/ui/common_widgets.dart'; + +/// A pane that displays the semantics tree of the connected app. +class AccessibilitySemanticsTreePane extends StatelessWidget { + const AccessibilitySemanticsTreePane({super.key}); + + @override + Widget build(BuildContext context) { + return const DevToolsAreaPane( + header: AreaPaneHeader( + title: Text('Semantics Tree'), + includeTopBorder: false, + ), + child: CenteredMessage( + message: + 'Accessibility semantics tree placeholder.\n' + '// TODO(hannah-hyj): Implement semantics tree view and details explorer.', + ), + ); + } +} diff --git a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart index 61a25e6bdfe..b6165a6506b 100644 --- a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart +++ b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart @@ -99,7 +99,7 @@ void main() { findsOneWidget, ); expect( - find.byType(RoundedDropDownButton), + find.byType(RoundedDropDownButton), findsOneWidget, ); @@ -147,12 +147,14 @@ void main() { } // 1. Test Brightness Dropdown - expect(controller.brightness.value, 'System'); - await tester.tap(find.byType(RoundedDropDownButton)); + expect(controller.brightness.value, BrightnessOverride.system); + await tester.tap( + find.byType(RoundedDropDownButton), + ); await tester.pumpAndSettle(); await tester.tap(find.text('Light Mode').last); await tester.pumpAndSettle(); - expect(controller.brightness.value, 'Light'); + expect(controller.brightness.value, BrightnessOverride.light); // 2. Test Bold Text Switch expect(controller.boldText.value, isFalse); From 64e52ef2ede85798ea8e4429c10179562edc3d69 Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 15:09:41 -0700 Subject: [PATCH 4/7] Update overrides_pane.dart --- .../src/screens/accessibility/overrides_pane.dart | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart index 61a33abfea4..c2bc26d1a50 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart @@ -46,8 +46,7 @@ class AccessibilityOverridesPane extends StatelessWidget { const SizedBox(height: defaultSpacing), _SwitchOverride( label: 'Screen Reader Debugger', - description: - 'Enables interactive screen reader simulation semantics.', + description: 'Debug and test screen reader layouts.', notifier: controller.screenReader, ), const SizedBox(height: defaultSpacing), @@ -79,15 +78,9 @@ class _AccessibilityPanelLabel extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - label, - style: theme.boldTextStyle, - ), + Text(label, style: theme.boldTextStyle), const SizedBox(height: densePadding), - Text( - description, - style: theme.subtleTextStyle, - ), + Text(description, style: theme.subtleTextStyle), ], ); } From 58e31e1cbff3150c5b6220398904ed7a58ab23a8 Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 15:14:35 -0700 Subject: [PATCH 5/7] small fix --- .../lib/src/screens/accessibility/overrides_pane.dart | 1 + .../lib/src/screens/accessibility/semantics_tree_pane.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart index c2bc26d1a50..149f038d5b4 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart @@ -19,6 +19,7 @@ class AccessibilityOverridesPane extends StatelessWidget { return DevToolsAreaPane( header: const AreaPaneHeader( title: Text('Accessibility Overrides'), + roundedTopBorder: false, includeTopBorder: false, ), child: Scrollbar( diff --git a/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart b/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart index d75be31c802..8081874954c 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/semantics_tree_pane.dart @@ -17,6 +17,7 @@ class AccessibilitySemanticsTreePane extends StatelessWidget { header: AreaPaneHeader( title: Text('Semantics Tree'), includeTopBorder: false, + roundedTopBorder: false, ), child: CenteredMessage( message: From f5f931d8a47b61ff47ee515681ccde62022270c2 Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 16:41:16 -0700 Subject: [PATCH 6/7] resolve comments --- .../src/screens/accessibility/overrides_pane.dart | 13 ++----------- .../accessibility/accessibility_screen_test.dart | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart index 149f038d5b4..f50ee3ab5f5 100644 --- a/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart +++ b/packages/devtools_app/lib/src/screens/accessibility/overrides_pane.dart @@ -6,6 +6,7 @@ import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/material.dart'; import '../../shared/globals.dart'; +import '../../shared/ui/common_widgets.dart'; import 'accessibility_controller.dart'; /// A pane that displays the accessibility overrides controls. @@ -196,17 +197,7 @@ class _SwitchOverride extends StatelessWidget { description: description, ), ), - ValueListenableBuilder( - valueListenable: notifier, - builder: (context, enabled, _) { - return Switch( - value: enabled, - onChanged: (newValue) { - notifier.value = newValue; - }, - ); - }, - ), + NotifierSwitch(notifier: notifier), ], ); } diff --git a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart index b6165a6506b..7eabe38ffd8 100644 --- a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart +++ b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart @@ -117,7 +117,7 @@ void main() { ); expect(find.text('Screen Reader Debugger'), findsOneWidget); expect( - find.text('Enables interactive screen reader simulation semantics.'), + find.text('Debug and test screen reader layouts.'), findsOneWidget, ); expect(find.text('High Contrast'), findsOneWidget); From fe091fe6c70faa48903d58a75424307b82158e1b Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 14 Jul 2026 17:10:46 -0700 Subject: [PATCH 7/7] lint --- .../screens/accessibility/accessibility_screen_test.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart index 7eabe38ffd8..ba5e53e637b 100644 --- a/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart +++ b/packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart @@ -138,10 +138,7 @@ void main() { Finder findSwitchFor(String label) { return find.descendant( - of: find.ancestor( - of: find.text(label), - matching: find.byType(Row), - ), + of: find.ancestor(of: find.text(label), matching: find.byType(Row)), matching: find.byType(Switch), ); } @@ -186,4 +183,3 @@ void main() { ); }); } -