Merge with ContactPage

This commit is contained in:
stcb 2024-10-26 15:47:45 +03:00
commit bf62fcc020
24 changed files with 355 additions and 298 deletions

View File

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <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 INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. 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"> <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 <application
android:label="dialer" android:label="com.example.dialer"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View File

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

View File

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

View File

@ -1,9 +0,0 @@
// Create contact lists
class Contact {
final String name;
final String phoneNumber;
final bool isFavorite;
final bool isLocked;
Contact(this.name, this.phoneNumber, {this.isFavorite = false, this.isLocked = false});
}

View File

@ -1,29 +0,0 @@
import 'package:dialer/classes/contactClass.dart';
import 'package:flutter/material.dart';
class DisplayAvatar extends StatelessWidget {
final Contact contact;
const DisplayAvatar({super.key, required this.contact});
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
CircleAvatar(
child: Text(contact.name[0]),
),
if (contact.isLocked)
const Positioned(
right: 0,
bottom: 0,
child: Icon(
Icons.lock,
color: Colors.white,
size: 20.0,
),
),
],
);
}
}

View File

@ -1,47 +0,0 @@
import 'package:dialer/pages/callingPage.dart';
import 'package:dialer/classes/contactClass.dart';
import 'package:dialer/classes/displayAvatar.dart';
import 'package:dialer/pages/history.dart';
import 'package:flutter/material.dart';
class DisplayContact extends StatelessWidget {
String getTimeElapsed(DateTime date) {
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inDays > 0) {
return '${difference.inDays} days ago';
} else if (difference.inHours > 0) {
return '${difference.inHours} hours ago';
} else if (difference.inMinutes > 0) {
return '${difference.inMinutes} minutes ago';
} else {
return 'Just now';
}
}
final Contact contact;
final History? history;
const DisplayContact({super.key, required this.contact, this.history});
void _openVisualPage(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CallingPage(contact: contact)));
}
@override
Widget build(BuildContext context) {
return ListTile(
leading: DisplayAvatar(contact: contact),
title: Text(contact.name),
subtitle: history != null ? Text(getTimeElapsed(history!.date)) : null,
trailing: InkWell(
onTap: () => _openVisualPage(context),
child: const Icon(Icons.call),
),
onTap: () {
// Ajoutez ici le code pour appeler le contact
},
);
}
}

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

@ -1,5 +1,3 @@
import 'package:dialer/classes/displayContact.dart';
import 'package:dialer/pages/contact.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class FavoritePage extends StatefulWidget { class FavoritePage extends StatefulWidget {
@ -17,16 +15,7 @@ class _FavoritePageState extends State<FavoritePage> {
appBar: AppBar( appBar: AppBar(
title: const Text('Favorites'), title: const Text('Favorites'),
), ),
body: ListView.builder( body: Text("Hello")
itemCount: contacts.length,
itemBuilder: (context, index) {
if (contacts[index].isFavorite) {
return DisplayContact(contact: contacts[index]);
} else {
return const SizedBox.shrink();
}
},
),
); );
} }
} }

View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
List<History> histories = [
History("Hello"),
];
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
_HistoryPageState createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: ListView.builder(
itemCount: histories.length,
itemBuilder: (context, index) {
return null;
//
},
),
);
}
}
class History {
final String text;
History(this.text);
}

View File

@ -1,8 +1,8 @@
import 'package:dialer/pages/contact.dart'; import 'package:dialer/features/composition/composition.dart';
import 'package:dialer/pages/favorites.dart';
import 'package:dialer/pages/history.dart';
import 'package:dialer/pages/composition.dart';
import 'package:flutter/material.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
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
@ -12,7 +12,7 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
super.initState(); super.initState();
_tabController = TabController(length: 4, vsync: this, initialIndex: 1); _tabController = TabController(length: 4, vsync: this, initialIndex: 1);
_tabController.addListener(_handleTabIndex); _tabController.addListener(_handleTabIndex);
} }
@override @override
@ -47,7 +47,7 @@ Widget build(BuildContext context) {
bottom: 20, bottom: 20,
child: FloatingActionButton( child: FloatingActionButton(
onPressed: () { onPressed: () {
_tabController.animateTo(3); _tabController.animateTo(3);
}, },
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
@ -89,7 +89,6 @@ Widget build(BuildContext context) {
} }
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); const MyHomePage({super.key});

View File

@ -1,7 +1,8 @@
// This is DEV // This is DEV
import 'package:dialer/pages/myHomePage.dart'; import 'package:dialer/features/home/home_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_state.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -12,11 +13,13 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return ContactState(
theme: ThemeData( child: MaterialApp(
brightness: Brightness.dark theme: ThemeData(
), brightness: Brightness.dark
home: const MyHomePage(), ),
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,42 +0,0 @@
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) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Contacts'),
),
body: ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
return DisplayContact(contact: contacts[index]);
},
),
);
}
}
List<Contact> contacts = [
Contact('Axel NAVARRO BOUZEHRIR (arabe)', '0618859419'),
Contact('Fabrice Iguet', '0618958419'),
Contact('La Banque Axial', '0619358514'),
Contact('Maman', '0618955417', isFavorite: true, isLocked: true),
Contact('Micheline Verdet', '0618527419', isLocked: true),
Contact('Philippe Mogue', '0618955889', isFavorite: true),
Contact('Pizza Enrico Pucci', '0618951439', isLocked: true),
Contact('Quentin Aumas', '0610252019'),
Contact('Yohan HATOT', '0618102552', isFavorite: true, isLocked: true),
Contact('Zizou', '0618514479'),
];

View File

@ -1,50 +0,0 @@
import 'package:dialer/classes/contactClass.dart';
import 'package:dialer/pages/contact.dart';
import 'package:flutter/material.dart';
import 'package:dialer/classes/displayContact.dart';
List<History> histories = [
History(contacts[0], DateTime.now().subtract(const Duration(hours: 2))),
History(contacts[1], DateTime.now().subtract(const Duration(hours: 8))),
History(contacts[2], DateTime.now().subtract(const Duration(days: 3, hours: 4))),
History(contacts[3], DateTime.now().subtract(const Duration(days: 4, hours: 5))),
History(contacts[4], DateTime.now().subtract(const Duration(days: 5, hours: 6))),
History(contacts[5], DateTime.now().subtract(const Duration(days: 6, hours: 7))),
History(contacts[6], DateTime.now().subtract(const Duration(days: 7, hours: 8))),
History(contacts[7], DateTime.now().subtract(const Duration(days: 8, hours: 9))),
History(contacts[8], DateTime.now().subtract(const Duration(days: 9, hours: 10))),
History(contacts[9], DateTime.now().subtract(const Duration(days: 10, hours: 11))),
];
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
_HistoryPageState createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: ListView.builder(
itemCount: histories.length,
itemBuilder: (context, index) {
return DisplayContact(contact: histories[index].contact, history: histories[index]);
},
),
);
}
}
class History {
final Contact contact;
final DateTime date;
History(this.contact, this.date);
}

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

@ -1,5 +1,5 @@
name: dialer name: dialer
description: "A new Flutter project." description: "Icing Dialer"
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
@ -35,6 +35,9 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 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
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -45,7 +48,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # 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 # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec