From fb5f155430dadafdbdcb09f2e9ef0f0bf96d23ce Mon Sep 17 00:00:00 2001 From: alexis Date: Fri, 7 Mar 2025 22:40:16 +0000 Subject: [PATCH] Add CallPage for initiating calls with contact details (#37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Demo call page avec les features de base: - Haut parleur - Couper/activer micro - keypad - raccrocher - Display Icing state (toucher pour switch l'état) S'active en faisant un appui long sur le bouton d'appel depuis les détails du contact. Compatible avec l'obfuscation des contacts. Co-authored-by: AlexisDanlos <91090088+AlexisDanlos@users.noreply.github.com> Reviewed-on: https://git.gmoker.com/icing/monorepo/pulls/37 Co-authored-by: alexis Co-committed-by: alexis --- dialer/lib/features/call/call_page.dart | 332 ++++++++++++++++++ .../contacts/widgets/contact_modal.dart | 13 + 2 files changed, 345 insertions(+) create mode 100644 dialer/lib/features/call/call_page.dart diff --git a/dialer/lib/features/call/call_page.dart b/dialer/lib/features/call/call_page.dart new file mode 100644 index 0000000..e8edf82 --- /dev/null +++ b/dialer/lib/features/call/call_page.dart @@ -0,0 +1,332 @@ +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:dialer/services/obfuscate_service.dart'; +import 'package:dialer/widgets/username_color_generator.dart'; + +class CallPage extends StatefulWidget { + final String displayName; + final Uint8List? thumbnail; + + const CallPage({super.key, required this.displayName, this.thumbnail}); + + @override + _CallPageState createState() => _CallPageState(); +} + +class _CallPageState extends State { + final ObfuscateService _obfuscateService = ObfuscateService(); + bool isMuted = false; + bool isSpeakerOn = false; + bool isKeypadVisible = false; + bool icingProtocolOk = true; + String _typedDigits = ""; // New state variable for pressed digits + + void _addDigit(String digit) { + setState(() { + _typedDigits += digit; + }); + } + + void _toggleMute() { + setState(() { + isMuted = !isMuted; + }); + } + + void _toggleSpeaker() { + setState(() { + isSpeakerOn = !isSpeakerOn; + }); + } + + void _toggleKeypad() { + setState(() { + isKeypadVisible = !isKeypadVisible; + }); + } + + void _toggleIcingProtocol() { + setState(() { + icingProtocolOk = !icingProtocolOk; + }); + } + + void _hangUp() { + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + final double avatarRadius = isKeypadVisible ? 45.0 : 45.0; // Smaller avatar + final double nameFontSize = isKeypadVisible ? 24.0 : 24.0; // Smaller font + final double statusFontSize = isKeypadVisible ? 16.0 : 16.0; // Smaller status + + return Scaffold( + body: Container( + color: Colors.black, + child: SafeArea( + child: Column( + children: [ + // Top section - make it more compact + Container( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 35), + ObfuscatedAvatar( + imageBytes: widget.thumbnail, + radius: avatarRadius, + backgroundColor: generateColorFromName(widget.displayName), + fallbackInitial: widget.displayName, + ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icingProtocolOk ? Icons.lock : Icons.lock_open, + color: icingProtocolOk ? Colors.green : Colors.red, + size: 16, + ), + const SizedBox(width: 4), + Text( + 'Icing protocol: ${icingProtocolOk ? "ok" : "ko"}', + style: TextStyle( + color: icingProtocolOk ? Colors.green : Colors.red, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + _obfuscateService.obfuscateData(widget.displayName), + style: TextStyle( + fontSize: nameFontSize, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Calling...', + style: TextStyle(fontSize: statusFontSize, color: Colors.white70), + ), + ], + ), + ), + + // Middle section - make it flexible and scrollable if needed + Expanded( + child: Column( + children: [ + if (isKeypadVisible) ...[ + // Add spacer to push keypad down + const Spacer(flex: 2), + + // Typed digits display + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + _typedDigits, + maxLines: 1, + textAlign: TextAlign.right, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 24, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + IconButton( + padding: EdgeInsets.zero, + onPressed: _toggleKeypad, + icon: const Icon(Icons.close, color: Colors.white), + ), + ], + ), + ), + + // Keypad grid + Container( + height: MediaQuery.of(context).size.height * 0.35, + margin: const EdgeInsets.symmetric(horizontal: 20), + child: GridView.count( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisCount: 3, + childAspectRatio: 1.3, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + children: List.generate(12, (index) { + String label; + if (index < 9) { + label = '${index + 1}'; + } else if (index == 9) { + label = '*'; + } else if (index == 10) { + label = '0'; + } else { + label = '#'; + } + return GestureDetector( + onTap: () => _addDigit(label), + child: Container( + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + ), + child: Center( + child: Text( + label, + style: const TextStyle(fontSize: 32, color: Colors.white), + ), + ), + ), + ); + }), + ), + ), + + // Add spacer after keypad + const Spacer(flex: 1), + ] else ...[ + const Spacer(), + // Control buttons + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Main control buttons + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Mute + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: _toggleMute, + icon: Icon( + isMuted ? Icons.mic_off : Icons.mic, + color: Colors.white, + size: 32, + ), + ), + Text( + isMuted ? 'Unmute' : 'Mute', + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + ], + ), + // Keypad + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: _toggleKeypad, + icon: const Icon(Icons.dialpad, color: Colors.white, size: 32), + ), + const Text( + 'Keypad', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ], + ), + // Speaker + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: _toggleSpeaker, + icon: Icon( + isSpeakerOn ? Icons.volume_up : Icons.volume_off, + color: isSpeakerOn ? Colors.amber : Colors.white, + size: 32, + ), + ), + const Text( + 'Speaker', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ], + ), + ], + ), + const SizedBox(height: 20), + // Additional buttons + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Add Contact + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + // ...existing code... + }, + icon: const Icon(Icons.person_add, color: Colors.white, size: 32), + ), + const Text('Add Contact', + style: TextStyle(color: Colors.white, fontSize: 14)), + ], + ), + // Change SIM + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + // ...existing code... + }, + icon: const Icon(Icons.sim_card, color: Colors.white, size: 32), + ), + const Text('Change SIM', + style: TextStyle(color: Colors.white, fontSize: 14)), + ], + ), + ], + ), + ], + ), + ), + const Spacer(flex: 3), + ], + ], + ), + ), + + // Bottom section - hang up button + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: GestureDetector( + onTap: _hangUp, + child: Container( + padding: const EdgeInsets.all(12), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.call_end, + color: Colors.white, + size: 32, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/dialer/lib/features/contacts/widgets/contact_modal.dart b/dialer/lib/features/contacts/widgets/contact_modal.dart index 9f641ad..0a6efad 100644 --- a/dialer/lib/features/contacts/widgets/contact_modal.dart +++ b/dialer/lib/features/contacts/widgets/contact_modal.dart @@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:dialer/widgets/username_color_generator.dart'; import '../../../services/block_service.dart'; import '../../../services/contact_service.dart'; +import '../../../features/call/call_page.dart'; import '../../../services/call_service.dart'; // Import CallService class ContactModal extends StatefulWidget { @@ -265,6 +266,18 @@ class _ContactModalState extends State { await _callService.makeGsmCall(phoneNumber); } }, + onLongPress: () { + // Navigate to the beautiful calling page demo + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => CallPage( + displayName: widget.contact.displayName, + thumbnail: widget.contact.thumbnail, + ), + ), + ); + }, ), ListTile( leading: const Icon(Icons.message, color: Colors.blue),