stealth-mode (#27)
All checks were successful
/ mirror (push) Successful in 4s
/ deploy (push) Successful in 56s

Reviewed-on: #27
This commit is contained in:
stcb 2025-01-26 12:32:37 +00:00 committed by Florian Griffon
parent e3f52d5ba4
commit b1331508a1
14 changed files with 249 additions and 144 deletions

View File

@ -11,12 +11,12 @@ android {
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8 jvmTarget = JavaVersion.VERSION_17
} }
defaultConfig { defaultConfig {

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip

View File

@ -18,8 +18,8 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false id "com.android.application" version "8.3.2" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false id "org.jetbrains.kotlin.android" version "2.0.20" apply false
} }
include ":app" include ":app"

View File

@ -4,6 +4,7 @@ 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 '../../services/contact_service.dart'; import '../../services/contact_service.dart';
import '../../services/obfuscate_service.dart'; // Import ObfuscateService
import '../contacts/widgets/add_contact_button.dart'; import '../contacts/widgets/add_contact_button.dart';
class CompositionPage extends StatefulWidget { class CompositionPage extends StatefulWidget {
@ -19,6 +20,9 @@ class _CompositionPageState extends State<CompositionPage> {
List<Contact> _filteredContacts = []; List<Contact> _filteredContacts = [];
final ContactService _contactService = ContactService(); final ContactService _contactService = ContactService();
// Instantiate the ObfuscateService
final ObfuscateService _obfuscateService = ObfuscateService();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -114,16 +118,12 @@ class _CompositionPageState extends State<CompositionPage> {
: 'No phone number'; : 'No phone number';
return ListTile( return ListTile(
title: Text( title: Text(
'${contact.displayName.isNotEmpty ? contact.displayName[0] : ''}...${contact.displayName.isNotEmpty ? contact.displayName[contact.displayName.length - 1] : ''}', _obfuscateService.obfuscateData(contact.displayName),
// contact.displayName style: const TextStyle(color: Colors.white),
style:
const TextStyle(color: Colors.white),
), ),
subtitle: Text( subtitle: Text(
'${phoneNumber.isNotEmpty ? phoneNumber[0] : ''}...${phoneNumber.isNotEmpty ? phoneNumber[phoneNumber.length - 1] : ''}', _obfuscateService.obfuscateData(phoneNumber),
// phoneNumber, style: const TextStyle(color: Colors.grey),
style:
const TextStyle(color: Colors.grey),
), ),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -1,8 +1,10 @@
// alphabet_scrollpage.dart
import 'dart:ui';
import 'package:dialer/services/obfuscate_service.dart';
import 'package:dialer/widgets/username_color_generator.dart'; import 'package:dialer/widgets/username_color_generator.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 '../contact_state.dart'; import '../contact_state.dart';
import '../../../widgets/color_darkener.dart';
import 'add_contact_button.dart'; import 'add_contact_button.dart';
import 'contact_modal.dart'; import 'contact_modal.dart';
import 'share_own_qr.dart'; import 'share_own_qr.dart';
@ -24,6 +26,8 @@ class AlphabetScrollPage extends StatefulWidget {
class _AlphabetScrollPageState extends State<AlphabetScrollPage> { class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
late ScrollController _scrollController; late ScrollController _scrollController;
final ObfuscateService _obfuscateService = ObfuscateService();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -124,7 +128,7 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
vertical: 8.0, horizontal: 16.0), vertical: 8.0, horizontal: 16.0),
child: Text( child: Text(
letter, letter,
style: TextStyle( style: const TextStyle(
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: Colors.white,
@ -134,31 +138,25 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
// Contact Entries // Contact Entries
...contactsForLetter.map((contact) { ...contactsForLetter.map((contact) {
String phoneNumber = contact.phones.isNotEmpty String phoneNumber = contact.phones.isNotEmpty
? contact.phones.first.number ? _obfuscateService.obfuscateData(contact.phones.first.number)
: 'No phone number'; : 'No phone number';
Color avatarColor = Color avatarColor =
generateColorFromName(contact.displayName); generateColorFromName(contact.displayName);
return ListTile( return ListTile(
leading: (contact.thumbnail != null && leading: ObfuscatedAvatar(
contact.thumbnail!.isNotEmpty) imageBytes: contact.thumbnail,
? CircleAvatar( radius: 25,
backgroundImage: backgroundColor: avatarColor,
MemoryImage(contact.thumbnail!), fallbackInitial: contact.displayName,
) ),
: CircleAvatar( title: Text(
backgroundColor: avatarColor, _obfuscateService.obfuscateData(contact.displayName),
child: Text( style: const TextStyle(color: Colors.white),
contact.displayName.isNotEmpty ),
? contact.displayName[0].toUpperCase() subtitle: Text(
: '?', phoneNumber,
style: TextStyle( style: const TextStyle(color: Colors.white70),
color: darken(avatarColor, 0.4)), ),
),
),
title: Text(contact.displayName,
style: TextStyle(color: Colors.white)),
subtitle: Text(phoneNumber,
style: TextStyle(color: Colors.white70)),
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
@ -170,8 +168,8 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
onEdit: () async { onEdit: () async {
if (await FlutterContacts.requestPermission()) { if (await FlutterContacts.requestPermission()) {
final updatedContact = final updatedContact =
await FlutterContacts.openExternalEdit( await FlutterContacts.openExternalEdit(
contact.id); contact.id);
if (updatedContact != null) { if (updatedContact != null) {
await _refreshContacts(); await _refreshContacts();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -187,7 +185,7 @@ class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
.showSnackBar( .showSnackBar(
SnackBar( SnackBar(
content: content:
Text('Edit canceled or failed.'), Text('Edit canceled or failed.'),
), ),
); );
} }

View File

@ -1,3 +1,6 @@
import 'dart:ui';
import 'package:dialer/services/obfuscate_service.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:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -25,6 +28,7 @@ class ContactModal extends StatefulWidget {
class _ContactModalState extends State<ContactModal> { class _ContactModalState extends State<ContactModal> {
late String phoneNumber; late String phoneNumber;
bool isBlocked = false; bool isBlocked = false;
final ObfuscateService _obfuscateService = ObfuscateService();
@override @override
void initState() { void initState() {
@ -94,50 +98,51 @@ class _ContactModalState extends State<ContactModal> {
} }
} }
void _deleteContact() async { void _deleteContact() async {
final bool shouldDelete = await showDialog( final bool shouldDelete = await showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Delete Contact'), title: const Text('Delete Contact'),
content: Text('Are you sure you want to delete ${widget.contact.displayName}?'), content: Text(
actions: [ 'Are you sure you want to delete ${_obfuscateService.obfuscateData(widget.contact.displayName)}?'),
TextButton( actions: [
onPressed: () => Navigator.of(context).pop(false), TextButton(
child: const Text('Cancel'), onPressed: () => Navigator.of(context).pop(false),
), child: const Text('Cancel'),
TextButton( ),
onPressed: () => Navigator.of(context).pop(true), TextButton(
child: const Text('Delete'), onPressed: () => Navigator.of(context).pop(true),
), child: const Text('Delete'),
], ),
), ],
); ),
);
if (shouldDelete) { if (shouldDelete) {
try { try {
// Delete the contact // Delete the contact
await FlutterContacts.deleteContact(widget.contact); await FlutterContacts.deleteContact(widget.contact);
// Show success message // Show success message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${widget.contact.displayName} deleted')), SnackBar(content: Text('${_obfuscateService.obfuscateData(widget.contact.displayName)} deleted')),
); );
// Close the modal // Close the modal
Navigator.of(context).pop(); Navigator.of(context).pop();
} catch (e) { } catch (e) {
// Handle errors and show a failure message // Handle errors and show a failure message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to delete ${widget.contact.displayName}: $e')), 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 email = widget.contact.emails.isNotEmpty
? widget.contact.emails.first.address ? _obfuscateService.obfuscateData(widget.contact.emails.first.address)
: 'No email'; : 'No email';
return GestureDetector( return GestureDetector(
@ -151,7 +156,7 @@ void _deleteContact() async {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[900], color: Colors.grey[900],
borderRadius: borderRadius:
const BorderRadius.vertical(top: Radius.circular(20)), const BorderRadius.vertical(top: Radius.circular(20)),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -184,6 +189,7 @@ void _deleteContact() async {
if (choice == 'delete') { if (choice == 'delete') {
_deleteContact(); _deleteContact();
} }
// Handle other choices if needed
}, },
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return [ return [
@ -202,7 +208,7 @@ void _deleteContact() async {
const PopupMenuItem<String>( const 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>( const PopupMenuItem<String>(
value: 'set_ringtone', value: 'set_ringtone',
@ -220,40 +226,32 @@ void _deleteContact() async {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
children: [ children: [
CircleAvatar( ObfuscatedAvatar(
imageBytes: widget.contact.thumbnail,
radius: 50, radius: 50,
backgroundImage: (widget.contact.thumbnail != null &&
widget.contact.thumbnail!.isNotEmpty)
? MemoryImage(widget.contact.thumbnail!)
: null,
backgroundColor: backgroundColor:
generateColorFromName(widget.contact.displayName), generateColorFromName(widget.contact.displayName),
child: (widget.contact.thumbnail == null || fallbackInitial: widget.contact.displayName,
widget.contact.thumbnail!.isEmpty)
? Text(
widget.contact.displayName.isNotEmpty
? widget.contact.displayName[0]
.toUpperCase()
: '?',
style: const TextStyle(
fontSize: 40, color: Colors.white),
)
: null,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
widget.contact.displayName, _obfuscateService.obfuscateData(widget.contact.displayName),
style: const TextStyle( style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold), fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
), ),
], ],
), ),
), ),
const Divider(), const Divider(color: Colors.grey),
// Contact Actions // Contact Actions
ListTile( ListTile(
leading: const Icon(Icons.phone, color: Colors.green), leading: const Icon(Icons.phone, color: Colors.green),
title: Text(phoneNumber), title: Text(
_obfuscateService.obfuscateData(phoneNumber),
style: const TextStyle(color: Colors.white),
),
onTap: () { onTap: () {
if (widget.contact.phones.isNotEmpty) { if (widget.contact.phones.isNotEmpty) {
_launchPhoneDialer(phoneNumber); _launchPhoneDialer(phoneNumber);
@ -262,7 +260,10 @@ void _deleteContact() async {
), ),
ListTile( ListTile(
leading: const Icon(Icons.message, color: Colors.blue), leading: const Icon(Icons.message, color: Colors.blue),
title: Text(phoneNumber), title: Text(
_obfuscateService.obfuscateData(phoneNumber),
style: const TextStyle(color: Colors.white),
),
onTap: () { onTap: () {
if (widget.contact.phones.isNotEmpty) { if (widget.contact.phones.isNotEmpty) {
_launchSms(phoneNumber); _launchSms(phoneNumber);
@ -271,14 +272,17 @@ void _deleteContact() async {
), ),
ListTile( ListTile(
leading: const Icon(Icons.email, color: Colors.orange), leading: const Icon(Icons.email, color: Colors.orange),
title: Text(email), title: Text(
email,
style: const TextStyle(color: Colors.white),
),
onTap: () { onTap: () {
if (widget.contact.emails.isNotEmpty) { if (widget.contact.emails.isNotEmpty) {
_launchEmail(email); _launchEmail(email);
} }
}, },
), ),
const Divider(), const Divider(color: Colors.grey),
// Favorite, Edit, and Block/Unblock Buttons // Favorite, Edit, and Block/Unblock Buttons
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
@ -286,8 +290,7 @@ void _deleteContact() async {
children: [ children: [
// Favorite button // Favorite button
SizedBox( SizedBox(
width: double width: double.infinity,
.infinity, // This makes the button take full width
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -296,35 +299,32 @@ void _deleteContact() async {
icon: Icon(widget.isFavorite icon: Icon(widget.isFavorite
? Icons.star ? Icons.star
: Icons.star_border), : Icons.star_border),
label: Text( label: Text(widget.isFavorite
widget.isFavorite ? 'Unfavorite' : 'Favorite'), ? 'Unfavorite'
: 'Favorite'),
), ),
), ),
const SizedBox(height: 10), // Space between buttons const SizedBox(height: 10),
// Edit button // Edit button
SizedBox( SizedBox(
width: double width: double.infinity,
.infinity, // This makes the button take full width
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => widget.onEdit(), onPressed: () => widget.onEdit(),
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
label: const Text('Edit Contact'), label: const Text('Edit Contact'),
), ),
), ),
const SizedBox(height: 10), // Space between buttons const SizedBox(height: 10),
// Block/Unblock button // Block/Unblock button
SizedBox( SizedBox(
width: double width: double.infinity,
.infinity, // This makes the button take full width
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: _toggleBlockState, onPressed: _toggleBlockState,
icon: Icon( icon: Icon(
isBlocked ? Icons.block : Icons.block_flipped), isBlocked ? Icons.block : Icons.block_flipped),
label: Text(isBlocked ? 'Unblock' : 'Block'), label: Text(isBlocked ? 'Unblock' : 'Block'),
),
), ),
),
], ],
), ),
), ),

View File

@ -1,10 +1,11 @@
import 'package:dialer/services/obfuscate_service.dart';
import 'package:dialer/widgets/color_darkener.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';
import 'package:url_launcher/url_launcher.dart'; 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/username_color_generator.dart';
import 'package:dialer/widgets/color_darkener.dart';
class History { class History {
final Contact contact; final Contact contact;
@ -34,6 +35,8 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
bool loading = true; bool loading = true;
int? _expandedIndex; int? _expandedIndex;
final ObfuscateService _obfuscateService = ObfuscateService();
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
@ -206,21 +209,21 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
return Column( return Column(
children: [ children: [
ListTile( ListTile(
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) leading: ObfuscatedAvatar(
? CircleAvatar( imageBytes: contact.thumbnail,
backgroundImage: MemoryImage(contact.thumbnail!), radius: 25,
) backgroundColor: avatarColor,
: CircleAvatar( fallbackInitial: contact.displayName,
backgroundColor: avatarColor,
child: Text(
contact.displayName.isNotEmpty
? contact.displayName[0].toUpperCase()
: '?',
style: TextStyle(color: darken(avatarColor, 0.4)),
), ),
), // child: Text(
// contact.displayName.isNotEmpty
// ? contact.displayName[0].toUpperCase()
// : '?',
// style: TextStyle(color: darken(avatarColor, 0.4)),
// ),
// ),
title: Text( title: Text(
contact.displayName, _obfuscateService.obfuscateData(contact.displayName),
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
subtitle: Text( subtitle: Text(
@ -329,12 +332,15 @@ class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStat
class CallDetailsPage extends StatelessWidget { class CallDetailsPage extends StatelessWidget {
final History history; final History history;
final ObfuscateService _obfuscateService = ObfuscateService();
const CallDetailsPage({Key? key, required this.history}) : super(key: key); CallDetailsPage({super.key, required this.history});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final contact = history.contact; final contact = history.contact;
final contactBg = generateColorFromName(contact.displayName);
final contactLetter = darken(contactBg);
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
@ -350,24 +356,26 @@ class CallDetailsPage extends StatelessWidget {
Row( Row(
children: [ children: [
(contact.thumbnail != null && contact.thumbnail!.isNotEmpty) (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? CircleAvatar( ? ObfuscatedAvatar(
backgroundImage: MemoryImage(contact.thumbnail!), imageBytes: contact.thumbnail,
radius: 30, radius: 30,
) backgroundColor: contactBg,
fallbackInitial: contact.displayName,
)
: CircleAvatar( : CircleAvatar(
backgroundColor: Colors.grey[700], backgroundColor: generateColorFromName(contact.displayName),
radius: 30, radius: 30,
child: Text( child: Text(
contact.displayName.isNotEmpty contact.displayName.isNotEmpty
? contact.displayName[0].toUpperCase() ? contact.displayName[0].toUpperCase()
: '?', : '?',
style: const TextStyle(color: Colors.white), style: TextStyle(color: contactLetter),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Text( child: Text(
contact.displayName, _obfuscateService.obfuscateData(contact.displayName),
style: const TextStyle(color: Colors.white, fontSize: 24), style: const TextStyle(color: Colors.white, fontSize: 24),
), ),
), ),
@ -399,7 +407,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: _obfuscateService.obfuscateData(contact.phones.first.number),
), ),
], ],
), ),

View File

@ -1,3 +1,4 @@
import 'package:dialer/services/obfuscate_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_page.dart'; import 'package:dialer/features/contacts/contact_page.dart';
import 'package:dialer/features/favorites/favorites_page.dart'; import 'package:dialer/features/favorites/favorites_page.dart';
@ -15,6 +16,7 @@ class _MyHomePageState extends State<MyHomePage>
List<Contact> _contactSuggestions = []; List<Contact> _contactSuggestions = [];
final ContactService _contactService = ContactService(); final ContactService _contactService = ContactService();
final SearchController _searchController = SearchController(); final SearchController _searchController = SearchController();
final ObfuscateService _obfuscateService = ObfuscateService();
@override @override
void initState() { void initState() {
@ -160,7 +162,7 @@ class _MyHomePageState extends State<MyHomePage>
return _contactSuggestions.map((contact) { return _contactSuggestions.map((contact) {
return ListTile( return ListTile(
key: ValueKey(contact.id), key: ValueKey(contact.id),
title: Text(contact.displayName, title: Text(_obfuscateService.obfuscateData(contact.displayName),
style: const TextStyle(color: Colors.white)), style: const TextStyle(color: Colors.white)),
onTap: () { onTap: () {
// Clear the search text input // Clear the search text input

1
dialer/lib/globals.dart Normal file
View File

@ -0,0 +1 @@
bool isStealthMode = false;

View File

@ -1,13 +1,16 @@
import 'package:dialer/features/home/home_page.dart'; import 'package:dialer/features/home/home_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_state.dart'; import 'package:dialer/features/contacts/contact_state.dart';
import 'globals.dart' as globals;
void main() { void main() {
runApp(const MyApp()); const stealthFlag = String.fromEnvironment('STEALTH', defaultValue: 'false');
globals.isStealthMode = stealthFlag.toLowerCase() == 'true';
runApp(const Dialer());
} }
class MyApp extends StatelessWidget { class Dialer extends StatelessWidget {
const MyApp({super.key}); const Dialer({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -0,0 +1,91 @@
// lib/services/obfuscate_service.dart
import 'package:dialer/widgets/color_darkener.dart';
import '../../globals.dart' as globals;
import 'dart:ui';
import 'dart:typed_data';
import 'package:flutter/material.dart';
class ObfuscateService {
// Private constructor
ObfuscateService._privateConstructor();
// Singleton instance
static final ObfuscateService _instance = ObfuscateService._privateConstructor();
// Factory constructor to return the same instance
factory ObfuscateService() {
return _instance;
}
// Public method to obfuscate data
String obfuscateData(String data) {
if (globals.isStealthMode) {
return _obfuscateData(data);
} else {
return data;
}
}
// Private helper method for obfuscation logic
String _obfuscateData(String data) {
if (data.isNotEmpty) {
// Ensure the string has at least two characters to obfuscate
if (data.length == 1) {
return '${data[0]}';
} else {
return '${data[0]}...${data[data.length - 1]}';
}
}
return '';
}
}
class ObfuscatedAvatar extends StatelessWidget {
final Uint8List? imageBytes;
final double radius;
final Color backgroundColor;
final String? fallbackInitial;
const ObfuscatedAvatar({
Key? key,
required this.imageBytes,
this.radius = 25,
this.backgroundColor = Colors.grey,
this.fallbackInitial,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (imageBytes != null && imageBytes!.isNotEmpty) {
return ClipOval(
child: ImageFiltered(
imageFilter: globals.isStealthMode
? ImageFilter.blur(sigmaX: 10, sigmaY: 10)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Image.memory(
imageBytes!,
fit: BoxFit.cover,
width: radius * 2,
height: radius * 2,
),
),
);
} else {
return CircleAvatar(
radius: radius,
backgroundColor: backgroundColor,
child: Text(
fallbackInitial != null && fallbackInitial!.isNotEmpty
? fallbackInitial![0].toUpperCase()
: '?',
style: TextStyle(
color: darken(backgroundColor),
fontSize: radius,
),
),
);
}
}
}

View File

@ -40,7 +40,6 @@ dependencies:
permission_handler: ^11.3.1 # For handling permissions permission_handler: ^11.3.1 # For handling permissions
cached_network_image: ^3.2.3 # For caching contact images cached_network_image: ^3.2.3 # For caching contact images
qr_flutter: ^4.1.0 qr_flutter: ^4.1.0
android_intent_plus: ^5.2.0
camera: ^0.10.0+2 camera: ^0.10.0+2
mobile_scanner: ^6.0.2 mobile_scanner: ^6.0.2
pretty_qr_code: ^3.3.0 pretty_qr_code: ^3.3.0

3
dialer/stealth_local_run.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
echo "Running Icing Dialer in STEALTH mode..."
flutter run --dart-define=STEALTH=true

View File

@ -13,7 +13,7 @@ import 'package:dialer/main.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(const MyApp()); await tester.pumpWidget(const Dialer());
// Verify that our counter starts at 0. // Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsOneWidget);