diff --git a/dialer/android/app/build.gradle b/dialer/android/app/build.gradle index 0fcc76c..48dd268 100644 --- a/dialer/android/app/build.gradle +++ b/dialer/android/app/build.gradle @@ -11,12 +11,12 @@ android { ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_17 } defaultConfig { diff --git a/dialer/android/gradle/wrapper/gradle-wrapper.properties b/dialer/android/gradle/wrapper/gradle-wrapper.properties index 7bb2df6..22041e5 100644 --- a/dialer/android/gradle/wrapper/gradle-wrapper.properties +++ b/dialer/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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 \ No newline at end of file diff --git a/dialer/android/settings.gradle b/dialer/android/settings.gradle index b9e43bd..b5e1b3f 100644 --- a/dialer/android/settings.gradle +++ b/dialer/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "com.android.application" version "8.3.2" apply false + id "org.jetbrains.kotlin.android" version "2.0.20" apply false } include ":app" diff --git a/dialer/lib/features/composition/composition.dart b/dialer/lib/features/composition/composition.dart index 5be09be..6edfa01 100644 --- a/dialer/lib/features/composition/composition.dart +++ b/dialer/lib/features/composition/composition.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../services/contact_service.dart'; +import '../../services/obfuscate_service.dart'; // Import ObfuscateService import '../contacts/widgets/add_contact_button.dart'; class CompositionPage extends StatefulWidget { @@ -19,6 +20,9 @@ class _CompositionPageState extends State { List _filteredContacts = []; final ContactService _contactService = ContactService(); + // Instantiate the ObfuscateService + final ObfuscateService _obfuscateService = ObfuscateService(); + @override void initState() { super.initState(); @@ -114,16 +118,12 @@ class _CompositionPageState extends State { : 'No phone number'; return ListTile( title: Text( - '${contact.displayName.isNotEmpty ? contact.displayName[0] : ''}...${contact.displayName.isNotEmpty ? contact.displayName[contact.displayName.length - 1] : ''}', - // contact.displayName - style: - const TextStyle(color: Colors.white), + _obfuscateService.obfuscateData(contact.displayName), + style: const TextStyle(color: Colors.white), ), subtitle: Text( - '${phoneNumber.isNotEmpty ? phoneNumber[0] : ''}...${phoneNumber.isNotEmpty ? phoneNumber[phoneNumber.length - 1] : ''}', - // phoneNumber, - style: - const TextStyle(color: Colors.grey), + _obfuscateService.obfuscateData(phoneNumber), + style: const TextStyle(color: Colors.grey), ), trailing: Row( mainAxisSize: MainAxisSize.min, diff --git a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart index c144084..4595b24 100644 --- a/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/features/contacts/widgets/alphabet_scroll_page.dart @@ -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:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../contact_state.dart'; -import '../../../widgets/color_darkener.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; import 'share_own_qr.dart'; @@ -24,6 +26,8 @@ class AlphabetScrollPage extends StatefulWidget { class _AlphabetScrollPageState extends State { late ScrollController _scrollController; + final ObfuscateService _obfuscateService = ObfuscateService(); + @override void initState() { super.initState(); @@ -124,7 +128,7 @@ class _AlphabetScrollPageState extends State { vertical: 8.0, horizontal: 16.0), child: Text( letter, - style: TextStyle( + style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white, @@ -134,31 +138,25 @@ class _AlphabetScrollPageState extends State { // Contact Entries ...contactsForLetter.map((contact) { String phoneNumber = contact.phones.isNotEmpty - ? contact.phones.first.number + ? _obfuscateService.obfuscateData(contact.phones.first.number) : 'No phone number'; Color avatarColor = - generateColorFromName(contact.displayName); + generateColorFromName(contact.displayName); return 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: TextStyle(color: Colors.white)), - subtitle: Text(phoneNumber, - style: TextStyle(color: Colors.white70)), + leading: ObfuscatedAvatar( + imageBytes: contact.thumbnail, + radius: 25, + backgroundColor: avatarColor, + fallbackInitial: contact.displayName, + ), + title: Text( + _obfuscateService.obfuscateData(contact.displayName), + style: const TextStyle(color: Colors.white), + ), + subtitle: Text( + phoneNumber, + style: const TextStyle(color: Colors.white70), + ), onTap: () { showModalBottomSheet( context: context, @@ -170,8 +168,8 @@ class _AlphabetScrollPageState extends State { onEdit: () async { if (await FlutterContacts.requestPermission()) { final updatedContact = - await FlutterContacts.openExternalEdit( - contact.id); + await FlutterContacts.openExternalEdit( + contact.id); if (updatedContact != null) { await _refreshContacts(); Navigator.of(context).pop(); @@ -187,7 +185,7 @@ class _AlphabetScrollPageState extends State { .showSnackBar( SnackBar( content: - Text('Edit canceled or failed.'), + Text('Edit canceled or failed.'), ), ); } diff --git a/dialer/lib/features/contacts/widgets/contact_modal.dart b/dialer/lib/features/contacts/widgets/contact_modal.dart index 4f99974..fe42bb2 100644 --- a/dialer/lib/features/contacts/widgets/contact_modal.dart +++ b/dialer/lib/features/contacts/widgets/contact_modal.dart @@ -1,3 +1,6 @@ +import 'dart:ui'; + +import 'package:dialer/services/obfuscate_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -25,6 +28,7 @@ class ContactModal extends StatefulWidget { class _ContactModalState extends State { late String phoneNumber; bool isBlocked = false; + final ObfuscateService _obfuscateService = ObfuscateService(); @override void initState() { @@ -94,50 +98,51 @@ class _ContactModalState extends State { } } -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'), - ), - ], - ), - ); + 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 ${_obfuscateService.obfuscateData(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); + 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')), - ); + // Show success message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${_obfuscateService.obfuscateData(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')), - ); + // 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 Widget build(BuildContext context) { String email = widget.contact.emails.isNotEmpty - ? widget.contact.emails.first.address + ? _obfuscateService.obfuscateData(widget.contact.emails.first.address) : 'No email'; return GestureDetector( @@ -151,7 +156,7 @@ void _deleteContact() async { decoration: BoxDecoration( color: Colors.grey[900], borderRadius: - const BorderRadius.vertical(top: Radius.circular(20)), + const BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, @@ -184,6 +189,7 @@ void _deleteContact() async { if (choice == 'delete') { _deleteContact(); } + // Handle other choices if needed }, itemBuilder: (BuildContext context) { return [ @@ -202,7 +208,7 @@ void _deleteContact() async { const PopupMenuItem( value: 'create_shortcut', child: - Text('Create shortcut (to home screen)'), + Text('Create shortcut (to home screen)'), ), const PopupMenuItem( value: 'set_ringtone', @@ -220,40 +226,32 @@ void _deleteContact() async { padding: const EdgeInsets.all(16.0), child: Column( children: [ - CircleAvatar( + ObfuscatedAvatar( + imageBytes: widget.contact.thumbnail, radius: 50, - backgroundImage: (widget.contact.thumbnail != null && - widget.contact.thumbnail!.isNotEmpty) - ? MemoryImage(widget.contact.thumbnail!) - : null, backgroundColor: - generateColorFromName(widget.contact.displayName), - child: (widget.contact.thumbnail == null || - widget.contact.thumbnail!.isEmpty) - ? Text( - widget.contact.displayName.isNotEmpty - ? widget.contact.displayName[0] - .toUpperCase() - : '?', - style: const TextStyle( - fontSize: 40, color: Colors.white), - ) - : null, + generateColorFromName(widget.contact.displayName), + fallbackInitial: widget.contact.displayName, ), const SizedBox(height: 10), Text( - widget.contact.displayName, + _obfuscateService.obfuscateData(widget.contact.displayName), 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 ListTile( leading: const Icon(Icons.phone, color: Colors.green), - title: Text(phoneNumber), + title: Text( + _obfuscateService.obfuscateData(phoneNumber), + style: const TextStyle(color: Colors.white), + ), onTap: () { if (widget.contact.phones.isNotEmpty) { _launchPhoneDialer(phoneNumber); @@ -262,7 +260,10 @@ void _deleteContact() async { ), ListTile( leading: const Icon(Icons.message, color: Colors.blue), - title: Text(phoneNumber), + title: Text( + _obfuscateService.obfuscateData(phoneNumber), + style: const TextStyle(color: Colors.white), + ), onTap: () { if (widget.contact.phones.isNotEmpty) { _launchSms(phoneNumber); @@ -271,14 +272,17 @@ void _deleteContact() async { ), ListTile( leading: const Icon(Icons.email, color: Colors.orange), - title: Text(email), + title: Text( + email, + style: const TextStyle(color: Colors.white), + ), onTap: () { if (widget.contact.emails.isNotEmpty) { _launchEmail(email); } }, ), - const Divider(), + const Divider(color: Colors.grey), // Favorite, Edit, and Block/Unblock Buttons Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), @@ -286,8 +290,7 @@ void _deleteContact() async { children: [ // Favorite button SizedBox( - width: double - .infinity, // This makes the button take full width + width: double.infinity, child: ElevatedButton.icon( onPressed: () { Navigator.of(context).pop(); @@ -296,35 +299,32 @@ void _deleteContact() async { icon: Icon(widget.isFavorite ? Icons.star : Icons.star_border), - label: Text( - widget.isFavorite ? 'Unfavorite' : 'Favorite'), + label: Text(widget.isFavorite + ? 'Unfavorite' + : 'Favorite'), ), ), - const SizedBox(height: 10), // Space between buttons - + const SizedBox(height: 10), // Edit button SizedBox( - width: double - .infinity, // This makes the button take full width + width: double.infinity, child: ElevatedButton.icon( onPressed: () => widget.onEdit(), icon: const Icon(Icons.edit), label: const Text('Edit Contact'), ), ), - const SizedBox(height: 10), // Space between buttons - + const SizedBox(height: 10), // Block/Unblock button SizedBox( - width: double - .infinity, // This makes the button take full width + width: double.infinity, child: ElevatedButton.icon( onPressed: _toggleBlockState, icon: Icon( isBlocked ? Icons.block : Icons.block_flipped), label: Text(isBlocked ? 'Unblock' : 'Block'), + ), ), - ), ], ), ), diff --git a/dialer/lib/features/history/history_page.dart b/dialer/lib/features/history/history_page.dart index 589a136..b8c0567 100644 --- a/dialer/lib/features/history/history_page.dart +++ b/dialer/lib/features/history/history_page.dart @@ -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_contacts/flutter_contacts.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.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 { final Contact contact; @@ -34,6 +35,8 @@ class _HistoryPageState extends State with SingleTickerProviderStat bool loading = true; int? _expandedIndex; + final ObfuscateService _obfuscateService = ObfuscateService(); + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -206,21 +209,21 @@ class _HistoryPageState extends State with SingleTickerProviderStat 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)), + leading: ObfuscatedAvatar( + imageBytes: contact.thumbnail, + radius: 25, + backgroundColor: avatarColor, + fallbackInitial: contact.displayName, ), - ), + // child: Text( + // contact.displayName.isNotEmpty + // ? contact.displayName[0].toUpperCase() + // : '?', + // style: TextStyle(color: darken(avatarColor, 0.4)), + // ), + // ), title: Text( - contact.displayName, + _obfuscateService.obfuscateData(contact.displayName), style: const TextStyle(color: Colors.white), ), subtitle: Text( @@ -329,12 +332,15 @@ class _HistoryPageState extends State with SingleTickerProviderStat class CallDetailsPage extends StatelessWidget { final History history; + final ObfuscateService _obfuscateService = ObfuscateService(); - const CallDetailsPage({Key? key, required this.history}) : super(key: key); + CallDetailsPage({super.key, required this.history}); @override Widget build(BuildContext context) { final contact = history.contact; + final contactBg = generateColorFromName(contact.displayName); + final contactLetter = darken(contactBg); return Scaffold( backgroundColor: Colors.black, @@ -350,24 +356,26 @@ class CallDetailsPage extends StatelessWidget { Row( children: [ (contact.thumbnail != null && contact.thumbnail!.isNotEmpty) - ? CircleAvatar( - backgroundImage: MemoryImage(contact.thumbnail!), - radius: 30, - ) + ? ObfuscatedAvatar( + imageBytes: contact.thumbnail, + radius: 30, + backgroundColor: contactBg, + fallbackInitial: contact.displayName, + ) : CircleAvatar( - backgroundColor: Colors.grey[700], + backgroundColor: generateColorFromName(contact.displayName), radius: 30, child: Text( contact.displayName.isNotEmpty ? contact.displayName[0].toUpperCase() : '?', - style: const TextStyle(color: Colors.white), + style: TextStyle(color: contactLetter), ), ), const SizedBox(width: 16), Expanded( child: Text( - contact.displayName, + _obfuscateService.obfuscateData(contact.displayName), style: const TextStyle(color: Colors.white, fontSize: 24), ), ), @@ -399,7 +407,7 @@ class CallDetailsPage extends StatelessWidget { if (contact.phones.isNotEmpty) DetailRow( label: 'Number:', - value: contact.phones.first.number, + value: _obfuscateService.obfuscateData(contact.phones.first.number), ), ], ), diff --git a/dialer/lib/features/home/home_page.dart b/dialer/lib/features/home/home_page.dart index 23530a1..ac55cf8 100644 --- a/dialer/lib/features/home/home_page.dart +++ b/dialer/lib/features/home/home_page.dart @@ -1,3 +1,4 @@ +import 'package:dialer/services/obfuscate_service.dart'; import 'package:flutter/material.dart'; import 'package:dialer/features/contacts/contact_page.dart'; import 'package:dialer/features/favorites/favorites_page.dart'; @@ -13,7 +14,7 @@ class _MyHomePageState extends State List _allContacts = []; List _contactSuggestions = []; final ContactService _contactService = ContactService(); - + final ObfuscateService _obfuscateService = ObfuscateService(); @override void initState() { @@ -130,7 +131,7 @@ class _MyHomePageState extends State return _contactSuggestions.map((contact) { return ListTile( key: ValueKey(contact.id), - title: Text(contact.displayName, + title: Text(_obfuscateService.obfuscateData(contact.displayName), style: const TextStyle(color: Colors.white)), onTap: () { controller.closeView(contact.displayName); diff --git a/dialer/lib/globals.dart b/dialer/lib/globals.dart new file mode 100644 index 0000000..2750ab2 --- /dev/null +++ b/dialer/lib/globals.dart @@ -0,0 +1 @@ +bool isStealthMode = false; \ No newline at end of file diff --git a/dialer/lib/main.dart b/dialer/lib/main.dart index a9ca957..1f760e8 100644 --- a/dialer/lib/main.dart +++ b/dialer/lib/main.dart @@ -1,13 +1,16 @@ import 'package:dialer/features/home/home_page.dart'; import 'package:flutter/material.dart'; import 'package:dialer/features/contacts/contact_state.dart'; +import 'globals.dart' as globals; void main() { - runApp(const MyApp()); + const stealthFlag = String.fromEnvironment('STEALTH', defaultValue: 'false'); + globals.isStealthMode = stealthFlag.toLowerCase() == 'true'; + runApp(const Dialer()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class Dialer extends StatelessWidget { + const Dialer({super.key}); @override Widget build(BuildContext context) { diff --git a/dialer/lib/services/obfuscate_service.dart b/dialer/lib/services/obfuscate_service.dart new file mode 100644 index 0000000..6eaf43f --- /dev/null +++ b/dialer/lib/services/obfuscate_service.dart @@ -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, + ), + ), + ); + } + } +} diff --git a/dialer/pubspec.yaml b/dialer/pubspec.yaml index 6e36f8c..33623b4 100644 --- a/dialer/pubspec.yaml +++ b/dialer/pubspec.yaml @@ -40,7 +40,6 @@ dependencies: permission_handler: ^11.3.1 # For handling permissions cached_network_image: ^3.2.3 # For caching contact images qr_flutter: ^4.1.0 - android_intent_plus: ^5.2.0 camera: ^0.10.0+2 mobile_scanner: ^6.0.2 pretty_qr_code: ^3.3.0 diff --git a/dialer/stealth_local_run.sh b/dialer/stealth_local_run.sh new file mode 100755 index 0000000..95cc270 --- /dev/null +++ b/dialer/stealth_local_run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +echo "Running Icing Dialer in STEALTH mode..." +flutter run --dart-define=STEALTH=true \ No newline at end of file diff --git a/dialer/test/widget_test.dart b/dialer/test/widget_test.dart index 7b0e078..15b52a2 100644 --- a/dialer/test/widget_test.dart +++ b/dialer/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:dialer/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const Dialer()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);