Unverified Commit 4d9fbe78 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Add a custom semantics sample (#12742)

* Add a custom semantics sample

* typo

* Review comments
parent 722fa869
// Copyright 2017 The Chromium 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 'package:flutter/material.dart';
/// A [ListTile] containing a dropdown menu that exposes itself as an
/// "Adjustable" to screen readers (e.g. TalkBack on Android and VoiceOver on
/// iOS).
///
/// This allows screen reader users to swipe up/down (on iOS) or use the volume
/// keys (on Android) to switch between the values in the dropdown menu.
/// Depending on what the values in the dropdown menu are this can be a more
/// intuitive way of switching values compared to exposing the content of the
/// drop down menu as a screen overlay from which the user can select.
///
/// Users that do not use a screen reader will just see a regular dropdown menu.
class AdjustableDropdownListTile extends StatelessWidget {
const AdjustableDropdownListTile({
this.label,
this.value,
this.items,
this.onChanged,
});
final String label;
final String value;
final List<String> items;
final ValueChanged<String> onChanged;
@override
Widget build(BuildContext context) {
final int indexOfValue = items.indexOf(value);
assert(indexOfValue != -1);
final bool canIncrease = indexOfValue < items.length - 1;
final bool canDecrease = indexOfValue > 0;
return new Semantics(
container: true,
label: label,
value: value,
increasedValue: canIncrease ? _increasedValue : null,
decreasedValue: canDecrease ? _decreasedValue : null,
onIncrease: canIncrease ? _performIncrease : null,
onDecrease: canDecrease ? _performDecrease : null,
child: new ExcludeSemantics(
child: new ListTile(
title: new Text(label),
trailing: new DropdownButton<String>(
value: value,
onChanged: onChanged,
items: items.map((String item) {
return new DropdownMenuItem<String>(
value: item,
child: new Text(item),
);
}).toList(),
),
),
)
);
}
String get _increasedValue {
final int indexOfValue = items.indexOf(value);
assert(indexOfValue < items.length - 1);
return items[indexOfValue + 1];
}
String get _decreasedValue {
final int indexOfValue = items.indexOf(value);
assert(indexOfValue > 0);
return items[indexOfValue - 1];
}
void _performIncrease() => onChanged(_increasedValue);
void _performDecrease() => onChanged(_decreasedValue);
}
class AdjustableDropdownExample extends StatefulWidget {
@override
AdjustableDropdownExampleState createState() => new AdjustableDropdownExampleState();
}
class AdjustableDropdownExampleState extends State<AdjustableDropdownExample> {
final List<String> items = <String>[
'1 second',
'5 seconds',
'15 seconds',
'30 seconds',
'1 minute'
];
String timeout;
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Adjustable DropDown'),
),
body: new ListView(
children: <Widget>[
new AdjustableDropdownListTile(
label: 'Timeout',
value: timeout ?? items[2],
items: items,
onChanged: (String value) {
setState(() {
timeout = value;
});
},
),
],
),
),
);
}
}
void main() {
runApp(new AdjustableDropdownExample());
}
/*
Sample Catalog
Title: AdjustableDropdownListTile
Summary: A dropdown menu that exposes itself as an "Adjustable" to screen
readers.
Description:
This app presents a dropdown menu to the user that exposes itself as an
"Adjustable" to screen readers (e.g. TalkBack on Android and VoiceOver on iOS).
This allows users of screen readers to cycle through the values of the dropdown
menu by swiping up or down on the screen with one finger (on iOS) or by using
the volume keys (on Android). Depending on the values in the dropdown this
behavior may be more intuitive to screen reader users compared to showing the
classical dropdown overlay on screen to choose a value.
When the screen reader is turned off, the dropdown menu behaves like any
dropdown menu would.
Classes: Semantics
Sample: AdjustableDropdownListTile
*/
// Copyright 2017 The Chromium 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 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../lib/custom_semantics.dart' as custom_semantics show main;
import '../lib/custom_semantics.dart';
void main() {
testWidgets('custom_semantics sample smoke test', (WidgetTester tester) async {
// Turn on Semantics
final SemanticsHandle semanticsHandler = tester.binding.pipelineOwner.ensureSemantics();
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner;
// Build the sample app
custom_semantics.main();
await tester.pump();
// Verify it correctly exposes its semantics.
// TODO(goderbauer): Use `SemanticsTester` after https://github.com/flutter/flutter/issues/12286.
final SemanticsNode semantics = tester
.renderObject(find.byType(AdjustableDropdownListTile))
.debugSemantics;
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '5 seconds',
value: '15 seconds',
increasedValue: '30 seconds',
);
// Increase
semanticsOwner.performAction(semantics.id, SemanticsAction.increase);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '15 seconds',
value: '30 seconds',
increasedValue: '1 minute',
);
// Increase all the way to highest value
semanticsOwner.performAction(semantics.id, SemanticsAction.increase);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: false,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '30 seconds',
value: '1 minute',
);
// Decrease
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: true,
label: 'Timeout',
decreasedValue: '15 seconds',
value: '30 seconds',
increasedValue: '1 minute',
);
// Decrease all the way to lowest value
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
semanticsOwner.performAction(semantics.id, SemanticsAction.decrease);
await tester.pump();
expectAdjustable(semantics,
hasIncreaseAction: true,
hasDecreaseAction: false,
label: 'Timeout',
value: '1 second',
increasedValue: '5 seconds',
);
// Clean-up
semanticsHandler.dispose();
});
}
void expectAdjustable(SemanticsNode node, {
bool hasIncreaseAction: true,
bool hasDecreaseAction: true,
String label: '',
String decreasedValue: '',
String value: '',
String increasedValue: '',
}) {
final SemanticsData semanticsData = node.getSemanticsData();
int actions = 0;
if (hasIncreaseAction)
actions |= SemanticsAction.increase.index;
if (hasDecreaseAction)
actions |= SemanticsAction.decrease.index;
expect(semanticsData.actions, actions);
expect(semanticsData.label, label);
expect(semanticsData.decreasedValue, decreasedValue);
expect(semanticsData.value, value);
expect(semanticsData.increasedValue, increasedValue);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment