import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../domain/models/voicemail.dart'; import '../../../domain/services/voicemail_service.dart'; import '../../../domain/services/call_service.dart'; import '../../../domain/services/obfuscate_service.dart'; import '../../../core/utils/color_utils.dart'; class VoicemailPage extends StatefulWidget { const VoicemailPage({super.key}); @override State createState() => _VoicemailPageState(); } class _VoicemailPageState extends State { final VoicemailService _voicemailService = VoicemailService(); final CallService _callService = CallService(); final ObfuscateService _obfuscateService = ObfuscateService(); final AudioPlayer _audioPlayer = AudioPlayer(); List _voicemails = []; bool _loading = true; String? _currentPlayingId; int? _expandedIndex; Duration _duration = Duration.zero; Duration _position = Duration.zero; bool _isPlaying = false; @override void initState() { super.initState(); _fetchVoicemails(); _audioPlayer.onDurationChanged.listen((Duration d) { setState(() => _duration = d); }); _audioPlayer.onPositionChanged.listen((Duration p) { setState(() => _position = p); }); _audioPlayer.onPlayerComplete.listen((event) { setState(() { _isPlaying = false; _position = Duration.zero; _currentPlayingId = null; }); }); } Future _fetchVoicemails() async { setState(() => _loading = true); try { final voicemails = await _voicemailService.getVoicemails(); setState(() { _voicemails = voicemails; _loading = false; }); // Show explanation if no voicemails found if (voicemails.isEmpty && mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('No voicemails found. Make sure this app is set as the default dialer and has all required permissions.'), duration: Duration(seconds: 5), ), ); } } catch (e) { debugPrint('Error fetching voicemails: $e'); setState(() => _loading = false); String errorMessage = 'Failed to load voicemails'; if (e.toString().contains('NOT_DEFAULT_DIALER')) { errorMessage = 'Please set this app as your default phone app to access voicemails'; } else if (e.toString().contains('MISSING_PERMISSIONS')) { errorMessage = 'Missing permissions needed to access voicemails'; } else if (e.toString().contains('ADD_VOICEMAIL')) { errorMessage = 'Samsung device detected. Additional voicemail permissions are required'; } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(errorMessage)), ); } } } Future _togglePlayPause(Voicemail voicemail) async { if (_currentPlayingId == voicemail.id && _isPlaying) { await _audioPlayer.pause(); setState(() => _isPlaying = false); } else { // If we're playing a different voicemail, stop current one if (_isPlaying && _currentPlayingId != voicemail.id) { await _audioPlayer.stop(); } // Start playing the new voicemail if (voicemail.filePath != null) { try { await _audioPlayer.play(DeviceFileSource(voicemail.filePath!)); setState(() { _isPlaying = true; _currentPlayingId = voicemail.id; }); // Mark as read if not already if (!voicemail.isRead) { await _voicemailService.markAsRead(voicemail.id); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error playing voicemail: $e')), ); } } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Voicemail file not available')), ); } } } void _makeCall(String phoneNumber) { _callService.makeGsmCall(context, phoneNumber: phoneNumber); } void _sendMessage(String phoneNumber) async { final Uri smsUri = Uri(scheme: 'sms', path: phoneNumber); try { await launchUrl(smsUri); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Could not send message')), ); } } } Future _deleteVoicemail(String id) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Delete Voicemail'), content: const Text('Are you sure you want to delete this voicemail?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('Delete'), ), ], ), ); if (confirmed == true) { try { final success = await _voicemailService.deleteVoicemail(id); if (success) { setState(() { _voicemails.removeWhere((vm) => vm.id == id); }); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Failed to delete voicemail')), ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error deleting voicemail: $e')), ); } } } @override void dispose() { _audioPlayer.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (_loading) { return const Scaffold( backgroundColor: Colors.black, body: Center(child: CircularProgressIndicator()), ); } if (_voicemails.isEmpty) { return Scaffold( backgroundColor: Colors.black, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.voicemail, size: 64, color: Colors.grey), const SizedBox(height: 16), const Text( 'No voicemails found', style: TextStyle(color: Colors.white, fontSize: 20), ), const SizedBox(height: 8), const Padding( padding: EdgeInsets.symmetric(horizontal: 32.0), child: Text( 'Make sure this app is set as your default dialer and has all required permissions', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), ), const SizedBox(height: 16), ElevatedButton( onPressed: _fetchVoicemails, child: const Text('Refresh'), ), ], ), ), ); } return Scaffold( backgroundColor: Colors.black, body: RefreshIndicator( onRefresh: _fetchVoicemails, child: ListView.builder( itemCount: _voicemails.length, itemBuilder: (context, index) { final voicemail = _voicemails[index]; final isExpanded = _expandedIndex == index; final isPlaying = _isPlaying && _currentPlayingId == voicemail.id; return FutureBuilder( future: _voicemailService.getSenderName(voicemail.phoneNumber), builder: (context, snapshot) { final senderName = snapshot.data ?? voicemail.sender ?? voicemail.phoneNumber; final initials = senderName.isNotEmpty ? senderName[0].toUpperCase() : '?'; final avatarColor = generateColorFromName(senderName); return GestureDetector( onTap: () { setState(() { _expandedIndex = isExpanded ? null : index; }); }, child: AnimatedContainer( duration: const Duration(milliseconds: 300), margin: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color.fromARGB(255, 30, 30, 30), borderRadius: BorderRadius.circular(12.0), ), padding: const EdgeInsets.all(16), child: isExpanded ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 28, backgroundColor: avatarColor, child: Text( initials, style: const TextStyle( color: Colors.white, fontSize: 28, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _obfuscateService.obfuscateData(senderName), style: const TextStyle(color: Colors.white), ), Text( '${DateFormat('MMM dd, h:mm a').format(voicemail.timestamp)} - ${voicemail.duration.inMinutes}:${(voicemail.duration.inSeconds % 60).toString().padLeft(2, '0')} min', style: const TextStyle(color: Colors.grey), ), ], ), ), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: Icon( isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white, ), onPressed: () => _togglePlayPause(voicemail), ), if (isPlaying) Expanded( child: Slider( min: 0, max: _duration.inSeconds.toDouble(), value: _position.inSeconds.toDouble(), onChanged: (value) async { final newPos = Duration(seconds: value.toInt()); await _audioPlayer.seek(newPos); }, activeColor: Colors.blue, inactiveColor: Colors.grey, ), ), ], ), const SizedBox(height: 16), Wrap( spacing: 16, runSpacing: 12, children: [ ActionButton( icon: Icons.call, label: 'Call', color: Colors.green, onTap: () => _makeCall(voicemail.phoneNumber), ), ActionButton( icon: Icons.message, label: 'Text', color: Colors.blue, onTap: () => _sendMessage(voicemail.phoneNumber), ), ActionButton( icon: Icons.delete, label: 'Delete', color: Colors.red, onTap: () => _deleteVoicemail(voicemail.id), ), ActionButton( icon: Icons.share, label: 'Share', color: Colors.white, onTap: () { // Implement share functionality }, ), ], ), ], ) : Row( children: [ CircleAvatar( radius: 28, backgroundColor: avatarColor, child: Text( initials, style: const TextStyle( color: Colors.white, fontSize: 28, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _obfuscateService.obfuscateData(senderName), style: TextStyle( color: Colors.white, fontWeight: voicemail.isRead ? FontWeight.normal : FontWeight.bold, ), ), Text( '${DateFormat('MMM dd, h:mm a').format(voicemail.timestamp)} - ${voicemail.duration.inMinutes}:${(voicemail.duration.inSeconds % 60).toString().padLeft(2, '0')} min', style: const TextStyle(color: Colors.grey), ), ], ), ), if (!voicemail.isRead) Container( width: 12, height: 12, decoration: const BoxDecoration( shape: BoxShape.circle, color: Colors.blue, ), ), ], ), ), ); } ); }, ), ), ); } } class ActionButton extends StatelessWidget { final IconData icon; final String label; final Color color; final VoidCallback onTap; const ActionButton({ Key? key, required this.icon, required this.label, required this.color, required this.onTap, }) : super(key: key); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Column( children: [ Icon(icon, color: color), const SizedBox(height: 4), Text(label, style: const TextStyle(color: Colors.white)), ], ), ); } }