335 lines
12 KiB
Dart
335 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import '../../services/contact_service.dart';
|
|
import '../../services/obfuscate_service.dart';
|
|
import '../../services/call_service.dart';
|
|
|
|
class CompositionPage extends StatefulWidget {
|
|
const CompositionPage({super.key});
|
|
|
|
@override
|
|
_CompositionPageState createState() => _CompositionPageState();
|
|
}
|
|
|
|
class _CompositionPageState extends State<CompositionPage> {
|
|
String dialedNumber = "";
|
|
List<Contact> _allContacts = [];
|
|
List<Contact> _filteredContacts = [];
|
|
final ContactService _contactService = ContactService();
|
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
|
final CallService _callService = CallService();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_fetchContacts();
|
|
}
|
|
|
|
Future<void> _fetchContacts() async {
|
|
_allContacts = await _contactService.fetchContacts();
|
|
_filteredContacts = _allContacts;
|
|
setState(() {});
|
|
}
|
|
|
|
void _filterContacts() {
|
|
setState(() {
|
|
_filteredContacts = _allContacts.where((contact) {
|
|
bool phoneMatch = contact.phones.any((phone) {
|
|
final rawPhoneNumber = phone.number;
|
|
final strippedPhoneNumber = rawPhoneNumber.replaceAll(RegExp(r'\D'), '');
|
|
final strippedDialedNumber = dialedNumber.replaceAll(RegExp(r'\D'), '');
|
|
return rawPhoneNumber.contains(dialedNumber) ||
|
|
strippedPhoneNumber.contains(strippedDialedNumber);
|
|
});
|
|
final nameMatch = contact.displayName
|
|
.toLowerCase()
|
|
.contains(dialedNumber.toLowerCase());
|
|
return phoneMatch || nameMatch;
|
|
}).toList();
|
|
});
|
|
}
|
|
|
|
void _onNumberPress(String number) {
|
|
setState(() {
|
|
dialedNumber += number;
|
|
_filterContacts();
|
|
});
|
|
}
|
|
|
|
void _onPlusPress() {
|
|
setState(() {
|
|
dialedNumber += '+';
|
|
_filterContacts();
|
|
});
|
|
}
|
|
|
|
void _onDeletePress() {
|
|
setState(() {
|
|
if (dialedNumber.isNotEmpty) {
|
|
dialedNumber = dialedNumber.substring(0, dialedNumber.length - 1);
|
|
_filterContacts();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _onClearPress() {
|
|
setState(() {
|
|
dialedNumber = "";
|
|
_filteredContacts = _allContacts;
|
|
});
|
|
}
|
|
|
|
void _makeCall(String phoneNumber) async {
|
|
try {
|
|
await _callService.makeGsmCall(context, phoneNumber: phoneNumber);
|
|
setState(() {
|
|
dialedNumber = phoneNumber;
|
|
});
|
|
} catch (e) {
|
|
debugPrint("Error making call: $e");
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Failed to make call: $e')),
|
|
);
|
|
}
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|
|
|
|
void _addContact() async {
|
|
if (await FlutterContacts.requestPermission()) {
|
|
final newContact = Contact()
|
|
..phones = [Phone(dialedNumber.isNotEmpty ? dialedNumber : '')];
|
|
final updatedContact = await FlutterContacts.openExternalInsert(newContact);
|
|
if (updatedContact != null) {
|
|
_fetchContacts();
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Contact added successfully!')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.black,
|
|
body: Stack(
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Expanded(
|
|
flex: 2,
|
|
child: Container(
|
|
padding: const EdgeInsets.only(
|
|
top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
|
|
color: Colors.black,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: ListView(
|
|
children: [
|
|
..._filteredContacts.map((contact) {
|
|
final phoneNumber = contact.phones.isNotEmpty
|
|
? contact.phones.first.number
|
|
: 'No phone number';
|
|
return ListTile(
|
|
title: Text(
|
|
_obfuscateService.obfuscateData(contact.displayName),
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
subtitle: Text(
|
|
_obfuscateService.obfuscateData(phoneNumber),
|
|
style: const TextStyle(color: Colors.grey),
|
|
),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
IconButton(
|
|
icon: Icon(Icons.phone, color: Colors.green[300], size: 20),
|
|
onPressed: () => _makeCall(phoneNumber),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.message, color: Colors.blue[300], size: 20),
|
|
onPressed: () => _launchSms(phoneNumber),
|
|
),
|
|
],
|
|
),
|
|
onTap: () {},
|
|
);
|
|
}).toList(),
|
|
ListTile(
|
|
title: const Text(
|
|
'Add a contact',
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
trailing: Icon(Icons.add, color: Colors.grey[600]),
|
|
onTap: _addContact,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 2,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Align(
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
dialedNumber,
|
|
style: const TextStyle(fontSize: 24, color: Colors.white),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
GestureDetector(
|
|
onTap: _onDeletePress,
|
|
onLongPress: _onClearPress,
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: Icon(Icons.backspace, color: Colors.white),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
_buildDialButton('1', Colors.white),
|
|
_buildDialButton('2', Colors.white),
|
|
_buildDialButton('3', Colors.white),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
_buildDialButton('4', Colors.white),
|
|
_buildDialButton('5', Colors.white),
|
|
_buildDialButton('6', Colors.white),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
_buildDialButton('7', Colors.white),
|
|
_buildDialButton('8', Colors.white),
|
|
_buildDialButton('9', Colors.white),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
_buildDialButton('*', const Color.fromRGBO(117, 117, 117, 1)),
|
|
_buildDialButtonWithPlus('0'),
|
|
_buildDialButton('#', const Color.fromRGBO(117, 117, 117, 1)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Positioned(
|
|
bottom: 20.0,
|
|
left: 0,
|
|
right: 0,
|
|
child: Center(
|
|
child: ElevatedButton(
|
|
onPressed: dialedNumber.isNotEmpty ? () => _makeCall(dialedNumber) : null,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green[700],
|
|
shape: const CircleBorder(),
|
|
padding: const EdgeInsets.all(20),
|
|
),
|
|
child: const Icon(Icons.phone, color: Colors.white, size: 30),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 40.0,
|
|
left: 16.0,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDialButton(String number, Color textColor) {
|
|
return ElevatedButton(
|
|
onPressed: () => _onNumberPress(number),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.black,
|
|
shape: const CircleBorder(),
|
|
padding: const EdgeInsets.all(16),
|
|
),
|
|
child: Text(
|
|
number,
|
|
style: TextStyle(fontSize: 24, color: textColor),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDialButtonWithPlus(String number) {
|
|
return Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
GestureDetector(
|
|
onLongPress: _onPlusPress,
|
|
child: ElevatedButton(
|
|
onPressed: () => _onNumberPress(number),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.black,
|
|
shape: const CircleBorder(),
|
|
padding: const EdgeInsets.all(16),
|
|
),
|
|
child: Text(
|
|
number,
|
|
style: const TextStyle(fontSize: 24, color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
bottom: 8,
|
|
child: Text(
|
|
'+',
|
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |