Commit 093afe02 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Haptic feedback on time changes in TimePicker (#8348)

Trigger a vibration on hour/minute changes, with an upper bound on
number of feedback events per second.

Note: state changes are expected to trigger haptic feedback on Android,
but not on iOS time pickers.
parent 3a0b83b1
...@@ -22,6 +22,7 @@ const double _kTwoPi = 2 * math.PI; ...@@ -22,6 +22,7 @@ const double _kTwoPi = 2 * math.PI;
const int _kHoursPerDay = 24; const int _kHoursPerDay = 24;
const int _kHoursPerPeriod = 12; const int _kHoursPerPeriod = 12;
const int _kMinutesPerHour = 60; const int _kMinutesPerHour = 60;
const Duration _kVibrateCommitDelay = const Duration(milliseconds: 100);
/// Whether the [TimeOfDay] is before or after noon. /// Whether the [TimeOfDay] is before or after noon.
enum DayPeriod { enum DayPeriod {
...@@ -644,12 +645,17 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { ...@@ -644,12 +645,17 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_TimePickerMode _mode = _TimePickerMode.hour; _TimePickerMode _mode = _TimePickerMode.hour;
TimeOfDay _selectedTime; TimeOfDay _selectedTime;
Timer _vibrateTimer;
void _vibrate() { void _vibrate() {
switch (Theme.of(context).platform) { switch (Theme.of(context).platform) {
case TargetPlatform.android: case TargetPlatform.android:
case TargetPlatform.fuchsia: case TargetPlatform.fuchsia:
HapticFeedback.vibrate(); _vibrateTimer?.cancel();
_vibrateTimer = new Timer(_kVibrateCommitDelay, () {
HapticFeedback.vibrate();
_vibrateTimer = null;
});
break; break;
case TargetPlatform.iOS: case TargetPlatform.iOS:
break; break;
...@@ -664,6 +670,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { ...@@ -664,6 +670,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
} }
void _handleTimeChanged(TimeOfDay value) { void _handleTimeChanged(TimeOfDay value) {
_vibrate();
setState(() { setState(() {
_selectedTime = value; _selectedTime = value;
}); });
...@@ -759,6 +766,13 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { ...@@ -759,6 +766,13 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
) )
); );
} }
@override
void dispose() {
_vibrateTimer?.cancel();
_vibrateTimer = null;
super.dispose();
}
} }
/// Shows a dialog containing a material design time picker. /// Shows a dialog containing a material design time picker.
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
class _TimePickerLauncher extends StatelessWidget { class _TimePickerLauncher extends StatelessWidget {
...@@ -110,4 +111,92 @@ void main() { ...@@ -110,4 +111,92 @@ void main() {
await finishPicker(tester); await finishPicker(tester);
expect(result.hour, equals(9)); expect(result.hour, equals(9));
}); });
group('haptic feedback', () {
const Duration kFastFeedbackInteral = const Duration(milliseconds: 10);
const Duration kSlowFeedbackInteral = const Duration(milliseconds: 200);
int hapticFeedbackCount;
setUpAll(() {
PlatformMessages.setMockJSONMessageHandler('flutter/platform', (dynamic message) {
if (message['method'] == "HapticFeedback.vibrate")
hapticFeedbackCount++;
});
});
setUp(() {
hapticFeedbackCount = 0;
});
testWidgets('tap-select vibrates once', (WidgetTester tester) async {
Point center = await startPicker(tester, (TimeOfDay time) { });
await tester.tapAt(new Point(center.x, center.y - 50.0));
await finishPicker(tester);
expect(hapticFeedbackCount, 1);
});
testWidgets('quick successive tap-selects vibrate once', (WidgetTester tester) async {
Point center = await startPicker(tester, (TimeOfDay time) { });
await tester.tapAt(new Point(center.x, center.y - 50.0));
await tester.pump(kFastFeedbackInteral);
await tester.tapAt(new Point(center.x, center.y + 50.0));
await finishPicker(tester);
expect(hapticFeedbackCount, 1);
});
testWidgets('slow successive tap-selects vibrate once per tap', (WidgetTester tester) async {
Point center = await startPicker(tester, (TimeOfDay time) { });
await tester.tapAt(new Point(center.x, center.y - 50.0));
await tester.pump(kSlowFeedbackInteral);
await tester.tapAt(new Point(center.x, center.y + 50.0));
await tester.pump(kSlowFeedbackInteral);
await tester.tapAt(new Point(center.x, center.y - 50.0));
await finishPicker(tester);
expect(hapticFeedbackCount, 3);
});
testWidgets('drag-select vibrates once', (WidgetTester tester) async {
Point center = await startPicker(tester, (TimeOfDay time) { });
Point hour0 = new Point(center.x, center.y - 50.0);
Point hour3 = new Point(center.x + 50.0, center.y);
TestGesture gesture = await tester.startGesture(hour3);
await gesture.moveBy(hour0 - hour3);
await gesture.up();
await finishPicker(tester);
expect(hapticFeedbackCount, 1);
});
testWidgets('quick drag-select vibrates once', (WidgetTester tester) async {
Point center = await startPicker(tester, (TimeOfDay time) { });
Point hour0 = new Point(center.x, center.y - 50.0);
Point hour3 = new Point(center.x + 50.0, center.y);
TestGesture gesture = await tester.startGesture(hour3);
await gesture.moveBy(hour0 - hour3);
await tester.pump(kFastFeedbackInteral);
await gesture.moveBy(hour3 - hour0);
await tester.pump(kFastFeedbackInteral);
await gesture.moveBy(hour0 - hour3);
await gesture.up();
await finishPicker(tester);
expect(hapticFeedbackCount, 1);
});
testWidgets('slow drag-select vibrates once', (WidgetTester tester) async {
Point center = await startPicker(tester, (TimeOfDay time) { });
Point hour0 = new Point(center.x, center.y - 50.0);
Point hour3 = new Point(center.x + 50.0, center.y);
TestGesture gesture = await tester.startGesture(hour3);
await gesture.moveBy(hour0 - hour3);
await tester.pump(kSlowFeedbackInteral);
await gesture.moveBy(hour3 - hour0);
await tester.pump(kSlowFeedbackInteral);
await gesture.moveBy(hour0 - hour3);
await gesture.up();
await finishPicker(tester);
expect(hapticFeedbackCount, 3);
});
});
} }
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