Compare commits

..

2 Commits

Author SHA1 Message Date
e6e05f2686 Argiliser exemples (#53)
Some checks failed
/ mirror (push) Failing after 1s
/ build (push) Successful in 9m44s
/ build-stealth (push) Failing after 1s
Reviewed-on: #53
2025-04-17 15:31:07 +03:00
ac004fd332 fix: search bar is non case sensitive and don't have delay (contact page) (#50)
Co-authored-by: stcb <21@stcb.cc>
Reviewed-on: #50
Co-authored-by: Florian Griffon <florian.griffon@epitech.eu>
Co-committed-by: Florian Griffon <florian.griffon@epitech.eu>
2025-04-17 15:31:07 +03:00
2 changed files with 94 additions and 79 deletions

View File

@ -10,53 +10,82 @@ import '../../services/contact_service.dart';
import 'package:dialer/features/voicemail/voicemail_page.dart'; import 'package:dialer/features/voicemail/voicemail_page.dart';
import '../contacts/widgets/contact_modal.dart'; import '../contacts/widgets/contact_modal.dart';
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
List<Contact> _allContacts = []; List<Contact> _allContacts = [];
List<Contact> _contactSuggestions = []; List<Contact> _contactSuggestions = [];
final ContactService _contactService = ContactService(); final ContactService _contactService = ContactService();
final ObfuscateService _obfuscateService = ObfuscateService(); final ObfuscateService _obfuscateService = ObfuscateService();
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
late SearchController _searchBarController;
String _rawSearchInput = '';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Set the TabController length to 4
_tabController = TabController(length: 4, vsync: this, initialIndex: 2); _tabController = TabController(length: 4, vsync: this, initialIndex: 2);
_tabController.addListener(_handleTabIndex); _tabController.addListener(_handleTabIndex);
_searchBarController = SearchController();
_searchBarController.addListener(() {
if (_searchController.text != _searchBarController.text) {
_rawSearchInput = _searchBarController.text;
_searchController.text = _rawSearchInput;
_onSearchChanged(_searchBarController.text);
}
});
_fetchContacts(); _fetchContacts();
} }
void _fetchContacts() async { void _fetchContacts() async {
_allContacts = await _contactService.fetchContacts(); _allContacts = await _contactService.fetchContacts();
setState(() {}); _contactSuggestions = List.from(_allContacts);
if (mounted) setState(() {});
} }
void _clearSearch() { void _clearSearch() {
_searchController.clear(); _searchController.clear();
_searchBarController.clear();
_rawSearchInput = '';
_onSearchChanged(''); _onSearchChanged('');
} }
void _onSearchChanged(String query) { void _onSearchChanged(String query) {
setState(() { setState(() {
if (query.isEmpty) { if (query.isEmpty) {
_contactSuggestions = List.from(_allContacts); // Reset suggestions _contactSuggestions = List.from(_allContacts);
} else { } else {
final normalizedQuery = _normalizeString(query.toLowerCase());
_contactSuggestions = _allContacts.where((contact) { _contactSuggestions = _allContacts.where((contact) {
return contact.displayName final normalizedName = _normalizeString(contact.displayName.toLowerCase());
.toLowerCase() return normalizedName.contains(normalizedQuery);
.contains(query.toLowerCase());
}).toList(); }).toList();
} }
}); });
} }
String _normalizeString(String input) {
const accentMap = {
'àáâãäå': 'a',
'èéêë': 'e',
'ìíîï': 'i',
'òóôõö': 'o',
'ùúûü': 'u',
'ç': 'c',
'ñ': 'n',
};
String normalized = input;
accentMap.forEach((accents, base) {
for (var accent in accents.split('')) {
normalized = normalized.replaceAll(accent, base);
}
});
return normalized;
}
@override @override
void dispose() { void dispose() {
_searchController.dispose(); _searchController.dispose();
_searchBarController.dispose();
_tabController.removeListener(_handleTabIndex); _tabController.removeListener(_handleTabIndex);
_tabController.dispose(); _tabController.dispose();
super.dispose(); super.dispose();
@ -69,19 +98,18 @@ class _MyHomePageState extends State<MyHomePage>
void _toggleFavorite(Contact contact) async { void _toggleFavorite(Contact contact) async {
try { try {
if (await FlutterContacts.requestPermission()) { if (await FlutterContacts.requestPermission()) {
Contact? fullContact = await FlutterContacts.getContact(contact.id, Contact? fullContact = await FlutterContacts.getContact(
contact.id,
withProperties: true, withProperties: true,
withAccounts: true, withAccounts: true,
withPhoto: true, withPhoto: true,
withThumbnail: true); withThumbnail: true,
);
if (fullContact != null) { if (fullContact != null) {
fullContact.isStarred = !fullContact.isStarred; fullContact.isStarred = !fullContact.isStarred;
await FlutterContacts.updateContact(fullContact); await FlutterContacts.updateContact(fullContact);
setState(() {
// Updating the contact list after toggling the favorite
_fetchContacts(); _fetchContacts();
});
} }
} else { } else {
print("Could not fetch contact details"); print("Could not fetch contact details");
@ -100,7 +128,6 @@ class _MyHomePageState extends State<MyHomePage>
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: Column( body: Column(
children: [ children: [
// Persistent Search Bar
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 24.0, top: 24.0,
@ -118,35 +145,33 @@ class _MyHomePageState extends State<MyHomePage>
border: Border.all(color: Colors.grey.shade800, width: 1), border: Border.all(color: Colors.grey.shade800, width: 1),
), ),
child: SearchAnchor( child: SearchAnchor(
builder: searchController: _searchBarController,
(BuildContext context, SearchController controller) { builder: (BuildContext context, SearchController controller) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
controller.openView(); // Open the search view controller.openView();
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color.fromARGB(255, 30, 30, 30), color: const Color.fromARGB(255, 30, 30, 30),
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
border: Border.all( border: Border.all(color: Colors.grey.shade800, width: 1),
color: Colors.grey.shade800, width: 1),
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
vertical: 12.0, horizontal: 16.0),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.search, const Icon(Icons.search, color: Colors.grey, size: 24.0),
color: Colors.grey, size: 24.0),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
Text( Expanded(
_searchController.text.isEmpty child: Text(
_rawSearchInput.isEmpty
? 'Search contacts' ? 'Search contacts'
: _searchController.text, : _rawSearchInput,
style: const TextStyle( style: const TextStyle(color: Colors.grey, fontSize: 16.0),
color: Colors.grey, fontSize: 16.0), overflow: TextOverflow.ellipsis,
), ),
const Spacer(), ),
if (_searchController.text.isNotEmpty) if (_rawSearchInput.isNotEmpty)
GestureDetector( GestureDetector(
onTap: _clearSearch, onTap: _clearSearch,
child: const Icon( child: const Icon(
@ -161,23 +186,24 @@ class _MyHomePageState extends State<MyHomePage>
); );
}, },
viewOnChanged: (query) { viewOnChanged: (query) {
_onSearchChanged(query); // Update immediately
if (_searchBarController.text != query) {
_rawSearchInput = query;
_searchBarController.text = query;
_searchController.text = query;
}
_onSearchChanged(query);
}, },
suggestionsBuilder: suggestionsBuilder: (BuildContext context, SearchController controller) {
(BuildContext context, SearchController controller) {
return _contactSuggestions.map((contact) { return _contactSuggestions.map((contact) {
return ListTile( return ListTile(
key: ValueKey(contact.id), key: ValueKey(contact.id),
title: Text(_obfuscateService.obfuscateData(contact.displayName), title: Text(
style: const TextStyle(color: Colors.white)), _obfuscateService.obfuscateData(contact.displayName),
style: const TextStyle(color: Colors.white),
),
onTap: () { onTap: () {
// Clear the search text input
controller.text = '';
// Close the search view
controller.closeView(contact.displayName); controller.closeView(contact.displayName);
// Show the ContactModal when a contact is tapped
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -186,34 +212,28 @@ class _MyHomePageState extends State<MyHomePage>
return ContactModal( return ContactModal(
contact: contact, contact: contact,
onEdit: () async { onEdit: () async {
if (await FlutterContacts if (await FlutterContacts.requestPermission()) {
.requestPermission()) { final updatedContact = await FlutterContacts
final updatedContact =
await FlutterContacts
.openExternalEdit(contact.id); .openExternalEdit(contact.id);
if (updatedContact != null) { if (updatedContact != null) {
_fetchContacts(); _fetchContacts();
Navigator.of(context).pop(); Navigator.of(context).pop();
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(
.showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
'${contact.displayName} updated successfully!'), '${contact.displayName} updated successfully!'),
), ),
); );
} else { } else {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(
.showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text('Edit canceled or failed.'),
'Edit canceled or failed.'),
), ),
); );
} }
} }
}, },
onToggleFavorite: () => onToggleFavorite: () => _toggleFavorite(contact),
_toggleFavorite(contact),
isFavorite: contact.isStarred, isFavorite: contact.isStarred,
); );
}, },
@ -225,7 +245,6 @@ class _MyHomePageState extends State<MyHomePage>
), ),
), ),
), ),
// 3-dot menu
PopupMenuButton<String>( PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Colors.white), icon: const Icon(Icons.more_vert, color: Colors.white),
itemBuilder: (BuildContext context) => [ itemBuilder: (BuildContext context) => [
@ -238,8 +257,7 @@ class _MyHomePageState extends State<MyHomePage>
if (value == 'settings') { if (value == 'settings') {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (context) => const SettingsPage()),
builder: (context) => const SettingsPage()),
); );
} }
}, },
@ -247,7 +265,6 @@ class _MyHomePageState extends State<MyHomePage>
], ],
), ),
), ),
// Main content with TabBarView
Expanded( Expanded(
child: Stack( child: Stack(
children: [ children: [

View File

@ -27,14 +27,13 @@ The protocol definition will include as completed:
- Handshakes - Handshakes
- Real-time data-stream encryption (and decryption) - Real-time data-stream encryption (and decryption)
- Encrypted stream compression - Encrypted stream compression
- Transmission over audio stream - Transmission over audio stream (at least one modulation type)
- Minimal error correction in audio-based transmission - First steps in FEC (Forward Error Correction): detecting half of transmission errors
- Error handling and user prevention
And should include prototype or scratches functionalities, among which: And should include prototype or scratches functionalities, among which:
- Embedded silent data transmission (silently transmit light data during an encrypted phone call) - Embedded silent data transmission (such as DTMF)
- On-the-fly key exchange (does not require prior key exchange, sacrifying some security) - On-the-fly key exchange (does not require prior key exchange, sacrifying some security)
- Strong error correction - Stronger FEC: detecting >80%, correcting 20% of transmission errors
#### The Icing dialer (based on Icing kotlin library, an Icing protocol implementation) #### The Icing dialer (based on Icing kotlin library, an Icing protocol implementation)
@ -128,16 +127,15 @@ The remote bank advisor asks him to authenticate, making him type his password o
By using the Icing protocol, not only would Jeff and the bank be assured that the informations are transmitted safely, By using the Icing protocol, not only would Jeff and the bank be assured that the informations are transmitted safely,
but also that the call is coming from Jeff's phone and not an impersonator. but also that the call is coming from Jeff's phone and not an impersonator.
Elise is a 42 years-old extreme reporter. Elise, 42 years-old, is a journalist covering sensitive topics.
After interviewing Russians opposition's leader, the FSB is looking to interview her. Her work draws attention from people who want to know what she's saying - and to whom.
She tries to stay discreet and hidden, but those measures constrains her to barely receive cellular network. Forced to stay discreet, with unreliable signal and a likely monitored phone line,
She suspects her phone line to be monitored, so the best she can do to call safely, is to use her Icing dialer. she uses Icing dialer to make secure calls without exposing herself.
Paul, a 22 years-old developer working for a big company, decides to go to China for vacations. Paul, a 22 years-old developer, is enjoying its vacations abroad.
But everything goes wrong! The company's product he works on, is failling in the middle of the day and no one is But everything goes wrong! The company's product he works on, is failling in the middle of the day and no one is
qualified to fix it. Paul doesn't have WiFi and his phone plan only covers voice calls in China. qualified to fix it. Paul doesn't have WiFi and his phone plan only covers voice calls in his country.
With Icing dialer, he can call his collegues and help fix the With Icing dialer, he can call his collegues and help fix the problem, completely safe.
problem, safe from potential Chinese spies.
## Evaluation Criteria ## Evaluation Criteria
### Protocol and lib ### Protocol and lib