feat: implement privacy mode to mask contact names and phone numbers
All checks were successful
/ mirror (push) Successful in 4s

This commit is contained in:
AlexisDanlos 2025-01-20 20:12:19 +01:00
parent 475f432047
commit f3fa38b027
4 changed files with 114 additions and 16 deletions

View File

@ -2,6 +2,39 @@ import 'package:dialer/features/contacts/contact_state.dart';
import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart'; import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/widgets/loading_indicator.dart'; import 'package:dialer/widgets/loading_indicator.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
const bool _privacyMode = bool.fromEnvironment('privacy-mode', defaultValue: false);
List<Contact> _maskContacts(List<Contact> original) {
if (!_privacyMode) return original;
return original.map((c) {
final maskedName = _maskName(c.displayName);
final phones = c.phones.map((p) => Phone(
_maskPhone(p.number),
label: p.label,
)).toList();
final masked = Contact()
..displayName = maskedName
..thumbnail = c.thumbnail
..phones = phones
..id = c.id;
return masked;
}).toList();
}
String _maskName(String name) {
final parts = name.split(' ');
return parts.map((part) {
if (part.length < 2) return part;
return '${part[0]}${'*' * (part.length - 1)}';
}).join(' ');
}
String _maskPhone(String phone) {
if (phone.length < 3) return phone;
return phone.substring(0, 2) + '*' * (phone.length - 2);
}
class ContactPage extends StatefulWidget { class ContactPage extends StatefulWidget {
const ContactPage({super.key}); const ContactPage({super.key});
@ -19,7 +52,7 @@ class _ContactPageState extends State<ContactPage> {
? const LoadingIndicatorWidget() ? const LoadingIndicatorWidget()
: AlphabetScrollPage( : AlphabetScrollPage(
scrollOffset: contactState.scrollOffset, scrollOffset: contactState.scrollOffset,
contacts: contactState.contacts, // Use all contacts here contacts: _maskContacts(contactState.contacts),
), ),
); );
} }

View File

@ -2,6 +2,39 @@ import 'package:dialer/features/contacts/contact_state.dart';
import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart'; import 'package:dialer/features/contacts/widgets/alphabet_scroll_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/widgets/loading_indicator.dart'; import 'package:dialer/widgets/loading_indicator.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
const bool _privacyMode = bool.fromEnvironment('privacy-mode', defaultValue: false);
List<Contact> _maskContacts(List<Contact> original) {
if (!_privacyMode) return original;
return original.map((c) {
final maskedName = _maskName(c.displayName);
final phones = c.phones.map((p) => Phone(
_maskPhone(p.number),
label: p.label,
)).toList();
final masked = Contact()
..displayName = maskedName
..thumbnail = c.thumbnail
..phones = phones
..id = c.id;
return masked;
}).toList();
}
String _maskName(String name) {
final parts = name.split(' ');
return parts.map((part) {
if (part.length < 2) return part;
return '${part[0]}${'*' * (part.length - 1)}';
}).join(' ');
}
String _maskPhone(String phone) {
if (phone.length < 3) return phone;
return phone.substring(0, 2) + '*' * (phone.length - 2);
}
class FavoritesPage extends StatefulWidget { class FavoritesPage extends StatefulWidget {
const FavoritesPage({super.key}); const FavoritesPage({super.key});
@ -24,8 +57,7 @@ class _FavoritesPageState extends State<FavoritesPage> {
? const LoadingIndicatorWidget() ? const LoadingIndicatorWidget()
: AlphabetScrollPage( : AlphabetScrollPage(
scrollOffset: contactState.scrollOffset, scrollOffset: contactState.scrollOffset,
contacts: contacts: _maskContacts(contactState.favoriteContacts),
contactState.favoriteContacts, // Use only favorites here
), ),
); );
} }

View File

@ -22,6 +22,23 @@ class History {
); );
} }
const bool _privacyMode = bool.fromEnvironment('privacy-mode', defaultValue: false);
String _maskName(String name) {
if (!_privacyMode) return name;
final parts = name.split(' ');
return parts.map((part) {
if (part.length < 2) return part;
return '${part[0]}${'*' * (part.length - 1)}';
}).join(' ');
}
String _maskPhone(String phone) {
if (!_privacyMode) return phone;
if (phone.length < 3) return phone;
return phone.substring(0, 2) + '*' * (phone.length - 2);
}
class HistoryPage extends StatefulWidget { class HistoryPage extends StatefulWidget {
const HistoryPage({Key? key}) : super(key: key); const HistoryPage({Key? key}) : super(key: key);
@ -198,6 +215,7 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
} else if (item is History) { } else if (item is History) {
final history = item; final history = item;
final contact = history.contact; final contact = history.contact;
final maskedName = _maskName(contact.displayName);
final isExpanded = _expandedIndex == index; final isExpanded = _expandedIndex == index;
// Generate the avatar color // Generate the avatar color
@ -213,14 +231,14 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
: CircleAvatar( : CircleAvatar(
backgroundColor: avatarColor, backgroundColor: avatarColor,
child: Text( child: Text(
contact.displayName.isNotEmpty maskedName.isNotEmpty
? contact.displayName[0].toUpperCase() ? maskedName[0]
: '?', : '?',
style: TextStyle(color: darken(avatarColor, 0.4)), style: TextStyle(color: darken(avatarColor, 0.4)),
), ),
), ),
title: Text( title: Text(
contact.displayName, maskedName,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
subtitle: Text( subtitle: Text(
@ -367,7 +385,7 @@ class CallDetailsPage extends StatelessWidget {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Text( child: Text(
contact.displayName, _maskName(contact.displayName),
style: const TextStyle(color: Colors.white, fontSize: 24), style: const TextStyle(color: Colors.white, fontSize: 24),
), ),
), ),
@ -399,7 +417,7 @@ class CallDetailsPage extends StatelessWidget {
if (contact.phones.isNotEmpty) if (contact.phones.isNotEmpty)
DetailRow( DetailRow(
label: 'Number:', label: 'Number:',
value: contact.phones.first.number, value: _maskPhone(contact.phones.first.number),
), ),
], ],
), ),

View File

@ -7,6 +7,17 @@ import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:dialer/features/settings/settings.dart'; import 'package:dialer/features/settings/settings.dart';
import '../../services/contact_service.dart'; import '../../services/contact_service.dart';
const bool _privacyMode = bool.fromEnvironment('privacy-mode', defaultValue: false);
String _maskName(String name) {
if (!_privacyMode) return name;
final parts = name.split(' ');
return parts.map((part) {
if (part.length < 2) return part;
return '${part[0]}${'*' * (part.length - 1)}';
}).join(' ');
}
class _MyHomePageState extends State<MyHomePage> class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
@ -128,14 +139,7 @@ class _MyHomePageState extends State<MyHomePage>
suggestionsBuilder: suggestionsBuilder:
(BuildContext context, SearchController controller) { (BuildContext context, SearchController controller) {
return _contactSuggestions.map((contact) { return _contactSuggestions.map((contact) {
return ListTile( return _buildSuggestionTile(contact, controller);
key: ValueKey(contact.id),
title: Text(contact.displayName,
style: const TextStyle(color: Colors.white)),
onTap: () {
controller.closeView(contact.displayName);
},
);
}).toList(); }).toList();
}, },
), ),
@ -225,6 +229,17 @@ class _MyHomePageState extends State<MyHomePage>
), ),
); );
} }
Widget _buildSuggestionTile(Contact contact, SearchController controller) {
final maskedName = _maskName(contact.displayName);
return ListTile(
key: ValueKey(contact.id),
title: Text(maskedName, style: const TextStyle(color: Colors.white)),
onTap: () {
controller.closeView(maskedName);
},
);
}
} }
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {