import 'package:flutter/services.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:vibration/vibration.dart'; import 'package:flutter/foundation.dart' show kIsWeb, debugPrint; import 'dart:io' show Platform; class AppFeedback { // Global settings for sound and haptic vibration feedback static bool isSoundEnabled = true; static bool isHapticEnabled = true; static final Map _players = { 'click.mp3': AudioPlayer(), 'select.mp3': AudioPlayer(), 'success.mp3': AudioPlayer(), 'error.mp3': AudioPlayer(), }; static bool _initialized = false; /// Preload sounds to achieve zero latency static void init() { if (_initialized) return; try { _players.forEach((key, player) { player.setReleaseMode(ReleaseMode.stop); player.setSource(AssetSource('sounds/$key')); }); _initialized = true; } catch (e) { debugPrint('Error initializing AppFeedback: $e'); } } /// Play a sound from assets/sounds/ with zero delay static Future playSound(String fileName) async { if (!isSoundEnabled) return; try { if (!_initialized) { init(); } final player = _players[fileName]; if (player != null) { // Stop/seek and resume for instant audio await player.seek(Duration.zero); await player.resume(); } else { // Fallback for dynamically added files final tempPlayer = AudioPlayer(); await tempPlayer.play(AssetSource('sounds/$fileName')); } } catch (e) { debugPrint('Audio playback error for $fileName: $e'); } } /// Helper to check Android platform safely static bool get _isAndroid => !kIsWeb && Platform.isAndroid; /// Trigger native vibration for Android with customized duration (in milliseconds) static void _triggerVibration(int duration) async { if (!isHapticEnabled || kIsWeb) return; try { if (_isAndroid) { if (await Vibration.hasVibrator() == true) { Vibration.vibrate(duration: duration); } } } catch (e) { debugPrint('Vibration error: $e'); } } /// Light haptic feedback + subtle click sound static void click() { if (isHapticEnabled) { HapticFeedback.lightImpact(); _triggerVibration(30); } playSound('click.mp3'); } /// Medium haptic + selection sound static void select() { if (isHapticEnabled) { HapticFeedback.mediumImpact(); _triggerVibration(60); } playSound('select.mp3'); } /// Celebration sound + strong haptic vibration static void success() { if (isHapticEnabled) { HapticFeedback.heavyImpact(); _doubleVibrate(); } playSound('success.mp3'); } /// Wrong input -> error haptic (double vibrate) + error sound static void error() { if (isHapticEnabled) { _doubleVibrate(); } playSound('error.mp3'); } /// Light haptic feedback (e.g. chip selections) static void lightHaptic() { if (!isHapticEnabled) return; HapticFeedback.lightImpact(); _triggerVibration(30); } /// Medium haptic feedback static void mediumHaptic() { if (!isHapticEnabled) return; HapticFeedback.mediumImpact(); _triggerVibration(60); } /// Heavy haptic feedback static void heavyHaptic() { if (!isHapticEnabled) return; HapticFeedback.heavyImpact(); _triggerVibration(100); } /// Subtle haptic feedback (e.g. screen navigation) static void subtleHaptic() { if (!isHapticEnabled) return; HapticFeedback.selectionClick(); _triggerVibration(20); } /// Double vibrate pattern using vibration package static Future _doubleVibrate() async { if (!isHapticEnabled) return; try { if (!kIsWeb && await Vibration.hasVibrator() == true) { // Double vibrate pattern: wait 0, vibrate 100ms, wait 100ms, vibrate 100ms Vibration.vibrate(pattern: [0, 100, 100, 100]); } else { HapticFeedback.mediumImpact(); } } catch (e) { HapticFeedback.mediumImpact(); } } }