Compare commits

...

1 Commits

Author SHA1 Message Date
AlexisDanlos
f3fa38b027 feat: implement privacy mode to mask contact names and phone numbers
All checks were successful
/ mirror (push) Successful in 4s
2025-01-20 20:12:19 +01:00
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:flutter/material.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 {
const ContactPage({super.key});
@ -19,7 +52,7 @@ class _ContactPageState extends State<ContactPage> {
? const LoadingIndicatorWidget()
: AlphabetScrollPage(
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:flutter/material.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 {
const FavoritesPage({super.key});
@ -24,8 +57,7 @@ class _FavoritesPageState extends State<FavoritesPage> {
? const LoadingIndicatorWidget()
: AlphabetScrollPage(
scrollOffset: contactState.scrollOffset,
contacts:
contactState.favoriteContacts, // Use only favorites here
contacts: _maskContacts(contactState.favoriteContacts),
),
);
}

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 {
const HistoryPage({Key? key}) : super(key: key);
@ -198,6 +215,7 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
} else if (item is History) {
final history = item;
final contact = history.contact;
final maskedName = _maskName(contact.displayName);
final isExpanded = _expandedIndex == index;
// Generate the avatar color
@ -213,14 +231,14 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
: CircleAvatar(
backgroundColor: avatarColor,
child: Text(
contact.displayName.isNotEmpty
? contact.displayName[0].toUpperCase()
maskedName.isNotEmpty
? maskedName[0]
: '?',
style: TextStyle(color: darken(avatarColor, 0.4)),
),
),
title: Text(
contact.displayName,
maskedName,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
@ -367,7 +385,7 @@ class CallDetailsPage extends StatelessWidget {
const SizedBox(width: 16),
Expanded(
child: Text(
contact.displayName,
_maskName(contact.displayName),
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
@ -399,7 +417,7 @@ class CallDetailsPage extends StatelessWidget {
if (contact.phones.isNotEmpty)
DetailRow(
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 '../../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>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@ -128,14 +139,7 @@ class _MyHomePageState extends State<MyHomePage>
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);
},
);
return _buildSuggestionTile(contact, controller);
}).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 {