Compare commits
No commits in common. "9bfb55821dca3fa883cbca53cfe52251a871c33e" and "7c7a4f28f4446224efb838132d58e0c94a649ae5" have entirely different histories.
9bfb55821d
...
7c7a4f28f4
@ -10,22 +10,8 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
subpath: dialer/
|
||||
- uses: docker://git.gmoker.com/icing/flutter:main
|
||||
- uses: icing/flutter@main
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: icing-dialer-${{ gitea.ref_name }}-${{ gitea.run_id }}.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
|
||||
|
@ -2,9 +2,4 @@
|
||||
|
||||
IMG=git.gmoker.com/icing/flutter:main
|
||||
|
||||
if [ "$1" == '-s' ]; then
|
||||
OPT+=(--dart-define=STEALTH=true)
|
||||
fi
|
||||
|
||||
set -x
|
||||
docker run --rm -v "$PWD:/app/" "$IMG" build apk "${OPT[@]}"
|
||||
docker run --rm -v "$PWD:/app/" "$IMG" build apk
|
||||
|
@ -11,7 +11,6 @@ import 'package:dialer/features/contacts/contact_state.dart';
|
||||
import 'package:dialer/widgets/username_color_generator.dart';
|
||||
import '../../services/block_service.dart';
|
||||
import '../contacts/widgets/contact_modal.dart';
|
||||
import '../../services/call_service.dart';
|
||||
|
||||
class History {
|
||||
final Contact contact;
|
||||
@ -21,12 +20,12 @@ class History {
|
||||
final int attempts;
|
||||
|
||||
History(
|
||||
this.contact,
|
||||
this.date,
|
||||
this.callType,
|
||||
this.callStatus,
|
||||
this.attempts,
|
||||
);
|
||||
this.contact,
|
||||
this.date,
|
||||
this.callType,
|
||||
this.callStatus,
|
||||
this.attempts,
|
||||
);
|
||||
}
|
||||
|
||||
class HistoryPage extends StatefulWidget {
|
||||
@ -42,7 +41,6 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
bool loading = true;
|
||||
int? _expandedIndex;
|
||||
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||
final CallService _callService = CallService();
|
||||
|
||||
// Create a MethodChannel instance.
|
||||
static const MethodChannel _channel = MethodChannel('com.example.calllog');
|
||||
@ -85,8 +83,8 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
}
|
||||
} catch (e) {
|
||||
print("Error updating favorite status: $e");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to update favorite status')));
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text('Failed to update favorite status')));
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +155,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
|
||||
// Convert timestamp to DateTime.
|
||||
DateTime callDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(entry['date'] ?? 0);
|
||||
DateTime.fromMillisecondsSinceEpoch(entry['date'] ?? 0);
|
||||
|
||||
int typeInt = entry['type'] ?? 0;
|
||||
int duration = entry['duration'] ?? 0;
|
||||
@ -195,8 +193,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
);
|
||||
}
|
||||
|
||||
callHistories
|
||||
.add(History(matchedContact, callDate, callType, callStatus, 1));
|
||||
callHistories.add(History(matchedContact, callDate, callType, callStatus, 1));
|
||||
}
|
||||
|
||||
// Sort histories by most recent.
|
||||
@ -221,7 +218,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
|
||||
for (var history in historyList) {
|
||||
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) {
|
||||
todayHistories.add(history);
|
||||
} else if (callDate == yesterday) {
|
||||
@ -294,7 +291,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
}
|
||||
|
||||
List<History> missedCalls =
|
||||
histories.where((h) => h.callStatus == 'missed').toList();
|
||||
histories.where((h) => h.callStatus == 'missed').toList();
|
||||
|
||||
final allItems = _buildGroupedList(histories);
|
||||
final missedItems = _buildGroupedList(missedCalls);
|
||||
@ -363,8 +360,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
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();
|
||||
@ -419,11 +415,18 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
icon: const Icon(Icons.phone, color: Colors.green),
|
||||
onPressed: () async {
|
||||
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 {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Contact has no phone number')),
|
||||
const SnackBar(content: Text('Contact has no phone number')),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -441,9 +444,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
color: Colors.grey[850],
|
||||
child: FutureBuilder<bool>(
|
||||
future: BlockService().isNumberBlocked(
|
||||
contact.phones.isNotEmpty
|
||||
? contact.phones.first.number
|
||||
: ''),
|
||||
contact.phones.isNotEmpty ? contact.phones.first.number : ''),
|
||||
builder: (context, snapshot) {
|
||||
final isBlocked = snapshot.data ?? false;
|
||||
return Row(
|
||||
@ -459,37 +460,29 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
await launchUrl(smsUri);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text('Could not send message')),
|
||||
const SnackBar(content: Text('Could not send message')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text('Contact has no phone number')),
|
||||
const SnackBar(content: Text('Contact has no phone number')),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon:
|
||||
const Icon(Icons.message, color: Colors.white),
|
||||
label: const Text('Message',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
icon: const Icon(Icons.message, color: Colors.white),
|
||||
label: const Text('Message', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
CallDetailsPage(history: history),
|
||||
builder: (_) => CallDetailsPage(history: history),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.info, color: Colors.white),
|
||||
label: const Text('Details',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
label: const Text('Details', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
@ -498,29 +491,24 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
: null;
|
||||
if (phoneNumber == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text('Contact has no phone number')),
|
||||
const SnackBar(content: Text('Contact has no phone number')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isBlocked) {
|
||||
await BlockService().unblockNumber(phoneNumber);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('$phoneNumber unblocked')),
|
||||
SnackBar(content: Text('$phoneNumber unblocked')),
|
||||
);
|
||||
} else {
|
||||
await BlockService().blockNumber(phoneNumber);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('$phoneNumber blocked')),
|
||||
SnackBar(content: Text('$phoneNumber blocked')),
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
icon: Icon(
|
||||
isBlocked ? Icons.lock_open : Icons.block,
|
||||
icon: Icon(isBlocked ? Icons.lock_open : Icons.block,
|
||||
color: Colors.white),
|
||||
label: Text(isBlocked ? 'Unblock' : 'Block',
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
@ -566,22 +554,21 @@ class CallDetailsPage extends StatelessWidget {
|
||||
children: [
|
||||
(contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
|
||||
? ObfuscatedAvatar(
|
||||
imageBytes: contact.thumbnail,
|
||||
radius: 30,
|
||||
backgroundColor: contactBg,
|
||||
fallbackInitial: contact.displayName,
|
||||
)
|
||||
imageBytes: contact.thumbnail,
|
||||
radius: 30,
|
||||
backgroundColor: contactBg,
|
||||
fallbackInitial: contact.displayName,
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundColor:
|
||||
generateColorFromName(contact.displayName),
|
||||
radius: 30,
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty
|
||||
? contact.displayName[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(color: contactLetter),
|
||||
),
|
||||
),
|
||||
backgroundColor: generateColorFromName(contact.displayName),
|
||||
radius: 30,
|
||||
child: Text(
|
||||
contact.displayName.isNotEmpty
|
||||
? contact.displayName[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(color: contactLetter),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@ -613,8 +600,7 @@ class CallDetailsPage extends StatelessWidget {
|
||||
if (contact.phones.isNotEmpty)
|
||||
DetailRow(
|
||||
label: 'Number:',
|
||||
value: _obfuscateService
|
||||
.obfuscateData(contact.phones.first.number),
|
||||
value: _obfuscateService.obfuscateData(contact.phones.first.number),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -8,7 +8,6 @@ import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:dialer/features/settings/settings.dart';
|
||||
import '../../services/contact_service.dart';
|
||||
import 'package:dialer/features/voicemail/voicemail_page.dart';
|
||||
import '../contacts/widgets/contact_modal.dart';
|
||||
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage>
|
||||
@ -18,8 +17,6 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
List<Contact> _contactSuggestions = [];
|
||||
final ContactService _contactService = ContactService();
|
||||
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -35,15 +32,12 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _clearSearch() {
|
||||
_searchController.clear();
|
||||
_onSearchChanged('');
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
print("Search query: $query");
|
||||
|
||||
setState(() {
|
||||
if (query.isEmpty) {
|
||||
_contactSuggestions = List.from(_allContacts); // Reset suggestions
|
||||
_contactSuggestions = List.from(_allContacts);
|
||||
} else {
|
||||
_contactSuggestions = _allContacts.where((contact) {
|
||||
return contact.displayName
|
||||
@ -56,7 +50,6 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_tabController.removeListener(_handleTabIndex);
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
@ -66,34 +59,6 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -115,109 +80,63 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 30, 30, 30),
|
||||
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(
|
||||
builder:
|
||||
(BuildContext context, SearchController controller) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
controller.openView(); // Open the search view
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 30, 30, 30),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade800, width: 1),
|
||||
return SearchBar(
|
||||
controller: controller,
|
||||
padding:
|
||||
WidgetStateProperty.all<EdgeInsetsGeometry>(
|
||||
const EdgeInsets.only(
|
||||
top: 6.0,
|
||||
bottom: 6.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.search,
|
||||
color: Colors.grey, size: 24.0),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
_searchController.text.isEmpty
|
||||
? 'Search contacts'
|
||||
: _searchController.text,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey, fontSize: 16.0),
|
||||
),
|
||||
const Spacer(),
|
||||
if (_searchController.text.isNotEmpty)
|
||||
GestureDetector(
|
||||
onTap: _clearSearch,
|
||||
child: const Icon(
|
||||
Icons.clear,
|
||||
color: Colors.grey,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
controller.openView();
|
||||
_onSearchChanged('');
|
||||
},
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
const Color.fromARGB(255, 30, 30, 30)),
|
||||
hintText: 'Search contacts',
|
||||
hintStyle: WidgetStateProperty.all(
|
||||
const TextStyle(color: Colors.grey, fontSize: 16.0),
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.grey,
|
||||
size: 24.0,
|
||||
),
|
||||
shape:
|
||||
WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
viewOnChanged: (query) {
|
||||
_onSearchChanged(query); // Update immediately
|
||||
_onSearchChanged(query);
|
||||
},
|
||||
suggestionsBuilder:
|
||||
(BuildContext context, SearchController controller) {
|
||||
return _contactSuggestions.map((contact) {
|
||||
return ListTile(
|
||||
return ListTile(
|
||||
key: ValueKey(contact.id),
|
||||
title: Text(_obfuscateService.obfuscateData(contact.displayName),
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
onTap: () {
|
||||
// Clear the search text input
|
||||
controller.text = '';
|
||||
|
||||
// Close the search view
|
||||
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();
|
||||
|
@ -2,9 +2,4 @@
|
||||
|
||||
IMG=git.gmoker.com/icing/flutter:main
|
||||
|
||||
if [ "$1" == '-s' ]; then
|
||||
OPT+=(--dart-define=STEALTH=true)
|
||||
fi
|
||||
|
||||
set -x
|
||||
docker run --rm -p 5037:5037 -v "$PWD:/app/" "$IMG" run "${OPTS[@]}"
|
||||
docker run --rm -p 5037:5037 -v "$PWD:/app/" "$IMG" run
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Running Icing Dialer in STEALTH mode..."
|
||||
flutter run --dart-define=STEALTH=true
|
||||
flutter run --dart-define=STEALTH=true
|
Loading…
Reference in New Issue
Block a user