import 'dart:io'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:vibration/vibration.dart'; class FeedbackService { FeedbackService() { _configurePlayers(); } final AudioPlayer _scanPlayer = AudioPlayer(); final AudioPlayer _successPlayer = AudioPlayer(); final AudioPlayer _warningPlayer = AudioPlayer(); static final AudioContext _loudContext = AudioContext( android: AudioContextAndroid( isSpeakerphoneOn: true, audioMode: AndroidAudioMode.normal, contentType: AndroidContentType.sonification, usageType: AndroidUsageType.alarm, audioFocus: AndroidAudioFocus.gain, ), iOS: AudioContextIOS( category: AVAudioSessionCategory.playback, options: { AVAudioSessionOptions.mixWithOthers, AVAudioSessionOptions.duckOthers, }, ), ); Future _configurePlayers() async { for (final player in [_scanPlayer, _successPlayer, _warningPlayer]) { await player.setReleaseMode(ReleaseMode.stop); await player.setPlayerMode(PlayerMode.lowLatency); await player.setVolume(1.0); await player.setAudioContext(_loudContext); } } Future onCodeDetected() async { await Future.wait([ _play(_scanPlayer, 'sounds/scan.wav'), _vibratePattern(const [0, 80]), HapticFeedback.mediumImpact(), ]); } Future onSuccess() async { await Future.wait([ _playSuccessSequence(), _vibratePattern(const [0, 120, 60, 180]), HapticFeedback.heavyImpact(), ]); } Future onWarning() async { await Future.wait([ _playWarningSequence(), _vibratePattern(const [0, 180, 80, 180, 80, 220]), HapticFeedback.heavyImpact(), ]); } Future _playSuccessSequence() async { await _play(_successPlayer, 'sounds/success.wav'); if (!kIsWeb && Platform.isAndroid) { await Future.delayed(const Duration(milliseconds: 120)); await _play(_successPlayer, 'sounds/success.wav'); } } Future _playWarningSequence() async { for (var i = 0; i < 2; i++) { await _play(_warningPlayer, 'sounds/warning.wav'); if (i == 0) { await Future.delayed(const Duration(milliseconds: 140)); } } } Future _play(AudioPlayer player, String asset) async { try { await player.stop(); await player.play(AssetSource(asset)); } catch (_) { await SystemSound.play(SystemSoundType.alert); } } Future _vibratePattern(List pattern) async { try { final hasVibrator = await Vibration.hasVibrator(); if (hasVibrator != true) return; final hasAmplitude = await Vibration.hasAmplitudeControl(); if (hasAmplitude == true) { final intensities = pattern .asMap() .entries .map((entry) => entry.key.isOdd ? 255 : 0) .toList(); await Vibration.vibrate( pattern: pattern, intensities: intensities, ); return; } await Vibration.vibrate(pattern: pattern); } catch (_) { await HapticFeedback.heavyImpact(); } } Future dispose() async { await Future.wait([ _scanPlayer.dispose(), _successPlayer.dispose(), _warningPlayer.dispose(), ]); } }