Skip to content

Provide haptic/acoustic feedback for tap & long-press on Android #10920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 23, 2017

Conversation

goderbauer
Copy link
Member

Fixes #6574.

}

static TargetPlatform _platform(BuildContext context) => Theme.of(context).platform;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extraneous blank line


/// Provides platform-specific feedback for a tap.
static Future<Null> forTap(BuildContext context) async {
switch(_platform(context)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: space after switch


/// Provides platform-specific feedback for a long press.
static Future<Null> forLongPress(BuildContext context) {
switch(_platform(context)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

FeedbackWrapper._();

/// Provides platform-specific feedback for a tap.
static GestureTapCallback forTap(GestureTapCallback callback, BuildContext context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really quite clever.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 😄

return new Future<Null>.value();
}
}

Copy link
Contributor

@Hixie Hixie Jun 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we merge these classes and have Feedback.forTap(context) and Feedback.wrapForTap(callback, context) ?

Having two classes with two static methods seems like it will lead to confusion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Done.

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

/// Provides platform-specific acoustic and/or haptic feedback for certain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docs need to be more elaborate.

Things to add at each place there are docs in this file (it's perfectly ok to have duplication in the docs):

  • What does this do on Android?
  • What does this do on iOS?
  • How do you handle things like onPressed: enabled ? callback : null?
  • Sample code? (with the ## Sample code pattern, see other files)

@@ -93,7 +94,8 @@ class InkResponse extends StatefulWidget {
this.borderRadius: BorderRadius.zero,
this.highlightColor,
this.splashColor,
}) : super(key: key);
bool enableFeedback,
}) : enableFeedback = enableFeedback ?? true, super(key: key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just make true the default value for the argument?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and then assert that it's not null, and mention in the docs for the constructor that it must be non-null)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was that this way I don't have to specify the default value in two places (here and below for InkWell). Changed it nonetheless as I didn't have strong feelings.

_currentSplash?.confirm();
_currentSplash = null;
if (widget.onLongPress != null)
if (widget.enableFeedback)
Feedback.forLongPress(context);
widget.onLongPress();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you missed the braces here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a test that would have failed here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Good catch!

I still wish ifs would always come with braces... goto fail; 😉

As to writing a test: there is actually another onLongPress != null check that guards the entire execution of this method (see line 359). So, it's actually impossible to exploit the missing braces in a test here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case maybe we should just assert(widget.onLongPress != null) and remove the test?

@@ -392,6 +405,7 @@ class InkWell extends InkResponse {
Color highlightColor,
Color splashColor,
BorderRadius borderRadius,
bool enableFeedback,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructor docs update

if (_entry != null) {
_timer?.cancel();
_timer = null;
_controller.forward();
return; // Already visible.
return false; // Already visible.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extraneous space before comment

@@ -650,3 +656,6 @@ class _Editable extends LeafRenderObjectWidget {
return new TextSpan(style: style, text: text);
}
}

/// Signature for the callback that reports when the user changes the selection.
typedef void SelectionChangedCallback(TextSelection selection, bool longPress);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put typedefs near the top of the file, before first use if possible

@@ -243,6 +249,7 @@ class _TextFieldState extends State<TextField> {
selectionControls: materialTextSelectionControls,
onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted,
onSelectionChanged: (TextSelection _, bool longPress) => _onSelectionChanged(context, longPress),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, i would have expected this to belong to the selection controls delegate. This works too.

@Hixie
Copy link
Contributor

Hixie commented Jun 23, 2017

This is fantastic.

/// tapped, call [forTap]. For the Android-specific vibration when long pressing
/// an element, call [forLongPress]. Alternatively, you can also wrap your
/// [onTap] or [onLongPress] callback in [wrapForTap] or [wrapForLongPress] to
/// achive the same (see example code below).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

achieve

/// typically don't provide haptic or acoustic feedback.
///
/// All methods in this class are usually called from within a [build] method
/// as you have to provide a [BuildContext].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or from a State's methods

/// To trigger platform-specific feedback before executing the actual callback:
///
/// ```dart
/// @override
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally each example would be something you can copy and paste into code and have work right away, so here we would usually include the StatefulWidget and State boilerplate, as in:
https://master-docs-flutter-io.firebaseapp.com/flutter/painting/TextSpan/recognizer.html

@@ -93,7 +94,8 @@ class InkResponse extends StatefulWidget {
this.borderRadius: BorderRadius.zero,
this.highlightColor,
this.splashColor,
}) : super(key: key);
this.enableFeedback: true,
}) : assert(enableFeedback != null), super(key: key);
Copy link
Contributor

@Hixie Hixie Jun 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the doc for the constructor should mention that containedInkWell, borderRadius, and enableFeedback must not be null, and the asserts should include the other two as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserts are throwing left and right when I assert on containedInkWell and borderRadius. If the assert is really true we should do that (and the associated clean-up) in a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Odd.

/// Whether detected gestures should provide acoustic and/or haptic feedback.
///
/// For example, on Android a tap will produce a clicking sound and a
/// long-press will produce a short vibration, when feedback is enabled.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe point to Feedback?

@@ -110,12 +111,13 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
_removeEntry();
}

void ensureTooltipVisible() {
/// Returns `false` when the tooltip was already visible.
bool ensureTooltipVisible() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't say the most important part, that it shows a tooltip! :-)

@@ -226,6 +230,9 @@ class EditableText extends StatefulWidget {
/// Called when the user indicates that they are done editing the text in the field.
final ValueChanged<String> onSubmitted;

/// Called when the selection of text changed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or when the cursor has moved? or not?


import 'package:flutter/services.dart';

/// Tracks how often feedback has been requested since its instantiation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably mention that only one of these can be used at a time.


/// Number of times the click sound was requested to play.
int get clickSoundCount => _clickSoundCount;
int _clickSoundCount = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add a dispose() method than unhooks the mock handler?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or don't bother, it's only used in tests, so...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it for completeness.

@Hixie
Copy link
Contributor

Hixie commented Jun 23, 2017

LGTM

@goderbauer goderbauer merged commit fe40eed into flutter:master Jun 23, 2017
@goderbauer goderbauer deleted the androidFeedback2 branch June 23, 2017 22:02
gspencergoog pushed a commit to gspencergoog/flutter that referenced this pull request Jul 1, 2017
…tter#10920)

* Provide haptic/acoustic feedback for tap & long-press on Android

* review comments

* fixed example code

* review comments

* comment fix
@goderbauer goderbauer added the a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) label Oct 17, 2017
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Touch events missing sound and haptic feedback on Android
3 participants