merge add new architectutre and add of keys settings page

This commit is contained in:
Bartosz 2024-11-05 23:08:21 +00:00
commit 3d19fa38f9
32 changed files with 785 additions and 160 deletions

View File

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<application
android:label="dialer"
android:label="com.example.dialer"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-amd64

View File

@ -1,10 +1,10 @@
// DisplayContact.dart
import 'package:dialer/pages/callingPage.dart';
import 'package:dialer/features/composition/composition.dart';
import 'package:dialer/classes/contactClass.dart';
import 'package:dialer/classes/displayAvatar.dart';
import 'package:flutter/material.dart';
import 'package:dialer/pages/history.dart';
import 'package:dialer/features/history/history_page.dart';
class DisplayContact extends StatelessWidget {
final Contact contact;
@ -34,7 +34,7 @@ class DisplayContact extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CallingPage(contact: contact),
builder: (context) => const CompositionPage(),
),
);
}
@ -50,7 +50,7 @@ class DisplayContact extends StatelessWidget {
Text(contact.phoneNumber),
if (contact.publicKey != null)
Text(
'Key: ${contact.publicKey!.substring(0, 2)}...${contact.publicKey!.substring(contact.publicKey!.length - 2)}',
'key: ${contact.publicKey!.substring(0, 2)}...${contact.publicKey!.substring(contact.publicKey!.length - 2)}',
),
if (history != null) ...[
Row(

View File

@ -31,7 +31,7 @@ class _SettingsCallPageState extends State<SettingsCallPage> {
},
),
SwitchListTile(
title: const Text('Enable Call Recording', style: TextStyle(color: Colors.white)),
title: const Text('Enable call Recording', style: TextStyle(color: Colors.white)),
value: _enableCallRecording,
onChanged: (bool value) {
setState(() {

View File

@ -0,0 +1,68 @@
// delete_keyPair.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'),
),
),
);
}
}

View File

@ -0,0 +1,112 @@
// export_privateKey.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<ExportationClePriveePage> {
final TextEditingController _passwordController = TextEditingController();
Future<void> _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'),
),
],
),
),
);
}
}

View File

@ -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<Map<String, String>> _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<int>.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'),
),
),
);
}
}

View File

@ -0,0 +1,83 @@
// manage_keysPage.dart
import 'package:flutter/material.dart';
import 'package:dialer/features/Settings/key/show_publicKeyText.dart';
import 'package:dialer/features/Settings/key/show_publicKeyQR.dart';
import 'package:dialer/features/Settings/key/generate_newKeyPair.dart';
import 'package:dialer/features/Settings/key/export_privateKey.dart';
import 'package:dialer/features/Settings/key/delete_keyPair.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]);
},
);
},
),
);
}
}

View File

@ -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,
),
),
);
}
}

View File

@ -0,0 +1,30 @@
// show_publicKeyText.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,
),
),
),
);
}
}

View File

@ -1,8 +1,9 @@
// settings.dart
import 'package:flutter/material.dart';
import 'package:dialer/pages/Settings/settingsCall.dart';
import 'package:dialer/pages/Settings/settingsAccounts.dart';
import 'package:dialer/features/Settings/call/settingsCall.dart';
import 'package:dialer/features/Settings/sim/settingsAccounts.dart';
import 'package:dialer/features/Settings/key/manage_keysPage.dart'; // Import the new page
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@ -21,6 +22,12 @@ class SettingsPage extends StatelessWidget {
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
@ -33,6 +40,7 @@ class SettingsPage extends StatelessWidget {
final settingsOptions = [
'Calling Settings',
'Page des comptes téléphoniques',
'Gestion de clés', // Add the new option here
];
return Scaffold(

View File

@ -1,8 +1,8 @@
// settingsAccounts.dart
import 'package:flutter/material.dart';
import 'package:dialer/pages/Settings/Sim/chooseSim.dart';
import 'package:dialer/pages/Settings/Sim/simParameters.dart';
import 'package:dialer/features/Settings/sim/chooseSim.dart';
import 'package:dialer/features/Settings/sim/simParameters.dart';
class SettingsAccountsPage extends StatelessWidget {
const SettingsAccountsPage({super.key});

View File

@ -0,0 +1,27 @@
import 'package:dialer/features/contacts/contact_state.dart';
import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart';
import 'package:flutter/material.dart';
import 'package:dialer/widgets/loading_indicator.dart';
class ContactPage extends StatefulWidget {
const ContactPage({super.key});
@override
_ContactPageState createState() => _ContactPageState();
}
class _ContactPageState extends State<ContactPage> {
@override
Widget build(BuildContext context) {
final contactState = ContactState.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Contacts'),
),
body: contactState.loading
? const LoadingIndicatorWidget()
// : ContactListWidget(contacts: contactState.contacts),
: AlphabetScrollPage(contacts: contactState.contacts, scrollOffset: contactState.scrollOffset),
);
}
}

View File

@ -0,0 +1,15 @@
import 'package:flutter_contacts/flutter_contacts.dart';
// Service to manage contact-related operations
class ContactService {
Future<List<Contact>> fetchContacts() async {
if (await FlutterContacts.requestPermission()) {
return await FlutterContacts.getContacts(withProperties: true, withThumbnail: true);
}
return [];
}
Future<void> addNewContact(Contact contact) async {
await FlutterContacts.insertContact(contact);
}
}

View File

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'contact_service.dart';
class ContactState extends StatefulWidget {
final Widget child;
const ContactState({super.key, required this.child});
static _ContactStateState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_InheritedContactState>()!.data;
}
@override
_ContactStateState createState() => _ContactStateState();
}
class _ContactStateState extends State<ContactState> {
final ContactService _contactService = ContactService();
List<Contact> _contacts = [];
bool _loading = true;
double _scrollOffset = 0.0;
List<Contact> get contacts => _contacts;
bool get loading => _loading;
double get scrollOffset => _scrollOffset;
@override
void initState() {
super.initState();
_fetchContacts();
}
Future<void> _fetchContacts() async {
List<Contact> contacts = await _contactService.fetchContacts();
contacts = contacts.where((contact) => contact.phones.isNotEmpty).toList();
contacts.sort((a, b) => a.displayName.compareTo(b.displayName));
// contacts.sort((a, b) {
// String aName = a.displayName.isNotEmpty ? a.displayName : '􏿿';
// String bName = b.displayName.isNotEmpty ? b.displayName : '􏿿';
// return aName.compareTo(bName);
// });
setState(() {
_contacts = contacts;
_loading = false;
});
}
Future<void> addNewContact(Contact contact) async {
await _contactService.addNewContact(contact);
await _fetchContacts();
}
void setScrollOffset(double offset) {
setState(() {
_scrollOffset = offset;
});
}
@override
Widget build(BuildContext context) {
return _InheritedContactState(
data: this,
child: widget.child,
);
}
}
class _InheritedContactState extends InheritedWidget {
final _ContactStateState data;
const _InheritedContactState({required this.data, required super.child});
@override
bool updateShouldNotify(_InheritedContactState oldWidget) => true;
}

View File

@ -0,0 +1,96 @@
import 'package:dialer/widgets/username_color_generator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import '../contact_state.dart';
class AlphabetScrollPage extends StatefulWidget {
final List<Contact> contacts;
final double scrollOffset;
const AlphabetScrollPage({super.key, required this.contacts, required this.scrollOffset});
@override
_AlphabetScrollPageState createState() => _AlphabetScrollPageState();
}
class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController(initialScrollOffset: widget.scrollOffset);
_scrollController.addListener(_onScroll);
}
void _onScroll() {
final contactState = ContactState.of(context);
contactState.setScrollOffset(_scrollController.offset);
}
@override
Widget build(BuildContext context) {
Map<String, List<Contact>> alphabetizedContacts = {};
for (var contact in widget.contacts) {
String firstLetter = contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '#';
if (!alphabetizedContacts.containsKey(firstLetter)) {
alphabetizedContacts[firstLetter] = [];
}
alphabetizedContacts[firstLetter]!.add(contact);
}
List<String> alphabetKeys = alphabetizedContacts.keys.toList()..sort();
return ListView.builder(
controller: _scrollController,
itemCount: alphabetKeys.length,
itemBuilder: (context, index) {
String letter = alphabetKeys[index];
List<Contact> contacts = alphabetizedContacts[letter]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Text(
letter,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
...contacts.map((contact) {
String phoneNumber = contact.phones.isNotEmpty ? contact.phones.first.number : 'No phone number';
Color avatarColor = generateColorFromName(contact.displayName);
return ListTile(
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? CircleAvatar(
backgroundImage: MemoryImage(contact.thumbnail!),
)
: CircleAvatar(
backgroundColor: avatarColor,
child: Text(
contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '?',
style: TextStyle(color: Colors.white),
),
),
title: Text(contact.displayName),
subtitle: Text(phoneNumber),
onTap: () {
// Handle contact tap
},
);
}),
],
);
},
);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,40 @@
import 'package:dialer/widgets/color_darkener.dart';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:dialer/widgets/username_color_generator.dart';
class ContactListWidget extends StatelessWidget {
final List<Contact> contacts;
const ContactListWidget({super.key, required this.contacts});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
Contact contact = contacts[index];
String phoneNumber = contact.phones.isNotEmpty ? contact.phones.first.number : 'No phone number';
Color avatarColor = generateColorFromName(contact.displayName);
return ListTile(
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? CircleAvatar(
backgroundImage: MemoryImage(contact.thumbnail!),
)
: CircleAvatar(
backgroundColor: avatarColor,
child: Text(
contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '?',
style: TextStyle(fontSize: 25, color: darken(avatarColor, 0.4)),
),
),
title: Text(contact.displayName),
subtitle: Text(phoneNumber),
onTap: () {
// Handle contact tap
},
);
},
);
}
}

View File

@ -24,9 +24,13 @@ class _FavoritePageState extends State<FavoritePage> {
title: const Text('Favorites'),
),
body: ListView.builder(
itemCount: favoriteContacts.length,
itemCount: contacts.length,
itemBuilder: (context, index) {
return DisplayContact(contact: favoriteContacts[index]);
if (contacts[index].isFavorite) {
return DisplayContact(contact: contacts[index]);
} else {
return const SizedBox.shrink();
}
},
),
);

View File

@ -1,9 +1,9 @@
import 'package:dialer/pages/contact.dart';
import 'package:dialer/pages/favorites.dart';
import 'package:dialer/pages/history.dart';
import 'package:dialer/pages/composition.dart';
import 'package:dialer/pages/Settings/settings.dart';
import 'package:dialer/features/composition/composition.dart';
import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_page.dart'; // Import ContactPage
import 'package:dialer/features/favorites/favorites_page.dart'; // Import FavoritePage
import 'package:dialer/features/history/history_page.dart'; // Import HistoryPage
import 'package:dialer/features/Settings/settings.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@ -18,6 +18,7 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
@override
void initState() {
super.initState();
// Update the length to 5
_tabController = TabController(length: 5, vsync: this, initialIndex: 1);
_tabController.addListener(_handleTabIndex);
}
@ -46,7 +47,7 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
HistoryPage(),
ContactPage(),
CompositionPage(),
SettingsPage(),
SettingsPage(), // Ensure this matches the number of tabs
],
),
if (_tabController.index != 3)

View File

@ -1,7 +1,8 @@
// This is DEV
import 'package:dialer/pages/myHomePage.dart';
import 'package:dialer/features/home/home_page.dart';
import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_state.dart';
void main() {
runApp(const MyApp());
@ -12,11 +13,13 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
brightness: Brightness.dark
),
home: const MyHomePage(),
return ContactState(
child: MaterialApp(
theme: ThemeData(
brightness: Brightness.dark
),
home: const MyHomePage(),
)
);
}
}

View File

@ -1,93 +0,0 @@
import 'package:dialer/classes/contactClass.dart';
import 'package:flutter/material.dart';
// display the calling page as if the call is already in progress
class _CallingPageState extends State<CallingPage> {
@override
Widget build(BuildContext context) {
final contact = widget.contact;
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Appel en cours'),
backgroundColor: Colors.black,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const CircleAvatar(
radius: 100.0,
backgroundImage:
NetworkImage('https://thispersondoesnotexist.com/'),
backgroundColor: Colors.transparent,
),
const SizedBox(height: 10.0),
// Add the contact name here
Text(contact.name, style: const TextStyle(fontSize: 40.0, color: Colors.white)),
// const SizedBox(height: 10.0),
const Text('99 : 59 : 59', style:
TextStyle(fontSize: 40.0, color:
Colors.white)),
const SizedBox(height: 50.0),
contact.isLocked
? const Text('Con. Health - 98% (excellent)',
style:
TextStyle(fontSize:
16.0,color:
Colors.green))
:
const Text('No Icing available',
style:
TextStyle(fontSize:
16.0,color:
Colors.white)),
const SizedBox(height:
50.0), // Adjust size box height as needed
const Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children:<Widget>[
Icon(Icons.mic_off,size:
30.0,color:
Colors.white),
Icon(Icons.dialpad,size:
30.0,color:
Colors.white),
Icon(Icons.more_vert,size:
30.0,color:
Colors.white),
Icon(Icons.volume_up,size:
30.0,color:
Colors.white),
],
),
const SizedBox(height:
50.0), // Adjust size box height as needed
const Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children:<Widget>[
Icon(Icons.pause,size:
60.0,color:
Colors.white),
Icon(Icons.call_end,size:
60.0,color:
Colors.red),
],
),
],
),
),
);
}
}
class CallingPage extends StatefulWidget {
final Contact contact;
const CallingPage({super.key, required this.contact});
@override
_CallingPageState createState() => _CallingPageState();
}

View File

@ -1,41 +0,0 @@
// contact.dart
import 'package:flutter/material.dart';
import 'package:dialer/classes/contactClass.dart';
import 'package:dialer/classes/displayContact.dart';
class ContactPage extends StatefulWidget {
const ContactPage({super.key});
@override
_ContactPageState createState() => _ContactPageState();
}
class _ContactPageState extends State<ContactPage> {
@override
Widget build(BuildContext context) {
// Optionally sort contacts alphabetically
final sortedContacts = [...contacts]..sort((a, b) => a.name.compareTo(b.name));
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Contacts'),
),
body: ListView.builder(
itemCount: sortedContacts.length,
itemBuilder: (context, index) {
return DisplayContact(contact: sortedContacts[index]);
},
),
);
}
}
List<Contact> contacts = [
Contact('Alice', '1234567890'),
Contact('Bob', '0987654321', isFavorite: true, isLocked: true, publicKey: 'AB...YZ'),
Contact('Charlie', '5555555555', isLocked: true),
];

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
Color darken(Color color, [double amount = .1]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
return hslDark.toColor();
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class LoadingIndicatorWidget extends StatelessWidget {
const LoadingIndicatorWidget({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: CircularProgressIndicator());
}
}

View File

@ -0,0 +1,12 @@
import 'dart:math';
import 'package:flutter/material.dart';
Color generateColorFromName(String name) {
final random = Random(name.hashCode);
return Color.fromARGB(
255,
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
);
}

View File

@ -35,8 +35,14 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
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
permission_handler: ^10.2.0
pretty_qr_code: ^3.3.0
pointycastle: ^3.4.0
file_picker: ^5.2.5
asn1lib: ^1.0.0
dev_dependencies:
flutter_test:
@ -47,7 +53,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec