feedback.dart 5.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:flutter/rendering.dart';
import 'package:flutter/semantics.dart';
7 8 9
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

10 11
import 'theme.dart';

12 13 14 15 16 17
/// 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
18 19 20
/// [GestureDetector.onTap] or [GestureDetector.onLongPress] callback in
/// [wrapForTap] or [wrapForLongPress] to achieve the same (see example code
/// below).
21 22 23 24
///
/// Calling any of these methods is a no-op on iOS as actions on that platform
/// typically don't provide haptic or acoustic feedback.
///
25 26 27
/// 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].
28
///
29
/// {@tool snippet}
30 31 32 33 34
///
/// To trigger platform-specific feedback before executing the actual callback:
///
/// ```dart
/// class WidgetWithWrappedHandler extends StatelessWidget {
35 36
///   const WidgetWithWrappedHandler({Key? key}) : super(key: key);
///
37 38
///   @override
///   Widget build(BuildContext context) {
39
///     return GestureDetector(
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
///       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.
///   }
/// }
/// ```
55
/// {@end-tool}
56
/// {@tool snippet}
57 58 59 60 61 62
///
/// Alternatively, you can also call [forTap] or [forLongPress] directly within
/// your tap or long press handler:
///
/// ```dart
/// class WidgetWithExplicitCall extends StatelessWidget {
63 64
///   const WidgetWithExplicitCall({Key? key}) : super(key: key);
///
65 66
///   @override
///   Widget build(BuildContext context) {
67
///     return GestureDetector(
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
///       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'),
///     );
///   }
/// }
/// ```
83
/// {@end-tool}
84
class Feedback {
85
  // This class is not meant to be instantiated or extended; this constructor
86
  // prevents instantiation and extension.
87 88 89 90 91 92 93 94 95 96
  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].
97
  static Future<void> forTap(BuildContext context) async {
98
    context.findRenderObject()!.sendSemanticsEvent(const TapSemanticEvent());
99 100 101 102
    switch (_platform(context)) {
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return SystemSound.play(SystemSoundType.click);
103 104 105 106
      case TargetPlatform.iOS:
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
107
        return Future<void>.value();
108 109 110 111 112 113 114 115 116 117 118 119 120
    }
  }

  /// 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].
121
  static GestureTapCallback? wrapForTap(GestureTapCallback? callback, BuildContext context) {
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
    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].
139
  static Future<void> forLongPress(BuildContext context) {
140
    context.findRenderObject()!.sendSemanticsEvent(const LongPressSemanticsEvent());
141 142 143 144
    switch (_platform(context)) {
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return HapticFeedback.vibrate();
145 146 147 148
      case TargetPlatform.iOS:
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
149
        return Future<void>.value();
150 151 152 153 154 155 156 157 158 159 160 161 162 163
    }
  }

  /// 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].
164
  static GestureLongPressCallback? wrapForLongPress(GestureLongPressCallback? callback, BuildContext context) {
165 166 167 168 169 170 171 172
    if (callback == null)
      return null;
    return () {
      Feedback.forLongPress(context);
      callback();
    };
  }

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