diff --git a/flutter_app/lib/feedback_helper.dart b/flutter_app/lib/feedback_helper.dart index a70a6ac..e9aca38 100644 --- a/flutter_app/lib/feedback_helper.dart +++ b/flutter_app/lib/feedback_helper.dart @@ -2,37 +2,88 @@ 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 { - static final AudioPlayer _player = AudioPlayer(); + static final Map _players = { + 'click.mp3': AudioPlayer(), + 'select.mp3': AudioPlayer(), + 'success.mp3': AudioPlayer(), + 'error.mp3': AudioPlayer(), + }; - /// Play a sound from assets/sounds/ + 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 { try { - // AudioPlayer play from assets/sounds/fileName - await _player.play(AssetSource('sounds/$fileName')); + 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) { - // Web might throw security exception if player triggers before user interaction - // or other runtime issues. Catching prevents app crashes. 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 (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() { HapticFeedback.lightImpact(); + _triggerVibration(30); playSound('click.mp3'); } /// Medium haptic + selection sound static void select() { HapticFeedback.mediumImpact(); + _triggerVibration(60); playSound('select.mp3'); } /// Celebration sound + strong haptic vibration static void success() { HapticFeedback.heavyImpact(); + _doubleVibrate(); playSound('success.mp3'); } @@ -45,21 +96,25 @@ class AppFeedback { /// Light haptic feedback (e.g. chip selections) static void lightHaptic() { HapticFeedback.lightImpact(); + _triggerVibration(30); } /// Medium haptic feedback static void mediumHaptic() { HapticFeedback.mediumImpact(); + _triggerVibration(60); } /// Heavy haptic feedback static void heavyHaptic() { HapticFeedback.heavyImpact(); + _triggerVibration(100); } /// Subtle haptic feedback (e.g. screen navigation) static void subtleHaptic() { HapticFeedback.selectionClick(); + _triggerVibration(20); } /// Double vibrate pattern using vibration package @@ -69,7 +124,6 @@ class AppFeedback { // Double vibrate pattern: wait 0, vibrate 100ms, wait 100ms, vibrate 100ms Vibration.vibrate(pattern: [0, 100, 100, 100]); } else { - // Fallback to native HapticFeedback HapticFeedback.mediumImpact(); } } catch (e) { diff --git a/flutter_app/lib/main.dart b/flutter_app/lib/main.dart index 96d9dae..b8cfa2c 100644 --- a/flutter_app/lib/main.dart +++ b/flutter_app/lib/main.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'theme.dart'; import 'screens/welcome_screen.dart'; +import 'feedback_helper.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); + AppFeedback.init(); runApp(const PozoApp()); } diff --git a/flutter_app/lib/screens/welcome_screen.dart b/flutter_app/lib/screens/welcome_screen.dart index 0d2e116..123b98c 100644 --- a/flutter_app/lib/screens/welcome_screen.dart +++ b/flutter_app/lib/screens/welcome_screen.dart @@ -76,6 +76,11 @@ class _WelcomeScreenState extends State with SingleTickerProvider ); _controller.forward(); + Future.delayed(const Duration(milliseconds: 300), () { + if (mounted) { + AppFeedback.success(); + } + }); } Future _initMobileNumberDetection() async {