Compare commits

..

2 Commits

Author SHA1 Message Date
759953b053
feat: build + run scripts
All checks were successful
/ mirror (push) Successful in 4s
2024-12-18 03:47:48 +00:00
44eef35ce2
Add docker build apk
All checks were successful
/ mirror (push) Successful in 4s
2024-12-17 04:24:51 +00:00
19 changed files with 249 additions and 1006 deletions

View File

@ -5,7 +5,6 @@ gradle-wrapper.jar
/gradlew.bat /gradlew.bat
/local.properties /local.properties
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java
gradle.properties
# Remember to never publicly share your keystore. # Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore # See https://flutter.dev/to/reference-keystore

View File

@ -3,8 +3,6 @@
<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.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
<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

View File

@ -3,8 +3,6 @@
<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.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
<application <application
android:label="com.example.dialer" android:label="com.example.dialer"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -4,8 +4,6 @@
<uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
<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

View File

@ -2,3 +2,5 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryErro
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
dev.steenbakker.mobile_scanner.useUnbundled=true dev.steenbakker.mobile_scanner.useUnbundled=true
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-amd64
#org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-17.0.13.0.11-3.fc41.x86_64

View File

@ -2,9 +2,8 @@ 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 'package:url_launcher/url_launcher.dart';
import 'package:dialer/widgets/username_color_generator.dart'; import 'package:dialer/widgets/username_color_generator.dart';
import '../../../widgets/block_service.dart';
class ContactModal extends StatefulWidget { class ContactModal extends StatelessWidget {
final Contact contact; final Contact contact;
final Function onEdit; final Function onEdit;
final Function onToggleFavorite; final Function onToggleFavorite;
@ -18,55 +17,6 @@ class ContactModal extends StatefulWidget {
required this.isFavorite, required this.isFavorite,
}); });
@override
_ContactModalState createState() => _ContactModalState();
}
class _ContactModalState extends State<ContactModal> {
late String phoneNumber;
bool isBlocked = false;
@override
void initState() {
super.initState();
phoneNumber = widget.contact.phones.isNotEmpty
? widget.contact.phones.first.number
: 'No phone number';
_checkIfBlocked();
}
Future<void> _checkIfBlocked() async {
if (phoneNumber != 'No phone number') {
bool blocked = await BlockService().isNumberBlocked(phoneNumber);
setState(() {
isBlocked = blocked;
});
}
}
Future<void> _toggleBlockState() async {
if (phoneNumber == 'No phone number') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No phone number to block or unblock')),
);
} else if (isBlocked) {
await BlockService().unblockNumber(phoneNumber);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$phoneNumber has been unblocked')),
);
} else {
await BlockService().blockNumber(phoneNumber);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$phoneNumber has been blocked')),
);
}
if (phoneNumber != 'No phone number') {
_checkIfBlocked();
}
Navigator.of(context).pop();
}
void _launchPhoneDialer(String phoneNumber) async { void _launchPhoneDialer(String phoneNumber) async {
final uri = Uri(scheme: 'tel', path: phoneNumber); final uri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(uri)) { if (await canLaunchUrl(uri)) {
@ -94,51 +44,13 @@ class _ContactModalState extends State<ContactModal> {
} }
} }
void _deleteContact() async {
final bool shouldDelete = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Contact'),
content: Text('Are you sure you want to delete ${widget.contact.displayName}?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Delete'),
),
],
),
);
if (shouldDelete) {
try {
// Delete the contact
await FlutterContacts.deleteContact(widget.contact);
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${widget.contact.displayName} deleted')),
);
// Close the modal
Navigator.of(context).pop();
} catch (e) {
// Handle errors and show a failure message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to delete ${widget.contact.displayName}: $e')),
);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String email = widget.contact.emails.isNotEmpty String phoneNumber = contact.phones.isNotEmpty
? widget.contact.emails.first.address ? contact.phones.first.number
: 'No email'; : 'No phone number';
String email =
contact.emails.isNotEmpty ? contact.emails.first.address : 'No email';
return GestureDetector( return GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
@ -147,16 +59,17 @@ void _deleteContact() async {
child: GestureDetector( child: GestureDetector(
onTap: () {}, onTap: () {},
child: FractionallySizedBox( child: FractionallySizedBox(
heightFactor: 0.7,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[900], color: Colors.grey[900],
borderRadius: borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
const BorderRadius.vertical(top: Radius.circular(20)),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Modal Handle and Three-Dot Menu // Modal Handle
// Top Bar with Handle and Three-Dot Menu
Stack( Stack(
children: [ children: [
Align( Align(
@ -178,33 +91,31 @@ void _deleteContact() async {
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 10, right: 10), padding: const EdgeInsets.only(top: 10, right: 10),
child: PopupMenuButton<String>( child: PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, icon: Icon(Icons.more_vert, color: Colors.white),
color: Colors.white),
onSelected: (String choice) { onSelected: (String choice) {
if (choice == 'delete') { print(
_deleteContact(); 'Selected: $choice'); // Placeholder for menu actions
}
}, },
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return [ return <PopupMenuEntry<String>>[
const PopupMenuItem<String>( PopupMenuItem<String>(
value: 'show_associated_contacts', value: 'show_associated_contacts',
child: Text('Show associated contacts'), child: Text('Show associated contacts'),
), ),
const PopupMenuItem<String>( PopupMenuItem<String>(
value: 'delete', value: 'delete',
child: Text('Delete'), child: Text('Delete'),
), ),
const PopupMenuItem<String>( PopupMenuItem<String>(
value: 'share', value: 'share',
child: Text('Share (via QR code)'), child: Text('Share (via QR code)'),
), ),
const PopupMenuItem<String>( PopupMenuItem<String>(
value: 'create_shortcut', value: 'create_shortcut',
child: child:
Text('Create shortcut (to home screen)'), Text('Create shortcut (to home screen)'),
), ),
const PopupMenuItem<String>( PopupMenuItem<String>(
value: 'set_ringtone', value: 'set_ringtone',
child: Text('Set ringtone'), child: Text('Set ringtone'),
), ),
@ -215,6 +126,7 @@ void _deleteContact() async {
), ),
], ],
), ),
// Contact Profile // Contact Profile
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@ -222,114 +134,88 @@ void _deleteContact() async {
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 50, radius: 50,
backgroundImage: (widget.contact.thumbnail != null && backgroundImage: (contact.thumbnail != null &&
widget.contact.thumbnail!.isNotEmpty) contact.thumbnail!.isNotEmpty)
? MemoryImage(widget.contact.thumbnail!) ? MemoryImage(contact.thumbnail!)
: null, : null,
backgroundColor: backgroundColor:
generateColorFromName(widget.contact.displayName), generateColorFromName(contact.displayName),
child: (widget.contact.thumbnail == null || child: (contact.thumbnail == null ||
widget.contact.thumbnail!.isEmpty) contact.thumbnail!.isEmpty)
? Text( ? Text(
widget.contact.displayName.isNotEmpty contact.displayName.isNotEmpty
? widget.contact.displayName[0] ? contact.displayName[0].toUpperCase()
.toUpperCase()
: '?', : '?',
style: const TextStyle( style: TextStyle(
fontSize: 40, color: Colors.white), fontSize: 40, color: Colors.white),
) )
: null, : null,
), ),
const SizedBox(height: 10), SizedBox(height: 10),
Text( Text(
widget.contact.displayName, contact.displayName,
style: const TextStyle( style: TextStyle(
fontSize: 24, fontWeight: FontWeight.bold), fontSize: 24, fontWeight: FontWeight.bold),
), ),
], ],
), ),
), ),
const Divider(),
// Contact Actions // Contact Actions
Divider(),
ListTile( ListTile(
leading: const Icon(Icons.phone, color: Colors.green), leading: Icon(Icons.phone, color: Colors.green),
title: Text(phoneNumber), title: Text(phoneNumber),
onTap: () { onTap: () {
if (widget.contact.phones.isNotEmpty) { if (contact.phones.isNotEmpty) {
_launchPhoneDialer(phoneNumber); _launchPhoneDialer(phoneNumber);
} }
}, },
), ),
ListTile( ListTile(
leading: const Icon(Icons.message, color: Colors.blue), leading: Icon(Icons.message, color: Colors.blue),
title: Text(phoneNumber), title: Text(phoneNumber),
onTap: () { onTap: () {
if (widget.contact.phones.isNotEmpty) { if (contact.phones.isNotEmpty) {
_launchSms(phoneNumber); _launchSms(phoneNumber);
} }
}, },
), ),
ListTile( ListTile(
leading: const Icon(Icons.email, color: Colors.orange), leading: Icon(Icons.email, color: Colors.orange),
title: Text(email), title: Text(email),
onTap: () { onTap: () {
if (widget.contact.emails.isNotEmpty) { if (contact.emails.isNotEmpty) {
_launchEmail(email); _launchEmail(email);
} }
}, },
), ),
const Divider(), Divider(),
// Favorite, Edit, and Block/Unblock Buttons // Favorite and Edit Buttons
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// Favorite button ElevatedButton.icon(
SizedBox( onPressed: () {
width: double Navigator.of(context).pop();
.infinity, // This makes the button take full width onToggleFavorite();
child: ElevatedButton.icon( },
onPressed: () { icon: Icon(contact.isStarred
Navigator.of(context).pop(); ? Icons.star
widget.onToggleFavorite(); : Icons.star_border),
}, label: Text(
icon: Icon(widget.isFavorite contact.isStarred ? 'Unfavorite' : 'Favorite'),
? Icons.star
: Icons.star_border),
label: Text(
widget.isFavorite ? 'Unfavorite' : 'Favorite'),
),
), ),
const SizedBox(height: 10), // Space between buttons ElevatedButton.icon(
onPressed: () => onEdit(),
// Edit button icon: Icon(Icons.edit),
SizedBox( label: Text('Edit Contact'),
width: double
.infinity, // This makes the button take full width
child: ElevatedButton.icon(
onPressed: () => widget.onEdit(),
icon: const Icon(Icons.edit),
label: const Text('Edit Contact'),
),
),
const SizedBox(height: 10), // Space between buttons
// Block/Unblock button
SizedBox(
width: double
.infinity, // This makes the button take full width
child: ElevatedButton.icon(
onPressed: _toggleBlockState,
icon: Icon(
isBlocked ? Icons.block : Icons.block_flipped),
label: Text(isBlocked ? 'Unblock' : 'Block'),
),
), ),
], ],
), ),
), ),
SizedBox(height: 16),
const SizedBox(height: 16),
], ],
), ),
), ),

View File

@ -1,15 +1,14 @@
// history_page.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 'package:intl/intl.dart'; import 'package:intl/intl.dart'; // For date formatting
import 'package:url_launcher/url_launcher.dart';
import 'package:dialer/features/contacts/contact_state.dart'; import 'package:dialer/features/contacts/contact_state.dart';
import 'package:dialer/widgets/username_color_generator.dart';
import 'package:dialer/widgets/color_darkener.dart';
class History { class History {
final Contact contact; final Contact contact;
final DateTime date; final DateTime date;
final String callType; // 'incoming' or 'outgoing' final String callType; // 'incoming' or 'outgoing'
final String callStatus; // 'missed' or 'answered' final String callStatus; // 'missed' or 'answered'
final int attempts; final int attempts;
@ -29,10 +28,9 @@ class HistoryPage extends StatefulWidget {
_HistoryPageState createState() => _HistoryPageState(); _HistoryPageState createState() => _HistoryPageState();
} }
class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStateMixin { class _HistoryPageState extends State<HistoryPage> {
List<History> histories = []; List<History> histories = [];
bool loading = true; bool loading = true;
int? _expandedIndex;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
@ -53,6 +51,7 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
} }
List<Contact> contacts = contactState.contacts; List<Contact> contacts = contactState.contacts;
// Ensure there are enough contacts
if (contacts.isEmpty) { if (contacts.isEmpty) {
setState(() { setState(() {
loading = false; loading = false;
@ -60,6 +59,7 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
return; return;
} }
// Build histories using the contacts
setState(() { setState(() {
histories = List.generate( histories = List.generate(
contacts.length >= 10 ? 10 : contacts.length, contacts.length >= 10 ? 10 : contacts.length,
@ -75,47 +75,6 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
}); });
} }
List _buildGroupedList(List<History> historyList) {
// Sort histories by date (most recent first)
historyList.sort((a, b) => b.date.compareTo(a.date));
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
List<History> todayHistories = [];
List<History> yesterdayHistories = [];
List<History> olderHistories = [];
for (var history in historyList) {
final callDate = DateTime(history.date.year, history.date.month, history.date.day);
if (callDate == today) {
todayHistories.add(history);
} else if (callDate == yesterday) {
yesterdayHistories.add(history);
} else {
olderHistories.add(history);
}
}
// Combine them with headers
final items = <dynamic>[];
if (todayHistories.isNotEmpty) {
items.add('Today');
items.addAll(todayHistories);
}
if (yesterdayHistories.isNotEmpty) {
items.add('Yesterday');
items.addAll(yesterdayHistories);
}
if (olderHistories.isNotEmpty) {
items.add('Older');
items.addAll(olderHistories);
}
return items;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final contactState = ContactState.of(context); final contactState = ContactState.of(context);
@ -123,6 +82,9 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
if (loading || contactState.loading) { if (loading || contactState.loading) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: const Center( body: const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
@ -132,6 +94,9 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
if (histories.isEmpty) { if (histories.isEmpty) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: const Center( body: const Center(
child: Text( child: Text(
'No call history available.', 'No call history available.',
@ -141,298 +106,46 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
); );
} }
// Filter missed calls
List<History> missedCalls = histories.where((h) => h.callStatus == 'missed').toList();
final allItems = _buildGroupedList(histories);
final missedItems = _buildGroupedList(missedCalls);
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Colors.black,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Container(
color: Colors.black,
child: const TabBar(
tabs: [
Tab(text: 'All Calls'),
Tab(text: 'Missed Calls'),
],
indicatorColor: Colors.white,
),
),
),
body: TabBarView(
children: [
// All Calls
_buildListView(allItems),
// Missed Calls
_buildListView(missedItems),
],
),
),
);
}
Widget _buildListView(List items) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
if (item is String) {
// This is a header item
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: Colors.grey[900],
child: Text(
item,
style: const TextStyle(
color: Colors.white70,
fontWeight: FontWeight.bold,
),
),
);
} else if (item is History) {
final history = item;
final contact = history.contact;
final isExpanded = _expandedIndex == index;
// Generate the avatar color
Color avatarColor = generateColorFromName(contact.displayName);
return Column(
children: [
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: darken(avatarColor, 0.4)),
),
),
title: Text(
contact.displayName,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
'${history.callType} - ${history.callStatus} - ${DateFormat('MMM dd, hh:mm a').format(history.date)}',
style: const TextStyle(color: Colors.grey),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${history.attempts}x',
style: const TextStyle(color: Colors.white),
),
IconButton(
icon: const Icon(Icons.phone, color: Colors.green),
onPressed: () async {
if (contact.phones.isNotEmpty) {
final Uri callUri =
Uri(scheme: 'tel', path: contact.phones.first.number);
if (await canLaunchUrl(callUri)) {
await launchUrl(callUri);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not launch call')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Contact has no phone number')),
);
}
},
),
],
),
onTap: () {
setState(() {
_expandedIndex = isExpanded ? null : index;
});
},
),
if (isExpanded)
Container(
color: Colors.grey[850],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TextButton.icon(
onPressed: () async {
if (history.contact.phones.isNotEmpty) {
final Uri smsUri =
Uri(scheme: 'sms', path: history.contact.phones.first.number);
if (await canLaunchUrl(smsUri)) {
await launchUrl(smsUri);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not send message')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Contact has no phone number')),
);
}
},
icon: const Icon(Icons.message, color: Colors.white),
label: const Text('Message', style: TextStyle(color: Colors.white)),
),
TextButton.icon(
onPressed: () {
// Navigate to Call Details page
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CallDetailsPage(history: history),
),
);
},
icon: const Icon(Icons.info, color: Colors.white),
label: const Text('Details', style: TextStyle(color: Colors.white)),
),
TextButton.icon(
onPressed: () {
// Implement block number functionality
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Number blocked (functionality not implemented)'),
),
);
},
icon: const Icon(Icons.block, color: Colors.white),
label: const Text('Block', style: TextStyle(color: Colors.white)),
),
],
),
),
],
);
}
return const SizedBox.shrink();
},
);
}
}
class CallDetailsPage extends StatelessWidget {
final History history;
const CallDetailsPage({Key? key, required this.history}) : super(key: key);
@override
Widget build(BuildContext context) {
final contact = history.contact;
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar( appBar: AppBar(
title: const Text('Call Details'), title: const Text('History'),
backgroundColor: Colors.black,
), ),
body: Padding( body: ListView.builder(
padding: const EdgeInsets.all(16.0), itemCount: histories.length,
child: Column( itemBuilder: (context, index) {
children: [ final history = histories[index];
// Display Contact Name and Thumbnail final contact = history.contact;
Row(
children: [
(contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? CircleAvatar(
backgroundImage: MemoryImage(contact.thumbnail!),
radius: 30,
)
: CircleAvatar(
backgroundColor: Colors.grey[700],
radius: 30,
child: Text(
contact.displayName.isNotEmpty
? contact.displayName[0].toUpperCase()
: '?',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
contact.displayName,
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
],
),
const SizedBox(height: 24),
// Display call type, status, date, attempts return ListTile(
DetailRow( leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
label: 'Call Type:', ? CircleAvatar(
value: history.callType, backgroundImage: MemoryImage(contact.thumbnail!),
), )
DetailRow( : CircleAvatar(
label: 'Call Status:', child: Text(
value: history.callStatus, contact.displayName.isNotEmpty
), ? contact.displayName[0]
DetailRow( : '?',
label: 'Date:',
value: DateFormat('MMM dd, yyyy - hh:mm a').format(history.date),
),
DetailRow(
label: 'Attempts:',
value: '${history.attempts}',
),
const SizedBox(height: 24),
// If you have more details like duration, contact number, etc.
if (contact.phones.isNotEmpty)
DetailRow(
label: 'Number:',
value: contact.phones.first.number,
), ),
],
),
),
);
}
}
class DetailRow extends StatelessWidget {
final String label;
final String value;
const DetailRow({Key? key, required this.label, required this.value}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Text(
label,
style: const TextStyle(color: Colors.white70, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Expanded(
child: Text(
value,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.right,
), ),
), title: Text(
], contact.displayName,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
'${history.callType} - ${history.callStatus} - ${DateFormat('MMM dd, hh:mm a').format(history.date)}',
style: const TextStyle(color: Colors.grey),
),
trailing: Text(
'${history.attempts}x',
style: const TextStyle(color: Colors.white),
),
onTap: () {
// Handle tap event if needed
},
);
},
), ),
); );
} }

View File

@ -18,8 +18,7 @@ class _MyHomePageState extends State<MyHomePage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Set the TabController length to 3 _tabController = TabController(length: 4, vsync: this, initialIndex: 1);
_tabController = TabController(length: 3, vsync: this, initialIndex: 1);
_tabController.addListener(_handleTabIndex); _tabController.addListener(_handleTabIndex);
_fetchContacts(); _fetchContacts();
} }
@ -70,97 +69,68 @@ class _MyHomePageState extends State<MyHomePage>
left: 16.0, left: 16.0,
right: 16.0, right: 16.0,
), ),
child: Row( child: Container(
children: [ decoration: BoxDecoration(
Expanded( color: const Color.fromARGB(255, 30, 30, 30),
child: Container( borderRadius: BorderRadius.circular(12.0),
decoration: BoxDecoration( border: Border(
color: const Color.fromARGB(255, 30, 30, 30), top: BorderSide(color: Colors.grey.shade800, width: 1),
borderRadius: BorderRadius.circular(12.0), left: BorderSide(color: Colors.grey.shade800, width: 1),
border: Border( right: BorderSide(color: Colors.grey.shade800, width: 1),
top: BorderSide(color: Colors.grey.shade800, width: 1), bottom: BorderSide(color: Colors.grey.shade800, width: 2),
left: BorderSide(color: Colors.grey.shade800, width: 1), ),
right: BorderSide(color: Colors.grey.shade800, width: 1), ),
bottom: child: SearchAnchor(
BorderSide(color: Colors.grey.shade800, width: 2), builder: (BuildContext context, SearchController controller) {
return SearchBar(
controller: controller,
padding: WidgetStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.only(
top: 6.0,
bottom: 6.0,
left: 16.0,
right: 16.0,
), ),
), ),
child: SearchAnchor( onTap: () {
builder: controller.openView();
(BuildContext context, SearchController controller) { _onSearchChanged('');
return SearchBar( },
controller: controller, backgroundColor: WidgetStateProperty.all(
padding: const Color.fromARGB(255, 30, 30, 30)),
MaterialStateProperty.all<EdgeInsetsGeometry>( hintText: 'Search contacts',
const EdgeInsets.only( hintStyle: WidgetStateProperty.all(
top: 6.0, const TextStyle(color: Colors.grey, fontSize: 16.0),
bottom: 6.0,
left: 16.0,
right: 16.0,
),
),
onTap: () {
controller.openView();
_onSearchChanged('');
},
backgroundColor: MaterialStateProperty.all(
const Color.fromARGB(255, 30, 30, 30)),
hintText: 'Search contacts',
hintStyle: MaterialStateProperty.all(
const TextStyle(color: Colors.grey, fontSize: 16.0),
),
leading: const Icon(
Icons.search,
color: Colors.grey,
size: 24.0,
),
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
),
);
},
viewOnChanged: (query) {
_onSearchChanged(query);
},
suggestionsBuilder:
(BuildContext context, SearchController controller) {
return _contactSuggestions.map((contact) {
return ListTile(
key: ValueKey(contact.id),
title: Text(contact.displayName,
style: const TextStyle(color: Colors.white)),
onTap: () {
controller.closeView(contact.displayName);
},
);
}).toList();
},
), ),
), leading: const Icon(
), Icons.search,
// 3-dot menu color: Colors.grey,
PopupMenuButton<String>( size: 24.0,
icon: const Icon(Icons.more_vert, color: Colors.white),
itemBuilder: (BuildContext context) => [
const PopupMenuItem<String>(
value: 'settings',
child: Text('Settings'),
), ),
], shape: WidgetStateProperty.all<RoundedRectangleBorder>(
onSelected: (String value) { RoundedRectangleBorder(
if (value == 'settings') { borderRadius: BorderRadius.circular(12.0),
Navigator.push( ),
context, ),
MaterialPageRoute( );
builder: (context) => const SettingsPage()), },
); viewOnChanged: (query) {
} _onSearchChanged(query);
}, },
), suggestionsBuilder:
], (BuildContext context, SearchController controller) {
return _contactSuggestions.map((contact) {
return ListTile(
key: ValueKey(contact.id),
title: Text(contact.displayName,
style: const TextStyle(color: Colors.white)),
onTap: () {
controller.closeView(contact.displayName);
},
);
}).toList();
},
),
), ),
), ),
// Main content with TabBarView // Main content with TabBarView
@ -173,6 +143,7 @@ class _MyHomePageState extends State<MyHomePage>
FavoritesPage(), FavoritesPage(),
HistoryPage(), HistoryPage(),
ContactPage(), ContactPage(),
SettingsPage(), // Add your SettingsPage here
], ],
), ),
Positioned( Positioned(
@ -216,6 +187,10 @@ class _MyHomePageState extends State<MyHomePage>
icon: Icon(_tabController.index == 2 icon: Icon(_tabController.index == 2
? Icons.contacts ? Icons.contacts
: Icons.contacts_outlined)), : Icons.contacts_outlined)),
Tab(
icon: Icon(_tabController.index == 3 // Corrected index
? Icons.settings
: Icons.settings_outlined)),
], ],
labelColor: Colors.white, labelColor: Colors.white,
unselectedLabelColor: const Color.fromARGB(255, 158, 158, 158), unselectedLabelColor: const Color.fromARGB(255, 158, 158, 158),

View File

@ -1,167 +0,0 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class BlockedNumbersPage extends StatefulWidget {
const BlockedNumbersPage({super.key});
@override
_BlockedNumbersPageState createState() => _BlockedNumbersPageState();
}
class _BlockedNumbersPageState extends State<BlockedNumbersPage> {
bool _blockUnknownNumbers = false; // Toggle for blocking unknown numbers
List<String> _blockedNumbers = []; // List of blocked numbers
final TextEditingController _numberController = TextEditingController();
@override
void initState() {
super.initState();
_loadPreferences(); // Load data on initialization
}
// Load preferences from local storage
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_blockUnknownNumbers = prefs.getBool('blockUnknownNumbers') ?? false;
_blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
});
}
// Save preferences to local storage
Future<void> _savePreferences() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('blockUnknownNumbers', _blockUnknownNumbers);
await prefs.setStringList('blockedNumbers', _blockedNumbers);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Blocked Numbers'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: const Text(
'Block Unknown Numbers',
style: TextStyle(color: Colors.white),
),
value: _blockUnknownNumbers,
onChanged: (bool value) {
setState(() {
_blockUnknownNumbers = value;
_savePreferences(); // Save the state to local storage
});
},
),
const SizedBox(height: 16),
ListTile(
title: const Text(
'Blocked Numbers',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
),
subtitle: _blockedNumbers.isEmpty
? const Text(
'No blocked numbers',
style: TextStyle(color: Colors.grey),
)
: null,
),
..._blockedNumbers.map(
(number) => ListTile(
title: Text(
number,
style: const TextStyle(color: Colors.white),
),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _unblockNumber(number),
),
),
),
const Divider(color: Colors.grey),
ListTile(
title: const Text(
'Block a Number',
style: TextStyle(color: Colors.white),
),
trailing: const Icon(Icons.add, color: Colors.white),
onTap: () => _showBlockNumberDialog(),
),
],
),
);
}
// Function to block a number
void _blockNumber(String number) {
if (number.isNotEmpty && !_blockedNumbers.contains(number)) {
setState(() {
_blockedNumbers.add(number);
_savePreferences(); // Save the updated list
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$number has been blocked')),
);
}
}
// Function to unblock a number
void _unblockNumber(String number) {
setState(() {
_blockedNumbers.remove(number);
_savePreferences(); // Save the updated list
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$number has been unblocked')),
);
}
// Dialog for blocking a new number
void _showBlockNumberDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.grey[900],
title: const Text('Block a Number', style: TextStyle(color: Colors.white)),
content: TextField(
controller: _numberController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
hintText: 'Enter number',
hintStyle: TextStyle(color: Colors.grey),
),
style: const TextStyle(color: Colors.white),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel', style: TextStyle(color: Colors.white)),
),
TextButton(
onPressed: () {
_blockNumber(_numberController.text);
_numberController.clear();
Navigator.pop(context);
},
child: const Text('Block', style: TextStyle(color: Colors.red)),
),
],
);
},
);
}
@override
void dispose() {
_numberController.dispose();
super.dispose();
}
}

View File

@ -1,19 +1,20 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'key_storage.dart';
class DeleteKeyPairPage extends StatelessWidget { class DeleteKeyPairPage extends StatelessWidget {
const DeleteKeyPairPage({super.key}); const DeleteKeyPairPage({super.key});
Future<void> _deleteKeyPair(BuildContext context) async { void _deleteKeyPair(BuildContext context) {
final keyStorage = KeyStorage(); // Key deletion logic (not implemented here)
await keyStorage.deleteKeys(); // ...
// Show confirmation message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('The key pair has been deleted.'), content: Text('The key pair has been deleted.'),
), ),
); );
// Navigate back or update the UI as needed
Navigator.pop(context); Navigator.pop(context);
} }

View File

@ -3,8 +3,6 @@ import 'dart:typed_data';
import 'dart:convert'; import 'dart:convert';
import 'package:pointycastle/export.dart' as crypto; import 'package:pointycastle/export.dart' as crypto;
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'dart:io';
import 'key_storage.dart';
class ExportPrivateKeyPage extends StatefulWidget { class ExportPrivateKeyPage extends StatefulWidget {
const ExportPrivateKeyPage({super.key}); const ExportPrivateKeyPage({super.key});
@ -17,81 +15,55 @@ class _ExportPrivateKeyPageState extends State<ExportPrivateKeyPage> {
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
Future<void> _exportPrivateKey() async { Future<void> _exportPrivateKey() async {
final keyStorage = KeyStorage(); // Replace with your actual private key retrieval logic
final privateKeyPem = await keyStorage.getPrivateKey(); final String privateKeyPem = 'Your private key here';
if (privateKeyPem == null) {
// Show error message if there's no key
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No private key found to export.'),
),
);
return;
}
// Get the password from the user input
final password = _passwordController.text; final password = _passwordController.text;
if (password.isEmpty) { if (password.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( // Show error message
const SnackBar(
content: Text('Please enter a password.'),
),
);
return; return;
} }
// Encrypt the private key using AES-256
final encryptedData = _encryptPrivateKey(privateKeyPem, password); final encryptedData = _encryptPrivateKey(privateKeyPem, password);
// Let the user pick a file location
final outputFile = await FilePicker.platform.saveFile( final outputFile = await FilePicker.platform.saveFile(
dialogTitle: 'Save encrypted private key', dialogTitle: 'Save encrypted private key',
fileName: 'private_key_encrypted.aes', fileName: 'private_key_encrypted.aes',
); );
if (outputFile != null) { if (outputFile != null) {
try { // Write the encrypted data to the file
final file = File(outputFile); // Use appropriate file I/O methods (not shown here)
await file.writeAsBytes(encryptedData); // ...
// Show a confirmation dialog or message
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Key Exported'), title: const Text('Key Exported'),
content: const Text('The encrypted private key has been exported successfully.'), content: const Text('The encrypted private key has been exported successfully.'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: const Text('OK'), child: const Text('OK'),
), ),
], ],
), ),
); );
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to write file: $e'),
),
);
}
} }
} }
Uint8List _encryptPrivateKey(String privateKey, String password) { Uint8List _encryptPrivateKey(String privateKey, String password) {
// Derive a key from the password using PBKDF2 // Encryption logic using AES-256
final derivator = crypto.PBKDF2KeyDerivator( final key = crypto.PBKDF2KeyDerivator(crypto.HMac(crypto.SHA256Digest(), 64))
crypto.HMac(crypto.SHA256Digest(), 64), .process(Uint8List.fromList(utf8.encode(password)));
);
final salt = Uint8List.fromList(utf8.encode('some_salt')); // In production, use a random salt and store it securely final params = crypto.PaddedBlockCipherParameters(
derivator.init(crypto.Pbkdf2Parameters(salt, 1000, 32)); crypto.ParametersWithIV(crypto.KeyParameter(key), Uint8List(16)), // Initialization Vector
final key = derivator.process(Uint8List.fromList(utf8.encode(password)));
// Initialize AES-CBC cipher with PKCS7 padding
final iv = Uint8List(16); // zero IV for example, in production use random IV and store it
final params = crypto.PaddedBlockCipherParameters<crypto.ParametersWithIV<crypto.KeyParameter>, Null>(
crypto.ParametersWithIV<crypto.KeyParameter>(crypto.KeyParameter(key), iv),
null, null,
); );
final cipher = crypto.PaddedBlockCipher('AES/CBC/PKCS7'); final cipher = crypto.PaddedBlockCipher('AES/CBC/PKCS7');
cipher.init(true, params); cipher.init(true, params);

View File

@ -4,12 +4,12 @@ import 'dart:math';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:asn1lib/asn1lib.dart'; import 'package:asn1lib/asn1lib.dart';
import 'key_storage.dart';
class GenerateNewKeyPairPage extends StatelessWidget { class GenerateNewKeyPairPage extends StatelessWidget {
const GenerateNewKeyPairPage({super.key}); const GenerateNewKeyPairPage({super.key});
Future<Map<String, String>> _generateKeyPair() async { Future<Map<String, String>> _generateKeyPair() async {
// Key generation logic using pointycastle
final keyParams = crypto.RSAKeyGeneratorParameters( final keyParams = crypto.RSAKeyGeneratorParameters(
BigInt.parse('65537'), BigInt.parse('65537'),
2048, 2048,
@ -17,6 +17,8 @@ class GenerateNewKeyPairPage extends StatelessWidget {
); );
final secureRandom = crypto.FortunaRandom(); final secureRandom = crypto.FortunaRandom();
// Seed the random number generator
final random = Random.secure(); final random = Random.secure();
final seeds = List<int>.generate(32, (_) => random.nextInt(256)); final seeds = List<int>.generate(32, (_) => random.nextInt(256));
secureRandom.seed(crypto.KeyParameter(Uint8List.fromList(seeds))); secureRandom.seed(crypto.KeyParameter(Uint8List.fromList(seeds)));
@ -29,12 +31,11 @@ class GenerateNewKeyPairPage extends StatelessWidget {
final publicKey = pair.publicKey as crypto.RSAPublicKey; final publicKey = pair.publicKey as crypto.RSAPublicKey;
final privateKey = pair.privateKey as crypto.RSAPrivateKey; final privateKey = pair.privateKey as crypto.RSAPrivateKey;
// Convert keys to PEM format
final publicKeyPem = _encodePublicKeyToPemPKCS1(publicKey); final publicKeyPem = _encodePublicKeyToPemPKCS1(publicKey);
final privateKeyPem = _encodePrivateKeyToPemPKCS1(privateKey); final privateKeyPem = _encodePrivateKeyToPemPKCS1(privateKey);
// Save keys securely // Save keys securely (not implemented here)
final keyStorage = KeyStorage();
await keyStorage.saveKeys(publicKey: publicKeyPem, privateKey: privateKeyPem);
return {'publicKey': publicKeyPem, 'privateKey': privateKeyPem}; return {'publicKey': publicKeyPem, 'privateKey': privateKeyPem};
} }
@ -51,8 +52,7 @@ class GenerateNewKeyPairPage extends StatelessWidget {
Uint8List _encodePublicKeyToDer(crypto.RSAPublicKey publicKey) { Uint8List _encodePublicKeyToDer(crypto.RSAPublicKey publicKey) {
final algorithmSeq = ASN1Sequence(); final algorithmSeq = ASN1Sequence();
// Create the OID directly with the arcs algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption'));
algorithmSeq.add(ASN1ObjectIdentifier([1, 2, 840, 113549, 1, 1, 1]));
algorithmSeq.add(ASN1Null()); algorithmSeq.add(ASN1Null());
final publicKeySeq = ASN1Sequence(); final publicKeySeq = ASN1Sequence();
@ -84,8 +84,8 @@ class GenerateNewKeyPairPage extends StatelessWidget {
} }
String _formatPem(Uint8List bytes, String label) { String _formatPem(Uint8List bytes, String label) {
final base64Data = base64Encode(bytes); final base64 = base64Encode(bytes);
final chunks = RegExp('.{1,64}').allMatches(base64Data).map((m) => m.group(0)!); final chunks = RegExp('.{1,64}').allMatches(base64).map((m) => m.group(0)!);
return '-----BEGIN $label-----\n${chunks.join('\n')}\n-----END $label-----'; return '-----BEGIN $label-----\n${chunks.join('\n')}\n-----END $label-----';
} }
@ -99,12 +99,13 @@ class GenerateNewKeyPairPage extends StatelessWidget {
body: Center( body: Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: () async { onPressed: () async {
await _generateKeyPair(); final keys = await _generateKeyPair();
// Display a confirmation dialog or message
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Keys Generated'), title: const Text('Keys Generated'),
content: const Text('The new key pair has been generated and stored securely.'), content: const Text('The new key pair has been generated successfully.'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),

View File

@ -1,28 +0,0 @@
// key_storage.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class KeyStorage {
static const _publicKeyKey = 'public_key';
static const _privateKeyKey = 'private_key';
final FlutterSecureStorage _storage = const FlutterSecureStorage();
Future<void> saveKeys({required String publicKey, required String privateKey}) async {
await _storage.write(key: _publicKeyKey, value: publicKey);
await _storage.write(key: _privateKeyKey, value: privateKey);
}
Future<String?> getPublicKey() async {
return await _storage.read(key: _publicKeyKey);
}
Future<String?> getPrivateKey() async {
return await _storage.read(key: _privateKeyKey);
}
Future<void> deleteKeys() async {
await _storage.delete(key: _publicKeyKey);
await _storage.delete(key: _privateKeyKey);
}
}

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'show_public_key_qr.dart'; import 'package:dialer/features/settings/key/show_public_key_qr.dart';
import 'show_public_key_text.dart'; import 'package:dialer/features/settings/key/show_public_key_text.dart';
import 'generate_new_key_pair.dart'; import 'package:dialer/features/settings/key/generate_new_key_pair.dart';
import 'export_private_key.dart'; import 'package:dialer/features/settings/key/export_private_key.dart';
import 'delete_key_pair.dart'; import 'package:dialer/features/settings/key/delete_key_pair.dart';
class KeyManagementPage extends StatelessWidget { class KeyManagementPage extends StatelessWidget {
const KeyManagementPage({super.key}); const KeyManagementPage({super.key});
@ -34,13 +34,14 @@ class KeyManagementPage extends StatelessWidget {
MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()), MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()),
); );
break; break;
case 'Delete a key pair': case 'Delete a key pair, warning POPUP':
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()), MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()),
); );
break; break;
default: default:
// Handle default or unknown options
break; break;
} }
} }
@ -52,7 +53,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', 'Delete a key pair, warning POPUP',
]; ];
return Scaffold( return Scaffold(

View File

@ -1,48 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pretty_qr_code/pretty_qr_code.dart'; import 'package:pretty_qr_code/pretty_qr_code.dart';
import 'key_storage.dart';
class DisplayPublicKeyQRCodePage extends StatelessWidget { class DisplayPublicKeyQRCodePage extends StatelessWidget {
const DisplayPublicKeyQRCodePage({super.key}); const DisplayPublicKeyQRCodePage({super.key});
Future<String?> _loadPublicKey() async {
final keyStorage = KeyStorage();
return keyStorage.getPublicKey();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Replace with your actual public key retrieval logic
final String publicKey = 'Your public key here';
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar( appBar: AppBar(
title: const Text('Public Key in QR Code'), title: const Text('Public Key in QR Code'),
), ),
body: FutureBuilder<String?>( body: Center(
future: _loadPublicKey(), child: PrettyQr(
builder: (context, snapshot) { data: publicKey,
if (snapshot.connectionState == ConnectionState.waiting) { size: 250,
return const Center(child: CircularProgressIndicator()); roundEdges: true,
} elementColor: Colors.white,
),
final publicKey = snapshot.data;
if (publicKey == null) {
return const Center(
child: Text(
'No public key found.',
style: TextStyle(color: Colors.white),
),
);
}
return Center(
child: PrettyQr(
data: publicKey,
size: 250,
roundEdges: true,
elementColor: Colors.white,
),
);
},
), ),
); );
} }

View File

@ -1,49 +1,27 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'key_storage.dart';
class DisplayPublicKeyTextPage extends StatelessWidget { class DisplayPublicKeyTextPage extends StatelessWidget {
const DisplayPublicKeyTextPage({super.key}); const DisplayPublicKeyTextPage({super.key});
Future<String?> _loadPublicKey() async {
final keyStorage = KeyStorage();
return await keyStorage.getPublicKey();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Replace with your actual public key retrieval logic
final String publicKey = 'Your public key here';
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar( appBar: AppBar(
title: const Text('Public Key as Text'), title: const Text('Public Key as Text'),
), ),
body: FutureBuilder<String?>( body: Center(
future: _loadPublicKey(), child: Padding(
builder: (context, snapshot) { padding: const EdgeInsets.all(16.0),
if (snapshot.connectionState == ConnectionState.waiting) { child: SelectableText(
return const Center(child: CircularProgressIndicator()); publicKey,
} style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
final publicKey = snapshot.data; ),
if (publicKey == null) { ),
return const Center(
child: Text(
'No public key found.',
style: TextStyle(color: Colors.white),
),
);
}
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SelectableText(
publicKey,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
);
},
), ),
); );
} }

View File

@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:dialer/features/settings/call/settingsCall.dart'; import 'package:dialer/features/settings/call/settingsCall.dart';
import 'package:dialer/features/settings/sim/settings_accounts.dart'; import 'package:dialer/features/settings/sim/settings_accounts.dart';
import 'package:dialer/features/settings/key/manage_keys_page.dart'; import 'package:dialer/features/settings/key/manage_keys_page.dart';
import 'package:dialer/features/settings/blocked/settings_blocked.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
const SettingsPage({super.key}); const SettingsPage({super.key});
@ -23,21 +22,15 @@ class SettingsPage extends StatelessWidget {
MaterialPageRoute(builder: (context) => const SettingsAccountsPage()), MaterialPageRoute(builder: (context) => const SettingsAccountsPage()),
); );
break; break;
case 'Key management': case 'Gestion de clés':
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => const KeyManagementPage()), MaterialPageRoute(builder: (context) => const KeyManagementPage()),
); );
break; break;
case 'Blocked numbers': // Add more cases for other settings pages
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BlockedNumbersPage()),
);
break;
// Add more cases for other settings pages
default: default:
// Handle default or unknown settings // Handle default or unknown settings
break; break;
} }
} }
@ -48,7 +41,6 @@ class SettingsPage extends StatelessWidget {
'Calling settings', 'Calling settings',
'Page of telephone accounts', 'Page of telephone accounts',
'Key management', 'Key management',
'Blocked numbers'
]; ];
return Scaffold( return Scaffold(

View File

@ -1,52 +0,0 @@
import 'package:shared_preferences/shared_preferences.dart';
class BlockService {
static final BlockService _instance = BlockService._internal();
factory BlockService() {
return _instance;
}
BlockService._internal();
// Function to add a number to the blocked list
Future<void> blockNumber(String number) async {
if (number.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
if (!blockedNumbers.contains(number)) {
blockedNumbers.add(number);
await prefs.setStringList('blockedNumbers', blockedNumbers);
print('$number has been blocked');
} else {
print('$number is already blocked');
}
}
// Function to remove a number from the blocked list
Future<void> unblockNumber(String number) async {
if (number.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
if (blockedNumbers.contains(number)) {
blockedNumbers.remove(number);
await prefs.setStringList('blockedNumbers', blockedNumbers);
print('$number has been unblocked');
} else {
print('$number is not blocked');
}
}
// Check if a number is blocked
Future<bool> isNumberBlocked(String number) async {
if (number.isEmpty) return false;
final prefs = await SharedPreferences.getInstance();
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
return blockedNumbers.contains(number);
}
}

View File

@ -34,7 +34,7 @@ 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.
shared_preferences: ^2.3.3 # Local storage (no critical data) url_launcher: ^6.1.9 # To manage system dialer. Call, message...
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
flutter_contacts: ^1.1.9+2 flutter_contacts: ^1.1.9+2
permission_handler: ^11.3.1 # For handling permissions permission_handler: ^11.3.1 # For handling permissions
@ -45,11 +45,9 @@ dependencies:
mobile_scanner: ^6.0.2 mobile_scanner: ^6.0.2
pretty_qr_code: ^3.3.0 pretty_qr_code: ^3.3.0
pointycastle: ^3.4.0 pointycastle: ^3.4.0
file_picker: ^8.1.6 file_picker: ^5.2.5
asn1lib: ^1.0.0 asn1lib: ^1.0.0
intl_utils: ^2.0.7 intl_utils: ^2.0.7
url_launcher: ^6.3.1
flutter_secure_storage: ^9.0.0
mobile_number: mobile_number:
path: packages/mobile_number path: packages/mobile_number