Merge with dev
All checks were successful
/ mirror (push) Successful in 4s

This commit is contained in:
stcb 2024-11-29 09:40:22 +02:00
commit e38d00af28
189 changed files with 24704 additions and 134 deletions

View File

@ -0,0 +1,11 @@
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

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

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

View File

@ -2,3 +2,5 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryErro
android.useAndroidX=true
android.enableJetifier=true
dev.steenbakker.mobile_scanner.useUnbundled=true
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-amd64
#org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-17.0.13.0.11-3.fc41.x86_64

View File

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

View File

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

View File

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

View File

@ -45,5 +45,8 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSContactsUsageDescription</key>
<string>Contacts for calls/string>
</dict>
</plist>

View File

@ -0,0 +1,259 @@
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.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 = [];
@override
void initState() {
super.initState();
_fetchContacts();
}
Future<void> _fetchContacts() async {
if (await FlutterContacts.requestPermission()) {
_allContacts = await FlutterContacts.getContacts(withProperties: true);
_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;
});
}
// Placeholder function for adding contact
void addContact(String number) {
// This function is empty for now
}
@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) {
return ListTile(
title: Text(
contact.displayName,
style: const TextStyle(color: Colors.white),
),
subtitle: contact.phones.isNotEmpty
? Text(
contact.phones.first.number,
style: const TextStyle(color: Colors.grey),
)
: null,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Call button
IconButton(
icon: Icon(Icons.phone, color: Colors.green[300], size: 20),
onPressed: () {
print('Calling ${contact.displayName}');
},
),
// Text button
IconButton(
icon: Icon(Icons.message, color: Colors.blue[300], size: 20),
onPressed: () {
print('Texting ${contact.displayName}');
},
),
],
),
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 with empty function call
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: FloatingActionButton(
backgroundColor: Colors.blue,
onPressed: () {
addContact(dialedNumber);
},
child: const Icon(Icons.person_add, color: Colors.white),
),
),
],
),
// 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

@ -15,9 +15,6 @@ class _ContactPageState extends State<ContactPage> {
Widget build(BuildContext context) {
final contactState = ContactState.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Contacts'),
),
body: contactState.loading
? const LoadingIndicatorWidget()
// : ContactListWidget(contacts: contactState.contacts),

View File

@ -12,10 +12,12 @@ class _FavoritePageState extends State<FavoritePage> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Favorites'),
body: Center( // Center the text within the body
child: Text(
"Hello",
style: TextStyle(color: Colors.white), // Change text color for visibility
),
),
body: Text("Hello")
);
}
}

View File

@ -0,0 +1,152 @@
// history_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:intl/intl.dart'; // For date formatting
import 'package:dialer/features/contacts/contact_state.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> {
List<History> histories = [];
bool loading = true;
@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;
// Ensure there are enough contacts
if (contacts.isEmpty) {
setState(() {
loading = false;
});
return;
}
// Build histories using the contacts
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;
});
}
@override
Widget build(BuildContext context) {
final contactState = ContactState.of(context);
if (loading || contactState.loading) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: const Center(
child: CircularProgressIndicator(),
),
);
}
if (histories.isEmpty) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: const Center(
child: Text(
'No call history available.',
style: TextStyle(color: Colors.white),
),
),
);
}
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('History'),
),
body: ListView.builder(
itemCount: histories.length,
itemBuilder: (context, index) {
final history = histories[index];
final contact = history.contact;
return ListTile(
leading: (contact.thumbnail != null && contact.thumbnail!.isNotEmpty)
? CircleAvatar(
backgroundImage: MemoryImage(contact.thumbnail!),
)
: CircleAvatar(
child: Text(
contact.displayName.isNotEmpty
? contact.displayName[0]
: '?',
),
),
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: Text(
'${history.attempts}x',
style: const TextStyle(color: Colors.white),
),
onTap: () {
// Handle tap event if needed
},
);
},
),
);
}
}

View File

@ -0,0 +1,209 @@
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';
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
List<Contact> _allContacts = [];
List<Contact> _contactSuggestions = [];
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this, initialIndex: 1);
_tabController.addListener(_handleTabIndex);
_fetchContacts();
}
void _fetchContacts() async {
if (await FlutterContacts.requestPermission()) {
_allContacts = await FlutterContacts.getContacts(withProperties: true);
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: 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();
},
),
),
),
// Main content with TabBarView
Expanded(
child: Stack(
children: [
TabBarView(
controller: _tabController,
children: const [
FavoritePage(),
HistoryPage(),
ContactPage(),
SettingsPage(), // Add your SettingsPage here
],
),
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)),
Tab(
icon: Icon(_tabController.index == 3 // Corrected index
? Icons.settings
: Icons.settings_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

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
class SettingsCallPage extends StatefulWidget {
const SettingsCallPage({super.key});
@override
_SettingsCallPageState createState() => _SettingsCallPageState();
}
class _SettingsCallPageState extends State<SettingsCallPage> {
bool _enableVoicemail = true;
bool _enableCallRecording = false;
String _ringtone = 'Default';
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Calling settings'),
),
body: ListView(
children: [
SwitchListTile(
title: const Text('Enable Voicemail', style: TextStyle(color: Colors.white)),
value: _enableVoicemail,
onChanged: (bool value) {
setState(() {
_enableVoicemail = value;
});
},
),
SwitchListTile(
title: const Text('Enable call Recording', style: TextStyle(color: Colors.white)),
value: _enableCallRecording,
onChanged: (bool value) {
setState(() {
_enableCallRecording = value;
});
},
),
ListTile(
title: const Text('Ringtone', style: TextStyle(color: Colors.white)),
subtitle: Text(_ringtone, style: const TextStyle(color: Colors.grey)),
trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white),
onTap: () {
_selectRingtone(context);
},
),
],
),
);
}
void _selectRingtone(BuildContext context) {
showDialog<String>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('Select Ringtone'),
children: <Widget>[
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'Default');
},
child: const Text('Default'),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'Classic');
},
child: const Text('Classic'),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'Beep');
},
child: const Text('Beep'),
),
// Add more ringtone options
],
);
},
).then((value) {
if (value != null) {
setState(() {
_ringtone = value;
});
}
});
}
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class DeleteKeyPairPage extends StatelessWidget {
const DeleteKeyPairPage({super.key});
void _deleteKeyPair(BuildContext context) {
// Key deletion logic (not implemented here)
// ...
// Show confirmation message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('The key pair has been deleted.'),
),
);
// Navigate back or update the UI as needed
Navigator.pop(context);
}
void _showConfirmationDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Confirm Deletion'),
content: const Text(
'Are you sure you want to delete the key pair? This action is irreversible.'),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Delete'),
onPressed: () {
Navigator.of(context).pop();
_deleteKeyPair(context);
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Delete a Key Pair'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_showConfirmationDialog(context);
},
child: const Text('Delete Key Pair'),
),
),
);
}
}

View File

@ -0,0 +1,110 @@
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';
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 {
// Replace with your actual private key retrieval logic
final String privateKeyPem = 'Your private key here';
// Get the password from the user input
final password = _passwordController.text;
if (password.isEmpty) {
// Show error message
return;
}
// Encrypt the private key using AES-256
final encryptedData = _encryptPrivateKey(privateKeyPem, password);
// Let the user pick a file location
final outputFile = await FilePicker.platform.saveFile(
dialogTitle: 'Save encrypted private key',
fileName: 'private_key_encrypted.aes',
);
if (outputFile != null) {
// Write the encrypted data to the file
// Use appropriate file I/O methods (not shown here)
// ...
// Show a confirmation dialog or message
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'),
),
],
),
);
}
}
Uint8List _encryptPrivateKey(String privateKey, String password) {
// Encryption logic using AES-256
final key = crypto.PBKDF2KeyDerivator(crypto.HMac(crypto.SHA256Digest(), 64))
.process(Uint8List.fromList(utf8.encode(password)));
final params = crypto.PaddedBlockCipherParameters(
crypto.ParametersWithIV(crypto.KeyParameter(key), Uint8List(16)), // Initialization Vector
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

@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:pointycastle/export.dart' as crypto;
import 'dart:math';
import 'dart:convert';
import 'dart:typed_data';
import 'package:asn1lib/asn1lib.dart';
class GenerateNewKeyPairPage extends StatelessWidget {
const GenerateNewKeyPairPage({super.key});
Future<Map<String, String>> _generateKeyPair() async {
// Key generation logic using pointycastle
final keyParams = crypto.RSAKeyGeneratorParameters(
BigInt.parse('65537'),
2048,
64,
);
final secureRandom = crypto.FortunaRandom();
// Seed the random number generator
final random = Random.secure();
final seeds = List<int>.generate(32, (_) => random.nextInt(256));
secureRandom.seed(crypto.KeyParameter(Uint8List.fromList(seeds)));
final rngParams = crypto.ParametersWithRandom(keyParams, secureRandom);
final keyGenerator = crypto.RSAKeyGenerator();
keyGenerator.init(rngParams);
final pair = keyGenerator.generateKeyPair();
final publicKey = pair.publicKey as crypto.RSAPublicKey;
final privateKey = pair.privateKey as crypto.RSAPrivateKey;
// Convert keys to PEM format
final publicKeyPem = _encodePublicKeyToPemPKCS1(publicKey);
final privateKeyPem = _encodePrivateKeyToPemPKCS1(privateKey);
// Save keys securely (not implemented here)
return {'publicKey': publicKeyPem, 'privateKey': privateKeyPem};
}
String _encodePublicKeyToPemPKCS1(crypto.RSAPublicKey publicKey) {
final bytes = _encodePublicKeyToDer(publicKey);
return _formatPem(bytes, 'RSA PUBLIC KEY');
}
String _encodePrivateKeyToPemPKCS1(crypto.RSAPrivateKey privateKey) {
final bytes = _encodePrivateKeyToDer(privateKey);
return _formatPem(bytes, 'RSA PRIVATE KEY');
}
Uint8List _encodePublicKeyToDer(crypto.RSAPublicKey publicKey) {
final algorithmSeq = ASN1Sequence();
algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption'));
algorithmSeq.add(ASN1Null());
final publicKeySeq = ASN1Sequence();
publicKeySeq.add(ASN1Integer(publicKey.modulus!));
publicKeySeq.add(ASN1Integer(publicKey.exponent!));
final publicKeyBitString = ASN1BitString(Uint8List.fromList(publicKeySeq.encodedBytes));
final topLevelSeq = ASN1Sequence();
topLevelSeq.add(algorithmSeq);
topLevelSeq.add(publicKeyBitString);
return Uint8List.fromList(topLevelSeq.encodedBytes);
}
Uint8List _encodePrivateKeyToDer(crypto.RSAPrivateKey privateKey) {
final privateKeySeq = ASN1Sequence();
privateKeySeq.add(ASN1Integer(BigInt.from(0))); // Version
privateKeySeq.add(ASN1Integer(privateKey.n!));
privateKeySeq.add(ASN1Integer(privateKey.exponent!));
privateKeySeq.add(ASN1Integer(privateKey.d!));
privateKeySeq.add(ASN1Integer(privateKey.p!));
privateKeySeq.add(ASN1Integer(privateKey.q!));
privateKeySeq.add(ASN1Integer(privateKey.d! % (privateKey.p! - BigInt.one)));
privateKeySeq.add(ASN1Integer(privateKey.d! % (privateKey.q! - BigInt.one)));
privateKeySeq.add(ASN1Integer(privateKey.q!.modInverse(privateKey.p!)));
return Uint8List.fromList(privateKeySeq.encodedBytes);
}
String _formatPem(Uint8List bytes, String label) {
final base64 = base64Encode(bytes);
final chunks = RegExp('.{1,64}').allMatches(base64).map((m) => m.group(0)!);
return '-----BEGIN $label-----\n${chunks.join('\n')}\n-----END $label-----';
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Generate a New Key Pair'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
final keys = await _generateKeyPair();
// Display a confirmation dialog or message
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Keys Generated'),
content: const Text('The new key pair has been generated successfully.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
},
child: const Text('Generate a New Key Pair'),
),
),
);
}
}

View File

@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:dialer/features/settings/key/show_public_key_qr.dart';
import 'package:dialer/features/settings/key/show_public_key_text.dart';
import 'package:dialer/features/settings/key/generate_new_key_pair.dart';
import 'package:dialer/features/settings/key/export_private_key.dart';
import 'package:dialer/features/settings/key/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, warning POPUP':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()),
);
break;
default:
// Handle default or unknown options
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, warning POPUP',
];
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

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:pretty_qr_code/pretty_qr_code.dart';
class DisplayPublicKeyQRCodePage extends StatelessWidget {
const DisplayPublicKeyQRCodePage({super.key});
@override
Widget build(BuildContext context) {
// Replace with your actual public key retrieval logic
final String publicKey = 'Your public key here';
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Public Key in QR Code'),
),
body: Center(
child: PrettyQr(
data: publicKey,
size: 250,
roundEdges: true,
elementColor: Colors.white,
),
),
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class DisplayPublicKeyTextPage extends StatelessWidget {
const DisplayPublicKeyTextPage({super.key});
@override
Widget build(BuildContext context) {
// Replace with your actual public key retrieval logic
final String publicKey = 'Your public key here';
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Public Key as Text'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SelectableText(
publicKey,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
),
);
}
}

View File

@ -0,0 +1,68 @@
// settings.dart
import 'package:flutter/material.dart';
import 'package:dialer/features/settings/call/settingsCall.dart';
import 'package:dialer/features/settings/sim/settings_accounts.dart';
import 'package:dialer/features/settings/key/manage_keys_page.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
void _navigateToSettings(BuildContext context, String setting) {
switch (setting) {
case 'Calling settings':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsCallPage()),
);
break;
case 'Page of telephone accounts':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsAccountsPage()),
);
break;
case 'Gestion de clés':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const KeyManagementPage()),
);
break;
// Add more cases for other settings pages
default:
// Handle default or unknown settings
break;
}
}
@override
Widget build(BuildContext context) {
final settingsOptions = [
'Calling settings',
'Page of telephone accounts',
'Key management',
];
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('settings'),
),
body: ListView.builder(
itemCount: settingsOptions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
settingsOptions[index],
style: const TextStyle(color: Colors.white),
),
trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white),
onTap: () {
_navigateToSettings(context, settingsOptions[index]);
},
);
},
),
);
}
}

View File

@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:mobile_number/mobile_number.dart';
class ChooseSimPage extends StatefulWidget {
const ChooseSimPage({Key? key}) : super(key: key);
@override
_ChooseSimPageState createState() => _ChooseSimPageState();
}
class _ChooseSimPageState extends State<ChooseSimPage> {
List<SimCard> _simCards = [];
int? _selectedSimIndex;
@override
void initState() {
super.initState();
_fetchSimCards();
}
Future<void> _fetchSimCards() async {
try {
bool permissionsGranted = await MobileNumber.hasPhonePermission;
if (!permissionsGranted) {
await MobileNumber.requestPhonePermission;
permissionsGranted = await MobileNumber.hasPhonePermission;
}
if (permissionsGranted) {
List<SimCard>? simCards = await MobileNumber.getSimCards;
if (simCards != null && mounted) {
setState(() {
_simCards = simCards;
_selectedSimIndex = 0;
});
} else {
print('No SIM cards found');
}
} else {
print('Phone permission denied');
}
} catch (e) {
print('Failed to get SIM cards: $e');
}
}
void _onSimSelected(int? index) {
if (index != null) {
setState(() {
_selectedSimIndex = index;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Choose SIM'),
),
body: _simCards.isEmpty
? const Center(
child: Text(
'No SIM cards found',
style: TextStyle(color: Colors.white),
),
)
: ListView.builder(
itemCount: _simCards.length,
itemBuilder: (context, index) {
final sim = _simCards[index];
return ListTile(
title: Text(
'SIM ${index + 1}',
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
'Operator: ${sim.carrierName ?? 'N/A'}',
style: const TextStyle(color: Colors.grey),
),
trailing: Radio<int>(
value: index,
groupValue: _selectedSimIndex,
onChanged: _onSimSelected,
),
);
},
),
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'choose_sim.dart';
import 'sim_parameters.dart';
class SettingsAccountsPage extends StatelessWidget {
const SettingsAccountsPage({Key? key}) : super(key: key);
void _navigateToAccountOption(BuildContext context, String option) {
switch (option) {
case 'Choisir la SIM':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ChooseSimPage()),
);
break;
case 'Paramètre SIM':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SimParametersPage()),
);
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
final accountOptions = [
'Choisir la SIM',
'Paramètre SIM',
];
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Page des comptes téléphoniques'),
),
body: ListView.builder(
itemCount: accountOptions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
accountOptions[index],
style: const TextStyle(color: Colors.white),
),
trailing:
const Icon(Icons.arrow_forward_ios, color: Colors.white),
onTap: () {
_navigateToAccountOption(context, accountOptions[index]);
},
);
},
),
);
}
}

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:mobile_number/mobile_number.dart';
class SimParametersPage extends StatefulWidget {
const SimParametersPage({Key? key}) : super(key: key);
@override
_SimParametersPageState createState() => _SimParametersPageState();
}
class _SimParametersPageState extends State<SimParametersPage> {
List<SimCard> _simCards = [];
@override
void initState() {
super.initState();
_fetchSimParameters();
}
Future<void> _fetchSimParameters() async {
try {
bool permissionsGranted = await MobileNumber.hasPhonePermission;
if (!permissionsGranted) {
await MobileNumber.requestPhonePermission;
// Check again if permission is granted
permissionsGranted = await MobileNumber.hasPhonePermission;
}
if (permissionsGranted) {
List<SimCard>? simCards = await MobileNumber.getSimCards;
if (simCards != null && mounted) {
setState(() {
_simCards = simCards;
});
} else {
print('No SIM cards found');
}
} else {
print('Phone permission denied');
}
} catch (e) {
print('Failed to get SIM cards: $e');
}
}
Widget _buildSimInfo(SimCard sim, int index) {
return Card(
color: Colors.grey[850],
child: ListTile(
title: Text(
'SIM ${index + 1}',
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
'''
Carrier Name: ${sim.carrierName ?? 'N/A'}
Country Iso: ${sim.countryIso ?? 'N/A'}
Display Name: ${sim.displayName ?? 'N/A'}
Slot Index: ${sim.slotIndex ?? 'N/A'}
Number: ${sim.number ?? 'N/A'}
''',
style: const TextStyle(color: Colors.grey),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('SIM Parameters'),
),
body: _simCards.isEmpty
? const Center(
child: Text(
'No SIM cards found',
style: TextStyle(color: Colors.white),
),
)
: ListView.builder(
itemCount: _simCards.length,
itemBuilder: (context, index) {
return _buildSimInfo(_simCards[index], index);
},
),
);
}
}

View File

@ -1,23 +1,23 @@
import 'package:dialer/features/home/home_page.dart';
import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_state.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ContactState(
child: MaterialApp(
theme: ThemeData(
brightness: Brightness.dark
),
home: const MyHomePage(),
)
);
}
}
import 'package:dialer/features/home/home_page.dart';
import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_state.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ContactState(
child: MaterialApp(
theme: ThemeData(
brightness: Brightness.dark
),
home: const MyHomePage(),
)
);
}
}

View File

@ -0,0 +1,59 @@
## 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

@ -0,0 +1,13 @@
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

@ -0,0 +1,66 @@
# 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

@ -0,0 +1,41 @@
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

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

View File

@ -0,0 +1,5 @@
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

@ -0,0 +1 @@
{}

View File

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

View File

@ -0,0 +1,10 @@
<?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

@ -0,0 +1,262 @@
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

@ -0,0 +1,248 @@
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

@ -0,0 +1,73 @@
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;
}
}

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