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