Skip to content

Commit 349a778

Browse files
committed
AccessibilityOverridesPane UI
1 parent 21f1838 commit 349a778

2 files changed

Lines changed: 306 additions & 6 deletions

File tree

packages/devtools_app/lib/src/screens/accessibility/accessibility_screen.dart

Lines changed: 209 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,216 @@ class AccessibilityOverridesPane extends StatelessWidget {
8282

8383
@override
8484
Widget build(BuildContext context) {
85-
return const DevToolsAreaPane(
86-
header: AreaPaneHeader(title: Text('Accessibility Overrides')),
87-
child: CenteredMessage(
88-
message:
89-
'Accessibility overrides placeholder.\n'
90-
'// TODO(hannah-hyj): Implement setting overrides (brightness, text scale, bold text, screen reader, high contrast).',
85+
final theme = Theme.of(context);
86+
final controller = screenControllers.lookup<AccessibilityController>();
87+
return DevToolsAreaPane(
88+
header: const AreaPaneHeader(title: Text('Accessibility Overrides')),
89+
child: Scrollbar(
90+
child: SingleChildScrollView(
91+
padding: const EdgeInsets.all(defaultSpacing),
92+
child: Column(
93+
crossAxisAlignment: CrossAxisAlignment.start,
94+
children: [
95+
Text(
96+
'Simulate and test accessibility settings on the connected device in real-time.',
97+
style: theme.subtleTextStyle,
98+
),
99+
const SizedBox(height: defaultSpacing),
100+
const Divider(),
101+
const SizedBox(height: denseSpacing),
102+
_BrightnessOverride(controller: controller),
103+
const SizedBox(height: defaultSpacing),
104+
_TextScaleOverride(controller: controller),
105+
const SizedBox(height: defaultSpacing),
106+
_SwitchOverride(
107+
label: 'Bold Text',
108+
description: 'Forces all text in the application to be bold.',
109+
notifier: controller.boldText,
110+
),
111+
const SizedBox(height: defaultSpacing),
112+
_SwitchOverride(
113+
label: 'Screen Reader Debugger',
114+
description:
115+
'Enables interactive screen reader simulation semantics.',
116+
notifier: controller.screenReader,
117+
),
118+
const SizedBox(height: defaultSpacing),
119+
_SwitchOverride(
120+
label: 'High Contrast',
121+
description: 'Increases the contrast of text and icons.',
122+
notifier: controller.highContrast,
123+
),
124+
],
125+
),
126+
),
91127
),
92128
);
93129
}
94130
}
131+
132+
class _BrightnessOverride extends StatelessWidget {
133+
const _BrightnessOverride({required this.controller});
134+
135+
final AccessibilityController controller;
136+
137+
@override
138+
Widget build(BuildContext context) {
139+
final theme = Theme.of(context);
140+
return Column(
141+
crossAxisAlignment: CrossAxisAlignment.start,
142+
children: [
143+
Text(
144+
'Brightness',
145+
style: theme.boldTextStyle,
146+
),
147+
const SizedBox(height: densePadding),
148+
Text(
149+
'Override the color scheme mode of the app.',
150+
style: theme.subtleTextStyle,
151+
),
152+
const SizedBox(height: denseSpacing),
153+
ValueListenableBuilder<String>(
154+
valueListenable: controller.brightness,
155+
builder: (context, value, _) {
156+
return RoundedDropDownButton<String>(
157+
isExpanded: true,
158+
value: value,
159+
items: const [
160+
DropdownMenuItem(
161+
value: 'System',
162+
child: Text('System Default'),
163+
),
164+
DropdownMenuItem(
165+
value: 'Light',
166+
child: Text('Light Mode'),
167+
),
168+
DropdownMenuItem(
169+
value: 'Dark',
170+
child: Text('Dark Mode'),
171+
),
172+
],
173+
onChanged: (newValue) {
174+
if (newValue != null) {
175+
controller.brightness.value = newValue;
176+
}
177+
},
178+
);
179+
},
180+
),
181+
],
182+
);
183+
}
184+
}
185+
186+
class _TextScaleOverride extends StatelessWidget {
187+
const _TextScaleOverride({required this.controller});
188+
189+
final AccessibilityController controller;
190+
191+
static const _minTextScale = 0.5;
192+
static const _maxTextScale = 3.0;
193+
static const _textScaleDivisions = 25;
194+
195+
@override
196+
Widget build(BuildContext context) {
197+
final theme = Theme.of(context);
198+
return Column(
199+
crossAxisAlignment: CrossAxisAlignment.start,
200+
children: [
201+
ValueListenableBuilder<double>(
202+
valueListenable: controller.textScale,
203+
builder: (context, value, _) {
204+
return Row(
205+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
206+
children: [
207+
Column(
208+
crossAxisAlignment: CrossAxisAlignment.start,
209+
children: [
210+
Text(
211+
'Text Scale',
212+
style: theme.boldTextStyle,
213+
),
214+
const SizedBox(height: densePadding),
215+
Text(
216+
'Scale the system font size.',
217+
style: theme.subtleTextStyle,
218+
),
219+
],
220+
),
221+
Text(
222+
'${value.toStringAsFixed(2)}x',
223+
style: theme.boldTextStyle,
224+
),
225+
],
226+
);
227+
},
228+
),
229+
const SizedBox(height: densePadding),
230+
ValueListenableBuilder<double>(
231+
valueListenable: controller.textScale,
232+
builder: (context, value, _) {
233+
return Slider(
234+
value: value,
235+
min: _minTextScale,
236+
max: _maxTextScale,
237+
divisions: _textScaleDivisions,
238+
onChanged: (newValue) {
239+
controller.textScale.value = newValue;
240+
},
241+
);
242+
},
243+
),
244+
],
245+
);
246+
}
247+
}
248+
249+
class _SwitchOverride extends StatelessWidget {
250+
const _SwitchOverride({
251+
required this.label,
252+
required this.description,
253+
required this.notifier,
254+
});
255+
256+
final String label;
257+
final String description;
258+
final ValueNotifier<bool> notifier;
259+
260+
@override
261+
Widget build(BuildContext context) {
262+
final theme = Theme.of(context);
263+
return Row(
264+
children: [
265+
Expanded(
266+
child: Column(
267+
crossAxisAlignment: CrossAxisAlignment.start,
268+
children: [
269+
Text(
270+
label,
271+
style: theme.boldTextStyle,
272+
),
273+
const SizedBox(height: densePadding),
274+
Text(
275+
description,
276+
style: theme.subtleTextStyle,
277+
),
278+
],
279+
),
280+
),
281+
ValueListenableBuilder<bool>(
282+
valueListenable: notifier,
283+
builder: (context, enabled, _) {
284+
return Switch(
285+
value: enabled,
286+
onChanged: (newValue) {
287+
notifier.value = newValue;
288+
},
289+
);
290+
},
291+
),
292+
],
293+
);
294+
}
295+
}
296+
297+

packages/devtools_app/test/screens/accessibility/accessibility_screen_test.dart

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,102 @@ void main() {
7676
// Semantics Tree pane should be visible
7777
expect(find.byType(AccessibilitySemanticsTreePane), findsOneWidget);
7878
});
79+
80+
testWidgetsWithWindowSize(
81+
'renders all override controls in AccessibilityOverridesPane',
82+
windowSize,
83+
(WidgetTester tester) async {
84+
await pumpAccessibilityScreen(tester);
85+
await tester.pumpAndSettle();
86+
87+
expect(find.text('Accessibility Overrides'), findsOneWidget);
88+
expect(
89+
find.text(
90+
'Simulate and test accessibility settings on the connected device in real-time.',
91+
),
92+
findsOneWidget,
93+
);
94+
95+
// Brightness controls
96+
expect(find.text('Brightness'), findsOneWidget);
97+
expect(
98+
find.text('Override the color scheme mode of the app.'),
99+
findsOneWidget,
100+
);
101+
expect(
102+
find.byType(RoundedDropDownButton<String>),
103+
findsOneWidget,
104+
);
105+
106+
// Text Scale controls
107+
expect(find.text('Text Scale'), findsOneWidget);
108+
expect(find.text('Scale the system font size.'), findsOneWidget);
109+
expect(find.text('1.00x'), findsOneWidget);
110+
expect(find.byType(Slider), findsOneWidget);
111+
112+
// Switches
113+
expect(find.text('Bold Text'), findsOneWidget);
114+
expect(
115+
find.text('Forces all text in the application to be bold.'),
116+
findsOneWidget,
117+
);
118+
expect(find.text('Screen Reader Debugger'), findsOneWidget);
119+
expect(
120+
find.text('Enables interactive screen reader simulation semantics.'),
121+
findsOneWidget,
122+
);
123+
expect(find.text('High Contrast'), findsOneWidget);
124+
expect(
125+
find.text('Increases the contrast of text and icons.'),
126+
findsOneWidget,
127+
);
128+
expect(find.byType(Switch), findsNWidgets(3));
129+
},
130+
);
131+
132+
testWidgetsWithWindowSize(
133+
'interacting with override controls updates controller state',
134+
windowSize,
135+
(WidgetTester tester) async {
136+
await pumpAccessibilityScreen(tester);
137+
await tester.pumpAndSettle();
138+
139+
// 1. Test Brightness Dropdown
140+
expect(controller.brightness.value, 'System');
141+
await tester.tap(find.byType(RoundedDropDownButton<String>));
142+
await tester.pumpAndSettle();
143+
await tester.tap(find.text('Light Mode').last);
144+
await tester.pumpAndSettle();
145+
expect(controller.brightness.value, 'Light');
146+
147+
// 2. Test Bold Text Switch
148+
expect(controller.boldText.value, isFalse);
149+
final boldTextSwitch = find.byType(Switch).at(0);
150+
await tester.ensureVisible(boldTextSwitch);
151+
await tester.pumpAndSettle();
152+
await tester.tap(boldTextSwitch);
153+
await tester.pumpAndSettle();
154+
expect(controller.boldText.value, isTrue);
155+
156+
// 3. Test Screen Reader Debugger Switch
157+
expect(controller.screenReader.value, isFalse);
158+
final screenReaderSwitch = find.byType(Switch).at(1);
159+
await tester.ensureVisible(screenReaderSwitch);
160+
await tester.pumpAndSettle();
161+
await tester.tap(screenReaderSwitch);
162+
await tester.pumpAndSettle();
163+
expect(controller.screenReader.value, isTrue);
164+
165+
// 4. Test High Contrast Switch
166+
expect(controller.highContrast.value, isFalse);
167+
final highContrastSwitch = find.byType(Switch).at(2);
168+
await tester.ensureVisible(highContrastSwitch);
169+
await tester.pumpAndSettle();
170+
await tester.tap(highContrastSwitch);
171+
await tester.pumpAndSettle();
172+
expect(controller.highContrast.value, isTrue);
173+
},
174+
);
79175
});
80176
}
177+

0 commit comments

Comments
 (0)