Compare commits

..

4 Commits

Author SHA1 Message Date
3d19fa38f9 merge add new architectutre and add of keys settings page 2024-11-05 23:08:21 +00:00
61fe810867 change directory order 2024-10-25 12:38:40 +01:00
eef5f273d0 delete 2024-10-25 12:29:44 +01:00
5c49b080d9 add of everything 2024-10-25 12:26:44 +01:00
254 changed files with 4257 additions and 5136 deletions

View File

@ -1,11 +0,0 @@
on: push
jobs:
mirror:
runs-on: debian
steps:
- uses: actions/mirror@v1
with:
ssh_priv: "${{ secrets.SSHGH }}"
known_hosts: "github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"
url: "git@github.com:EpitechPromo2026/G-EIP-700-TLS-7-1-eip-stephane.corbiere.git"

View File

@ -1,38 +0,0 @@
on:
push:
paths:
- website/**
jobs:
deploy:
runs-on: debian
defaults:
run:
working-directory: website
steps:
- uses: actions/checkout@v1
- name: setup env
run: |
. ./.env || true
if [ "${{ gitea.ref_name }}" == prod ] && [ -n "$PROD_URL" ]; then
BASE_URL="$PROD_URL"
else
BASE_URL="${{ gitea.ref_name }}.$(tr / '\n' <<< "${{ gitea.repository }}" | tac | tr '\n' .)k8s.gmoker.com"
fi
REGISTRY="$(sed 's .*:// ' <<< ${{ gitea.server_url }})"
cat <<EOF >> .env
BASE_URL="$(printf '%s' "$BASE_URL" | tr '[:upper:]' '[:lower:]' | tr -c '[:lower:][:digit:]-.' -)"
IMAGEAPP="$REGISTRY/$(printf '%s' "${{ gitea.repository }}:${{ gitea.ref_name }}" | tr '[:upper:]' '[:lower:]' | tr -c '[:lower:][:digit:]-/:_' _)"
EOF
cat .env
- uses: actions/kaniko@v1
with:
password: "${{ secrets.PKGRW }}"
dockerfile: website/Dockerfile
- uses: actions/k8sdeploy@v1
with:
kubeconfig: "${{ secrets.K8S }}"
registry_password: "${{ secrets.PKGRW }}"
workdir: website

View File

@ -1,13 +1,5 @@
# Icing
# ICING Dialer
An Epitech Innovation Project
Project aiming to <i>ice</i> your **non-internet** phone calls, thanks to a custom and super-cryptographic dialer app.
*By*
**Bartosz Michalak - Alexis Danlos - Florian Griffon - Ange Duhayon - Stéphane Corbière**
---
The **docs** folder contains documentation about:
- The project
- A user manual
- Our automations
Like & Follow !

View File

@ -5,7 +5,6 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
gradle.properties
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore

View File

@ -1,11 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,15 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
<application
android:label="com.example.dialer"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true">
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
@ -49,4 +44,7 @@
<data android:mimeType="text/plain"/>
</intent>
</queries>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 721 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,12 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.

View File

@ -16,4 +16,3 @@ subprojects {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
dev.steenbakker.mobile_scanner.useUnbundled=true
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-amd64

View File

@ -1,293 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../widgets/contact_service.dart';
import '../contacts/widgets/add_contact_button.dart';
class CompositionPage extends StatefulWidget {
const CompositionPage({super.key});
@override
_CompositionPageState createState() => _CompositionPageState();
}
class _CompositionPageState extends State<CompositionPage> {
String dialedNumber = "";
List<Contact> _allContacts = [];
List<Contact> _filteredContacts = [];
final ContactService _contactService = ContactService();
@override
void initState() {
super.initState();
_fetchContacts();
}
Future<void> _fetchContacts() async {
_allContacts = await _contactService.fetchContacts();
_filteredContacts = _allContacts;
setState(() {});
}
void _filterContacts() {
setState(() {
_filteredContacts = _allContacts.where((contact) {
final phoneMatch = contact.phones.any((phone) =>
phone.number.replaceAll(RegExp(r'\D'), '').contains(dialedNumber));
final nameMatch = contact.displayName
.toLowerCase()
.contains(dialedNumber.toLowerCase());
return phoneMatch || nameMatch;
}).toList();
});
}
void _onNumberPress(String number) {
setState(() {
dialedNumber += number;
_filterContacts();
});
}
void _onDeletePress() {
setState(() {
if (dialedNumber.isNotEmpty) {
dialedNumber = dialedNumber.substring(0, dialedNumber.length - 1);
_filterContacts();
}
});
}
void _onClearPress() {
setState(() {
dialedNumber = "";
_filteredContacts = _allContacts;
});
}
// Function to call a contact's number
void _launchPhoneDialer(String phoneNumber) async {
final uri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not launch $phoneNumber');
}
}
// Function to send an SMS to a contact's number
void _launchSms(String phoneNumber) async {
final uri = Uri(scheme: 'sms', path: phoneNumber);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not send SMS to $phoneNumber');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
Column(
children: [
// Top half: Display contacts matching dialed number
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.only(
top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
color: Colors.black,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView(
children: _filteredContacts.isNotEmpty
? _filteredContacts.map((contact) {
final phoneNumber = contact.phones.isNotEmpty
? contact.phones.first.number
: 'No phone number';
return ListTile(
title: Text(
contact.displayName,
style:
const TextStyle(color: Colors.white),
),
subtitle: Text(
phoneNumber,
style:
const TextStyle(color: Colors.grey),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Call button
IconButton(
icon: Icon(Icons.phone,
color: Colors.green[300],
size: 20),
onPressed: () {
_launchPhoneDialer(phoneNumber);
},
),
// Message button
IconButton(
icon: Icon(Icons.message,
color: Colors.blue[300],
size: 20),
onPressed: () {
_launchSms(phoneNumber);
},
),
],
),
onTap: () {
// Handle contact selection if needed
},
);
}).toList()
: [
Center(
child: Text('No contacts found',
style:
TextStyle(color: Colors.white)))
],
),
),
],
),
),
),
// Bottom half: Dialpad and Dialed number display with erase button
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Display dialed number with erase button
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Align(
alignment: Alignment.center,
child: Text(
dialedNumber,
style: const TextStyle(
fontSize: 24, color: Colors.white),
overflow: TextOverflow.ellipsis,
),
),
),
IconButton(
onPressed: _onClearPress,
icon: const Icon(Icons.backspace,
color: Colors.white),
),
],
),
const SizedBox(height: 10),
// Dialpad
Expanded(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
_buildDialButton('1'),
_buildDialButton('2'),
_buildDialButton('3'),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
_buildDialButton('4'),
_buildDialButton('5'),
_buildDialButton('6'),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
_buildDialButton('7'),
_buildDialButton('8'),
_buildDialButton('9'),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
_buildDialButton('*'),
_buildDialButton('0'),
_buildDialButton('#'),
],
),
],
),
),
),
],
),
),
),
],
),
// Add Contact Button
Positioned(
bottom: 20.0,
left: 0,
right: 0,
child: Center(
child: AddContactButton(),
),
),
// Top Row with Back Arrow
Positioned(
top: 40.0,
left: 16.0,
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
);
}
Widget _buildDialButton(String number) {
return ElevatedButton(
onPressed: () => _onNumberPress(number),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
shape: const CircleBorder(),
padding: const EdgeInsets.all(16),
),
child: Text(
number,
style: const TextStyle(
fontSize: 24,
color: Colors.white,
),
),
);
}
}

View File

@ -1,122 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import '../../widgets/contact_service.dart';
class ContactState extends StatefulWidget {
final Widget child;
const ContactState({super.key, required this.child});
static _ContactStateState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<_InheritedContactState>()!
.data;
}
@override
_ContactStateState createState() => _ContactStateState();
}
class _ContactStateState extends State<ContactState> {
final ContactService _contactService = ContactService();
List<Contact> _allContacts = [];
List<Contact> _favoriteContacts = [];
bool _loading = true;
double _scrollOffset = 0.0;
Contact? _selfContact = Contact();
// Getters for all contacts and favorites
List<Contact> get contacts => _allContacts;
List<Contact> get favoriteContacts => _favoriteContacts;
bool get loading => _loading;
double get scrollOffset => _scrollOffset;
Contact? get selfContact => _selfContact;
@override
void initState() {
super.initState();
fetchContacts(); // Fetch all contacts by default
FlutterContacts.addListener(_onContactChange);
}
void _onContactChange() => fetchContacts();
@override
void dispose() {
FlutterContacts.removeListener(_onContactChange);
super.dispose();
}
// Fetch all contacts
Future<void> fetchContacts() async {
setState(() => _loading = true);
try {
List<Contact> contacts = await _contactService.fetchContacts();
_processContacts(contacts);
} finally {
setState(() => _loading = false);
}
}
// Fetch only favorite contacts
Future<void> fetchFavoriteContacts() async {
setState(() => _loading = true);
try {
List<Contact> contacts = await _contactService.fetchFavoriteContacts();
setState(() => _favoriteContacts = contacts);
} finally {
setState(() => _loading = false);
}
}
void _processContacts(List<Contact> contacts) {
_selfContact = contacts.firstWhere(
(contact) => contact.displayName.toLowerCase() == "user",
orElse: () => Contact(),
);
if (_selfContact!.phones.isEmpty) {
debugPrint("Self contact has no phone numbers");
_selfContact = null;
}
contacts = contacts.where((contact) => contact.phones.isNotEmpty).toList();
contacts.sort((a, b) => a.displayName.compareTo(b.displayName));
setState(() {
_allContacts = contacts;
_favoriteContacts =
contacts.where((contact) => contact.isStarred).toList();
_selfContact = _selfContact;
});
}
Future<void> addNewContact(Contact contact) async {
await _contactService.addNewContact(contact);
await fetchContacts();
}
void setScrollOffset(double offset) {
setState(() {
_scrollOffset = offset;
});
}
@override
Widget build(BuildContext context) {
return _InheritedContactState(
data: this,
child: widget.child,
);
}
}
class _InheritedContactState extends InheritedWidget {
final _ContactStateState data;
const _InheritedContactState({required this.data, required super.child});
@override
bool updateShouldNotify(_InheritedContactState oldWidget) => true;
}

View File

@ -1,56 +0,0 @@
import 'package:android_intent_plus/android_intent.dart';
import 'package:dialer/widgets/qr_scanner.dart';
import 'package:flutter/material.dart';
class AddContactButton extends StatelessWidget {
const AddContactButton({super.key});
Future<void> createNewContact() async {
AndroidIntent intent = AndroidIntent(
action: 'android.intent.action.INSERT',
type: 'vnd.android.cursor.dir/contact',
);
await intent.launch();
}
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add, color: Colors.blue),
onPressed: () {
showDialog(
context: context,
barrierDismissible: true, // Allows dismissal by tapping outside
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.black,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => QRCodeScannerScreen()),
);
},
child: Text("Scan QR code", style: TextStyle(color: Colors.white)),
),
TextButton(
onPressed: () async {
createNewContact();
Navigator.of(context).pop();
},
child: Text("Create new contact", style: TextStyle(color: Colors.white)),
),
],
),
);
},
);
},
);
}
}

View File

@ -1,221 +0,0 @@
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';
class AlphabetScrollPage extends StatefulWidget {
final double scrollOffset;
final List<Contact> contacts;
const AlphabetScrollPage({
super.key,
required this.scrollOffset,
required this.contacts,
});
@override
_AlphabetScrollPageState createState() => _AlphabetScrollPageState();
}
class _AlphabetScrollPageState extends State<AlphabetScrollPage> {
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController(initialScrollOffset: widget.scrollOffset);
_scrollController.addListener(_onScroll);
}
void _onScroll() {
final contactState = ContactState.of(context);
contactState.setScrollOffset(_scrollController.offset);
}
Future<void> _refreshContacts() async {
final contactState = ContactState.of(context);
try {
await contactState.fetchContacts();
} catch (e) {
print('Error refreshing contacts: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to refresh contacts')),
);
}
}
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);
}
await _refreshContacts();
} 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) {
final contacts = widget.contacts;
final selfContact = ContactState.of(context).selfContact;
Map<String, List<Contact>> alphabetizedContacts = {};
for (var contact in contacts) {
String firstLetter = contact.displayName.isNotEmpty
? contact.displayName[0].toUpperCase()
: '#';
if (!alphabetizedContacts.containsKey(firstLetter)) {
alphabetizedContacts[firstLetter] = [];
}
alphabetizedContacts[firstLetter]!.add(contact);
}
List<String> alphabetKeys = alphabetizedContacts.keys.toList()..sort();
return Scaffold(
backgroundColor: Colors.black,
body: Column(
children: [
// Top buttons row
Container(
color: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AddContactButton(),
QRCodeButton(contacts: contacts, selfContact: selfContact),
],
),
),
// Contact List
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: alphabetKeys.length,
itemBuilder: (context, index) {
String letter = alphabetKeys[index];
List<Contact> contactsForLetter = alphabetizedContacts[letter]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Alphabet Letter Header
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0, horizontal: 16.0),
child: Text(
letter,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
// Contact Entries
...contactsForLetter.map((contact) {
String phoneNumber = contact.phones.isNotEmpty
? contact.phones.first.number
: 'No phone number';
Color avatarColor =
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)),
onTap: () {
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) {
await _refreshContacts();
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,
);
},
);
},
);
}),
],
);
},
),
),
],
),
);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}

View File

@ -1,341 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:dialer/widgets/username_color_generator.dart';
import '../../../widgets/block_service.dart';
class ContactModal extends StatefulWidget {
final Contact contact;
final Function onEdit;
final Function onToggleFavorite;
final bool isFavorite;
const ContactModal({
super.key,
required this.contact,
required this.onEdit,
required this.onToggleFavorite,
required this.isFavorite,
});
@override
_ContactModalState createState() => _ContactModalState();
}
class _ContactModalState extends State<ContactModal> {
late String phoneNumber;
bool isBlocked = false;
@override
void initState() {
super.initState();
phoneNumber = widget.contact.phones.isNotEmpty
? widget.contact.phones.first.number
: 'No phone number';
_checkIfBlocked();
}
Future<void> _checkIfBlocked() async {
if (phoneNumber != 'No phone number') {
bool blocked = await BlockService().isNumberBlocked(phoneNumber);
setState(() {
isBlocked = blocked;
});
}
}
Future<void> _toggleBlockState() async {
if (phoneNumber == 'No phone number') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No phone number to block or unblock')),
);
} else if (isBlocked) {
await BlockService().unblockNumber(phoneNumber);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$phoneNumber has been unblocked')),
);
} else {
await BlockService().blockNumber(phoneNumber);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$phoneNumber has been blocked')),
);
}
if (phoneNumber != 'No phone number') {
_checkIfBlocked();
}
Navigator.of(context).pop();
}
void _launchPhoneDialer(String phoneNumber) async {
final uri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not launch $phoneNumber');
}
}
void _launchSms(String phoneNumber) async {
final uri = Uri(scheme: 'sms', path: phoneNumber);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not launch SMS to $phoneNumber');
}
}
void _launchEmail(String email) async {
final uri = Uri(scheme: 'mailto', path: email);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not launch email to $email');
}
}
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'),
),
],
),
);
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')),
);
// 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
: 'No email';
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
color: Colors.black.withOpacity(0.5),
child: GestureDetector(
onTap: () {},
child: FractionallySizedBox(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[900],
borderRadius:
const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Modal Handle and Three-Dot Menu
Stack(
children: [
Align(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Container(
width: 50,
height: 5,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(5),
),
),
),
),
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(top: 10, right: 10),
child: PopupMenuButton<String>(
icon: const Icon(Icons.more_vert,
color: Colors.white),
onSelected: (String choice) {
if (choice == 'delete') {
_deleteContact();
}
},
itemBuilder: (BuildContext context) {
return [
const PopupMenuItem<String>(
value: 'show_associated_contacts',
child: Text('Show associated contacts'),
),
const PopupMenuItem<String>(
value: 'delete',
child: Text('Delete'),
),
const PopupMenuItem<String>(
value: 'share',
child: Text('Share (via QR code)'),
),
const PopupMenuItem<String>(
value: 'create_shortcut',
child:
Text('Create shortcut (to home screen)'),
),
const PopupMenuItem<String>(
value: 'set_ringtone',
child: Text('Set ringtone'),
),
];
},
),
),
),
],
),
// Contact Profile
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
CircleAvatar(
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,
),
const SizedBox(height: 10),
Text(
widget.contact.displayName,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
),
],
),
),
const Divider(),
// Contact Actions
ListTile(
leading: const Icon(Icons.phone, color: Colors.green),
title: Text(phoneNumber),
onTap: () {
if (widget.contact.phones.isNotEmpty) {
_launchPhoneDialer(phoneNumber);
}
},
),
ListTile(
leading: const Icon(Icons.message, color: Colors.blue),
title: Text(phoneNumber),
onTap: () {
if (widget.contact.phones.isNotEmpty) {
_launchSms(phoneNumber);
}
},
),
ListTile(
leading: const Icon(Icons.email, color: Colors.orange),
title: Text(email),
onTap: () {
if (widget.contact.emails.isNotEmpty) {
_launchEmail(email);
}
},
),
const Divider(),
// Favorite, Edit, and Block/Unblock Buttons
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
// Favorite button
SizedBox(
width: double
.infinity, // This makes the button take full width
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
widget.onToggleFavorite();
},
icon: Icon(widget.isFavorite
? Icons.star
: Icons.star_border),
label: Text(
widget.isFavorite ? 'Unfavorite' : 'Favorite'),
),
),
const SizedBox(height: 10), // Space between buttons
// Edit button
SizedBox(
width: double
.infinity, // This makes the button take full width
child: ElevatedButton.icon(
onPressed: () => widget.onEdit(),
icon: const Icon(Icons.edit),
label: const Text('Edit Contact'),
),
),
const SizedBox(height: 10), // Space between buttons
// Block/Unblock button
SizedBox(
width: double
.infinity, // This makes the button take full width
child: ElevatedButton.icon(
onPressed: _toggleBlockState,
icon: Icon(
isBlocked ? Icons.block : Icons.block_flipped),
label: Text(isBlocked ? 'Unblock' : 'Block'),
),
),
],
),
),
const SizedBox(height: 16),
],
),
),
),
),
),
);
}
}

View File

@ -1,55 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/contact.dart';
import 'package:qr_flutter/qr_flutter.dart';
class QRCodeButton extends StatelessWidget {
final List<Contact> contacts;
final Contact? selfContact;
const QRCodeButton({super.key, required this.contacts, this.selfContact});
Contact? getSelfContact() {
if (kDebugMode) {
debugPrint("Checking for self contact");
}
for (var contact in contacts) {
if (contact.groups.any((group) => group.name.toLowerCase() == "user")) {
return contact;
}
}
return null;
}
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.qr_code, color: selfContact != null ? Colors.blue : Colors.grey),
onPressed: selfContact != null
? () {
showDialog(
barrierColor: Colors.white24,
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.black,
content: SizedBox(
width: 200,
height: 220,
child: QrImageView(
data: selfContact!.toVCard(),
version: QrVersions.auto,
backgroundColor: Colors.white, // Ensure QR code is visible on black background
size: 200.0,
),
),
);
},
);
}
: null,
);
}
}

View File

@ -1,32 +0,0 @@
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';
class FavoritesPage extends StatefulWidget {
const FavoritesPage({super.key});
@override
_FavoritesPageState createState() => _FavoritesPageState();
}
class _FavoritesPageState extends State<FavoritesPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final contactState = ContactState.of(context);
return Scaffold(
body: contactState.loading
? const LoadingIndicatorWidget()
: AlphabetScrollPage(
scrollOffset: contactState.scrollOffset,
contacts:
contactState.favoriteContacts, // Use only favorites here
),
);
}
}

View File

@ -1,439 +0,0 @@
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;
final DateTime date;
final String callType; // 'incoming' or 'outgoing'
final String callStatus; // 'missed' or 'answered'
final int attempts;
History(
this.contact,
this.date,
this.callType,
this.callStatus,
this.attempts,
);
}
class HistoryPage extends StatefulWidget {
const HistoryPage({Key? key}) : super(key: key);
@override
_HistoryPageState createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> with SingleTickerProviderStateMixin {
List<History> histories = [];
bool loading = true;
int? _expandedIndex;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (loading) {
_buildHistories();
}
}
Future<void> _buildHistories() async {
final contactState = ContactState.of(context);
if (contactState.loading) {
// Wait for contacts to be loaded
await Future.doWhile(() async {
await Future.delayed(const Duration(milliseconds: 100));
return contactState.loading;
});
}
List<Contact> contacts = contactState.contacts;
if (contacts.isEmpty) {
setState(() {
loading = false;
});
return;
}
setState(() {
histories = List.generate(
contacts.length >= 10 ? 10 : contacts.length,
(index) => History(
contacts[index],
DateTime.now().subtract(Duration(hours: (index + 1) * 2)),
index % 2 == 0 ? 'outgoing' : 'incoming',
index % 3 == 0 ? 'missed' : 'answered',
index % 3 + 1,
),
);
loading = false;
});
}
List _buildGroupedList(List<History> historyList) {
// Sort histories by date (most recent first)
historyList.sort((a, b) => b.date.compareTo(a.date));
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
List<History> todayHistories = [];
List<History> yesterdayHistories = [];
List<History> olderHistories = [];
for (var history in historyList) {
final callDate = DateTime(history.date.year, history.date.month, history.date.day);
if (callDate == today) {
todayHistories.add(history);
} else if (callDate == yesterday) {
yesterdayHistories.add(history);
} else {
olderHistories.add(history);
}
}
// Combine them with headers
final items = <dynamic>[];
if (todayHistories.isNotEmpty) {
items.add('Today');
items.addAll(todayHistories);
}
if (yesterdayHistories.isNotEmpty) {
items.add('Yesterday');
items.addAll(yesterdayHistories);
}
if (olderHistories.isNotEmpty) {
items.add('Older');
items.addAll(olderHistories);
}
return items;
}
@override
Widget build(BuildContext context) {
final contactState = ContactState.of(context);
if (loading || contactState.loading) {
return Scaffold(
backgroundColor: Colors.black,
body: const Center(
child: CircularProgressIndicator(),
),
);
}
if (histories.isEmpty) {
return Scaffold(
backgroundColor: Colors.black,
body: const Center(
child: Text(
'No call history available.',
style: TextStyle(color: Colors.white),
),
),
);
}
// Filter missed calls
List<History> missedCalls = histories.where((h) => h.callStatus == 'missed').toList();
final allItems = _buildGroupedList(histories);
final missedItems = _buildGroupedList(missedCalls);
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Colors.black,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Container(
color: Colors.black,
child: const TabBar(
tabs: [
Tab(text: 'All Calls'),
Tab(text: 'Missed Calls'),
],
indicatorColor: Colors.white,
),
),
),
body: TabBarView(
children: [
// All Calls
_buildListView(allItems),
// Missed Calls
_buildListView(missedItems),
],
),
),
);
}
Widget _buildListView(List items) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
if (item is String) {
// This is a header item
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: Colors.grey[900],
child: Text(
item,
style: const TextStyle(
color: Colors.white70,
fontWeight: FontWeight.bold,
),
),
);
} else if (item is History) {
final history = item;
final contact = history.contact;
final isExpanded = _expandedIndex == index;
// Generate the avatar color
Color avatarColor = generateColorFromName(contact.displayName);
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)),
),
),
title: Text(
contact.displayName,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
'${history.callType} - ${history.callStatus} - ${DateFormat('MMM dd, hh:mm a').format(history.date)}',
style: const TextStyle(color: Colors.grey),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${history.attempts}x',
style: const TextStyle(color: Colors.white),
),
IconButton(
icon: const Icon(Icons.phone, color: Colors.green),
onPressed: () async {
if (contact.phones.isNotEmpty) {
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')),
);
}
},
),
],
),
onTap: () {
setState(() {
_expandedIndex = isExpanded ? null : index;
});
},
),
if (isExpanded)
Container(
color: Colors.grey[850],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TextButton.icon(
onPressed: () async {
if (history.contact.phones.isNotEmpty) {
final Uri smsUri =
Uri(scheme: 'sms', path: history.contact.phones.first.number);
if (await canLaunchUrl(smsUri)) {
await launchUrl(smsUri);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not send message')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
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)),
),
TextButton.icon(
onPressed: () {
// Navigate to Call Details page
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CallDetailsPage(history: history),
),
);
},
icon: const Icon(Icons.info, color: Colors.white),
label: const Text('Details', style: TextStyle(color: Colors.white)),
),
TextButton.icon(
onPressed: () {
// Implement block number functionality
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Number blocked (functionality not implemented)'),
),
);
},
icon: const Icon(Icons.block, color: Colors.white),
label: const Text('Block', style: TextStyle(color: Colors.white)),
),
],
),
),
],
);
}
return const SizedBox.shrink();
},
);
}
}
class CallDetailsPage extends StatelessWidget {
final History history;
const CallDetailsPage({Key? key, required this.history}) : super(key: key);
@override
Widget build(BuildContext context) {
final contact = history.contact;
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Call Details'),
backgroundColor: Colors.black,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Display Contact Name and Thumbnail
Row(
children: [
(contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? CircleAvatar(
backgroundImage: MemoryImage(contact.thumbnail!),
radius: 30,
)
: CircleAvatar(
backgroundColor: Colors.grey[700],
radius: 30,
child: Text(
contact.displayName.isNotEmpty
? contact.displayName[0].toUpperCase()
: '?',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
contact.displayName,
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
],
),
const SizedBox(height: 24),
// Display call type, status, date, attempts
DetailRow(
label: 'Call Type:',
value: history.callType,
),
DetailRow(
label: 'Call Status:',
value: history.callStatus,
),
DetailRow(
label: 'Date:',
value: DateFormat('MMM dd, yyyy - hh:mm a').format(history.date),
),
DetailRow(
label: 'Attempts:',
value: '${history.attempts}',
),
const SizedBox(height: 24),
// If you have more details like duration, contact number, etc.
if (contact.phones.isNotEmpty)
DetailRow(
label: 'Number:',
value: contact.phones.first.number,
),
],
),
),
);
}
}
class DetailRow extends StatelessWidget {
final String label;
final String value;
const DetailRow({Key? key, required this.label, required this.value}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Text(
label,
style: const TextStyle(color: Colors.white70, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Expanded(
child: Text(
value,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.right,
),
),
],
),
);
}
}

View File

@ -1,235 +0,0 @@
import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_page.dart';
import 'package:dialer/features/favorites/favorites_page.dart';
import 'package:dialer/features/history/history_page.dart';
import 'package:dialer/features/composition/composition.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:dialer/features/settings/settings.dart';
import '../../widgets/contact_service.dart';
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
List<Contact> _allContacts = [];
List<Contact> _contactSuggestions = [];
final ContactService _contactService = ContactService();
@override
void initState() {
super.initState();
// Set the TabController length to 3
_tabController = TabController(length: 3, vsync: this, initialIndex: 1);
_tabController.addListener(_handleTabIndex);
_fetchContacts();
}
void _fetchContacts() async {
_allContacts = await _contactService.fetchContacts();
setState(() {});
}
void _onSearchChanged(String query) {
print("Search query: $query");
setState(() {
if (query.isEmpty) {
_contactSuggestions = List.from(_allContacts);
} else {
_contactSuggestions = _allContacts.where((contact) {
return contact.displayName
.toLowerCase()
.contains(query.toLowerCase());
}).toList();
}
});
}
@override
void dispose() {
_tabController.removeListener(_handleTabIndex);
_tabController.dispose();
super.dispose();
}
void _handleTabIndex() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Column(
children: [
// Persistent Search Bar
Padding(
padding: const EdgeInsets.only(
top: 24.0,
bottom: 10.0,
left: 16.0,
right: 16.0,
),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 30, 30, 30),
borderRadius: BorderRadius.circular(12.0),
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 SearchBar(
controller: controller,
padding:
MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.only(
top: 6.0,
bottom: 6.0,
left: 16.0,
right: 16.0,
),
),
onTap: () {
controller.openView();
_onSearchChanged('');
},
backgroundColor: MaterialStateProperty.all(
const Color.fromARGB(255, 30, 30, 30)),
hintText: 'Search contacts',
hintStyle: MaterialStateProperty.all(
const TextStyle(color: Colors.grey, fontSize: 16.0),
),
leading: const Icon(
Icons.search,
color: Colors.grey,
size: 24.0,
),
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
),
);
},
viewOnChanged: (query) {
_onSearchChanged(query);
},
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);
},
);
}).toList();
},
),
),
),
// 3-dot menu
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Colors.white),
itemBuilder: (BuildContext context) => [
const PopupMenuItem<String>(
value: 'settings',
child: Text('Settings'),
),
],
onSelected: (String value) {
if (value == 'settings') {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsPage()),
);
}
},
),
],
),
),
// Main content with TabBarView
Expanded(
child: Stack(
children: [
TabBarView(
controller: _tabController,
children: const [
FavoritesPage(),
HistoryPage(),
ContactPage(),
],
),
Positioned(
right: 20,
bottom: 20,
child: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CompositionPage(),
),
);
},
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(45),
),
child: const Icon(Icons.dialpad, color: Colors.white),
),
),
],
),
),
],
),
bottomNavigationBar: Container(
color: Colors.black,
child: TabBar(
controller: _tabController,
tabs: [
Tab(
icon: Icon(_tabController.index == 0
? Icons.star
: Icons.star_border)),
Tab(
icon: Icon(_tabController.index == 1
? Icons.access_time_filled
: Icons.access_time_outlined)),
Tab(
icon: Icon(_tabController.index == 2
? Icons.contacts
: Icons.contacts_outlined)),
],
labelColor: Colors.white,
unselectedLabelColor: const Color.fromARGB(255, 158, 158, 158),
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Colors.white,
),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
_MyHomePageState createState() => _MyHomePageState();
}

View File

@ -1,167 +0,0 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class BlockedNumbersPage extends StatefulWidget {
const BlockedNumbersPage({super.key});
@override
_BlockedNumbersPageState createState() => _BlockedNumbersPageState();
}
class _BlockedNumbersPageState extends State<BlockedNumbersPage> {
bool _blockUnknownNumbers = false; // Toggle for blocking unknown numbers
List<String> _blockedNumbers = []; // List of blocked numbers
final TextEditingController _numberController = TextEditingController();
@override
void initState() {
super.initState();
_loadPreferences(); // Load data on initialization
}
// Load preferences from local storage
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_blockUnknownNumbers = prefs.getBool('blockUnknownNumbers') ?? false;
_blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
});
}
// Save preferences to local storage
Future<void> _savePreferences() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('blockUnknownNumbers', _blockUnknownNumbers);
await prefs.setStringList('blockedNumbers', _blockedNumbers);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Blocked Numbers'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: const Text(
'Block Unknown Numbers',
style: TextStyle(color: Colors.white),
),
value: _blockUnknownNumbers,
onChanged: (bool value) {
setState(() {
_blockUnknownNumbers = value;
_savePreferences(); // Save the state to local storage
});
},
),
const SizedBox(height: 16),
ListTile(
title: const Text(
'Blocked Numbers',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
),
subtitle: _blockedNumbers.isEmpty
? const Text(
'No blocked numbers',
style: TextStyle(color: Colors.grey),
)
: null,
),
..._blockedNumbers.map(
(number) => ListTile(
title: Text(
number,
style: const TextStyle(color: Colors.white),
),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _unblockNumber(number),
),
),
),
const Divider(color: Colors.grey),
ListTile(
title: const Text(
'Block a Number',
style: TextStyle(color: Colors.white),
),
trailing: const Icon(Icons.add, color: Colors.white),
onTap: () => _showBlockNumberDialog(),
),
],
),
);
}
// Function to block a number
void _blockNumber(String number) {
if (number.isNotEmpty && !_blockedNumbers.contains(number)) {
setState(() {
_blockedNumbers.add(number);
_savePreferences(); // Save the updated list
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$number has been blocked')),
);
}
}
// Function to unblock a number
void _unblockNumber(String number) {
setState(() {
_blockedNumbers.remove(number);
_savePreferences(); // Save the updated list
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$number has been unblocked')),
);
}
// Dialog for blocking a new number
void _showBlockNumberDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.grey[900],
title: const Text('Block a Number', style: TextStyle(color: Colors.white)),
content: TextField(
controller: _numberController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
hintText: 'Enter number',
hintStyle: TextStyle(color: Colors.grey),
),
style: const TextStyle(color: Colors.white),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel', style: TextStyle(color: Colors.white)),
),
TextButton(
onPressed: () {
_blockNumber(_numberController.text);
_numberController.clear();
Navigator.pop(context);
},
child: const Text('Block', style: TextStyle(color: Colors.red)),
),
],
);
},
);
}
@override
void dispose() {
_numberController.dispose();
super.dispose();
}
}

View File

@ -1,138 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'dart:convert';
import 'package:pointycastle/export.dart' as crypto;
import 'package:file_picker/file_picker.dart';
import 'dart:io';
import 'key_storage.dart';
class ExportPrivateKeyPage extends StatefulWidget {
const ExportPrivateKeyPage({super.key});
@override
_ExportPrivateKeyPageState createState() => _ExportPrivateKeyPageState();
}
class _ExportPrivateKeyPageState extends State<ExportPrivateKeyPage> {
final TextEditingController _passwordController = TextEditingController();
Future<void> _exportPrivateKey() async {
final keyStorage = KeyStorage();
final privateKeyPem = await keyStorage.getPrivateKey();
if (privateKeyPem == null) {
// Show error message if there's no key
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No private key found to export.'),
),
);
return;
}
final password = _passwordController.text;
if (password.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter a password.'),
),
);
return;
}
final encryptedData = _encryptPrivateKey(privateKeyPem, password);
final outputFile = await FilePicker.platform.saveFile(
dialogTitle: 'Save encrypted private key',
fileName: 'private_key_encrypted.aes',
);
if (outputFile != null) {
try {
final file = File(outputFile);
await file.writeAsBytes(encryptedData);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Key Exported'),
content: const Text('The encrypted private key has been exported successfully.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to write file: $e'),
),
);
}
}
}
Uint8List _encryptPrivateKey(String privateKey, String password) {
// Derive a key from the password using PBKDF2
final derivator = crypto.PBKDF2KeyDerivator(
crypto.HMac(crypto.SHA256Digest(), 64),
);
final salt = Uint8List.fromList(utf8.encode('some_salt')); // In production, use a random salt and store it securely
derivator.init(crypto.Pbkdf2Parameters(salt, 1000, 32));
final key = derivator.process(Uint8List.fromList(utf8.encode(password)));
// Initialize AES-CBC cipher with PKCS7 padding
final iv = Uint8List(16); // zero IV for example, in production use random IV and store it
final params = crypto.PaddedBlockCipherParameters<crypto.ParametersWithIV<crypto.KeyParameter>, Null>(
crypto.ParametersWithIV<crypto.KeyParameter>(crypto.KeyParameter(key), iv),
null,
);
final cipher = crypto.PaddedBlockCipher('AES/CBC/PKCS7');
cipher.init(true, params);
final input = Uint8List.fromList(utf8.encode(privateKey));
final output = cipher.process(input);
return output;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Export Private Key'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text(
'Enter a password to encrypt the private key:',
style: TextStyle(color: Colors.white),
),
TextField(
controller: _passwordController,
obscureText: true,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
hintText: 'Password',
hintStyle: TextStyle(color: Colors.grey),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _exportPrivateKey,
child: const Text('Export Encrypted Private Key'),
),
],
),
),
);
}
}

View File

@ -1,28 +0,0 @@
// key_storage.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class KeyStorage {
static const _publicKeyKey = 'public_key';
static const _privateKeyKey = 'private_key';
final FlutterSecureStorage _storage = const FlutterSecureStorage();
Future<void> saveKeys({required String publicKey, required String privateKey}) async {
await _storage.write(key: _publicKeyKey, value: publicKey);
await _storage.write(key: _privateKeyKey, value: privateKey);
}
Future<String?> getPublicKey() async {
return await _storage.read(key: _publicKeyKey);
}
Future<String?> getPrivateKey() async {
return await _storage.read(key: _privateKeyKey);
}
Future<void> deleteKeys() async {
await _storage.delete(key: _publicKeyKey);
await _storage.delete(key: _privateKeyKey);
}
}

View File

@ -1,80 +0,0 @@
import 'package:flutter/material.dart';
import 'show_public_key_qr.dart';
import 'show_public_key_text.dart';
import 'generate_new_key_pair.dart';
import 'export_private_key.dart';
import 'delete_key_pair.dart';
class KeyManagementPage extends StatelessWidget {
const KeyManagementPage({super.key});
void _navigateToOption(BuildContext context, String option) {
switch (option) {
case 'Display public key as text':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DisplayPublicKeyTextPage()),
);
break;
case 'Display public key as QR code':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DisplayPublicKeyQRCodePage()),
);
break;
case 'Generate a new key pair':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const GenerateNewKeyPairPage()),
);
break;
case 'Export private key to password-encrypted file (AES 256)':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()),
);
break;
case 'Delete a key pair':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()),
);
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
final keyManagementOptions = [
'Display public key as text',
'Display public key as QR code',
'Generate a new key pair',
'Export private key to password-encrypted file (AES 256)',
'Delete a key pair',
];
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Key Management'),
),
body: ListView.builder(
itemCount: keyManagementOptions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
keyManagementOptions[index],
style: const TextStyle(color: Colors.white),
),
trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white),
onTap: () {
_navigateToOption(context, keyManagementOptions[index]);
},
);
},
),
);
}
}

View File

@ -1,49 +0,0 @@
import 'package:flutter/material.dart';
import 'package:pretty_qr_code/pretty_qr_code.dart';
import 'key_storage.dart';
class DisplayPublicKeyQRCodePage extends StatelessWidget {
const DisplayPublicKeyQRCodePage({super.key});
Future<String?> _loadPublicKey() async {
final keyStorage = KeyStorage();
return keyStorage.getPublicKey();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Public Key in QR Code'),
),
body: FutureBuilder<String?>(
future: _loadPublicKey(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final publicKey = snapshot.data;
if (publicKey == null) {
return const Center(
child: Text(
'No public key found.',
style: TextStyle(color: Colors.white),
),
);
}
return Center(
child: PrettyQr(
data: publicKey,
size: 250,
roundEdges: true,
elementColor: Colors.white,
),
);
},
),
);
}
}

View File

@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import 'key_storage.dart';
class DisplayPublicKeyTextPage extends StatelessWidget {
const DisplayPublicKeyTextPage({super.key});
Future<String?> _loadPublicKey() async {
final keyStorage = KeyStorage();
return await keyStorage.getPublicKey();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Public Key as Text'),
),
body: FutureBuilder<String?>(
future: _loadPublicKey(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final publicKey = snapshot.data;
if (publicKey == null) {
return const Center(
child: Text(
'No public key found.',
style: TextStyle(color: Colors.white),
),
);
}
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SelectableText(
publicKey,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
);
},
),
);
}
}

View File

@ -1,52 +0,0 @@
import 'package:shared_preferences/shared_preferences.dart';
class BlockService {
static final BlockService _instance = BlockService._internal();
factory BlockService() {
return _instance;
}
BlockService._internal();
// Function to add a number to the blocked list
Future<void> blockNumber(String number) async {
if (number.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
if (!blockedNumbers.contains(number)) {
blockedNumbers.add(number);
await prefs.setStringList('blockedNumbers', blockedNumbers);
print('$number has been blocked');
} else {
print('$number is already blocked');
}
}
// Function to remove a number from the blocked list
Future<void> unblockNumber(String number) async {
if (number.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
if (blockedNumbers.contains(number)) {
blockedNumbers.remove(number);
await prefs.setStringList('blockedNumbers', blockedNumbers);
print('$number has been unblocked');
} else {
print('$number is not blocked');
}
}
// Check if a number is blocked
Future<bool> isNumberBlocked(String number) async {
if (number.isEmpty) return false;
final prefs = await SharedPreferences.getInstance();
List<String> blockedNumbers = prefs.getStringList('blockedNumbers') ?? [];
return blockedNumbers.contains(number);
}
}

View File

@ -1,31 +0,0 @@
import 'package:flutter_contacts/flutter_contacts.dart';
// Service to manage contact-related operations
class ContactService {
Future<List<Contact>> fetchContacts() async {
if (await FlutterContacts.requestPermission()) {
return await FlutterContacts.getContacts(
withProperties: true,
withThumbnail: true,
withAccounts: true,
withGroups: true,
withPhoto: true);
}
return [];
}
Future<List<Contact>> fetchFavoriteContacts() async {
// Fetch all contacts
List<Contact> contacts = await fetchContacts();
// Filter contacts to only include those with isStarred: true
List<Contact> favoriteContacts =
contacts.where((contact) => contact.isStarred).toList();
return favoriteContacts;
}
Future<void> addNewContact(Contact contact) async {
await FlutterContacts.insertContact(contact);
}
}

View File

@ -1,161 +0,0 @@
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
class QRCodeScannerScreen extends StatefulWidget {
const QRCodeScannerScreen({Key? key}) : super(key: key);
@override
_QRCodeScannerScreenState createState() => _QRCodeScannerScreenState();
}
class _QRCodeScannerScreenState extends State<QRCodeScannerScreen> {
MobileScannerController cameraController = MobileScannerController();
bool isScanning = true;
@override
void dispose() {
cameraController.dispose();
super.dispose();
}
Future<void> _createContactFromVCard(String vCardData) async {
// Parse VCard data
final Map<String, dynamic> contactInfo = _parseVCard(vCardData);
debugPrint("contactInfo: $contactInfo");
// // Create a new contact
// Contact newContact = Contact();
//
// // Set contact's name
// newContact.name = Name(
// first: contactInfo['firstName'] ?? '',
// last: contactInfo['lastName'] ?? '',
// );
//
// // Set contact's phone numbers
// if (contactInfo['phone'] != null) {
// newContact.phones = [
// Phone(contactInfo['phone'], label: PhoneLabel.mobile),
// ];
// }
//
// // Set contact's emails
// if (contactInfo['email'] != null) {
// newContact.emails = [
// Email(contactInfo['email'], label: EmailLabel.home),
// ];
// }
//
// // Request permission to write contacts
// bool permission = await FlutterContacts.requestPermission(readonly: false);
// if (!permission) {
// // Handle permission denied
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text('Contacts permission denied')),
// );
// return;
// }
//
// // Save the contact
// await newContact.insert();
}
Map<String, dynamic> _parseVCard(String vCardData) {
// Simple parser for VCard data
final Map<String, dynamic> contactInfo = {};
final lines = vCardData.split(RegExp(r'\r?\n'));
for (var line in lines) {
if (line.startsWith('FN:')) {
contactInfo['fullName'] = line.substring(3).trim();
} else if (line.startsWith('N:')) {
final names = line.substring(2).split(';');
contactInfo['lastName'] = names[0].trim();
contactInfo['firstName'] = names.length > 1 ? names[1].trim() : '';
} else if (line.startsWith('TEL:')) {
contactInfo['phone'] = line.substring(4).trim();
} else if (line.startsWith('EMAIL:')) {
contactInfo['email'] = line.substring(6).trim();
}
// Add more fields as needed
}
return contactInfo;
}
void _showContactAddedDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Contact Added'),
content: Text('The contact has been added to your address book.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Close dialog
Navigator.of(context).pop(); // Go back to previous screen
},
child: Text('OK'),
),
],
),
);
}
void _showInvalidQRDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Invalid QR Code'),
content: Text('The scanned QR code does not contain valid contact information.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Close dialog
isScanning = true; // Resume scanning
},
child: Text('Try Again'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scan Contact QR Code'),
),
body: MobileScanner(
controller: cameraController,
onDetect: (capture) {
if (!isScanning) return;
isScanning = false; // Prevent multiple scans
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
final String? code = barcode.rawValue;
if (code != null && code.contains('BEGIN:VCARD')) {
// Handle valid vCard QR code
_createContactFromVCard(code);
_showContactAddedDialog(); // TODO: Be a confirmation button "do you want to add XXX to your contacts?"
break;
}
}
if (!barcodes.any((barcode) =>
barcode.rawValue != null && barcode.rawValue!.contains('BEGIN:VCARD'))) {
// If no valid QR code is found
_showInvalidQRDialog();
isScanning = true; // Allow scanning again for invalid QR codes
}
},
),
);
}
}

View File

@ -1,59 +0,0 @@
## 2.1.1
* Fix Android building
## 2.1.0
* Make min sdk to Flutter 3.0.0
## 2.0.0
* Remove null check operator from method ListenPhone
## 1.0.4
* Null safety support
## 1.0.3
* Fix crash because of null value on empty number
## 1.0.2
* Support old Flutter plugin V1
## 1.0.1
* Fix crash related to Android 10
## 1.0.0
* Addded has phone permission
* Added request phone permission
* Added phone permission listener
* Fix bugs
## 0.0.6
* Add sample image.
## 0.0.5
* Add support for dual sim card.
## 0.0.4
* Print exception message on debugging console.
## 0.0.3
* Migrate to AndroidX
## 0.0.2
* Add gt mobile number native code to Android
## 0.0.1
* Initial Release.

View File

@ -1,13 +0,0 @@
Copyright 2023 Amr Eniou
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,66 +0,0 @@
# mobile_number
This is a FLutter Plugin to get the device mobile number.
#### Note: It works for Android only because getting mobile number of sim card is not supported in iOS.
#### Note: If the mobile number is not pre-exist on sim card it will not return te phone number.
## Installation
#### Link on Flutter plugins
https://pub.dev/packages/mobile_number
#### Note:
if you still using depecated FlutterActivty on MainActivity.java
which is import of
- `import io.flutter.app.FlutterActivity;`
not
- `import io.flutter.embedding.android.FlutterActivity;`
then you need to add the following to your MainActivity.java
```
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MobileNumberPlugin.registerWith(registrarFor("com.amorenew.mobile_number.MobileNumberPlugin()"));
}
```
## Usage
#### Check Phone Permission
```await MobileNumber.hasPhonePermission```
#### Request Phone Permission
```await MobileNumber.requestPhonePermission```
#### Listen to widget resume after Phone Permission request
```MobileNumber.listenPhonePermission((isPermissionGranted) {
if (isPermissionGranted) {
//Get mobile number
} else {
//Request Phone Permission
}
});
```
#### Get first sim card number
```Future<String> getMobileNumber() async {
final String mobileNumber = await MobileNumber.mobileNumber;
return mobileNumber;
}
```
#### Get List of sim cards for dual sim cards
```Future<List<SimCard>> geSimCards() async {
final List<SimCard> simCards = await MobileNumber.getSimCards;
return simCards;
}
```
![alt text](https://raw.githubusercontent.com/amorenew/Flutter-Mobile-Number-Plugin/master/sample1.png)

View File

@ -1,41 +0,0 @@
group 'com.amorenew.mobile_number'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 33
namespace "com.amorenew.mobile_number"
defaultConfig {
targetSdkVersion 33
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
implementation 'androidx.core:core:1.9.0'
// Other dependencies...
}

View File

@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true

View File

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1 +0,0 @@
rootProject.name = 'mobile_number'

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.amorenew.mobile_number"> <!-- Package is "com.amorenew.mobile_number" -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</manifest>

View File

@ -1,262 +0,0 @@
package com.amorenew.mobile_number;
import java.util.HashMap;
import java.util.Map;
class CountryToPhonePrefix {
private static Map<String, String> map = new HashMap<>();
static String prefixFor(String iso2CountryCode) {
String result = map.get(iso2CountryCode.toUpperCase());
if (result == null) {
return "";
}
return result;
}
static {
map.put("AC", "247");
map.put("AD", "376");
map.put("AE", "971");
map.put("AF", "93");
map.put("AG", "1268");
map.put("AI", "1264");
map.put("AL", "355");
map.put("AM", "374");
map.put("AN", "599");
map.put("AO", "244");
map.put("AR", "54");
map.put("AS", "1684");
map.put("AT", "43");
map.put("AU", "61");
map.put("AW", "297");
map.put("AX", "35818");
// map.put("AZ", "37497");
map.put("AZ", "994");
map.put("BA", "387");
map.put("BB", "1246");
map.put("BD", "880");
map.put("BE", "32");
map.put("BF", "226");
map.put("BG", "359");
map.put("BH", "973");
map.put("BI", "257");
map.put("BJ", "229");
map.put("BM", "1441");
map.put("BN", "673");
map.put("BO", "591");
map.put("BR", "55");
map.put("BS", "1242");
map.put("BT", "975");
map.put("BW", "267");
map.put("BY", "375");
map.put("BZ", "501");
map.put("CA", "1");
map.put("CC", "61");
map.put("CD", "243");
map.put("CF", "236");
map.put("CG", "242");
map.put("CH", "41");
map.put("CI", "225");
map.put("CK", "682");
map.put("CL", "56");
map.put("CM", "237");
map.put("CN", "86");
map.put("CO", "57");
map.put("CR", "506");
map.put("CS", "381");
map.put("CU", "53");
map.put("CV", "238");
map.put("CX", "61");
// map.put("CY", "90392");
map.put("CY", "357");
map.put("CZ", "420");
map.put("DE", "49");
map.put("DJ", "253");
map.put("DK", "45");
map.put("DM", "1767");
map.put("DO", "1809"); // and 1829?
map.put("DZ", "213");
map.put("EC", "593");
map.put("EE", "372");
map.put("EG", "20");
map.put("EH", "212");
map.put("ER", "291");
map.put("ES", "34");
map.put("ET", "251");
map.put("FI", "358");
map.put("FJ", "679");
map.put("FK", "500");
map.put("FM", "691");
map.put("FO", "298");
map.put("FR", "33");
map.put("GA", "241");
map.put("GB", "44");
map.put("GD", "1473");
map.put("GE", "995");
map.put("GF", "594");
map.put("GG", "44");
map.put("GH", "233");
map.put("GI", "350");
map.put("GL", "299");
map.put("GM", "220");
map.put("GN", "224");
map.put("GP", "590");
map.put("GQ", "240");
map.put("GR", "30");
map.put("GT", "502");
map.put("GU", "1671");
map.put("GW", "245");
map.put("GY", "592");
map.put("HK", "852");
map.put("HN", "504");
map.put("HR", "385");
map.put("HT", "509");
map.put("HU", "36");
map.put("ID", "62");
map.put("IE", "353");
map.put("IL", "972");
map.put("IM", "44");
map.put("IN", "91");
map.put("IO", "246");
map.put("IQ", "964");
map.put("IR", "98");
map.put("IS", "354");
map.put("IT", "39");
map.put("JE", "44");
map.put("JM", "1876");
map.put("JO", "962");
map.put("JP", "81");
map.put("KE", "254");
map.put("KG", "996");
map.put("KH", "855");
map.put("KI", "686");
map.put("KM", "269");
map.put("KN", "1869");
map.put("KP", "850");
map.put("KR", "82");
map.put("KW", "965");
map.put("KY", "1345");
map.put("KZ", "7");
map.put("LA", "856");
map.put("LB", "961");
map.put("LC", "1758");
map.put("LI", "423");
map.put("LK", "94");
map.put("LR", "231");
map.put("LS", "266");
map.put("LT", "370");
map.put("LU", "352");
map.put("LV", "371");
map.put("LY", "218");
map.put("MA", "212");
map.put("MC", "377");
// map.put("MD", "373533");
map.put("MD", "373");
map.put("ME", "382");
map.put("MG", "261");
map.put("MH", "692");
map.put("MK", "389");
map.put("ML", "223");
map.put("MM", "95");
map.put("MN", "976");
map.put("MO", "853");
map.put("MP", "1670");
map.put("MQ", "596");
map.put("MR", "222");
map.put("MS", "1664");
map.put("MT", "356");
map.put("MU", "230");
map.put("MV", "960");
map.put("MW", "265");
map.put("MX", "52");
map.put("MY", "60");
map.put("MZ", "258");
map.put("NA", "264");
map.put("NC", "687");
map.put("NE", "227");
map.put("NF", "672");
map.put("NG", "234");
map.put("NI", "505");
map.put("NL", "31");
map.put("NO", "47");
map.put("NP", "977");
map.put("NR", "674");
map.put("NU", "683");
map.put("NZ", "64");
map.put("OM", "968");
map.put("PA", "507");
map.put("PE", "51");
map.put("PF", "689");
map.put("PG", "675");
map.put("PH", "63");
map.put("PK", "92");
map.put("PL", "48");
map.put("PM", "508");
map.put("PR", "1787"); // and 1939 ?
map.put("PS", "970");
map.put("PT", "351");
map.put("PW", "680");
map.put("PY", "595");
map.put("QA", "974");
map.put("RE", "262");
map.put("RO", "40");
map.put("RS", "381");
map.put("RU", "7");
map.put("RW", "250");
map.put("SA", "966");
map.put("SB", "677");
map.put("SC", "248");
map.put("SD", "249");
map.put("SE", "46");
map.put("SG", "65");
map.put("SH", "290");
map.put("SI", "386");
map.put("SJ", "47");
map.put("SK", "421");
map.put("SL", "232");
map.put("SM", "378");
map.put("SN", "221");
map.put("SO", "252");
map.put("SR", "597");
map.put("ST", "239");
map.put("SV", "503");
map.put("SY", "963");
map.put("SZ", "268");
map.put("TA", "290");
map.put("TC", "1649");
map.put("TD", "235");
map.put("TG", "228");
map.put("TH", "66");
map.put("TJ", "992");
map.put("TK", "690");
map.put("TL", "670");
map.put("TM", "993");
map.put("TN", "216");
map.put("TO", "676");
map.put("TR", "90");
map.put("TT", "1868");
map.put("TV", "688");
map.put("TW", "886");
map.put("TZ", "255");
map.put("UA", "380");
map.put("UG", "256");
map.put("US", "1");
map.put("UY", "598");
map.put("UZ", "998");
map.put("VA", "379");
map.put("VC", "1784");
map.put("VE", "58");
map.put("VG", "1284");
map.put("VI", "1340");
map.put("VN", "84");
map.put("VU", "678");
map.put("WF", "681");
map.put("WS", "685");
map.put("YE", "967");
map.put("YT", "262");
map.put("ZA", "27");
map.put("ZM", "260");
map.put("ZW", "263");
}}

View File

@ -1,248 +0,0 @@
package com.amorenew.mobile_number;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.List;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener;
/**
* MobileNumberPlugin
*/
public class MobileNumberPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, RequestPermissionsResultListener {
private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;
final String Event_phonePermissionResult = "requestPhonePermission=";
private EventChannel.EventSink permissionEvent;
private Context applicationContext;
private Activity activity;
private TelephonyManager telephonyManager;
private Result result;
private MethodChannel methodChannel;
private EventChannel permissionEventChannel;
/**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) {
final MobileNumberPlugin instance = new MobileNumberPlugin();
instance.onAttachedToEngine(registrar.context(), registrar.messenger(), registrar.activity());
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
onAttachedToEngine(flutterPluginBinding.getApplicationContext(), flutterPluginBinding.getBinaryMessenger(), null);
}
private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger, Activity _activity) {
this.applicationContext = applicationContext;
if(_activity!=null)
this.activity=_activity;
methodChannel = new MethodChannel(messenger, "mobile_number");
methodChannel.setMethodCallHandler(this);
permissionEventChannel = new EventChannel(messenger, "phone_permission_event");
permissionEventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
permissionEvent = eventSink;
}
@Override
public void onCancel(Object o) {
}
});
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
//MobileNumberPlugin.activity = activityPluginBinding.getActivity();
//activityV2 = activityPluginBinding.getActivity();
activity = activityPluginBinding.getActivity();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding activityPluginBinding) {
}
@Override
public void onDetachedFromActivity() {
}
@Override
public void onMethodCall(MethodCall call, Result result) {
this.result = result;
final String method_GetMobileNumber = "getMobileNumber";
final String method_hasPhonePermission = "hasPhonePermission";
final String method_requestPhonePermission = "requestPhonePermission";
switch (call.method) {
case method_GetMobileNumber:
telephonyManager = (TelephonyManager) applicationContext
.getSystemService(Context.TELEPHONY_SERVICE);
getMobileNumber();
break;
case method_hasPhonePermission:
result.success(hasPhonePermission());
break;
case method_requestPhonePermission:
requestPhonePermission();
break;
default:
result.notImplemented();
break;
}
}
private boolean hasPhonePermission() {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
return ContextCompat.checkSelfPermission(applicationContext,
Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED;
} else {
return ContextCompat.checkSelfPermission(applicationContext,
Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
}
}
private void requestPhonePermission() {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.READ_PHONE_NUMBERS)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_PHONE_NUMBERS}, MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.READ_PHONE_STATE)) {
} else {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_PHONE_STATE}, MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
}
}
private void getMobileNumber() {
if (!hasPhonePermission()) {
requestPhonePermission();
} else {
// Permission has already been granted
generateMobileNumber();
}
}
@SuppressLint("HardwareIds")
private void generateMobileNumber() {
JSONArray simJsonArray = new JSONArray();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
for (SubscriptionInfo subscriptionInfo : getSubscriptions()) {
SimCard simCard = new SimCard(telephonyManager, subscriptionInfo);
simJsonArray.put(simCard.toJSON());
}
}
if (simJsonArray.length()==0) {
SimCard simCard = getSingleSimCard();
if (simCard != null) {
simJsonArray.put(simCard.toJSON());
}
}
if (simJsonArray.toString().isEmpty()) {
Log.d("UNAVAILABLE", "No phone number on sim card#3");
result.error("UNAVAILABLE", "No phone number on sim card", null);
} else result.success(simJsonArray.toString());
}
@SuppressLint("HardwareIds")
SimCard getSingleSimCard() {
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_DENIED
&& ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) {
Log.e("UNAVAILABLE", "No phone number on sim card Permission Denied#2", null);
return null;
} else if (telephonyManager.getLine1Number() == null || telephonyManager.getLine1Number().isEmpty()) {
Log.e("UNAVAILABLE", "No phone number on sim card#2", null);
return null;
}
return new SimCard(telephonyManager);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
List<SubscriptionInfo> getSubscriptions() {
final SubscriptionManager subscriptionManager = (SubscriptionManager) activity.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_DENIED
&& ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) {
Log.e("UNAVAILABLE", "No phone number on sim card Permission Denied#1", null);
return new ArrayList<>();
} else if (subscriptionManager == null) {
Log.e("UNAVAILABLE", "No phone number on sim card#1", null);
return new ArrayList<>();
}
return subscriptionManager.getActiveSubscriptionInfoList();
}
@Override
public boolean onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
// If request is cancelled, the result arrays are empty.
if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (permissionEvent != null)
permissionEvent.success(true);
generateMobileNumber();
return true;
} else {
if (permissionEvent != null)
permissionEvent.success(false);
}
}
result.error("PERMISSION", "onRequestPermissionsResult is not granted", null);
return false;
}
}

View File

@ -1,73 +0,0 @@
package com.amorenew.mobile_number;
import android.annotation.SuppressLint;
import android.os.Build;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import androidx.annotation.RequiresApi;
import org.json.JSONException;
import org.json.JSONObject;
public class SimCard {
private String carrierName = "";
private String displayName = "";
private int slotIndex = 0;
private String number = "";
private String countryIso = "";
private String countryPhonePrefix = "";
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
public SimCard(TelephonyManager telephonyManager, SubscriptionInfo subscriptionInfo) {
this.carrierName = subscriptionInfo.getCarrierName().toString();
this.displayName = subscriptionInfo.getDisplayName().toString();
this.slotIndex = subscriptionInfo.getSimSlotIndex();
this.number = subscriptionInfo.getNumber();
if (subscriptionInfo.getCountryIso() != null && !subscriptionInfo.getCountryIso().isEmpty())
this.countryIso = subscriptionInfo.getCountryIso();
else if (telephonyManager.getSimCountryIso() != null)
this.countryIso = telephonyManager.getSimCountryIso();
this.countryPhonePrefix = CountryToPhonePrefix.prefixFor(this.countryIso);
}
@SuppressLint({"MissingPermission", "HardwareIds"})
public SimCard(TelephonyManager telephonyManager) {
if (telephonyManager.getSimOperator() != null)
carrierName = telephonyManager.getSimOperatorName();
if (telephonyManager.getSimOperator() != null)
displayName = telephonyManager.getSimOperatorName();
if (telephonyManager.getSimCountryIso() != null) {
countryIso = telephonyManager.getSimCountryIso();
countryPhonePrefix = CountryToPhonePrefix.prefixFor(countryIso);
}
if (telephonyManager.getLine1Number() != null && !telephonyManager.getLine1Number().isEmpty()) {
if (telephonyManager.getLine1Number().startsWith("0"))
number = countryPhonePrefix + telephonyManager.getLine1Number().substring(1);
number = telephonyManager.getLine1Number();
}
}
// final JSONArray jsonArray = new JSONArray();
JSONObject toJSON() {
JSONObject json = new JSONObject();
try {
json.put("carrierName", carrierName);
json.put("displayName", displayName);
json.put("slotIndex", slotIndex);
json.put("number", number);
json.put("countryIso", countryIso);
json.put("countryPhonePrefix", countryPhonePrefix);
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
}

View File

@ -1,16 +0,0 @@
# mobile_number_example
Demonstrates how to use the mobile_number plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -1,58 +0,0 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdk 33
defaultConfig {
applicationId "com.example.mobile_number_example"
minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
lint {
disable 'InvalidPackage'
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobile_number_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,58 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobile_number_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="mobile_number_example">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
</activity>
<activity
android:name=".EmbeddingV1Activity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -1,14 +0,0 @@
package com.example.mobile_number_example;
import android.os.Bundle;
import com.amorenew.mobile_number.MobileNumberPlugin;
import io.flutter.app.FlutterActivity;
public class EmbeddingV1Activity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MobileNumberPlugin.registerWith(registrarFor("com.amorenew.mobile_number.MobileNumberPlugin()"));
}
}

View File

@ -1,15 +0,0 @@
package com.example.mobile_number_example;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
// @Override
// public void configureFlutterEngine(FlutterEngine flutterEngine) {
// super.configureFlutterEngine(flutterEngine);
// flutterEngine.getPlugins().add(new com.example.mobile_number.MobileNumberPlugin());
// }
}

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobile_number_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,29 +0,0 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -1,4 +0,0 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true

View File

@ -1,6 +0,0 @@
#Tue May 19 14:57:48 GST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip

View File

@ -1,15 +0,0 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@ -1,2 +0,0 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1,2 +0,0 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@ -1,84 +0,0 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

View File

@ -1,6 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@ -1,13 +0,0 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

View File

@ -1,9 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -1,78 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobile_number/mobile_number.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mobileNumber = '';
List<SimCard> _simCard = <SimCard>[];
@override
void initState() {
super.initState();
MobileNumber.listenPhonePermission((isPermissionGranted) {
if (isPermissionGranted) {
initMobileNumberState();
} else {}
});
initMobileNumberState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initMobileNumberState() async {
if (!await MobileNumber.hasPhonePermission) {
await MobileNumber.requestPhonePermission;
return;
}
// Platform messages may fail, so we use a try/catch PlatformException.
try {
_mobileNumber = (await MobileNumber.mobileNumber)!;
_simCard = (await MobileNumber.getSimCards)!;
} on PlatformException catch (e) {
debugPrint("Failed to get mobile number because of '${e.message}'");
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {});
}
Widget fillCards() {
List<Widget> widgets = _simCard
.map((SimCard sim) => Text(
'Sim Card Number: (${sim.countryPhonePrefix}) - ${sim.number}\nCarrier Name: ${sim.carrierName}\nCountry Iso: ${sim.countryIso}\nDisplay Name: ${sim.displayName}\nSim Slot Index: ${sim.slotIndex}\n\n'))
.toList();
return Column(children: widgets);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text('Running on: $_mobileNumber\n'),
fillCards()
],
),
),
),
);
}
}

View File

@ -1,64 +0,0 @@
name: mobile_number_example
description: Demonstrates how to use the mobile_number plugin.
publish_to: 'none'
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
mobile_number:
path: ../
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@ -1,27 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
// import 'package:flutter/material.dart';
// import 'package:flutter_test/flutter_test.dart';
// import '../../example/lib/main.dart';
void main() {
// testWidgets('Verify Platform version', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(MyApp());
// // Verify that platform version is retrieved.
// expect(
// find.byWidgetPredicate(
// (Widget widget) => widget is Text &&
// widget.data.startsWith('Running on:'),
// ),
// findsOneWidget,
// );
// });
}

View File

@ -1,4 +0,0 @@
#import <Flutter/Flutter.h>
@interface MobileNumberPlugin : NSObject<FlutterPlugin>
@end

View File

@ -1,20 +0,0 @@
#import "MobileNumberPlugin.h"
@implementation MobileNumberPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"mobile_number"
binaryMessenger:[registrar messenger]];
MobileNumberPlugin* instance = [[MobileNumberPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
} else {
result(FlutterMethodNotImplemented);
}
}
@end

Some files were not shown because too many files have changed in this diff Show More