From 84329cb4d04b432abdec7681a512c1b3d1123cb9 Mon Sep 17 00:00:00 2001 From: alexis Date: Wed, 5 Feb 2025 21:50:24 +0000 Subject: [PATCH] feat: Add Voicemail feature with playback functionality (#32) Page messagerie vocale Co-authored-by: AlexisDanlos <91090088+AlexisDanlos@users.noreply.github.com> Co-authored-by: stcb <21@stcb.cc> Reviewed-on: https://git.gmoker.com/icing/monorepo/pulls/32 Co-authored-by: alexis Co-committed-by: alexis --- dialer/lib/features/home/home_page.dart | 22 +- .../features/voicemail/voicemail_page.dart | 209 ++++++++++++++++++ dialer/pubspec.yaml | 1 + 3 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 dialer/lib/features/voicemail/voicemail_page.dart diff --git a/dialer/lib/features/home/home_page.dart b/dialer/lib/features/home/home_page.dart index ac55cf8..97e2c88 100644 --- a/dialer/lib/features/home/home_page.dart +++ b/dialer/lib/features/home/home_page.dart @@ -7,6 +7,8 @@ import 'package:dialer/features/composition/composition.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:dialer/features/settings/settings.dart'; import '../../services/contact_service.dart'; +import 'package:dialer/features/voicemail/voicemail_page.dart'; + class _MyHomePageState extends State with SingleTickerProviderStateMixin { @@ -19,8 +21,8 @@ class _MyHomePageState extends State @override void initState() { super.initState(); - // Set the TabController length to 3 - _tabController = TabController(length: 3, vsync: this, initialIndex: 1); + // Set the TabController length to 4 + _tabController = TabController(length: 4, vsync: this, initialIndex: 1); _tabController.addListener(_handleTabIndex); _fetchContacts(); } @@ -92,7 +94,7 @@ class _MyHomePageState extends State return SearchBar( controller: controller, padding: - MaterialStateProperty.all( + WidgetStateProperty.all( const EdgeInsets.only( top: 6.0, bottom: 6.0, @@ -104,10 +106,10 @@ class _MyHomePageState extends State controller.openView(); _onSearchChanged(''); }, - backgroundColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all( const Color.fromARGB(255, 30, 30, 30)), hintText: 'Search contacts', - hintStyle: MaterialStateProperty.all( + hintStyle: WidgetStateProperty.all( const TextStyle(color: Colors.grey, fontSize: 16.0), ), leading: const Icon( @@ -116,7 +118,7 @@ class _MyHomePageState extends State size: 24.0, ), shape: - MaterialStateProperty.all( + WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), @@ -129,7 +131,7 @@ class _MyHomePageState extends State suggestionsBuilder: (BuildContext context, SearchController controller) { return _contactSuggestions.map((contact) { - return ListTile( + return ListTile( key: ValueKey(contact.id), title: Text(_obfuscateService.obfuscateData(contact.displayName), style: const TextStyle(color: Colors.white)), @@ -174,6 +176,7 @@ class _MyHomePageState extends State FavoritesPage(), HistoryPage(), ContactPage(), + VoicemailPage(), ], ), Positioned( @@ -217,6 +220,11 @@ class _MyHomePageState extends State icon: Icon(_tabController.index == 2 ? Icons.contacts : Icons.contacts_outlined)), + Tab( + icon: Icon(_tabController.index == 3 + ? Icons.voicemail + : Icons.voicemail_outlined), + ), ], labelColor: Colors.white, unselectedLabelColor: const Color.fromARGB(255, 158, 158, 158), diff --git a/dialer/lib/features/voicemail/voicemail_page.dart b/dialer/lib/features/voicemail/voicemail_page.dart new file mode 100644 index 0000000..1fadac8 --- /dev/null +++ b/dialer/lib/features/voicemail/voicemail_page.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:audioplayers/audioplayers.dart'; + +class VoicemailPage extends StatefulWidget { + const VoicemailPage({Key? key}) : super(key: key); + + @override + State createState() => _VoicemailPageState(); +} + +class _VoicemailPageState extends State { + bool _expanded = false; + bool _isPlaying = false; + Duration _duration = Duration.zero; + Duration _position = Duration.zero; + late AudioPlayer _audioPlayer; + + @override + void initState() { + super.initState(); + _audioPlayer = AudioPlayer(); + _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; + }); + }); + } + + Future _togglePlayPause() async { + if (_isPlaying) { + await _audioPlayer.pause(); + } else { + await _audioPlayer.play(UrlSource('voicemail.mp3')); + } + setState(() => _isPlaying = !_isPlaying); + } + + @override + void dispose() { + _audioPlayer.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + // appBar: AppBar( + // // title: const Text('Voicemail'), + // backgroundColor: Colors.black, + // ), + body: ListView( + children: [ + GestureDetector( + onTap: () { + setState(() { + _expanded = !_expanded; + }); + }, + 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: _expanded + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const CircleAvatar( + radius: 28, + backgroundColor: Colors.amber, + child: Text( + "JD", + style: TextStyle( + color: Colors.deepOrange, + fontSize: 28, + ), + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'John Doe', + style: TextStyle(color: Colors.white), + ), + Text( + 'Wed 3:00 PM - 1:20 min', + style: 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, + ), + SizedBox( + width: 200, + 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), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: const [ + Icon(Icons.call, color: Colors.green), + SizedBox(width: 8), + Text('Call', style: TextStyle(color: Colors.white)), + ], + ), + const SizedBox(height: 12), + Row( + children: const [ + Icon(Icons.message, color: Colors.blue), + SizedBox(width: 8), + Text('Text', style: TextStyle(color: Colors.white)), + ], + ), + const SizedBox(height: 12), + Row( + children: const [ + Icon(Icons.block, color: Colors.red), + SizedBox(width: 8), + Text('Block', style: TextStyle(color: Colors.white)), + ], + ), + const SizedBox(height: 12), + Row( + children: const [ + Icon(Icons.share, color: Colors.white), + SizedBox(width: 8), + Text('Share', style: TextStyle(color: Colors.white)), + ], + ), + ], + ), + ], + ) + : Row( + children: [ + const CircleAvatar( + radius: 28, + backgroundColor: Colors.amber, + child: Text( + "JD", + style: TextStyle( + color: Colors.deepOrange, + fontSize: 28, + ), + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'John Doe', + style: TextStyle(color: Colors.white), + ), + Text( + 'Wed 3:00 PM - 1:20 min', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/dialer/pubspec.yaml b/dialer/pubspec.yaml index 33623b4..87aa442 100644 --- a/dialer/pubspec.yaml +++ b/dialer/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: intl_utils: ^2.0.7 url_launcher: ^6.3.1 flutter_secure_storage: ^9.0.0 + audioplayers: ^6.1.0 mobile_number: path: packages/mobile_number