Compare commits

..

No commits in common. "9bfb55821dca3fa883cbca53cfe52251a871c33e" and "7c7a4f28f4446224efb838132d58e0c94a649ae5" have entirely different histories.

6 changed files with 94 additions and 214 deletions

View File

@ -10,22 +10,8 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
with: with:
subpath: dialer/ subpath: dialer/
- uses: docker://git.gmoker.com/icing/flutter:main - uses: icing/flutter@main
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
with: with:
name: icing-dialer-${{ gitea.ref_name }}-${{ gitea.run_id }}.apk name: icing-dialer-${{ gitea.ref_name }}-${{ gitea.run_id }}.apk
path: build/app/outputs/flutter-apk/app-release.apk path: build/app/outputs/flutter-apk/app-release.apk
build-stealth:
runs-on: debian
steps:
- uses: actions/checkout@v1
with:
subpath: dialer/
- uses: docker://git.gmoker.com/icing/flutter:main
with:
args: "build apk --dart-define=STEALTH=true"
- uses: actions/upload-artifact@v1
with:
name: icing-dialer-stealth-${{ gitea.ref_name }}-${{ gitea.run_id }}.apk
path: build/app/outputs/flutter-apk/app-release.apk

View File

@ -2,9 +2,4 @@
IMG=git.gmoker.com/icing/flutter:main IMG=git.gmoker.com/icing/flutter:main
if [ "$1" == '-s' ]; then docker run --rm -v "$PWD:/app/" "$IMG" build apk
OPT+=(--dart-define=STEALTH=true)
fi
set -x
docker run --rm -v "$PWD:/app/" "$IMG" build apk "${OPT[@]}"

View File

@ -11,7 +11,6 @@ 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 '../../services/block_service.dart'; import '../../services/block_service.dart';
import '../contacts/widgets/contact_modal.dart'; import '../contacts/widgets/contact_modal.dart';
import '../../services/call_service.dart';
class History { class History {
final Contact contact; final Contact contact;
@ -21,12 +20,12 @@ class History {
final int attempts; final int attempts;
History( History(
this.contact, this.contact,
this.date, this.date,
this.callType, this.callType,
this.callStatus, this.callStatus,
this.attempts, this.attempts,
); );
} }
class HistoryPage extends StatefulWidget { class HistoryPage extends StatefulWidget {
@ -42,7 +41,6 @@ class _HistoryPageState extends State<HistoryPage>
bool loading = true; bool loading = true;
int? _expandedIndex; int? _expandedIndex;
final ObfuscateService _obfuscateService = ObfuscateService(); final ObfuscateService _obfuscateService = ObfuscateService();
final CallService _callService = CallService();
// Create a MethodChannel instance. // Create a MethodChannel instance.
static const MethodChannel _channel = MethodChannel('com.example.calllog'); static const MethodChannel _channel = MethodChannel('com.example.calllog');
@ -85,8 +83,8 @@ class _HistoryPageState extends State<HistoryPage>
} }
} catch (e) { } catch (e) {
print("Error updating favorite status: $e"); print("Error updating favorite status: $e");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context)
SnackBar(content: Text('Failed to update favorite status'))); .showSnackBar(SnackBar(content: Text('Failed to update favorite status')));
} }
} }
@ -157,7 +155,7 @@ class _HistoryPageState extends State<HistoryPage>
// Convert timestamp to DateTime. // Convert timestamp to DateTime.
DateTime callDate = DateTime callDate =
DateTime.fromMillisecondsSinceEpoch(entry['date'] ?? 0); DateTime.fromMillisecondsSinceEpoch(entry['date'] ?? 0);
int typeInt = entry['type'] ?? 0; int typeInt = entry['type'] ?? 0;
int duration = entry['duration'] ?? 0; int duration = entry['duration'] ?? 0;
@ -195,8 +193,7 @@ class _HistoryPageState extends State<HistoryPage>
); );
} }
callHistories callHistories.add(History(matchedContact, callDate, callType, callStatus, 1));
.add(History(matchedContact, callDate, callType, callStatus, 1));
} }
// Sort histories by most recent. // Sort histories by most recent.
@ -221,7 +218,7 @@ class _HistoryPageState extends State<HistoryPage>
for (var history in historyList) { for (var history in historyList) {
final callDate = final callDate =
DateTime(history.date.year, history.date.month, history.date.day); DateTime(history.date.year, history.date.month, history.date.day);
if (callDate == today) { if (callDate == today) {
todayHistories.add(history); todayHistories.add(history);
} else if (callDate == yesterday) { } else if (callDate == yesterday) {
@ -294,7 +291,7 @@ class _HistoryPageState extends State<HistoryPage>
} }
List<History> missedCalls = List<History> missedCalls =
histories.where((h) => h.callStatus == 'missed').toList(); histories.where((h) => h.callStatus == 'missed').toList();
final allItems = _buildGroupedList(histories); final allItems = _buildGroupedList(histories);
final missedItems = _buildGroupedList(missedCalls); final missedItems = _buildGroupedList(missedCalls);
@ -363,8 +360,7 @@ class _HistoryPageState extends State<HistoryPage>
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();
@ -419,11 +415,18 @@ class _HistoryPageState extends State<HistoryPage>
icon: const Icon(Icons.phone, color: Colors.green), icon: const Icon(Icons.phone, color: Colors.green),
onPressed: () async { onPressed: () async {
if (contact.phones.isNotEmpty) { if (contact.phones.isNotEmpty) {
_callService.makeGsmCall(contact.phones.first.number); final Uri callUri =
Uri(scheme: 'tel', path: contact.phones.first.number);
if (await canLaunchUrl(callUri)) {
await launchUrl(callUri);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not launch call')),
);
}
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Contact has no phone number')),
content: Text('Contact has no phone number')),
); );
} }
}, },
@ -441,9 +444,7 @@ class _HistoryPageState extends State<HistoryPage>
color: Colors.grey[850], color: Colors.grey[850],
child: FutureBuilder<bool>( child: FutureBuilder<bool>(
future: BlockService().isNumberBlocked( future: BlockService().isNumberBlocked(
contact.phones.isNotEmpty contact.phones.isNotEmpty ? contact.phones.first.number : ''),
? contact.phones.first.number
: ''),
builder: (context, snapshot) { builder: (context, snapshot) {
final isBlocked = snapshot.data ?? false; final isBlocked = snapshot.data ?? false;
return Row( return Row(
@ -459,37 +460,29 @@ class _HistoryPageState extends State<HistoryPage>
await launchUrl(smsUri); await launchUrl(smsUri);
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Could not send message')),
content:
Text('Could not send message')),
); );
} }
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Contact has no phone number')),
content:
Text('Contact has no phone number')),
); );
} }
}, },
icon: icon: const Icon(Icons.message, color: Colors.white),
const Icon(Icons.message, color: Colors.white), label: const Text('Message', style: TextStyle(color: Colors.white)),
label: const Text('Message',
style: TextStyle(color: Colors.white)),
), ),
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => builder: (_) => CallDetailsPage(history: history),
CallDetailsPage(history: history),
), ),
); );
}, },
icon: const Icon(Icons.info, color: Colors.white), icon: const Icon(Icons.info, color: Colors.white),
label: const Text('Details', label: const Text('Details', style: TextStyle(color: Colors.white)),
style: TextStyle(color: Colors.white)),
), ),
TextButton.icon( TextButton.icon(
onPressed: () async { onPressed: () async {
@ -498,29 +491,24 @@ class _HistoryPageState extends State<HistoryPage>
: null; : null;
if (phoneNumber == null) { if (phoneNumber == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Contact has no phone number')),
content:
Text('Contact has no phone number')),
); );
return; return;
} }
if (isBlocked) { if (isBlocked) {
await BlockService().unblockNumber(phoneNumber); await BlockService().unblockNumber(phoneNumber);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('$phoneNumber unblocked')),
content: Text('$phoneNumber unblocked')),
); );
} else { } else {
await BlockService().blockNumber(phoneNumber); await BlockService().blockNumber(phoneNumber);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('$phoneNumber blocked')),
content: Text('$phoneNumber blocked')),
); );
} }
setState(() {}); setState(() {});
}, },
icon: Icon( icon: Icon(isBlocked ? Icons.lock_open : Icons.block,
isBlocked ? Icons.lock_open : Icons.block,
color: Colors.white), color: Colors.white),
label: Text(isBlocked ? 'Unblock' : 'Block', label: Text(isBlocked ? 'Unblock' : 'Block',
style: const TextStyle(color: Colors.white)), style: const TextStyle(color: Colors.white)),
@ -566,22 +554,21 @@ class CallDetailsPage extends StatelessWidget {
children: [ children: [
(contact.thumbnail != null && contact.thumbnail!.isNotEmpty) (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? ObfuscatedAvatar( ? ObfuscatedAvatar(
imageBytes: contact.thumbnail, imageBytes: contact.thumbnail,
radius: 30, radius: 30,
backgroundColor: contactBg, backgroundColor: contactBg,
fallbackInitial: contact.displayName, fallbackInitial: contact.displayName,
) )
: CircleAvatar( : CircleAvatar(
backgroundColor: backgroundColor: generateColorFromName(contact.displayName),
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: TextStyle(color: contactLetter),
style: TextStyle(color: contactLetter), ),
), ),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Text( child: Text(
@ -613,8 +600,7 @@ class CallDetailsPage extends StatelessWidget {
if (contact.phones.isNotEmpty) if (contact.phones.isNotEmpty)
DetailRow( DetailRow(
label: 'Number:', label: 'Number:',
value: _obfuscateService value: _obfuscateService.obfuscateData(contact.phones.first.number),
.obfuscateData(contact.phones.first.number),
), ),
], ],
), ),

View File

@ -8,7 +8,6 @@ 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';
import 'package:dialer/features/voicemail/voicemail_page.dart'; import 'package:dialer/features/voicemail/voicemail_page.dart';
import '../contacts/widgets/contact_modal.dart';
class _MyHomePageState extends State<MyHomePage> class _MyHomePageState extends State<MyHomePage>
@ -18,8 +17,6 @@ class _MyHomePageState extends State<MyHomePage>
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();
@override @override
void initState() { void initState() {
@ -35,15 +32,12 @@ class _MyHomePageState extends State<MyHomePage>
setState(() {}); setState(() {});
} }
void _clearSearch() {
_searchController.clear();
_onSearchChanged('');
}
void _onSearchChanged(String query) { void _onSearchChanged(String query) {
print("Search query: $query");
setState(() { setState(() {
if (query.isEmpty) { if (query.isEmpty) {
_contactSuggestions = List.from(_allContacts); // Reset suggestions _contactSuggestions = List.from(_allContacts);
} else { } else {
_contactSuggestions = _allContacts.where((contact) { _contactSuggestions = _allContacts.where((contact) {
return contact.displayName return contact.displayName
@ -56,7 +50,6 @@ class _MyHomePageState extends State<MyHomePage>
@override @override
void dispose() { void dispose() {
_searchController.dispose();
_tabController.removeListener(_handleTabIndex); _tabController.removeListener(_handleTabIndex);
_tabController.dispose(); _tabController.dispose();
super.dispose(); super.dispose();
@ -66,34 +59,6 @@ class _MyHomePageState extends State<MyHomePage>
setState(() {}); setState(() {});
} }
void _toggleFavorite(Contact contact) async {
try {
if (await FlutterContacts.requestPermission()) {
Contact? fullContact = await FlutterContacts.getContact(contact.id,
withProperties: true,
withAccounts: true,
withPhoto: true,
withThumbnail: true);
if (fullContact != null) {
fullContact.isStarred = !fullContact.isStarred;
await FlutterContacts.updateContact(fullContact);
setState(() {
// Updating the contact list after toggling the favorite
_fetchContacts();
});
}
} else {
print("Could not fetch contact details");
}
} catch (e) {
print("Error updating favorite status: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to update contact favorite status')),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -115,109 +80,63 @@ class _MyHomePageState extends State<MyHomePage>
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(color: Colors.grey.shade800, width: 1), border: Border(
top: BorderSide(color: Colors.grey.shade800, width: 1),
left: BorderSide(color: Colors.grey.shade800, width: 1),
right: BorderSide(color: Colors.grey.shade800, width: 1),
bottom:
BorderSide(color: Colors.grey.shade800, width: 2),
),
), ),
child: SearchAnchor( child: SearchAnchor(
builder: builder:
(BuildContext context, SearchController controller) { (BuildContext context, SearchController controller) {
return GestureDetector( return SearchBar(
onTap: () { controller: controller,
controller.openView(); // Open the search view padding:
}, WidgetStateProperty.all<EdgeInsetsGeometry>(
child: Container( const EdgeInsets.only(
decoration: BoxDecoration( top: 6.0,
color: const Color.fromARGB(255, 30, 30, 30), bottom: 6.0,
borderRadius: BorderRadius.circular(12.0), left: 16.0,
border: Border.all( right: 16.0,
color: Colors.grey.shade800, width: 1),
), ),
padding: const EdgeInsets.symmetric( ),
vertical: 12.0, horizontal: 16.0), onTap: () {
child: Row( controller.openView();
children: [ _onSearchChanged('');
const Icon(Icons.search, },
color: Colors.grey, size: 24.0), backgroundColor: WidgetStateProperty.all(
const SizedBox(width: 8.0), const Color.fromARGB(255, 30, 30, 30)),
Text( hintText: 'Search contacts',
_searchController.text.isEmpty hintStyle: WidgetStateProperty.all(
? 'Search contacts' const TextStyle(color: Colors.grey, fontSize: 16.0),
: _searchController.text, ),
style: const TextStyle( leading: const Icon(
color: Colors.grey, fontSize: 16.0), Icons.search,
), color: Colors.grey,
const Spacer(), size: 24.0,
if (_searchController.text.isNotEmpty) ),
GestureDetector( shape:
onTap: _clearSearch, WidgetStateProperty.all<RoundedRectangleBorder>(
child: const Icon( RoundedRectangleBorder(
Icons.clear, borderRadius: BorderRadius.circular(12.0),
color: Colors.grey,
size: 24.0,
),
),
],
), ),
), ),
); );
}, },
viewOnChanged: (query) { viewOnChanged: (query) {
_onSearchChanged(query); // Update immediately _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(_obfuscateService.obfuscateData(contact.displayName),
style: const TextStyle(color: Colors.white)), 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(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return ContactModal(
contact: contact,
onEdit: () async {
if (await FlutterContacts
.requestPermission()) {
final updatedContact =
await FlutterContacts
.openExternalEdit(contact.id);
if (updatedContact != null) {
_fetchContacts();
Navigator.of(context).pop();
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'${contact.displayName} updated successfully!'),
),
);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'Edit canceled or failed.'),
),
);
}
}
},
onToggleFavorite: () =>
_toggleFavorite(contact),
isFavorite: contact.isStarred,
);
},
);
}, },
); );
}).toList(); }).toList();

View File

@ -2,9 +2,4 @@
IMG=git.gmoker.com/icing/flutter:main IMG=git.gmoker.com/icing/flutter:main
if [ "$1" == '-s' ]; then docker run --rm -p 5037:5037 -v "$PWD:/app/" "$IMG" run
OPT+=(--dart-define=STEALTH=true)
fi
set -x
docker run --rm -p 5037:5037 -v "$PWD:/app/" "$IMG" run "${OPTS[@]}"

View File

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