| // 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; |
| } |