diff --git a/dialer/ios/Runner/Info.plist b/dialer/ios/Runner/Info.plist index d6f23c4..366d0f5 100644 --- a/dialer/ios/Runner/Info.plist +++ b/dialer/ios/Runner/Info.plist @@ -45,5 +45,8 @@ UIApplicationSupportsIndirectInputEvents + NSContactsUsageDescription + Contacts for calls/string> + diff --git a/dialer/lib/features/history/history_page.dart b/dialer/lib/features/history/history_page.dart index 034b286..4c38d15 100644 --- a/dialer/lib/features/history/history_page.dart +++ b/dialer/lib/features/history/history_page.dart @@ -1,36 +1,152 @@ +// history_page.dart + import 'package:flutter/material.dart'; +import 'package:flutter_contacts/flutter_contacts.dart'; +import 'package:intl/intl.dart'; // For date formatting +import 'package:dialer/features/contacts/contact_state.dart'; +class History { + final Contact contact; + final DateTime date; + final String callType; // 'incoming' or 'outgoing' + final String callStatus; // 'missed' or 'answered' + final int attempts; -List histories = [ - History("Hello"), -]; + History( + this.contact, + this.date, + this.callType, + this.callStatus, + this.attempts, + ); +} class HistoryPage extends StatefulWidget { - const HistoryPage({super.key}); + const HistoryPage({Key? key}) : super(key: key); @override _HistoryPageState createState() => _HistoryPageState(); } class _HistoryPageState extends State { + List histories = []; + bool loading = true; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (loading) { + _buildHistories(); + } + } + + Future _buildHistories() async { + final contactState = ContactState.of(context); + if (contactState.loading) { + // Wait for contacts to be loaded + await Future.doWhile(() async { + await Future.delayed(const Duration(milliseconds: 100)); + return contactState.loading; + }); + } + List contacts = contactState.contacts; + + // Ensure there are enough contacts + if (contacts.isEmpty) { + setState(() { + loading = false; + }); + return; + } + + // Build histories using the contacts + setState(() { + histories = List.generate( + contacts.length >= 10 ? 10 : contacts.length, + (index) => History( + contacts[index], + DateTime.now().subtract(Duration(hours: (index + 1) * 2)), + index % 2 == 0 ? 'outgoing' : 'incoming', + index % 3 == 0 ? 'missed' : 'answered', + index % 3 + 1, + ), + ); + loading = false; + }); + } + @override Widget build(BuildContext context) { + final contactState = ContactState.of(context); + + if (loading || contactState.loading) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('History'), + ), + body: const Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (histories.isEmpty) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('History'), + ), + body: const Center( + child: Text( + 'No call history available.', + style: TextStyle(color: Colors.white), + ), + ), + ); + } + return Scaffold( backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('History'), + ), body: ListView.builder( itemCount: histories.length, itemBuilder: (context, index) { - return null; - - // + final history = histories[index]; + final contact = history.contact; + + return ListTile( + leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) + ? CircleAvatar( + backgroundImage: MemoryImage(contact.thumbnail!), + ) + : CircleAvatar( + child: Text( + contact.displayName.isNotEmpty + ? contact.displayName[0] + : '?', + ), + ), + title: Text( + contact.displayName, + style: const TextStyle(color: Colors.white), + ), + subtitle: Text( + '${history.callType} - ${history.callStatus} - ${DateFormat('MMM dd, hh:mm a').format(history.date)}', + style: const TextStyle(color: Colors.grey), + ), + trailing: Text( + '${history.attempts}x', + style: const TextStyle(color: Colors.white), + ), + onTap: () { + // Handle tap event if needed + }, + ); }, ), ); } } - -class History { - final String text; - - History(this.text); -} diff --git a/dialer/lib/features/home/home_page.dart b/dialer/lib/features/home/home_page.dart index 09ded0c..f16de1f 100644 --- a/dialer/lib/features/home/home_page.dart +++ b/dialer/lib/features/home/home_page.dart @@ -4,6 +4,7 @@ import 'package:dialer/features/favorites/favorites_page.dart'; import 'package:dialer/features/history/history_page.dart'; import 'package:dialer/features/composition/composition.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; +import 'package:dialer/features/settings/settings.dart'; class _MyHomePageState extends State with SingleTickerProviderStateMixin { @@ -14,7 +15,7 @@ class _MyHomePageState extends State @override void initState() { super.initState(); - _tabController = TabController(length: 3, vsync: this, initialIndex: 1); + _tabController = TabController(length: 4, vsync: this, initialIndex: 1); _tabController.addListener(_handleTabIndex); _fetchContacts(); } @@ -83,7 +84,7 @@ class _MyHomePageState extends State return SearchBar( controller: controller, padding: MaterialStateProperty.all( - EdgeInsets.only( + const EdgeInsets.only( top: 6.0, bottom: 6.0, left: 16.0, @@ -141,6 +142,7 @@ class _MyHomePageState extends State FavoritePage(), HistoryPage(), ContactPage(), + SettingsPage(), // Add your SettingsPage here ], ), Positioned( @@ -184,6 +186,10 @@ class _MyHomePageState extends State icon: Icon(_tabController.index == 2 ? Icons.contacts : Icons.contacts_outlined)), + Tab( + icon: Icon(_tabController.index == 3 // Corrected index + ? Icons.settings + : Icons.settings_outlined)), ], labelColor: Colors.white, unselectedLabelColor: const Color.fromARGB(255, 158, 158, 158), diff --git a/dialer/lib/features/settings/call/settingsCall.dart b/dialer/lib/features/settings/call/settingsCall.dart new file mode 100644 index 0000000..a052b29 --- /dev/null +++ b/dialer/lib/features/settings/call/settingsCall.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +class SettingsCallPage extends StatefulWidget { + const SettingsCallPage({super.key}); + + @override + _SettingsCallPageState createState() => _SettingsCallPageState(); +} + +class _SettingsCallPageState extends State { + bool _enableVoicemail = true; + bool _enableCallRecording = false; + String _ringtone = 'Default'; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Calling settings'), + ), + body: ListView( + children: [ + SwitchListTile( + title: const Text('Enable Voicemail', style: TextStyle(color: Colors.white)), + value: _enableVoicemail, + onChanged: (bool value) { + setState(() { + _enableVoicemail = value; + }); + }, + ), + SwitchListTile( + title: const Text('Enable call Recording', style: TextStyle(color: Colors.white)), + value: _enableCallRecording, + onChanged: (bool value) { + setState(() { + _enableCallRecording = value; + }); + }, + ), + ListTile( + title: const Text('Ringtone', style: TextStyle(color: Colors.white)), + subtitle: Text(_ringtone, style: const TextStyle(color: Colors.grey)), + trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white), + onTap: () { + _selectRingtone(context); + }, + ), + ], + ), + ); + } + + void _selectRingtone(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return SimpleDialog( + title: const Text('Select Ringtone'), + children: [ + SimpleDialogOption( + onPressed: () { + Navigator.pop(context, 'Default'); + }, + child: const Text('Default'), + ), + SimpleDialogOption( + onPressed: () { + Navigator.pop(context, 'Classic'); + }, + child: const Text('Classic'), + ), + SimpleDialogOption( + onPressed: () { + Navigator.pop(context, 'Beep'); + }, + child: const Text('Beep'), + ), + // Add more ringtone options + ], + ); + }, + ).then((value) { + if (value != null) { + setState(() { + _ringtone = value; + }); + } + }); + } +} diff --git a/dialer/lib/features/settings/key/delete_key_pair.dart b/dialer/lib/features/settings/key/delete_key_pair.dart new file mode 100644 index 0000000..b38276f --- /dev/null +++ b/dialer/lib/features/settings/key/delete_key_pair.dart @@ -0,0 +1,68 @@ +// delete_key_pair.dart + +import 'package:flutter/material.dart'; + +class SuppressionPaireClesPage extends StatelessWidget { + const SuppressionPaireClesPage({super.key}); + + void _deleteKeyPair(BuildContext context) { + // key deletion logic (not implemented here) + // ... + + // Show confirmation message + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('La paire de clés a été supprimée.'), + ), + ); + + // Navigate back or update the UI as needed + Navigator.pop(context); + } + + void _showConfirmationDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmer la Suppression'), + content: const Text( + 'Êtes-vous sûr de vouloir supprimer la paire de clés ? Cette action est irréversible.'), + actions: [ + TextButton( + child: const Text('Annuler'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Supprimer'), + onPressed: () { + Navigator.of(context).pop(); + _deleteKeyPair(context); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Suppression d\'une Paire de Clés'), + ), + body: Center( + child: ElevatedButton( + onPressed: () { + _showConfirmationDialog(context); + }, + child: const Text('Supprimer la Paire de Clés'), + ), + ), + ); + } +} diff --git a/dialer/lib/features/settings/key/export_private_key.dart b/dialer/lib/features/settings/key/export_private_key.dart new file mode 100644 index 0000000..1878a2b --- /dev/null +++ b/dialer/lib/features/settings/key/export_private_key.dart @@ -0,0 +1,112 @@ +// export_private_key.dart + +import 'package:flutter/material.dart'; +import 'dart:typed_data'; +import 'dart:convert'; +import 'package:pointycastle/export.dart' as crypto; +import 'package:file_picker/file_picker.dart'; + +class ExportationClePriveePage extends StatefulWidget { + const ExportationClePriveePage({super.key}); + + @override + _ExportationClePriveePageState createState() => _ExportationClePriveePageState(); +} + +class _ExportationClePriveePageState extends State { + final TextEditingController _passwordController = TextEditingController(); + + Future _exportPrivateKey() async { + // Replace with your actual private key retrieval logic + final String privateKeyPem = 'Votre clé privée ici'; + + // Get the password from the user input + final password = _passwordController.text; + if (password.isEmpty) { + // Show error message + return; + } + + // Encrypt the private key using AES-256 + final encryptedData = _encryptPrivateKey(privateKeyPem, password); + + // Let the user pick a file location + final outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Enregistrer la clé privée chiffrée', + fileName: 'private_key_encrypted.aes', + ); + + if (outputFile != null) { + // Write the encrypted data to the file + // Use appropriate file I/O methods (not shown here) + // ... + // Show a confirmation dialog or message + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Clé Exportée'), + content: const Text('La clé privée chiffrée a été exportée avec succès.'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); + } + } + + Uint8List _encryptPrivateKey(String privateKey, String password) { + // Encryption logic using AES-256 + final key = crypto.PBKDF2KeyDerivator(crypto.HMac(crypto.SHA256Digest(), 64)) + .process(Uint8List.fromList(utf8.encode(password))); + + final params = crypto.PaddedBlockCipherParameters( + crypto.ParametersWithIV(crypto.KeyParameter(key), Uint8List(16)), // Initialization Vector + null, + ); + final cipher = crypto.PaddedBlockCipher('AES/CBC/PKCS7'); + cipher.init(true, params); + + final input = Uint8List.fromList(utf8.encode(privateKey)); + final output = cipher.process(input); + + return output; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Exportation de la Clé Privée'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Text( + 'Entrez un mot de passe pour chiffrer la clé privée:', + style: TextStyle(color: Colors.white), + ), + TextField( + controller: _passwordController, + obscureText: true, + style: const TextStyle(color: Colors.white), + decoration: const InputDecoration( + hintText: 'Mot de passe', + hintStyle: TextStyle(color: Colors.grey), + ), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _exportPrivateKey, + child: const Text('Exporter la Clé Privée Chiffrée'), + ), + ], + ), + ), + ); + } +} diff --git a/dialer/lib/features/settings/key/generate_new_key_pair.dart b/dialer/lib/features/settings/key/generate_new_key_pair.dart new file mode 100644 index 0000000..0f8e08d --- /dev/null +++ b/dialer/lib/features/settings/key/generate_new_key_pair.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:pointycastle/export.dart' as crypto; +import 'dart:math'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:asn1lib/asn1lib.dart'; + +class GenerationNouvellePaireClesPage extends StatelessWidget { + const GenerationNouvellePaireClesPage({super.key}); + + Future> _generateKeyPair() async { + // key generation logic using pointycastle + final keyParams = crypto.RSAKeyGeneratorParameters( + BigInt.parse('65537'), + 2048, + 64, + ); + + final secureRandom = crypto.FortunaRandom(); + + // Seed the random number generator + final random = Random.secure(); + final seeds = List.generate(32, (_) => random.nextInt(256)); + secureRandom.seed(crypto.KeyParameter(Uint8List.fromList(seeds))); + + final rngParams = crypto.ParametersWithRandom(keyParams, secureRandom); + final keyGenerator = crypto.RSAKeyGenerator(); + keyGenerator.init(rngParams); + + final pair = keyGenerator.generateKeyPair(); + final publicKey = pair.publicKey as crypto.RSAPublicKey; + final privateKey = pair.privateKey as crypto.RSAPrivateKey; + + // Convert keys to PEM format + final publicKeyPem = _encodePublicKeyToPemPKCS1(publicKey); + final privateKeyPem = _encodePrivateKeyToPemPKCS1(privateKey); + + // Save keys securely (not implemented here) + + return {'publicKey': publicKeyPem, 'privateKey': privateKeyPem}; + } + + String _encodePublicKeyToPemPKCS1(crypto.RSAPublicKey publicKey) { + final bytes = _encodePublicKeyToDer(publicKey); + return _formatPem(bytes, 'RSA PUBLIC KEY'); + } + + String _encodePrivateKeyToPemPKCS1(crypto.RSAPrivateKey privateKey) { + final bytes = _encodePrivateKeyToDer(privateKey); + return _formatPem(bytes, 'RSA PRIVATE KEY'); + } + + Uint8List _encodePublicKeyToDer(crypto.RSAPublicKey publicKey) { + final algorithmSeq = ASN1Sequence(); + algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption')); + algorithmSeq.add(ASN1Null()); + + final publicKeySeq = ASN1Sequence(); + publicKeySeq.add(ASN1Integer(publicKey.modulus!)); + publicKeySeq.add(ASN1Integer(publicKey.exponent!)); + + final publicKeyBitString = ASN1BitString(Uint8List.fromList(publicKeySeq.encodedBytes)); + + final topLevelSeq = ASN1Sequence(); + topLevelSeq.add(algorithmSeq); + topLevelSeq.add(publicKeyBitString); + + return Uint8List.fromList(topLevelSeq.encodedBytes); + } + + Uint8List _encodePrivateKeyToDer(crypto.RSAPrivateKey privateKey) { + final privateKeySeq = ASN1Sequence(); + privateKeySeq.add(ASN1Integer(BigInt.from(0))); // Version + privateKeySeq.add(ASN1Integer(privateKey.n!)); + privateKeySeq.add(ASN1Integer(privateKey.exponent!)); + privateKeySeq.add(ASN1Integer(privateKey.d!)); + privateKeySeq.add(ASN1Integer(privateKey.p!)); + privateKeySeq.add(ASN1Integer(privateKey.q!)); + privateKeySeq.add(ASN1Integer(privateKey.d! % (privateKey.p! - BigInt.one))); + privateKeySeq.add(ASN1Integer(privateKey.d! % (privateKey.q! - BigInt.one))); + privateKeySeq.add(ASN1Integer(privateKey.q!.modInverse(privateKey.p!))); + + return Uint8List.fromList(privateKeySeq.encodedBytes); + } + + String _formatPem(Uint8List bytes, String label) { + final base64 = base64Encode(bytes); + final chunks = RegExp('.{1,64}').allMatches(base64).map((m) => m.group(0)!); + return '-----BEGIN $label-----\n${chunks.join('\n')}\n-----END $label-----'; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Génération d\'une Nouvelle Paire de Clés'), + ), + body: Center( + child: ElevatedButton( + onPressed: () async { + final keys = await _generateKeyPair(); + // Display a confirmation dialog or message + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Clés Générées'), + content: const Text('La nouvelle paire de clés a été générée avec succès.'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); + }, + child: const Text('Générer une Nouvelle Paire de Clés'), + ), + ), + ); + } +} diff --git a/dialer/lib/features/settings/key/manage_keys_page.dart b/dialer/lib/features/settings/key/manage_keys_page.dart new file mode 100644 index 0000000..65db77d --- /dev/null +++ b/dialer/lib/features/settings/key/manage_keys_page.dart @@ -0,0 +1,83 @@ +// manage_keys_page.dart + +import 'package:flutter/material.dart'; +import 'package:dialer/features/settings/key/show_public_key_text.dart'; +import 'package:dialer/features/settings/key/show_public_key_qr.dart'; +import 'package:dialer/features/settings/key/generate_new_key_pair.dart'; +import 'package:dialer/features/settings/key/export_private_key.dart'; +import 'package:dialer/features/settings/key/delete_key_pair.dart'; + +class GestionDeClesPage extends StatelessWidget { + const GestionDeClesPage({super.key}); + + void _navigateToOption(BuildContext context, String option) { + switch (option) { + case 'Affichage de la clé publique en texte': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const AffichageClePubliqueTextePage()), + ); + break; + case 'Affichage de la clé publique en QR code': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const AffichageClePubliqueQRCodePage()), + ); + break; + case 'Génération d\'une nouvelle paire de clés': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const GenerationNouvellePaireClesPage()), + ); + break; + case 'Exportation de la clé privée en fichier chiffré par mot de passe (AES 256)': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ExportationClePriveePage()), + ); + break; + case 'Suppression d\'une paire de clés, POPUP d\'avertissement': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SuppressionPaireClesPage()), + ); + break; + default: + // Handle default or unknown options + break; + } + } + + @override + Widget build(BuildContext context) { + final keyManagementOptions = [ + 'Affichage de la clé publique en texte', + 'Affichage de la clé publique en QR code', + 'Génération d\'une nouvelle paire de clés', + 'Exportation de la clé privée en fichier chiffré par mot de passe (AES 256)', + 'Suppression d\'une paire de clés, POPUP d\'avertissement', + ]; + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Gestion de clés'), + ), + body: ListView.builder( + itemCount: keyManagementOptions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text( + keyManagementOptions[index], + style: const TextStyle(color: Colors.white), + ), + trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white), + onTap: () { + _navigateToOption(context, keyManagementOptions[index]); + }, + ); + }, + ), + ); + } +} diff --git a/dialer/lib/features/settings/key/show_public_key_qr.dart b/dialer/lib/features/settings/key/show_public_key_qr.dart new file mode 100644 index 0000000..bfc9853 --- /dev/null +++ b/dialer/lib/features/settings/key/show_public_key_qr.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; + +class AffichageClePubliqueQRCodePage extends StatelessWidget { + const AffichageClePubliqueQRCodePage({super.key}); + + @override + Widget build(BuildContext context) { + // Replace with your actual public key retrieval logic + final String publicKey = 'Votre clé publique ici'; + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Clé Publique en QR Code'), + ), + body: Center( + child: PrettyQr( + data: publicKey, + size: 250, + roundEdges: true, + elementColor: Colors.white, + ), + ), + ); + } +} diff --git a/dialer/lib/features/settings/key/show_public_key_text.dart b/dialer/lib/features/settings/key/show_public_key_text.dart new file mode 100644 index 0000000..ab69e49 --- /dev/null +++ b/dialer/lib/features/settings/key/show_public_key_text.dart @@ -0,0 +1,30 @@ +// show_public_key_text.dart + +import 'package:flutter/material.dart'; + +class AffichageClePubliqueTextePage extends StatelessWidget { + const AffichageClePubliqueTextePage({super.key}); + + @override + Widget build(BuildContext context) { + // Replace with your actual public key retrieval logic + final String publicKey = 'Votre clé publique ici'; + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Clé Publique en Texte'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: SelectableText( + publicKey, + style: const TextStyle(color: Colors.white), + textAlign: TextAlign.center, + ), + ), + ), + ); + } +} diff --git a/dialer/lib/features/settings/settings.dart b/dialer/lib/features/settings/settings.dart new file mode 100644 index 0000000..bdc22f3 --- /dev/null +++ b/dialer/lib/features/settings/settings.dart @@ -0,0 +1,68 @@ +// settings.dart + +import 'package:flutter/material.dart'; +import 'package:dialer/features/settings/call/settingsCall.dart'; +import 'package:dialer/features/settings/sim/settings_accounts.dart'; +import 'package:dialer/features/settings/key/manage_keys_page.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({super.key}); + + void _navigateToSettings(BuildContext context, String setting) { + switch (setting) { + case 'Calling settings': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SettingsCallPage()), + ); + break; + case 'Page des comptes téléphoniques': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SettingsAccountsPage()), + ); + break; + case 'Gestion de clés': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const GestionDeClesPage()), + ); + break; + // Add more cases for other settings pages + default: + // Handle default or unknown settings + break; + } + } + + @override + Widget build(BuildContext context) { + final settingsOptions = [ + 'Calling settings', + 'Page des comptes téléphoniques', + 'Gestion de clés', // Add the new option here + ]; + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('settings'), + ), + body: ListView.builder( + itemCount: settingsOptions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text( + settingsOptions[index], + style: const TextStyle(color: Colors.white), + ), + trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white), + onTap: () { + _navigateToSettings(context, settingsOptions[index]); + }, + ); + }, + ), + ); + } +} diff --git a/dialer/lib/features/settings/sim/choose_sim.dart b/dialer/lib/features/settings/sim/choose_sim.dart new file mode 100644 index 0000000..5bd5f7a --- /dev/null +++ b/dialer/lib/features/settings/sim/choose_sim.dart @@ -0,0 +1,87 @@ +// choose_sim.dart + +import 'package:flutter/material.dart'; +import 'package:sim_data/sim_data.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class ChooseSimPage extends StatefulWidget { + const ChooseSimPage({super.key}); + + @override + _ChooseSimPageState createState() => _ChooseSimPageState(); +} + +class _ChooseSimPageState extends State { + List _simCards = []; + int? _selectedSimIndex; + + @override + void initState() { + super.initState(); + _fetchSimCards(); + } + + Future _fetchSimCards() async { + if (await Permission.phone.request().isGranted) { + try { + final simData = await SimDataPlugin.getSimData(); + setState(() { + _simCards = simData.cards; + _selectedSimIndex = 0; + }); + } catch (e) { + // Handle error + print('Error fetching SIM data: $e'); + } + } else { + // Permission denied + print('Phone permission denied'); + } + } + + void _onSimSelected(int? index) { + if (index != null) { + setState(() { + _selectedSimIndex = index; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Choisir la SIM'), + ), + body: _simCards.isEmpty + ? const Center( + child: Text( + 'Aucune carte SIM trouvée', + style: TextStyle(color: Colors.white), + ), + ) + : ListView.builder( + itemCount: _simCards.length, + itemBuilder: (context, index) { + final sim = _simCards[index]; + return ListTile( + title: Text( + 'SIM ${index + 1}', + style: const TextStyle(color: Colors.white), + ), + subtitle: Text( + 'Opérateur: ${sim.carrierName}', + style: const TextStyle(color: Colors.grey), + ), + trailing: Radio( + value: index, + groupValue: _selectedSimIndex, + onChanged: _onSimSelected, + ), + ); + }, + ), + ); + } +} diff --git a/dialer/lib/features/settings/sim/settings_accounts.dart b/dialer/lib/features/settings/sim/settings_accounts.dart new file mode 100644 index 0000000..1ababb4 --- /dev/null +++ b/dialer/lib/features/settings/sim/settings_accounts.dart @@ -0,0 +1,59 @@ +// settings_accounts.dart + +import 'package:flutter/material.dart'; +import 'package:dialer/features/settings/sim/choose_sim.dart'; +import 'package:dialer/features/settings/sim/sim_parameters.dart'; + +class SettingsAccountsPage extends StatelessWidget { + const SettingsAccountsPage({super.key}); + + void _navigateToAccountOption(BuildContext context, String option) { + switch (option) { + case 'Choisir la SIM': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ChooseSimPage()), + ); + break; + case 'Paramètre SIM': + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SimParametersPage()), + ); + break; + // Handle more options if needed + default: + break; + } + } + + @override + Widget build(BuildContext context) { + final accountOptions = [ + 'Choisir la SIM', + 'Paramètre SIM', + ]; + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Page des comptes téléphoniques'), + ), + body: ListView.builder( + itemCount: accountOptions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text( + accountOptions[index], + style: const TextStyle(color: Colors.white), + ), + trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white), + onTap: () { + _navigateToAccountOption(context, accountOptions[index]); + }, + ); + }, + ), + ); + } +} diff --git a/dialer/lib/features/settings/sim/sim_parameters.dart b/dialer/lib/features/settings/sim/sim_parameters.dart new file mode 100644 index 0000000..ff83299 --- /dev/null +++ b/dialer/lib/features/settings/sim/sim_parameters.dart @@ -0,0 +1,85 @@ +// sim_parameters.dart + +import 'package:flutter/material.dart'; +import 'package:sim_data/sim_data.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class SimParametersPage extends StatefulWidget { + const SimParametersPage({super.key}); + + @override + _SimParametersPageState createState() => _SimParametersPageState(); +} + +class _SimParametersPageState extends State { + List _simCards = []; + + @override + void initState() { + super.initState(); + _fetchSimParameters(); + } + + Future _fetchSimParameters() async { + if (await Permission.phone.request().isGranted) { + try { + final simData = await SimDataPlugin.getSimData(); + setState(() { + _simCards = simData.cards; + }); + } catch (e) { + // Handle error + print('Error fetching SIM data: $e'); + } + } else { + // Permission denied + print('Phone permission denied'); + } + } + + Widget _buildSimInfo(SimCard sim, int index) { + return Card( + color: Colors.grey[850], + child: ListTile( + title: Text( + 'SIM ${index + 1}', + style: const TextStyle(color: Colors.white), + ), + subtitle: Text( + ''' +Opérateur: ${sim.carrierName} +Pays: ${sim.countryCode ?? 'N/A'} +MCC: ${sim.mcc ?? 'N/A'} +MNC: ${sim.mnc ?? 'N/A'} +Slot Index: ${sim.slotIndex ?? 'N/A'} +Display Name: ${sim.displayName ?? 'N/A'} + ''', + style: const TextStyle(color: Colors.grey), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + title: const Text('Paramètre SIM'), + ), + body: _simCards.isEmpty + ? const Center( + child: Text( + 'Aucune carte SIM trouvée', + style: TextStyle(color: Colors.white), + ), + ) + : ListView.builder( + itemCount: _simCards.length, + itemBuilder: (context, index) { + return _buildSimInfo(_simCards[index], index); + }, + ), + ); + } +} diff --git a/dialer/pubspec.yaml b/dialer/pubspec.yaml index 94c0f54..6618f25 100644 --- a/dialer/pubspec.yaml +++ b/dialer/pubspec.yaml @@ -38,6 +38,12 @@ dependencies: flutter_contacts: ^1.1.9+2 permission_handler: ^10.2.0 # For handling permissions cached_network_image: ^3.2.3 # For caching contact images + sim_data: ^0.0.2 + pretty_qr_code: ^3.3.0 + pointycastle: ^3.4.0 + file_picker: ^5.2.5 + asn1lib: ^1.0.0 + intl_utils: ^2.0.7 dev_dependencies: flutter_test: