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;
const int _kHoursPerDay = 24;
const int _kHoursPerPeriod = 12;
const int _kMinutesPerHour = 60;
const Duration _kVibrateCommitDelay = const Duration(milliseconds: 100);
/// Whether the [TimeOfDay] is before or after noon.
enum DayPeriod {
......@@ -644,12 +645,17 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_TimePickerMode _mode = _TimePickerMode.hour;
TimeOfDay _selectedTime;
Timer _vibrateTimer;
void _vibrate() {
switch (Theme.of(context).platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
_vibrateTimer?.cancel();
_vibrateTimer = new Timer(_kVibrateCommitDelay, () {
HapticFeedback.vibrate();
_vibrateTimer = null;
});
break;
case TargetPlatform.iOS:
break;
......@@ -664,6 +670,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
}
void _handleTimeChanged(TimeOfDay value) {
_vibrate();
setState(() {
_selectedTime = value;
});
......@@ -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.
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
class _TimePickerLauncher extends StatelessWidget {
......@@ -110,4 +111,92 @@ void main() {
await finishPicker(tester);
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