blob: b0a04be23139eb9762898e54ec69724a54dff970 [file] [log] [blame]
// Copyright 2014 The Flutter 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 'dart:async';
import 'package:flutter/rendering.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// Provides platform-specific acoustic and/or haptic feedback for certain
/// actions.
///
/// For example, to play the Android-typically click sound when a button is
/// tapped, call [forTap]. For the Android-specific vibration when long pressing
/// an element, call [forLongPress]. Alternatively, you can also wrap your
/// [GestureDetector.onTap] or [GestureDetector.onLongPress] callback in
/// [wrapForTap] or [wrapForLongPress] to achieve the same (see example code
/// below).
///
/// Calling any of these methods is a no-op on iOS as actions on that platform
/// typically don't provide haptic or acoustic feedback.
///
/// All methods in this class are usually called from within a
/// [StatelessWidget.build] method or from a [State]'s methods as you have to
/// provide a [BuildContext].
///
/// {@tool snippet}
///
/// To trigger platform-specific feedback before executing the actual callback:
///
/// ```dart
/// class WidgetWithWrappedHandler extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return GestureDetector(
/// onTap: Feedback.wrapForTap(_onTapHandler, context),
/// onLongPress: Feedback.wrapForLongPress(_onLongPressHandler, context),
/// child: const Text('X'),
/// );
/// }
///
/// void _onTapHandler() {
/// // Respond to tap.
/// }
///
/// void _onLongPressHandler() {
/// // Respond to long press.
/// }
/// }
/// ```
/// {@end-tool}
/// {@tool snippet}
///
/// Alternatively, you can also call [forTap] or [forLongPress] directly within
/// your tap or long press handler:
///
/// ```dart
/// class WidgetWithExplicitCall extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return GestureDetector(
/// onTap: () {
/// // Do some work (e.g. check if the tap is valid)
/// Feedback.forTap(context);
/// // Do more work (e.g. respond to the tap)
/// },
/// onLongPress: () {
/// // Do some work (e.g. check if the long press is valid)
/// Feedback.forLongPress(context);
/// // Do more work (e.g. respond to the long press)
/// },
/// child: const Text('X'),
/// );
/// }
/// }
/// ```
/// {@end-tool}
class Feedback {
// This class is not meant to be instatiated or extended; this constructor
// prevents instantiation and extension.
// ignore: unused_element
Feedback._();
/// Provides platform-specific feedback for a tap.
///
/// On Android the click system sound is played. On iOS this is a no-op.
///
/// See also:
///
/// * [wrapForTap] to trigger platform-specific feedback before executing a
/// [GestureTapCallback].
static Future<void> forTap(BuildContext context) async {
context.findRenderObject().sendSemanticsEvent(const TapSemanticEvent());
switch (_platform(context)) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return SystemSound.play(SystemSoundType.click);
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return Future<void>.value();
break;
}
assert(false, 'Unhandled TargetPlatform ${_platform(context)}');
return Future<void>.value();
}
/// Wraps a [GestureTapCallback] to provide platform specific feedback for a
/// tap before the provided callback is executed.
///
/// On Android the platform-typical click system sound is played. On iOS this
/// is a no-op as that platform usually doesn't provide feedback for a tap.
///
/// See also:
///
/// * [forTap] to just trigger the platform-specific feedback without wrapping
/// a [GestureTapCallback].
static GestureTapCallback wrapForTap(GestureTapCallback callback, BuildContext context) {
if (callback == null)
return null;
return () {
Feedback.forTap(context);
callback();
};
}
/// Provides platform-specific feedback for a long press.
///
/// On Android the platform-typical vibration is triggered. On iOS this is a
/// no-op as that platform usually doesn't provide feedback for long presses.
///
/// See also:
///
/// * [wrapForLongPress] to trigger platform-specific feedback before
/// executing a [GestureLongPressCallback].
static Future<void> forLongPress(BuildContext context) {
context.findRenderObject().sendSemanticsEvent(const LongPressSemanticsEvent());
switch (_platform(context)) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return HapticFeedback.vibrate();
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return Future<void>.value();
break;
}
assert(false, 'Unhandled TargetPlatform ${_platform(context)}');
return Future<void>.value();
}
/// Wraps a [GestureLongPressCallback] to provide platform specific feedback
/// for a long press before the provided callback is executed.
///
/// On Android the platform-typical vibration is triggered. On iOS this
/// is a no-op as that platform usually doesn't provide feedback for a long
/// press.
///
/// See also:
///
/// * [forLongPress] to just trigger the platform-specific feedback without
/// wrapping a [GestureLongPressCallback].
static GestureLongPressCallback wrapForLongPress(GestureLongPressCallback callback, BuildContext context) {
if (callback == null)
return null;
return () {
Feedback.forLongPress(context);
callback();
};
}
static TargetPlatform _platform(BuildContext context) => Theme.of(context).platform;
}