Merge branch 'dev' into fixSearchBar
# Conflicts: # dialer/lib/features/home/home_page.dart
This commit is contained in:
commit
6de0da949d
@ -4,7 +4,6 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import '../../services/contact_service.dart';
|
import '../../services/contact_service.dart';
|
||||||
import '../../services/obfuscate_service.dart';
|
import '../../services/obfuscate_service.dart';
|
||||||
import '../../services/call_service.dart';
|
import '../../services/call_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});
|
||||||
@ -18,11 +17,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
List<Contact> _allContacts = [];
|
List<Contact> _allContacts = [];
|
||||||
List<Contact> _filteredContacts = [];
|
List<Contact> _filteredContacts = [];
|
||||||
final ContactService _contactService = ContactService();
|
final ContactService _contactService = ContactService();
|
||||||
|
|
||||||
// Instantiate the ObfuscateService
|
|
||||||
final ObfuscateService _obfuscateService = ObfuscateService();
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||||
|
|
||||||
// Instantiate the CallService
|
|
||||||
final CallService _callService = CallService();
|
final CallService _callService = CallService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,8 +35,13 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
void _filterContacts() {
|
void _filterContacts() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_filteredContacts = _allContacts.where((contact) {
|
_filteredContacts = _allContacts.where((contact) {
|
||||||
final phoneMatch = contact.phones.any((phone) =>
|
bool phoneMatch = contact.phones.any((phone) {
|
||||||
phone.number.replaceAll(RegExp(r'\D'), '').contains(dialedNumber));
|
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
|
final nameMatch = contact.displayName
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(dialedNumber.toLowerCase());
|
.contains(dialedNumber.toLowerCase());
|
||||||
@ -57,6 +57,13 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onPlusPress() {
|
||||||
|
setState(() {
|
||||||
|
dialedNumber += '+';
|
||||||
|
_filterContacts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _onDeletePress() {
|
void _onDeletePress() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (dialedNumber.isNotEmpty) {
|
if (dialedNumber.isNotEmpty) {
|
||||||
@ -73,7 +80,6 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to call a contact's number using the CallService
|
|
||||||
void _makeCall(String phoneNumber) async {
|
void _makeCall(String phoneNumber) async {
|
||||||
try {
|
try {
|
||||||
await _callService.makeGsmCall(context, phoneNumber: phoneNumber);
|
await _callService.makeGsmCall(context, phoneNumber: phoneNumber);
|
||||||
@ -82,10 +88,12 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error making call: $e");
|
debugPrint("Error making call: $e");
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Failed to make call: $e')),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to send an SMS to a contact's number
|
|
||||||
void _launchSms(String phoneNumber) async {
|
void _launchSms(String phoneNumber) async {
|
||||||
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
||||||
if (await canLaunchUrl(uri)) {
|
if (await canLaunchUrl(uri)) {
|
||||||
@ -95,6 +103,20 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -103,7 +125,6 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
// Top half: Display contacts matching dialed number
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Container(
|
child: Container(
|
||||||
@ -115,8 +136,8 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: _filteredContacts.isNotEmpty
|
children: [
|
||||||
? _filteredContacts.map((contact) {
|
..._filteredContacts.map((contact) {
|
||||||
final phoneNumber = contact.phones.isNotEmpty
|
final phoneNumber = contact.phones.isNotEmpty
|
||||||
? contact.phones.first.number
|
? contact.phones.first.number
|
||||||
: 'No phone number';
|
: 'No phone number';
|
||||||
@ -132,40 +153,34 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Call button (Now using CallService)
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.phone,
|
icon: Icon(Icons.phone, color: Colors.green[300], size: 20),
|
||||||
color: Colors.green[300],
|
onPressed: () => _makeCall(phoneNumber),
|
||||||
size: 20),
|
|
||||||
onPressed: () {
|
|
||||||
_makeCall(phoneNumber); // Make a call using CallService
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
// Message button
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.message,
|
icon: Icon(Icons.message, color: Colors.blue[300], size: 20),
|
||||||
color: Colors.blue[300],
|
onPressed: () => _launchSms(phoneNumber),
|
||||||
size: 20),
|
|
||||||
onPressed: () {
|
|
||||||
_launchSms(phoneNumber);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {},
|
||||||
// Handle contact selection if needed
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}).toList()
|
}).toList(),
|
||||||
: [],
|
ListTile(
|
||||||
|
title: const Text(
|
||||||
|
'Add a contact',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
trailing: Icon(Icons.add, color: Colors.grey[600]),
|
||||||
|
onTap: _addContact,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Bottom half: Dialpad and Dialed number display with erase button
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Container(
|
child: Container(
|
||||||
@ -173,7 +188,6 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Display dialed number with erase button
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -182,61 +196,57 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
dialedNumber,
|
dialedNumber,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 24, color: Colors.white),
|
||||||
fontSize: 24, color: Colors.white),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
GestureDetector(
|
||||||
onPressed: _onClearPress,
|
onTap: _onDeletePress,
|
||||||
icon: const Icon(Icons.backspace,
|
onLongPress: _onClearPress,
|
||||||
color: Colors.white),
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Icon(Icons.backspace, color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// Dialpad
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('1'),
|
_buildDialButton('1', Colors.white),
|
||||||
_buildDialButton('2'),
|
_buildDialButton('2', Colors.white),
|
||||||
_buildDialButton('3'),
|
_buildDialButton('3', Colors.white),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('4'),
|
_buildDialButton('4', Colors.white),
|
||||||
_buildDialButton('5'),
|
_buildDialButton('5', Colors.white),
|
||||||
_buildDialButton('6'),
|
_buildDialButton('6', Colors.white),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('7'),
|
_buildDialButton('7', Colors.white),
|
||||||
_buildDialButton('8'),
|
_buildDialButton('8', Colors.white),
|
||||||
_buildDialButton('9'),
|
_buildDialButton('9', Colors.white),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
_buildDialButton('*'),
|
_buildDialButton('*', const Color.fromRGBO(117, 117, 117, 1)),
|
||||||
_buildDialButton('0'),
|
_buildDialButtonWithPlus('0'),
|
||||||
_buildDialButton('#'),
|
_buildDialButton('#', const Color.fromRGBO(117, 117, 117, 1)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -249,26 +259,28 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// Add Contact Button
|
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 20.0,
|
bottom: 20.0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: AddContactButton(),
|
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),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Top Row with Back Arrow
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 40.0,
|
top: 40.0,
|
||||||
left: 16.0,
|
left: 16.0,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -276,7 +288,7 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDialButton(String number) {
|
Widget _buildDialButton(String number, Color textColor) {
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
onPressed: () => _onNumberPress(number),
|
onPressed: () => _onNumberPress(number),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -286,11 +298,38 @@ class _CompositionPageState extends State<CompositionPage> {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
number,
|
number,
|
||||||
style: const TextStyle(
|
style: TextStyle(fontSize: 24, color: textColor),
|
||||||
fontSize: 24,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -37,7 +37,7 @@ class HistoryPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HistoryPageState extends State<HistoryPage>
|
class _HistoryPageState extends State<HistoryPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
|
||||||
List<History> histories = [];
|
List<History> histories = [];
|
||||||
bool loading = true;
|
bool loading = true;
|
||||||
int? _expandedIndex;
|
int? _expandedIndex;
|
||||||
@ -47,10 +47,13 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
// Create a MethodChannel instance.
|
// Create a MethodChannel instance.
|
||||||
static const MethodChannel _channel = MethodChannel('com.example.calllog');
|
static const MethodChannel _channel = MethodChannel('com.example.calllog');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true; // Preserve state when switching pages
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
if (loading) {
|
if (loading && histories.isEmpty) {
|
||||||
_buildHistories();
|
_buildHistories();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,9 +152,9 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
List<Contact> contacts = contactState.contacts;
|
List<Contact> contacts = contactState.contacts;
|
||||||
|
|
||||||
List<History> callHistories = [];
|
List<History> callHistories = [];
|
||||||
// Process each log entry.
|
// Process each log entry with intermittent yields to avoid freezing.
|
||||||
for (var entry in nativeLogs) {
|
for (int i = 0; i < nativeLogs.length; i++) {
|
||||||
// Each entry is a Map with keys: number, type, date, duration.
|
final entry = nativeLogs[i];
|
||||||
final String number = entry['number'] ?? '';
|
final String number = entry['number'] ?? '';
|
||||||
if (number.isEmpty) continue;
|
if (number.isEmpty) continue;
|
||||||
|
|
||||||
@ -197,6 +200,8 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
|
|
||||||
callHistories
|
callHistories
|
||||||
.add(History(matchedContact, callDate, callType, callStatus, 1));
|
.add(History(matchedContact, callDate, callType, callStatus, 1));
|
||||||
|
// Yield every 10 iterations to avoid blocking the UI.
|
||||||
|
if (i % 10 == 0) await Future.delayed(Duration(milliseconds: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort histories by most recent.
|
// Sort histories by most recent.
|
||||||
@ -272,6 +277,7 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context); // required due to AutomaticKeepAliveClientMixin
|
||||||
final contactState = ContactState.of(context);
|
final contactState = ContactState.of(context);
|
||||||
|
|
||||||
if (loading || contactState.loading) {
|
if (loading || contactState.loading) {
|
||||||
|
@ -23,7 +23,7 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: 4, vsync: this, initialIndex: 1);
|
_tabController = TabController(length: 4, vsync: this, initialIndex: 2);
|
||||||
_tabController.addListener(_handleTabIndex);
|
_tabController.addListener(_handleTabIndex);
|
||||||
_searchBarController = SearchController();
|
_searchBarController = SearchController();
|
||||||
_searchBarController.addListener(() {
|
_searchBarController.addListener(() {
|
||||||
|
Loading…
Reference in New Issue
Block a user