feedback.dart 5.66 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
///   const WidgetWithWrappedHandler({super.key});
36
///
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
///   const WidgetWithExplicitCall({super.key});
64
///
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
abstract final class Feedback {
85 86 87 88 89 90 91 92
  /// 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].
93
  static Future<void> forTap(BuildContext context) async {
94
    context.findRenderObject()!.sendSemanticsEvent(const TapSemanticEvent());
95 96 97 98
    switch (_platform(context)) {
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return SystemSound.play(SystemSoundType.click);
99 100 101 102
      case TargetPlatform.iOS:
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
103
        return Future<void>.value();
104 105 106 107 108 109 110 111 112 113 114 115 116
    }
  }

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

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

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