commit
179f2015bc
@ -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.READ_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<!-- 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
|
||||||
|
@ -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.READ_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||||
<application
|
<application
|
||||||
android:label="com.example.dialer"
|
android:label="com.example.dialer"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
@ -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.READ_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import '../../widgets/contact_service.dart';
|
||||||
|
import '../contacts/widgets/add_contact_button.dart';
|
||||||
|
|
||||||
class CompositionPage extends StatefulWidget {
|
class CompositionPage extends StatefulWidget {
|
||||||
const CompositionPage({super.key});
|
const CompositionPage({super.key});
|
||||||
@ -12,6 +15,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
String dialedNumber = "";
|
String dialedNumber = "";
|
||||||
List<Contact> _allContacts = [];
|
List<Contact> _allContacts = [];
|
||||||
List<Contact> _filteredContacts = [];
|
List<Contact> _filteredContacts = [];
|
||||||
|
final ContactService _contactService = ContactService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -20,12 +24,10 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchContacts() async {
|
Future<void> _fetchContacts() async {
|
||||||
if (await FlutterContacts.requestPermission()) {
|
_allContacts = await _contactService.fetchContacts();
|
||||||
_allContacts = await FlutterContacts.getContacts(withProperties: true);
|
|
||||||
_filteredContacts = _allContacts;
|
_filteredContacts = _allContacts;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _filterContacts() {
|
void _filterContacts() {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -63,9 +65,24 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder function for adding contact
|
// Function to call a contact's number
|
||||||
void addContact(String number) {
|
void _launchPhoneDialer(String phoneNumber) async {
|
||||||
// This function is empty for now
|
final uri = Uri(scheme: 'tel', path: phoneNumber);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
debugPrint('Could not launch $phoneNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send an SMS to a contact's number
|
||||||
|
void _launchSms(String phoneNumber) async {
|
||||||
|
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
debugPrint('Could not send SMS to $phoneNumber');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -79,9 +96,9 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
// Top half: Display contacts matching dialed number
|
// Top half: Display contacts matching dialed number
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child:
|
child: Container(
|
||||||
Container(
|
padding: const EdgeInsets.only(
|
||||||
padding: const EdgeInsets.only(top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
|
top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -90,32 +107,39 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
children: _filteredContacts.isNotEmpty
|
children: _filteredContacts.isNotEmpty
|
||||||
? _filteredContacts.map((contact) {
|
? _filteredContacts.map((contact) {
|
||||||
|
final phoneNumber = contact.phones.isNotEmpty
|
||||||
|
? contact.phones.first.number
|
||||||
|
: 'No phone number';
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
contact.displayName,
|
contact.displayName,
|
||||||
style: const TextStyle(color: Colors.white),
|
style:
|
||||||
|
const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
phoneNumber,
|
||||||
|
style:
|
||||||
|
const TextStyle(color: Colors.grey),
|
||||||
),
|
),
|
||||||
subtitle: contact.phones.isNotEmpty
|
|
||||||
? Text(
|
|
||||||
contact.phones.first.number,
|
|
||||||
style: const TextStyle(color: Colors.grey),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Call button
|
// Call button
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.phone, color: Colors.green[300], size: 20),
|
icon: Icon(Icons.phone,
|
||||||
|
color: Colors.green[300],
|
||||||
|
size: 20),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print('Calling ${contact.displayName}');
|
_launchPhoneDialer(phoneNumber);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// Text button
|
// Message button
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.message, color: Colors.blue[300], size: 20),
|
icon: Icon(Icons.message,
|
||||||
|
color: Colors.blue[300],
|
||||||
|
size: 20),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print('Texting ${contact.displayName}');
|
_launchSms(phoneNumber);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -125,7 +149,12 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}).toList()
|
}).toList()
|
||||||
: [Center(child: Text('No contacts found', style: TextStyle(color: Colors.white)))],
|
: [
|
||||||
|
Center(
|
||||||
|
child: Text('No contacts found',
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.white)))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -150,14 +179,16 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
dialedNumber,
|
dialedNumber,
|
||||||
style: const TextStyle(fontSize: 24, color: Colors.white),
|
style: const TextStyle(
|
||||||
|
fontSize: 24, color: Colors.white),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: _onClearPress,
|
onPressed: _onClearPress,
|
||||||
icon: const Icon(Icons.backspace, color: Colors.white),
|
icon: const Icon(Icons.backspace,
|
||||||
|
color: Colors.white),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -170,7 +201,8 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('1'),
|
_buildDialButton('1'),
|
||||||
_buildDialButton('2'),
|
_buildDialButton('2'),
|
||||||
@ -178,7 +210,8 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('4'),
|
_buildDialButton('4'),
|
||||||
_buildDialButton('5'),
|
_buildDialButton('5'),
|
||||||
@ -186,7 +219,8 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('7'),
|
_buildDialButton('7'),
|
||||||
_buildDialButton('8'),
|
_buildDialButton('8'),
|
||||||
@ -194,7 +228,8 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('*'),
|
_buildDialButton('*'),
|
||||||
_buildDialButton('0'),
|
_buildDialButton('0'),
|
||||||
@ -209,20 +244,19 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Add Contact Button with empty function call
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 20.0),
|
|
||||||
child: FloatingActionButton(
|
|
||||||
backgroundColor: Colors.blue,
|
|
||||||
onPressed: () {
|
|
||||||
addContact(dialedNumber);
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.person_add, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Add Contact Button
|
||||||
|
Positioned(
|
||||||
|
bottom: 20.0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Center(
|
||||||
|
child: AddContactButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Top Row with Back Arrow
|
// Top Row with Back Arrow
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 40.0,
|
top: 40.0,
|
||||||
|
@ -17,8 +17,10 @@ class _ContactPageState extends State<ContactPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: contactState.loading
|
body: contactState.loading
|
||||||
? const LoadingIndicatorWidget()
|
? const LoadingIndicatorWidget()
|
||||||
// : ContactListWidget(contacts: contactState.contacts),
|
: AlphabetScrollPage(
|
||||||
: AlphabetScrollPage(contacts: contactState.contacts, scrollOffset: contactState.scrollOffset),
|
scrollOffset: contactState.scrollOffset,
|
||||||
|
contacts: contactState.contacts, // Use all contacts here
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
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, withAccounts: true, withGroups: true);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addNewContact(Contact contact) async {
|
|
||||||
await FlutterContacts.insertContact(contact);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +1,33 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'contact_service.dart';
|
import '../../widgets/contact_service.dart';
|
||||||
|
|
||||||
class ContactState extends StatefulWidget {
|
class ContactState extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const ContactState({super.key, required this.child});
|
const ContactState({super.key, required this.child});
|
||||||
|
|
||||||
static _ContactStateState of(BuildContext context) {
|
static _ContactStateState of(BuildContext context) {
|
||||||
return context.dependOnInheritedWidgetOfExactType<_InheritedContactState>()!.data;
|
return context
|
||||||
|
.dependOnInheritedWidgetOfExactType<_InheritedContactState>()!
|
||||||
|
.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ContactStateState createState() => _ContactStateState();
|
_ContactStateState createState() => _ContactStateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ContactStateState extends State<ContactState> {
|
class _ContactStateState extends State<ContactState> {
|
||||||
final ContactService _contactService = ContactService();
|
final ContactService _contactService = ContactService();
|
||||||
List<Contact> _contacts = [];
|
List<Contact> _allContacts = [];
|
||||||
|
List<Contact> _favoriteContacts = [];
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
double _scrollOffset = 0.0;
|
double _scrollOffset = 0.0;
|
||||||
Contact? _selfContact = Contact();
|
Contact? _selfContact = Contact();
|
||||||
|
|
||||||
List<Contact> get contacts => _contacts;
|
// Getters for all contacts and favorites
|
||||||
|
List<Contact> get contacts => _allContacts;
|
||||||
|
List<Contact> get favoriteContacts => _favoriteContacts;
|
||||||
bool get loading => _loading;
|
bool get loading => _loading;
|
||||||
double get scrollOffset => _scrollOffset;
|
double get scrollOffset => _scrollOffset;
|
||||||
Contact? get selfContact => _selfContact;
|
Contact? get selfContact => _selfContact;
|
||||||
@ -30,31 +35,46 @@
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchContacts();
|
fetchContacts(); // Fetch all contacts by default
|
||||||
|
|
||||||
// Add listener for contact changes
|
|
||||||
FlutterContacts.addListener(_onContactChange);
|
FlutterContacts.addListener(_onContactChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onContactChange() => _fetchContacts();
|
void _onContactChange() => fetchContacts();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Remove listener
|
|
||||||
FlutterContacts.removeListener(_onContactChange);
|
FlutterContacts.removeListener(_onContactChange);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchContacts() async {
|
// Fetch all contacts
|
||||||
|
Future<void> fetchContacts() async {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
try {
|
||||||
List<Contact> contacts = await _contactService.fetchContacts();
|
List<Contact> contacts = await _contactService.fetchContacts();
|
||||||
|
_processContacts(contacts);
|
||||||
|
} finally {
|
||||||
|
setState(() => _loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debugPrint("Fetched ${contacts.length} contacts");
|
// Fetch only favorite contacts
|
||||||
|
Future<void> fetchFavoriteContacts() async {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
try {
|
||||||
|
List<Contact> contacts = await _contactService.fetchFavoriteContacts();
|
||||||
|
setState(() => _favoriteContacts = contacts);
|
||||||
|
} finally {
|
||||||
|
setState(() => _loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find selfContact before filtering
|
void _processContacts(List<Contact> contacts) {
|
||||||
_selfContact = contacts.firstWhere(
|
_selfContact = contacts.firstWhere(
|
||||||
(contact) => contact.displayName.toLowerCase() == "user",
|
(contact) => contact.displayName.toLowerCase() == "user",
|
||||||
orElse: () => Contact(),
|
orElse: () => Contact(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_selfContact!.phones.isEmpty) {
|
if (_selfContact!.phones.isEmpty) {
|
||||||
debugPrint("Self contact has no phone numbers");
|
debugPrint("Self contact has no phone numbers");
|
||||||
_selfContact = null;
|
_selfContact = null;
|
||||||
@ -62,16 +82,18 @@
|
|||||||
|
|
||||||
contacts = contacts.where((contact) => contact.phones.isNotEmpty).toList();
|
contacts = contacts.where((contact) => contact.phones.isNotEmpty).toList();
|
||||||
contacts.sort((a, b) => a.displayName.compareTo(b.displayName));
|
contacts.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_contacts = contacts;
|
_allContacts = contacts;
|
||||||
_loading = false;
|
_favoriteContacts =
|
||||||
|
contacts.where((contact) => contact.isStarred).toList();
|
||||||
_selfContact = _selfContact;
|
_selfContact = _selfContact;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addNewContact(Contact contact) async {
|
Future<void> addNewContact(Contact contact) async {
|
||||||
await _contactService.addNewContact(contact);
|
await _contactService.addNewContact(contact);
|
||||||
await _fetchContacts();
|
await fetchContacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setScrollOffset(double offset) {
|
void setScrollOffset(double offset) {
|
||||||
@ -87,13 +109,14 @@
|
|||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InheritedContactState extends InheritedWidget {
|
|
||||||
|
class _InheritedContactState extends InheritedWidget {
|
||||||
final _ContactStateState data;
|
final _ContactStateState data;
|
||||||
|
|
||||||
const _InheritedContactState({required this.data, required super.child});
|
const _InheritedContactState({required this.data, required super.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(_InheritedContactState oldWidget) => true;
|
bool updateShouldNotify(_InheritedContactState oldWidget) => true;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import 'package:dialer/widgets/username_color_generator.dart';
|
import 'package:dialer/widgets/username_color_generator.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import '../../../widgets/color_darkener.dart';
|
|
||||||
import '../contact_state.dart';
|
import '../contact_state.dart';
|
||||||
|
import '../../../widgets/color_darkener.dart';
|
||||||
import 'add_contact_button.dart';
|
import 'add_contact_button.dart';
|
||||||
|
import 'contact_modal.dart';
|
||||||
import 'share_own_qr.dart';
|
import 'share_own_qr.dart';
|
||||||
|
|
||||||
class AlphabetScrollPage extends StatefulWidget {
|
class AlphabetScrollPage extends StatefulWidget {
|
||||||
final List<Contact> contacts;
|
|
||||||
final double scrollOffset;
|
final double scrollOffset;
|
||||||
|
final List<Contact> contacts;
|
||||||
|
|
||||||
const AlphabetScrollPage({super.key, required this.contacts, required this.scrollOffset});
|
const AlphabetScrollPage({
|
||||||
|
super.key,
|
||||||
|
required this.scrollOffset,
|
||||||
|
required this.contacts,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AlphabetScrollPageState createState() => _AlphabetScrollPageState();
|
_AlphabetScrollPageState createState() => _AlphabetScrollPageState();
|
||||||
@ -31,11 +36,53 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
|||||||
contactState.setScrollOffset(_scrollController.offset);
|
contactState.setScrollOffset(_scrollController.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshContacts() async {
|
||||||
|
final contactState = ContactState.of(context);
|
||||||
|
try {
|
||||||
|
await contactState.fetchContacts();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error refreshing contacts: $e');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Failed to refresh contacts')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleFavorite(Contact contact) async {
|
||||||
|
try {
|
||||||
|
if (await FlutterContacts.requestPermission()) {
|
||||||
|
Contact? fullContact = await FlutterContacts.getContact(contact.id,
|
||||||
|
withProperties: true,
|
||||||
|
withAccounts: true,
|
||||||
|
withPhoto: true,
|
||||||
|
withThumbnail: true);
|
||||||
|
|
||||||
|
if (fullContact != null) {
|
||||||
|
fullContact.isStarred = !fullContact.isStarred;
|
||||||
|
await FlutterContacts.updateContact(fullContact);
|
||||||
|
}
|
||||||
|
await _refreshContacts();
|
||||||
|
} else {
|
||||||
|
print("Could not fetch contact details");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error updating favorite status: $e");
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Failed to update contact favorite status')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final contacts = widget.contacts;
|
||||||
|
final selfContact = ContactState.of(context).selfContact;
|
||||||
|
|
||||||
Map<String, List<Contact>> alphabetizedContacts = {};
|
Map<String, List<Contact>> alphabetizedContacts = {};
|
||||||
for (var contact in widget.contacts) {
|
for (var contact in contacts) {
|
||||||
String firstLetter = contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '#';
|
String firstLetter = contact.displayName.isNotEmpty
|
||||||
|
? contact.displayName[0].toUpperCase()
|
||||||
|
: '#';
|
||||||
if (!alphabetizedContacts.containsKey(firstLetter)) {
|
if (!alphabetizedContacts.containsKey(firstLetter)) {
|
||||||
alphabetizedContacts[firstLetter] = [];
|
alphabetizedContacts[firstLetter] = [];
|
||||||
}
|
}
|
||||||
@ -47,7 +94,8 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [ // Top buttons row
|
children: [
|
||||||
|
// Top buttons row
|
||||||
Container(
|
Container(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||||
@ -55,7 +103,7 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
AddContactButton(),
|
AddContactButton(),
|
||||||
QRCodeButton(contacts: widget.contacts, selfContact: ContactState.of(context).selfContact),
|
QRCodeButton(contacts: contacts, selfContact: selfContact),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -66,13 +114,14 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
|||||||
itemCount: alphabetKeys.length,
|
itemCount: alphabetKeys.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
String letter = alphabetKeys[index];
|
String letter = alphabetKeys[index];
|
||||||
List<Contact> contacts = alphabetizedContacts[letter]!;
|
List<Contact> contactsForLetter = alphabetizedContacts[letter]!;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Alphabet Letter Header
|
// Alphabet Letter Header
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8.0, horizontal: 16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
letter,
|
letter,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -83,25 +132,74 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Contact Entries
|
// Contact Entries
|
||||||
...contacts.map((contact) {
|
...contactsForLetter.map((contact) {
|
||||||
String phoneNumber = contact.phones.isNotEmpty ? contact.phones.first.number : 'No phone number';
|
String phoneNumber = contact.phones.isNotEmpty
|
||||||
Color avatarColor = generateColorFromName(contact.displayName);
|
? contact.phones.first.number
|
||||||
|
: 'No phone number';
|
||||||
|
Color avatarColor =
|
||||||
|
generateColorFromName(contact.displayName);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
|
leading: (contact.thumbnail != null &&
|
||||||
|
contact.thumbnail!.isNotEmpty)
|
||||||
? CircleAvatar(
|
? CircleAvatar(
|
||||||
backgroundImage: MemoryImage(contact.thumbnail!),
|
backgroundImage:
|
||||||
|
MemoryImage(contact.thumbnail!),
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
backgroundColor: avatarColor,
|
backgroundColor: avatarColor,
|
||||||
child: Text(
|
child: Text(
|
||||||
contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '?',
|
contact.displayName.isNotEmpty
|
||||||
style: TextStyle(color: darken(avatarColor, 0.4)),
|
? contact.displayName[0].toUpperCase()
|
||||||
|
: '?',
|
||||||
|
style: TextStyle(
|
||||||
|
color: darken(avatarColor, 0.4)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(contact.displayName, style: TextStyle(color: Colors.white)),
|
title: Text(contact.displayName,
|
||||||
subtitle: Text(phoneNumber, style: TextStyle(color: Colors.white70)),
|
style: TextStyle(color: Colors.white)),
|
||||||
|
subtitle: Text(phoneNumber,
|
||||||
|
style: TextStyle(color: Colors.white70)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Handle contact tap
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (context) {
|
||||||
|
return ContactModal(
|
||||||
|
contact: contact,
|
||||||
|
onEdit: () async {
|
||||||
|
if (await FlutterContacts.requestPermission()) {
|
||||||
|
final updatedContact =
|
||||||
|
await FlutterContacts.openExternalEdit(
|
||||||
|
contact.id);
|
||||||
|
if (updatedContact != null) {
|
||||||
|
await _refreshContacts();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'${contact.displayName} updated successfully!'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content:
|
||||||
|
Text('Edit canceled or failed.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onToggleFavorite: () {
|
||||||
|
_toggleFavorite(contact);
|
||||||
|
},
|
||||||
|
isFavorite: contact.isStarred,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
227
dialer/lib/features/contacts/widgets/contact_modal.dart
Normal file
227
dialer/lib/features/contacts/widgets/contact_modal.dart
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:dialer/widgets/username_color_generator.dart';
|
||||||
|
|
||||||
|
class ContactModal extends StatelessWidget {
|
||||||
|
final Contact contact;
|
||||||
|
final Function onEdit;
|
||||||
|
final Function onToggleFavorite;
|
||||||
|
final bool isFavorite;
|
||||||
|
|
||||||
|
const ContactModal({
|
||||||
|
super.key,
|
||||||
|
required this.contact,
|
||||||
|
required this.onEdit,
|
||||||
|
required this.onToggleFavorite,
|
||||||
|
required this.isFavorite,
|
||||||
|
});
|
||||||
|
|
||||||
|
void _launchPhoneDialer(String phoneNumber) async {
|
||||||
|
final uri = Uri(scheme: 'tel', path: phoneNumber);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
debugPrint('Could not launch $phoneNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _launchSms(String phoneNumber) async {
|
||||||
|
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
debugPrint('Could not launch SMS to $phoneNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _launchEmail(String email) async {
|
||||||
|
final uri = Uri(scheme: 'mailto', path: email);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
debugPrint('Could not launch email to $email');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
String phoneNumber = contact.phones.isNotEmpty
|
||||||
|
? contact.phones.first.number
|
||||||
|
: 'No phone number';
|
||||||
|
String email =
|
||||||
|
contact.emails.isNotEmpty ? contact.emails.first.address : 'No email';
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
heightFactor: 0.7,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[900],
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Modal Handle
|
||||||
|
// Top Bar with Handle and Three-Dot Menu
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Container(
|
||||||
|
width: 50,
|
||||||
|
height: 5,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10, right: 10),
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
icon: Icon(Icons.more_vert, color: Colors.white),
|
||||||
|
onSelected: (String choice) {
|
||||||
|
print(
|
||||||
|
'Selected: $choice'); // Placeholder for menu actions
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
return <PopupMenuEntry<String>>[
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'show_associated_contacts',
|
||||||
|
child: Text('Show associated contacts'),
|
||||||
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'delete',
|
||||||
|
child: Text('Delete'),
|
||||||
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'share',
|
||||||
|
child: Text('Share (via QR code)'),
|
||||||
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'create_shortcut',
|
||||||
|
child:
|
||||||
|
Text('Create shortcut (to home screen)'),
|
||||||
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'set_ringtone',
|
||||||
|
child: Text('Set ringtone'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Contact Profile
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 50,
|
||||||
|
backgroundImage: (contact.thumbnail != null &&
|
||||||
|
contact.thumbnail!.isNotEmpty)
|
||||||
|
? MemoryImage(contact.thumbnail!)
|
||||||
|
: null,
|
||||||
|
backgroundColor:
|
||||||
|
generateColorFromName(contact.displayName),
|
||||||
|
child: (contact.thumbnail == null ||
|
||||||
|
contact.thumbnail!.isEmpty)
|
||||||
|
? Text(
|
||||||
|
contact.displayName.isNotEmpty
|
||||||
|
? contact.displayName[0].toUpperCase()
|
||||||
|
: '?',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 40, color: Colors.white),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
contact.displayName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Contact Actions
|
||||||
|
Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.phone, color: Colors.green),
|
||||||
|
title: Text(phoneNumber),
|
||||||
|
onTap: () {
|
||||||
|
if (contact.phones.isNotEmpty) {
|
||||||
|
_launchPhoneDialer(phoneNumber);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.message, color: Colors.blue),
|
||||||
|
title: Text(phoneNumber),
|
||||||
|
onTap: () {
|
||||||
|
if (contact.phones.isNotEmpty) {
|
||||||
|
_launchSms(phoneNumber);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.email, color: Colors.orange),
|
||||||
|
title: Text(email),
|
||||||
|
onTap: () {
|
||||||
|
if (contact.emails.isNotEmpty) {
|
||||||
|
_launchEmail(email);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
// Favorite and Edit Buttons
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onToggleFavorite();
|
||||||
|
},
|
||||||
|
icon: Icon(contact.isStarred
|
||||||
|
? Icons.star
|
||||||
|
: Icons.star_border),
|
||||||
|
label: Text(
|
||||||
|
contact.isStarred ? 'Unfavorite' : 'Favorite'),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () => onEdit(),
|
||||||
|
icon: Icon(Icons.edit),
|
||||||
|
label: Text('Edit Contact'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,31 @@
|
|||||||
|
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:flutter/material.dart';
|
||||||
|
import 'package:dialer/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
class FavoritePage extends StatefulWidget {
|
class FavoritesPage extends StatefulWidget {
|
||||||
const FavoritePage({super.key});
|
const FavoritesPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FavoritePageState createState() => _FavoritePageState();
|
_FavoritesPageState createState() => _FavoritesPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FavoritePageState extends State<FavoritePage> {
|
class _FavoritesPageState extends State<FavoritesPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final contactState = ContactState.of(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
body: contactState.loading
|
||||||
body: Center( // Center the text within the body
|
? const LoadingIndicatorWidget()
|
||||||
child: Text(
|
: AlphabetScrollPage(
|
||||||
"Hello",
|
scrollOffset: contactState.scrollOffset,
|
||||||
style: TextStyle(color: Colors.white), // Change text color for visibility
|
contacts:
|
||||||
),
|
contactState.favoriteContacts, // Use only favorites here
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,15 @@ import 'package:dialer/features/history/history_page.dart';
|
|||||||
import 'package:dialer/features/composition/composition.dart';
|
import 'package:dialer/features/composition/composition.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'package:dialer/features/settings/settings.dart';
|
import 'package:dialer/features/settings/settings.dart';
|
||||||
|
import '../../widgets/contact_service.dart';
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage>
|
class _MyHomePageState extends State<MyHomePage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
List<Contact> _allContacts = [];
|
List<Contact> _allContacts = [];
|
||||||
List<Contact> _contactSuggestions = [];
|
List<Contact> _contactSuggestions = [];
|
||||||
|
final ContactService _contactService = ContactService();
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -22,11 +25,9 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _fetchContacts() async {
|
void _fetchContacts() async {
|
||||||
if (await FlutterContacts.requestPermission()) {
|
_allContacts = await _contactService.fetchContacts();
|
||||||
_allContacts = await FlutterContacts.getContacts(withProperties: true);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _onSearchChanged(String query) {
|
void _onSearchChanged(String query) {
|
||||||
print("Search query: $query");
|
print("Search query: $query");
|
||||||
@ -61,7 +62,7 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// Search Bar and 3-dot menu
|
// Persistent Search Bar
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 24.0,
|
top: 24.0,
|
||||||
@ -169,7 +170,7 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
TabBarView(
|
TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: const [
|
children: const [
|
||||||
FavoritePage(),
|
FavoritesPage(),
|
||||||
HistoryPage(),
|
HistoryPage(),
|
||||||
ContactPage(),
|
ContactPage(),
|
||||||
],
|
],
|
||||||
|
@ -34,7 +34,7 @@ class KeyManagementPage extends StatelessWidget {
|
|||||||
MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()),
|
MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'Delete a key pair, warning POPUP':
|
case 'Delete a key pair':
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()),
|
MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()),
|
||||||
@ -52,7 +52,7 @@ class KeyManagementPage extends StatelessWidget {
|
|||||||
'Display public key as QR code',
|
'Display public key as QR code',
|
||||||
'Generate a new key pair',
|
'Generate a new key pair',
|
||||||
'Export private key to password-encrypted file (AES 256)',
|
'Export private key to password-encrypted file (AES 256)',
|
||||||
'Delete a key pair, warning POPUP',
|
'Delete a key pair',
|
||||||
];
|
];
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -16,7 +16,7 @@ class MyApp extends StatelessWidget {
|
|||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.dark
|
brightness: Brightness.dark
|
||||||
),
|
),
|
||||||
home: const MyHomePage(),
|
home: SafeArea(child: MyHomePage()),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
31
dialer/lib/widgets/contact_service.dart
Normal file
31
dialer/lib/widgets/contact_service.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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,
|
||||||
|
withAccounts: true,
|
||||||
|
withGroups: true,
|
||||||
|
withPhoto: true);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Contact>> fetchFavoriteContacts() async {
|
||||||
|
// Fetch all contacts
|
||||||
|
List<Contact> contacts = await fetchContacts();
|
||||||
|
|
||||||
|
// Filter contacts to only include those with isStarred: true
|
||||||
|
List<Contact> favoriteContacts =
|
||||||
|
contacts.where((contact) => contact.isStarred).toList();
|
||||||
|
|
||||||
|
return favoriteContacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addNewContact(Contact contact) async {
|
||||||
|
await FlutterContacts.insertContact(contact);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user