add classic contact page scroll

This commit is contained in:
stcb 2024-10-17 16:16:39 +03:00
parent 69ce11ab1a
commit 2fee7495ee
9 changed files with 111 additions and 113 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,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

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/classes/contactClass.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; // Updated package
import 'package:dialer/classes/displayContact.dart';
class ContactPage extends StatefulWidget { class ContactPage extends StatefulWidget {
const ContactPage({super.key}); const ContactPage({super.key});
@ -8,35 +7,94 @@ class ContactPage extends StatefulWidget {
@override @override
_ContactPageState createState() => _ContactPageState(); _ContactPageState createState() => _ContactPageState();
} }
class _ContactPageState extends State<ContactPage> { class _ContactPageState extends State<ContactPage> {
List<Contact> _contacts = [];
bool _loading = true;
@override
void initState() {
super.initState();
_fetchContacts();
}
// Request permission and fetch contacts
Future<void> _fetchContacts() async {
if (await FlutterContacts.requestPermission()) {
List<Contact> contacts = await FlutterContacts.getContacts(withProperties: true, withThumbnail: true);
setState(() {
_contacts = contacts;
_loading = false;
});
} else {
setState(() {
_loading = false;
});
}
}
// Add a new contact using flutter_contacts
Future<void> _addNewContact() async {
Contact newContact = Contact(
name: Name(first: 'John', last: 'Doe'),
phones: [Phone('123456789')],
);
await FlutterContacts.insertContact(newContact);
_fetchContacts(); // Refresh the contact list
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar( appBar: AppBar(
title: const Text('Contacts'), title: const Text('Contacts'),
), ),
body: ListView.builder( body: _loading
itemCount: contacts.length, ? const Center(child: CircularProgressIndicator())
itemBuilder: (context, index) { : _contacts.isEmpty
return DisplayContact(contact: contacts[index]); ? const Center(child: Text('No contacts found'))
}, : ListView.builder(
itemCount: _contacts.length,
itemBuilder: (context, index) {
return ContactTile(contact: _contacts[index]);
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: _addNewContact,
), ),
); );
} }
} }
List<Contact> contacts = [ // Contact Tile to display each contact
Contact('Axel NAVARRO BOUZEHRIR (arabe)', '0618859419'), class ContactTile extends StatelessWidget {
Contact('Fabrice Iguet', '0618958419'), final Contact contact;
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'),
];
const ContactTile({super.key, required this.contact});
@override
Widget build(BuildContext context) {
return ListTile(
leading: (contact.thumbnail != null)
? CircleAvatar(backgroundImage: MemoryImage(contact.thumbnail!))
: CircleAvatar(child: Text(_getInitials(contact.displayName))),
title: Text(contact.displayName ?? 'No Name'),
subtitle: contact.phones.isNotEmpty
? Text(contact.phones.first.number)
: const Text('No phone number'),
trailing: IconButton(
icon: const Icon(Icons.call),
onPressed: () {
// Handle call action
},
),
);
}
String _getInitials(String? name) {
if (name == null || name.isEmpty) return "";
List<String> names = name.split(' ');
return names.map((n) => n[0]).take(2).join().toUpperCase();
}
}

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

@ -1,20 +1,9 @@
import 'package:dialer/classes/contactClass.dart'; import 'package:dialer/classes/contactClass.dart';
import 'package:dialer/pages/contact.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/classes/displayContact.dart';
List<History> histories = [ List<History> histories = [
History(contacts[0], DateTime.now().subtract(const Duration(hours: 2))), History("Hello"),
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 { class HistoryPage extends StatefulWidget {
@ -35,7 +24,7 @@ class _HistoryPageState extends State<HistoryPage> {
body: ListView.builder( body: ListView.builder(
itemCount: histories.length, itemCount: histories.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return DisplayContact(contact: histories[index].contact, history: histories[index]); //
}, },
), ),
); );
@ -43,8 +32,7 @@ class _HistoryPageState extends State<HistoryPage> {
} }
class History { class History {
final Contact contact; final String text;
final DateTime date;
History(this.contact, this.date); History(this.text);
} }

View File

@ -1,7 +1,7 @@
import 'package:dialer/pages/contact.dart';
import 'package:dialer/pages/favorites.dart';
import 'package:dialer/pages/history.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/pages/contact.dart'; // Import ContactPage
import 'package:dialer/pages/favorites.dart'; // Import FavoritePage
import 'package:dialer/pages/history.dart'; // Import HistoryPage
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
@ -9,7 +9,8 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_tabController = TabController(length: 2, vsync: this, initialIndex: 1); // Update TabController to handle 3 tabs (Favorites, History, Contacts)
_tabController = TabController(length: 3, vsync: this, initialIndex: 1);
_tabController.addListener(_handleTabIndex); _tabController.addListener(_handleTabIndex);
} }
@ -30,10 +31,11 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: TabBarView( body: TabBarView(
controller: _tabController, controller: _tabController,
children: const [ // Add the new ContactPage in the TabBarView
FavoritePage(), // Page des favoris children: [
HistoryPage(), // Page de l'historique FavoritePage(), // Favorites page
ContactPage(), // Page des contacts HistoryPage(), // History page
ContactPage(), // New Contacts page
], ],
), ),
bottomNavigationBar: Container( bottomNavigationBar: Container(
@ -41,22 +43,21 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
child: TabBar( child: TabBar(
controller: _tabController, controller: _tabController,
tabs: [ tabs: [
Tab(icon: Icon(_tabController.index == 0 ? Icons.star : Icons.star_border)), Tab(icon: Icon(_tabController.index == 0 ? Icons.star : Icons.star_border)), // Favorite tab
Tab(icon: Icon(_tabController.index == 1 ? Icons.access_time_filled : Icons.access_time_outlined)), Tab(icon: Icon(_tabController.index == 1 ? Icons.access_time_filled : Icons.access_time_outlined)), // History tab
Tab(icon: Icon(_tabController.index == 2 ? Icons.contacts : Icons.contacts_outlined)), Tab(icon: Icon(_tabController.index == 2 ? Icons.contacts : Icons.contacts_outlined)), // Contact tab
], ],
labelColor: Colors.white, labelColor: Colors.white,
unselectedLabelColor: Colors.grey, unselectedLabelColor: Colors.grey,
indicatorSize: TabBarIndicatorSize.label, indicatorSize: TabBarIndicatorSize.label,
indicatorPadding: const EdgeInsets.only(bottom: -0), // Ajustez le padding pour élever la ligne blanche au-dessus des icônes si nécessaire indicatorPadding: const EdgeInsets.only(bottom: -0), // Adjust padding if needed
indicatorColor: Colors.white, // Couleur de la barre horizontale blanche indicatorColor: Colors.white, // White horizontal indicator line
), ),
), ),
); );
} }
} }
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); const MyHomePage({super.key});

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