From 612cccc381c5194a6bcbeaa414cc67b2d51896df Mon Sep 17 00:00:00 2001 From: AlexisDanlos <91090088+AlexisDanlos@users.noreply.github.com> Date: Sat, 5 Apr 2025 01:48:34 +0200 Subject: [PATCH] Refactor obfuscate service and avatar widget; move to domain layer and remove unused files - Moved ObfuscateService and ObfuscatedAvatar from services to domain/services. - Updated references to use globals for stealth mode instead of AppConfig. - Removed old obfuscated_avatar.dart file and updated imports in call and incoming call pages. - Cleaned up imports in composition, contact, and history pages to reflect new structure. - Deleted unused BlockService, CallService, ContactService, AsymmetricCryptoService, and QRScanner classes. --- dialer/lib/domain/services/call_service.dart | 117 ++--- .../asymmetric_crypto_service.dart | 442 +++++++----------- .../domain/services/obfuscate_service.dart | 59 ++- .../common/widgets/obfuscated_avatar.dart | 53 --- .../presentation/features/call/call_page.dart | 2 +- .../features/call/incoming_call_page.dart | 1 - .../features/composition/composition.dart | 4 +- .../features/contacts/contact_page.dart | 4 - .../widgets/alphabet_scroll_page.dart | 1 - .../contacts/widgets/contact_modal.dart | 8 +- .../contacts/widgets/share_own_qr.dart | 2 +- .../features/history/history_page.dart | 6 +- .../settings/cryptography/key_management.dart | 2 +- dialer/lib/services/block_service.dart | 78 ---- dialer/lib/services/call_service.dart | 154 ------ dialer/lib/services/contact_service.dart | 86 ---- .../asymmetric_crypto_service.dart | 170 ------- dialer/lib/services/obfuscate_service.dart | 91 ---- dialer/lib/services/qr/qr_scanner.dart | 57 --- 19 files changed, 271 insertions(+), 1066 deletions(-) delete mode 100644 dialer/lib/presentation/common/widgets/obfuscated_avatar.dart delete mode 100644 dialer/lib/services/block_service.dart delete mode 100644 dialer/lib/services/call_service.dart delete mode 100644 dialer/lib/services/contact_service.dart delete mode 100644 dialer/lib/services/cryptography/asymmetric_crypto_service.dart delete mode 100644 dialer/lib/services/obfuscate_service.dart delete mode 100644 dialer/lib/services/qr/qr_scanner.dart diff --git a/dialer/lib/domain/services/call_service.dart b/dialer/lib/domain/services/call_service.dart index b46a1d3..c348b59 100644 --- a/dialer/lib/domain/services/call_service.dart +++ b/dialer/lib/domain/services/call_service.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:typed_data'; +import '../../presentation/features/call/call_page.dart'; +import '../../presentation/features/call/incoming_call_page.dart'; // Import the new page class CallService { static const MethodChannel _channel = MethodChannel('call_service'); @@ -9,20 +10,7 @@ class CallService { static final GlobalKey navigatorKey = GlobalKey(); - // Private constructor - CallService._privateConstructor() { - _initializeMethodCallHandler(); - } - - // Singleton instance - static final CallService _instance = CallService._privateConstructor(); - - // Factory constructor to return the same instance - factory CallService() { - return _instance; - } - - void _initializeMethodCallHandler() { + CallService() { _channel.setMethodCallHandler((call) async { final context = navigatorKey.currentContext; print('CallService: Received method ${call.method} with args ${call.arguments}'); @@ -33,70 +21,56 @@ class CallService { switch (call.method) { case "callAdded": - _handleCallAdded(context, call.arguments); + final phoneNumber = call.arguments["callId"] as String; + final state = call.arguments["state"] as String; + currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); + print('CallService: Call added, number: $currentPhoneNumber, state: $state'); + if (state == "ringing") { + _navigateToIncomingCallPage(context); + } else { + _navigateToCallPage(context); + } break; case "callStateChanged": - _handleCallStateChanged(context, call.arguments); + final state = call.arguments["state"] as String; + print('CallService: State changed to $state'); + if (state == "disconnected" || state == "disconnecting") { + _closeCallPage(context); + } else if (state == "active" || state == "dialing") { + _navigateToCallPage(context); + } else if (state == "ringing") { + _navigateToIncomingCallPage(context); + } break; case "callEnded": case "callRemoved": - _handleCallEnded(context); + print('CallService: Call ended/removed'); + _closeCallPage(context); + currentPhoneNumber = null; break; } }); } - void _handleCallAdded(BuildContext context, dynamic arguments) { - final phoneNumber = arguments["callId"] as String; - final state = arguments["state"] as String; - currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); - print('CallService: Call added, number: $currentPhoneNumber, state: $state'); - - if (state == "ringing") { - _navigateToIncomingCallPage(context); - } else { - _navigateToCallPage(context); - } - } - - void _handleCallStateChanged(BuildContext context, dynamic arguments) { - final state = arguments["state"] as String; - print('CallService: State changed to $state'); - - if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(context); - } else if (state == "active" || state == "dialing") { - _navigateToCallPage(context); - } else if (state == "ringing") { - _navigateToIncomingCallPage(context); - } - } - - void _handleCallEnded(BuildContext context) { - print('CallService: Call ended/removed'); - _closeCallPage(context); - currentPhoneNumber = null; - } - void _navigateToCallPage(BuildContext context) { if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/call') { print('CallService: CallPage already visible, skipping navigation'); return; } - print('CallService: Navigating to CallPage'); - Navigator.pushReplacementNamed( - context, - '/call', - arguments: { - 'displayName': currentPhoneNumber!, - 'phoneNumber': currentPhoneNumber!, - 'thumbnail': null, - } + Navigator.pushReplacement( + context, + MaterialPageRoute( + settings: const RouteSettings(name: '/call'), + builder: (context) => CallPage( + displayName: currentPhoneNumber!, + phoneNumber: currentPhoneNumber!, + thumbnail: null, + ), + ), ).then((_) { _isCallPageVisible = false; }); - _isCallPageVisible = true; } @@ -105,20 +79,20 @@ class CallService { print('CallService: IncomingCallPage already visible, skipping navigation'); return; } - print('CallService: Navigating to IncomingCallPage'); - Navigator.pushNamed( + Navigator.push( context, - '/incoming_call', - arguments: { - 'displayName': currentPhoneNumber!, - 'phoneNumber': currentPhoneNumber!, - 'thumbnail': null, - } + MaterialPageRoute( + settings: const RouteSettings(name: '/incoming_call'), + builder: (context) => IncomingCallPage( + displayName: currentPhoneNumber!, + phoneNumber: currentPhoneNumber!, + thumbnail: null, + ), + ), ).then((_) { _isCallPageVisible = false; }); - _isCallPageVisible = true; } @@ -127,7 +101,6 @@ class CallService { print('CallService: CallPage not visible, skipping pop'); return; } - if (Navigator.canPop(context)) { print('CallService: Popping CallPage'); Navigator.pop(context); @@ -146,7 +119,6 @@ class CallService { print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - if (result["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), @@ -166,7 +138,6 @@ class CallService { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - if (result["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), @@ -180,4 +151,4 @@ class CallService { rethrow; } } -} +} \ No newline at end of file diff --git a/dialer/lib/domain/services/cryptography/asymmetric_crypto_service.dart b/dialer/lib/domain/services/cryptography/asymmetric_crypto_service.dart index d52cb91..07c2964 100644 --- a/dialer/lib/domain/services/cryptography/asymmetric_crypto_service.dart +++ b/dialer/lib/domain/services/cryptography/asymmetric_crypto_service.dart @@ -1,296 +1,170 @@ +import 'dart:async'; import 'dart:convert'; -import 'dart:math'; -import 'dart:typed_data'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:pointycastle/export.dart'; -import 'package:pointycastle/asymmetric/api.dart'; -import 'package:asn1lib/asn1lib.dart'; +import 'package:uuid/uuid.dart'; -/// Service for handling asymmetric cryptography operations class AsymmetricCryptoService { - static const String _privateKeyTag = 'private_key'; - static const String _publicKeyTag = 'public_key'; - - final FlutterSecureStorage _secureStorage = const FlutterSecureStorage(); - - AsymmetricKeyPair? _currentKeyPair; - - /// Initialize with the default key pair, creating one if it doesn't exist - Future initializeDefaultKeyPair() async { + static const MethodChannel _channel = MethodChannel('com.example.keystore'); + final FlutterSecureStorage _secureStorage = FlutterSecureStorage(); + final String _aliasPrefix = 'icing_'; + final Uuid _uuid = Uuid(); + + /// Generates an ECDSA P-256 key pair with a unique alias and stores its metadata. + Future generateKeyPair({String? label}) async { try { - // Try to load existing keys - final privateKeyStr = await _secureStorage.read(key: _privateKeyTag); - final publicKeyStr = await _secureStorage.read(key: _publicKeyTag); + // Generate a unique identifier for the key + final String uuid = _uuid.v4(); + final String alias = '$_aliasPrefix$uuid'; + + // Invoke native method to generate the key pair + await _channel.invokeMethod('generateKeyPair', {'alias': alias}); + + // Store key metadata securely + final Map keyMetadata = { + 'alias': alias, + 'label': label ?? 'Key $uuid', + 'created_at': DateTime.now().toIso8601String(), + }; + + // Retrieve existing keys + final String? existingKeys = await _secureStorage.read(key: 'keys'); + List keysList = existingKeys != null ? jsonDecode(existingKeys) : []; + + // Add the new key + keysList.add(keyMetadata); + + // Save updated keys list + await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); + + return alias; + } on PlatformException catch (e) { + throw Exception("Failed to generate key pair: ${e.message}"); + } + } + + /// Signs data using the specified key alias. + Future signData(String alias, String data) async { + try { + final String signature = await _channel.invokeMethod('signData', { + 'alias': alias, + 'data': data, + }); + return signature; + } on PlatformException catch (e) { + throw Exception("Failed to sign data with alias '$alias': ${e.message}"); + } + } + + /// Retrieves the public key for the specified alias. + Future getPublicKey(String alias) async { + try { + final String publicKey = await _channel.invokeMethod('getPublicKey', { + 'alias': alias, + }); + return publicKey; + } on PlatformException catch (e) { + throw Exception("Failed to retrieve public key: ${e.message}"); + } + } + + /// Deletes the key pair associated with the specified alias and removes its metadata. + Future deleteKeyPair(String alias) async { + try { + await _channel.invokeMethod('deleteKeyPair', {'alias': alias}); - if (privateKeyStr != null && publicKeyStr != null) { - try { - // Parse existing keys - final privateKey = _parsePrivateKeyFromPem(privateKeyStr); - final publicKey = _parsePublicKeyFromPem(publicKeyStr); - - _currentKeyPair = AsymmetricKeyPair(publicKey, privateKey); - debugPrint('Loaded existing key pair successfully'); - return; - } catch (e) { - debugPrint('Error parsing stored keys: $e'); - // Continue to generate new keys + final String? existingKeys = await _secureStorage.read(key: 'keys'); + if (existingKeys != null) { + List keysList = jsonDecode(existingKeys); + keysList.removeWhere((key) => key['alias'] == alias); + await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); + } + } on PlatformException catch (e) { + throw Exception("Failed to delete key pair: ${e.message}"); + } + } + + /// Retrieves all stored key metadata. + Future>> getAllKeys() async { + try { + final String? existingKeys = await _secureStorage.read(key: 'keys'); + if (existingKeys == null) { + print("No keys found"); + return []; + } + List keysList = jsonDecode(existingKeys); + return keysList.cast>(); + } catch (e) { + throw Exception("Failed to retrieve keys: $e"); + } + } + + /// Checks if a key pair exists for the given alias. + Future keyPairExists(String alias) async { + try { + final bool exists = await _channel.invokeMethod('keyPairExists', {'alias': alias}); + return exists; + } on PlatformException catch (e) { + throw Exception("Failed to check key pair existence: ${e.message}"); + } + } + + /// Initializes the default key pair if it doesn't exist. + Future initializeDefaultKeyPair() async { + const String defaultAlias = 'icing_default'; + final List> keys = await getAllKeys(); + + // Check if the key exists in metadata + final bool defaultKeyExists = keys.any((key) => key['alias'] == defaultAlias); + + if (!defaultKeyExists) { + await _channel.invokeMethod('generateKeyPair', {'alias': defaultAlias}); + + final Map keyMetadata = { + 'alias': defaultAlias, + 'label': 'Default Key', + 'created_at': DateTime.now().toIso8601String(), + }; + + keys.add(keyMetadata); + await _secureStorage.write(key: 'keys', value: jsonEncode(keys)); + } + } + + /// Updates the label of a key with the specified alias. + /// + /// [alias]: The unique alias of the key to update. + /// [newLabel]: The new label to assign to the key. + /// + /// Throws an exception if the key is not found or the update fails. + Future updateKeyLabel(String alias, String newLabel) async { + try { + // Retrieve existing keys + final String? existingKeys = await _secureStorage.read(key: 'keys'); + if (existingKeys == null) { + throw Exception("No keys found to update."); + } + + List keysList = jsonDecode(existingKeys); + + // Find the key with the specified alias + bool keyFound = false; + for (var key in keysList) { + if (key['alias'] == alias) { + key['label'] = newLabel; + keyFound = true; + break; } } - - // Generate new key pair - await generateAndStoreKeyPair(); + + if (!keyFound) { + throw Exception("Key with alias \"$alias\" not found."); + } + + // Save the updated keys list + await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); } catch (e) { - debugPrint('Error initializing key pair: $e'); - // Instead of rethrowing, we'll continue without encryption capability - // This ensures the app doesn't crash during startup + throw Exception("Failed to update key label: $e"); } } - - /// Generate a new key pair and store it securely - Future generateAndStoreKeyPair() async { - try { - debugPrint('Generating new RSA key pair...'); - - // Generate key pair with a simpler approach - final keyPair = await _generateRSAKeyPairSimple(1024); // Smaller keys for faster generation - _currentKeyPair = keyPair; - - // Export to PEM format - final publicKeyPem = _encodePublicKeyToPem(keyPair.publicKey); - final privateKeyPem = _encodePrivateKeyToPem(keyPair.privateKey); - - // Store in secure storage - await _secureStorage.write(key: _publicKeyTag, value: publicKeyPem); - await _secureStorage.write(key: _privateKeyTag, value: privateKeyPem); - - debugPrint('New key pair generated and stored successfully'); - } catch (e) { - debugPrint('Failed to generate key pair: $e'); - // Don't throw, allow the app to continue without encryption - } - } - - /// Encrypt data with the public key - Uint8List? encrypt(String plainText) { - if (_currentKeyPair == null) { - debugPrint('No key pair available for encryption'); - return null; - } - - try { - final cipher = PKCS1Encoding(RSAEngine()) - ..init(true, PublicKeyParameter(_currentKeyPair!.publicKey)); - - final input = Uint8List.fromList(utf8.encode(plainText)); - return cipher.process(input); - } catch (e) { - debugPrint('Encryption error: $e'); - return null; - } - } - - /// Decrypt data with the private key - String? decrypt(Uint8List encryptedData) { - if (_currentKeyPair == null) { - debugPrint('No key pair available for decryption'); - return null; - } - - try { - final cipher = PKCS1Encoding(RSAEngine()) - ..init(false, PrivateKeyParameter(_currentKeyPair!.privateKey)); - - final decrypted = cipher.process(encryptedData); - return utf8.decode(decrypted); - } catch (e) { - debugPrint('Decryption error: $e'); - return null; - } - } - - /// Get the current public key in PEM format - Future getPublicKeyPem() async { - return await _secureStorage.read(key: _publicKeyTag); - } - - // Simpler RSA key pair generation that doesn't use FortunaRandom - Future> _generateRSAKeyPairSimple(int bitLength) async { - // Use a simple secure random instead of FortunaRandom - final secureRandom = _SecureRandom(); - - // Create RSA key generator - final keyGen = RSAKeyGenerator() - ..init(ParametersWithRandom( - RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64), - secureRandom, - )); - - // Generate and return the key pair - return keyGen.generateKeyPair() as AsymmetricKeyPair; - } - - // Original method (but no longer used directly) - static AsymmetricKeyPair _generateRSAKeyPair(int bitLength) { - try { - final secureRandom = _SecureRandom(); - - final keyGen = RSAKeyGenerator() - ..init(ParametersWithRandom( - RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64), - secureRandom, - )); - - return keyGen.generateKeyPair() as AsymmetricKeyPair; - } catch (e) { - debugPrint('Error in _generateRSAKeyPair: $e'); - rethrow; - } - } - - String _encodePublicKeyToPem(RSAPublicKey publicKey) { - final asn1Sequence = ASN1Sequence(); - - asn1Sequence.add(ASN1Integer(publicKey.modulus!)); - asn1Sequence.add(ASN1Integer(publicKey.exponent!)); - - final base64 = base64Encode(asn1Sequence.encodedBytes); - return '-----BEGIN PUBLIC KEY-----\n$base64\n-----END PUBLIC KEY-----'; - } - - String _encodePrivateKeyToPem(RSAPrivateKey privateKey) { - final asn1Sequence = ASN1Sequence(); - - asn1Sequence.add(ASN1Integer(BigInt.from(0))); // version - asn1Sequence.add(ASN1Integer(privateKey.modulus!)); - asn1Sequence.add(ASN1Integer(privateKey.publicExponent!)); - asn1Sequence.add(ASN1Integer(privateKey.privateExponent!)); - asn1Sequence.add(ASN1Integer(privateKey.p!)); - asn1Sequence.add(ASN1Integer(privateKey.q!)); - // d mod (p-1) - asn1Sequence.add(ASN1Integer(privateKey.privateExponent! % (privateKey.p! - BigInt.from(1)))); - // d mod (q-1) - asn1Sequence.add(ASN1Integer(privateKey.privateExponent! % (privateKey.q! - BigInt.from(1)))); - // q^-1 mod p - asn1Sequence.add(ASN1Integer(_modInverse(privateKey.q!, privateKey.p!))); - - final base64 = base64Encode(asn1Sequence.encodedBytes); - return '-----BEGIN RSA PRIVATE KEY-----\n$base64\n-----END RSA PRIVATE KEY-----'; - } - - RSAPrivateKey _parsePrivateKeyFromPem(String pemString) { - final pemContent = pemString - .replaceAll('-----BEGIN RSA PRIVATE KEY-----', '') - .replaceAll('-----END RSA PRIVATE KEY-----', '') - .replaceAll('\n', ''); - - final asn1Parser = ASN1Parser(base64Decode(pemContent)); - final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; - - // Parse sequence values - final values = topLevelSeq.elements!.map((obj) => (obj as ASN1Integer).valueAsBigInteger).toList(); - - // Create RSA private key from components - return RSAPrivateKey( - values[1], // modulus - values[3], // privateExponent - values[4], // p - values[5], // q - ); - } - - RSAPublicKey _parsePublicKeyFromPem(String pemString) { - final pemContent = pemString - .replaceAll('-----BEGIN PUBLIC KEY-----', '') - .replaceAll('-----END PUBLIC KEY-----', '') - .replaceAll('\n', ''); - - final asn1Parser = ASN1Parser(base64Decode(pemContent)); - final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; - - // Extract modulus and exponent - final modulus = (topLevelSeq.elements![0] as ASN1Integer).valueAsBigInteger; - final exponent = (topLevelSeq.elements![1] as ASN1Integer).valueAsBigInteger; - - return RSAPublicKey(modulus, exponent); - } - - // Modular multiplicative inverse - BigInt _modInverse(BigInt a, BigInt m) { - // Extended Euclidean Algorithm to find modular inverse - BigInt t = BigInt.zero, newT = BigInt.one; - BigInt r = m, newR = a; - - while (newR != BigInt.zero) { - final quotient = r ~/ newR; - - final tempT = t; - t = newT; - newT = tempT - quotient * newT; - - final tempR = r; - r = newR; - newR = tempR - quotient * newR; - } - - if (r > BigInt.one) throw Exception('$a is not invertible modulo $m'); - if (t < BigInt.zero) t += m; - - return t; - } -} - -// Simple secure random implementation that doesn't use AESEngine -class _SecureRandom implements SecureRandom { - final Random _random = Random.secure(); - - @override - String get algorithmName => 'Dart_SecureRandom'; - - @override - void seed(CipherParameters params) { - // No additional seeding required as Random.secure() is already seeded - } - - @override - BigInt nextBigInteger(int bitLength) { - final fullBytes = bitLength ~/ 8; - final remainingBits = bitLength % 8; - - final bytes = Uint8List(fullBytes + (remainingBits > 0 ? 1 : 0)); - for (var i = 0; i < bytes.length; i++) { - bytes[i] = nextUint8(); - } - - // Adjust the last byte to match the remaining bits - if (remainingBits > 0) { - bytes[bytes.length - 1] &= (1 << remainingBits) - 1; - } - - return BigInt.parse(bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''), radix: 16); - } - - @override - int nextUint16() => (_random.nextInt(1 << 16) & 0xFFFF); - - @override - int nextUint32() => (_random.nextInt(1 << 32) & 0xFFFFFFFF); - - @override - int nextUint8() => (_random.nextInt(1 << 8) & 0xFF); - - @override - void reset() { - // No reset needed for Random.secure() - } - - @override - @override - Uint8List nextBytes(int count) { - final bytes = Uint8List(count); - for (var i = 0; i < count; i++) { - bytes[i] = nextUint8(); - } - return bytes; - } } diff --git a/dialer/lib/domain/services/obfuscate_service.dart b/dialer/lib/domain/services/obfuscate_service.dart index 7806222..6eaf43f 100644 --- a/dialer/lib/domain/services/obfuscate_service.dart +++ b/dialer/lib/domain/services/obfuscate_service.dart @@ -1,4 +1,10 @@ -import '../../core/config/app_config.dart'; +// lib/services/obfuscate_service.dart +import 'package:dialer/widgets/color_darkener.dart'; + +import '../../globals.dart' as globals; +import 'dart:ui'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; class ObfuscateService { // Private constructor @@ -14,7 +20,7 @@ class ObfuscateService { // Public method to obfuscate data String obfuscateData(String data) { - if (AppConfig.isStealthMode) { + if (globals.isStealthMode) { return _obfuscateData(data); } else { return data; @@ -34,3 +40,52 @@ class ObfuscateService { return ''; } } + + +class ObfuscatedAvatar extends StatelessWidget { + final Uint8List? imageBytes; + final double radius; + final Color backgroundColor; + final String? fallbackInitial; + + const ObfuscatedAvatar({ + Key? key, + required this.imageBytes, + this.radius = 25, + this.backgroundColor = Colors.grey, + this.fallbackInitial, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (imageBytes != null && imageBytes!.isNotEmpty) { + return ClipOval( + child: ImageFiltered( + imageFilter: globals.isStealthMode + ? ImageFilter.blur(sigmaX: 10, sigmaY: 10) + : ImageFilter.blur(sigmaX: 0, sigmaY: 0), + child: Image.memory( + imageBytes!, + fit: BoxFit.cover, + width: radius * 2, + height: radius * 2, + ), + ), + ); + } else { + return CircleAvatar( + radius: radius, + backgroundColor: backgroundColor, + child: Text( + fallbackInitial != null && fallbackInitial!.isNotEmpty + ? fallbackInitial![0].toUpperCase() + : '?', + style: TextStyle( + color: darken(backgroundColor), + fontSize: radius, + ), + ), + ); + } + } +} diff --git a/dialer/lib/presentation/common/widgets/obfuscated_avatar.dart b/dialer/lib/presentation/common/widgets/obfuscated_avatar.dart deleted file mode 100644 index 36d49dd..0000000 --- a/dialer/lib/presentation/common/widgets/obfuscated_avatar.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:typed_data'; -import 'dart:ui'; -import 'package:flutter/material.dart'; -import '../../../core/config/app_config.dart'; -import '../../../core/utils/color_utils.dart'; - -class ObfuscatedAvatar extends StatelessWidget { - final Uint8List? imageBytes; - final double radius; - final Color backgroundColor; - final String? fallbackInitial; - - const ObfuscatedAvatar({ - Key? key, - required this.imageBytes, - this.radius = 25, - this.backgroundColor = Colors.grey, - this.fallbackInitial, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - if (imageBytes != null && imageBytes!.isNotEmpty) { - return ClipOval( - child: ImageFiltered( - imageFilter: AppConfig.isStealthMode - ? ImageFilter.blur(sigmaX: 10, sigmaY: 10) - : ImageFilter.blur(sigmaX: 0, sigmaY: 0), - child: Image.memory( - imageBytes!, - fit: BoxFit.cover, - width: radius * 2, - height: radius * 2, - ), - ), - ); - } else { - return CircleAvatar( - radius: radius, - backgroundColor: backgroundColor, - child: Text( - fallbackInitial != null && fallbackInitial!.isNotEmpty - ? fallbackInitial![0].toUpperCase() - : '?', - style: TextStyle( - color: darken(backgroundColor), - fontSize: radius, - ), - ), - ); - } - } -} diff --git a/dialer/lib/presentation/features/call/call_page.dart b/dialer/lib/presentation/features/call/call_page.dart index 6471361..137e7cf 100644 --- a/dialer/lib/presentation/features/call/call_page.dart +++ b/dialer/lib/presentation/features/call/call_page.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import '../../../domain/services/call_service.dart'; import '../../../domain/services/obfuscate_service.dart'; import '../../../core/utils/color_utils.dart'; -import '../../../presentation/common/widgets/obfuscated_avatar.dart'; +// import '../../../presentation/common/widgets/obfuscated_avatar.dart'; class CallPage extends StatefulWidget { final String displayName; diff --git a/dialer/lib/presentation/features/call/incoming_call_page.dart b/dialer/lib/presentation/features/call/incoming_call_page.dart index c127b74..1abe72d 100644 --- a/dialer/lib/presentation/features/call/incoming_call_page.dart +++ b/dialer/lib/presentation/features/call/incoming_call_page.dart @@ -4,7 +4,6 @@ import 'dart:typed_data'; import '../../../domain/services/call_service.dart'; import '../../../domain/services/obfuscate_service.dart'; import '../../../core/utils/color_utils.dart'; -import '../../../presentation/common/widgets/obfuscated_avatar.dart'; import 'call_page.dart'; class IncomingCallPage extends StatefulWidget { diff --git a/dialer/lib/presentation/features/composition/composition.dart b/dialer/lib/presentation/features/composition/composition.dart index 1497325..da81e67 100644 --- a/dialer/lib/presentation/features/composition/composition.dart +++ b/dialer/lib/presentation/features/composition/composition.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../services/contact_service.dart'; -import '../../../services/obfuscate_service.dart'; -import '../../../services/call_service.dart'; +import '../../../domain/services/obfuscate_service.dart'; +import '../../../domain/services/call_service.dart'; import '../contacts/widgets/add_contact_button.dart'; class CompositionPage extends StatefulWidget { diff --git a/dialer/lib/presentation/features/contacts/contact_page.dart b/dialer/lib/presentation/features/contacts/contact_page.dart index ead0797..73f8ea3 100644 --- a/dialer/lib/presentation/features/contacts/contact_page.dart +++ b/dialer/lib/presentation/features/contacts/contact_page.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import '../contacts/contact_state.dart'; import '../contacts/widgets/alphabet_scroll_page.dart'; -import '../../common/widgets/loading_indicator.dart'; -import '../../../domain/services/obfuscate_service.dart'; class ContactPage extends StatefulWidget { const ContactPage({super.key}); @@ -12,8 +10,6 @@ class ContactPage extends StatefulWidget { } class _ContactPageState extends State { - final ObfuscateService _obfuscateService = ObfuscateService(); - @override Widget build(BuildContext context) { final contactState = ContactState.of(context); diff --git a/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart b/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart index d34f3a7..9e3ded9 100644 --- a/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart +++ b/dialer/lib/presentation/features/contacts/widgets/alphabet_scroll_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../../../../domain/services/obfuscate_service.dart'; import '../../../../core/utils/color_utils.dart'; -import '../../../common/widgets/obfuscated_avatar.dart'; import '../contact_state.dart'; import 'add_contact_button.dart'; import 'contact_modal.dart'; diff --git a/dialer/lib/presentation/features/contacts/widgets/contact_modal.dart b/dialer/lib/presentation/features/contacts/widgets/contact_modal.dart index 4f5d659..21109ec 100644 --- a/dialer/lib/presentation/features/contacts/widgets/contact_modal.dart +++ b/dialer/lib/presentation/features/contacts/widgets/contact_modal.dart @@ -3,10 +3,10 @@ import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../../widgets/username_color_generator.dart'; import '../../../../widgets/color_darkener.dart'; -import '../../../../services/obfuscate_service.dart'; -import '../../../../services/block_service.dart'; -import '../../../../services/contact_service.dart'; -import '../../../../services/call_service.dart'; +import '../../../../domain/services/obfuscate_service.dart'; +import '../../../../domain/services/block_service.dart'; +import '../../../../domain/services/contact_service.dart'; +import '../../../../domain/services/call_service.dart'; class ContactModal extends StatefulWidget { final Contact contact; diff --git a/dialer/lib/presentation/features/contacts/widgets/share_own_qr.dart b/dialer/lib/presentation/features/contacts/widgets/share_own_qr.dart index 058ed35..10648b7 100644 --- a/dialer/lib/presentation/features/contacts/widgets/share_own_qr.dart +++ b/dialer/lib/presentation/features/contacts/widgets/share_own_qr.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/contact.dart'; -import 'package:dialer/services/contact_service.dart'; +import 'package:dialer/domain/services/contact_service.dart'; class QRCodeButton extends StatelessWidget { final List contacts; diff --git a/dialer/lib/presentation/features/history/history_page.dart b/dialer/lib/presentation/features/history/history_page.dart index ceeee4b..317f0e2 100644 --- a/dialer/lib/presentation/features/history/history_page.dart +++ b/dialer/lib/presentation/features/history/history_page.dart @@ -5,11 +5,11 @@ import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:intl/intl.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../../services/obfuscate_service.dart'; +import '../../../domain/services/obfuscate_service.dart'; import '../../../widgets/color_darkener.dart'; import '../../../widgets/username_color_generator.dart'; -import '../../../services/block_service.dart'; -import '../../../services/call_service.dart'; +import '../../../domain/services/block_service.dart'; +import '../../../domain/services/call_service.dart'; import '../contacts/contact_state.dart'; import '../contacts/widgets/contact_modal.dart'; diff --git a/dialer/lib/presentation/features/settings/cryptography/key_management.dart b/dialer/lib/presentation/features/settings/cryptography/key_management.dart index f766ebc..681db26 100644 --- a/dialer/lib/presentation/features/settings/cryptography/key_management.dart +++ b/dialer/lib/presentation/features/settings/cryptography/key_management.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart'; +import 'package:dialer/domain/services/cryptography/asymmetric_crypto_service.dart'; class ManageKeysPage extends StatefulWidget { const ManageKeysPage({Key? key}) : super(key: key); diff --git a/dialer/lib/services/block_service.dart b/dialer/lib/services/block_service.dart deleted file mode 100644 index 5ec0cb7..0000000 --- a/dialer/lib/services/block_service.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -/// Service for managing blocked phone numbers -class BlockService { - static const String _blockedNumbersKey = 'blocked_numbers'; - - // Private constructor - BlockService._privateConstructor(); - - // Singleton instance - static final BlockService _instance = BlockService._privateConstructor(); - - // Factory constructor to return the same instance - factory BlockService() { - return _instance; - } - - /// Block a phone number - Future blockNumber(String phoneNumber) async { - try { - final prefs = await SharedPreferences.getInstance(); - final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? []; - - // Don't add if already blocked - if (blockedNumbers.contains(phoneNumber)) { - return true; - } - - blockedNumbers.add(phoneNumber); - return await prefs.setStringList(_blockedNumbersKey, blockedNumbers); - } catch (e) { - debugPrint('Error blocking number: $e'); - return false; - } - } - - /// Unblock a phone number - Future unblockNumber(String phoneNumber) async { - try { - final prefs = await SharedPreferences.getInstance(); - final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? []; - - if (!blockedNumbers.contains(phoneNumber)) { - return true; - } - - blockedNumbers.remove(phoneNumber); - return await prefs.setStringList(_blockedNumbersKey, blockedNumbers); - } catch (e) { - debugPrint('Error unblocking number: $e'); - return false; - } - } - - /// Check if a number is blocked - Future isNumberBlocked(String phoneNumber) async { - try { - final prefs = await SharedPreferences.getInstance(); - final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? []; - return blockedNumbers.contains(phoneNumber); - } catch (e) { - debugPrint('Error checking if number is blocked: $e'); - return false; - } - } - - /// Get all blocked numbers - Future> getBlockedNumbers() async { - try { - final prefs = await SharedPreferences.getInstance(); - return prefs.getStringList(_blockedNumbersKey) ?? []; - } catch (e) { - debugPrint('Error getting blocked numbers: $e'); - return []; - } - } -} diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart deleted file mode 100644 index 903a13c..0000000 --- a/dialer/lib/services/call_service.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import '../presentation/features/call/call_page.dart'; -import '../presentation/features/call/incoming_call_page.dart'; // Import the new page - -class CallService { - static const MethodChannel _channel = MethodChannel('call_service'); - static String? currentPhoneNumber; - static bool _isCallPageVisible = false; - - static final GlobalKey navigatorKey = GlobalKey(); - - CallService() { - _channel.setMethodCallHandler((call) async { - final context = navigatorKey.currentContext; - print('CallService: Received method ${call.method} with args ${call.arguments}'); - if (context == null) { - print('CallService: Navigator context is null, cannot navigate'); - return; - } - - switch (call.method) { - case "callAdded": - final phoneNumber = call.arguments["callId"] as String; - final state = call.arguments["state"] as String; - currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); - print('CallService: Call added, number: $currentPhoneNumber, state: $state'); - if (state == "ringing") { - _navigateToIncomingCallPage(context); - } else { - _navigateToCallPage(context); - } - break; - case "callStateChanged": - final state = call.arguments["state"] as String; - print('CallService: State changed to $state'); - if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(context); - } else if (state == "active" || state == "dialing") { - _navigateToCallPage(context); - } else if (state == "ringing") { - _navigateToIncomingCallPage(context); - } - break; - case "callEnded": - case "callRemoved": - print('CallService: Call ended/removed'); - _closeCallPage(context); - currentPhoneNumber = null; - break; - } - }); - } - - void _navigateToCallPage(BuildContext context) { - if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/call') { - print('CallService: CallPage already visible, skipping navigation'); - return; - } - print('CallService: Navigating to CallPage'); - Navigator.pushReplacement( - context, - MaterialPageRoute( - settings: const RouteSettings(name: '/call'), - builder: (context) => CallPage( - displayName: currentPhoneNumber!, - phoneNumber: currentPhoneNumber!, - thumbnail: null, - ), - ), - ).then((_) { - _isCallPageVisible = false; - }); - _isCallPageVisible = true; - } - - void _navigateToIncomingCallPage(BuildContext context) { - if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/incoming_call') { - print('CallService: IncomingCallPage already visible, skipping navigation'); - return; - } - print('CallService: Navigating to IncomingCallPage'); - Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: '/incoming_call'), - builder: (context) => IncomingCallPage( - displayName: currentPhoneNumber!, - phoneNumber: currentPhoneNumber!, - thumbnail: null, - ), - ), - ).then((_) { - _isCallPageVisible = false; - }); - _isCallPageVisible = true; - } - - void _closeCallPage(BuildContext context) { - if (!_isCallPageVisible) { - print('CallService: CallPage not visible, skipping pop'); - return; - } - if (Navigator.canPop(context)) { - print('CallService: Popping CallPage'); - Navigator.pop(context); - _isCallPageVisible = false; - } - } - - Future makeGsmCall( - BuildContext context, { - required String phoneNumber, - String? displayName, - Uint8List? thumbnail, - }) async { - try { - currentPhoneNumber = phoneNumber; - print('CallService: Making GSM call to $phoneNumber'); - final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); - print('CallService: makeGsmCall result: $result'); - if (result["status"] != "calling") { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Failed to initiate call")), - ); - } - } catch (e) { - print("CallService: Error making call: $e"); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error making call: $e")), - ); - rethrow; - } - } - - Future hangUpCall(BuildContext context) async { - try { - print('CallService: Hanging up call'); - final result = await _channel.invokeMethod('hangUpCall'); - print('CallService: hangUpCall result: $result'); - if (result["status"] != "ended") { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Failed to end call")), - ); - } - } catch (e) { - print("CallService: Error hanging up call: $e"); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error hanging up call: $e")), - ); - rethrow; - } - } -} \ No newline at end of file diff --git a/dialer/lib/services/contact_service.dart b/dialer/lib/services/contact_service.dart deleted file mode 100644 index d961100..0000000 --- a/dialer/lib/services/contact_service.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_contacts/flutter_contacts.dart'; -import 'package:qr_flutter/qr_flutter.dart'; - -class ContactService { - Future> fetchContacts() async { - if (await FlutterContacts.requestPermission()) { - List contacts = await FlutterContacts.getContacts( - withProperties: true, - withThumbnail: true, - ); - return contacts; - } else { - // Permission denied - return []; - } - } - - Future> fetchFavoriteContacts() async { - if (await FlutterContacts.requestPermission()) { - // Get all contacts and filter for favorites - List allContacts = await FlutterContacts.getContacts( - withProperties: true, - withThumbnail: true, - ); - return allContacts.where((c) => c.isStarred).toList(); - } else { - // Permission denied - return []; - } - } - - Future addNewContact(Contact contact) async { - if (await FlutterContacts.requestPermission()) { - try { - return await FlutterContacts.insertContact(contact); - } catch (e) { - debugPrint('Error adding contact: $e'); - return null; - } - } - return null; - } - - void showContactQRCodeDialog(BuildContext context, Contact contact) { - final String vCard = contact.toVCard(); - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - backgroundColor: Colors.grey[900], - title: Text( - 'QR Code for ${contact.displayName}', - style: const TextStyle(color: Colors.white), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - color: Colors.white, - padding: const EdgeInsets.all(16.0), - child: QrImageView( - data: vCard, - version: QrVersions.auto, - size: 200.0, - ), - ), - const SizedBox(height: 16.0), - const Text( - 'Scan this code to add this contact', - style: TextStyle(color: Colors.white70), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Close'), - ), - ], - ); - }, - ); - } -} diff --git a/dialer/lib/services/cryptography/asymmetric_crypto_service.dart b/dialer/lib/services/cryptography/asymmetric_crypto_service.dart deleted file mode 100644 index 07c2964..0000000 --- a/dialer/lib/services/cryptography/asymmetric_crypto_service.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'package:flutter/services.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:uuid/uuid.dart'; - -class AsymmetricCryptoService { - static const MethodChannel _channel = MethodChannel('com.example.keystore'); - final FlutterSecureStorage _secureStorage = FlutterSecureStorage(); - final String _aliasPrefix = 'icing_'; - final Uuid _uuid = Uuid(); - - /// Generates an ECDSA P-256 key pair with a unique alias and stores its metadata. - Future generateKeyPair({String? label}) async { - try { - // Generate a unique identifier for the key - final String uuid = _uuid.v4(); - final String alias = '$_aliasPrefix$uuid'; - - // Invoke native method to generate the key pair - await _channel.invokeMethod('generateKeyPair', {'alias': alias}); - - // Store key metadata securely - final Map keyMetadata = { - 'alias': alias, - 'label': label ?? 'Key $uuid', - 'created_at': DateTime.now().toIso8601String(), - }; - - // Retrieve existing keys - final String? existingKeys = await _secureStorage.read(key: 'keys'); - List keysList = existingKeys != null ? jsonDecode(existingKeys) : []; - - // Add the new key - keysList.add(keyMetadata); - - // Save updated keys list - await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); - - return alias; - } on PlatformException catch (e) { - throw Exception("Failed to generate key pair: ${e.message}"); - } - } - - /// Signs data using the specified key alias. - Future signData(String alias, String data) async { - try { - final String signature = await _channel.invokeMethod('signData', { - 'alias': alias, - 'data': data, - }); - return signature; - } on PlatformException catch (e) { - throw Exception("Failed to sign data with alias '$alias': ${e.message}"); - } - } - - /// Retrieves the public key for the specified alias. - Future getPublicKey(String alias) async { - try { - final String publicKey = await _channel.invokeMethod('getPublicKey', { - 'alias': alias, - }); - return publicKey; - } on PlatformException catch (e) { - throw Exception("Failed to retrieve public key: ${e.message}"); - } - } - - /// Deletes the key pair associated with the specified alias and removes its metadata. - Future deleteKeyPair(String alias) async { - try { - await _channel.invokeMethod('deleteKeyPair', {'alias': alias}); - - final String? existingKeys = await _secureStorage.read(key: 'keys'); - if (existingKeys != null) { - List keysList = jsonDecode(existingKeys); - keysList.removeWhere((key) => key['alias'] == alias); - await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); - } - } on PlatformException catch (e) { - throw Exception("Failed to delete key pair: ${e.message}"); - } - } - - /// Retrieves all stored key metadata. - Future>> getAllKeys() async { - try { - final String? existingKeys = await _secureStorage.read(key: 'keys'); - if (existingKeys == null) { - print("No keys found"); - return []; - } - List keysList = jsonDecode(existingKeys); - return keysList.cast>(); - } catch (e) { - throw Exception("Failed to retrieve keys: $e"); - } - } - - /// Checks if a key pair exists for the given alias. - Future keyPairExists(String alias) async { - try { - final bool exists = await _channel.invokeMethod('keyPairExists', {'alias': alias}); - return exists; - } on PlatformException catch (e) { - throw Exception("Failed to check key pair existence: ${e.message}"); - } - } - - /// Initializes the default key pair if it doesn't exist. - Future initializeDefaultKeyPair() async { - const String defaultAlias = 'icing_default'; - final List> keys = await getAllKeys(); - - // Check if the key exists in metadata - final bool defaultKeyExists = keys.any((key) => key['alias'] == defaultAlias); - - if (!defaultKeyExists) { - await _channel.invokeMethod('generateKeyPair', {'alias': defaultAlias}); - - final Map keyMetadata = { - 'alias': defaultAlias, - 'label': 'Default Key', - 'created_at': DateTime.now().toIso8601String(), - }; - - keys.add(keyMetadata); - await _secureStorage.write(key: 'keys', value: jsonEncode(keys)); - } - } - - /// Updates the label of a key with the specified alias. - /// - /// [alias]: The unique alias of the key to update. - /// [newLabel]: The new label to assign to the key. - /// - /// Throws an exception if the key is not found or the update fails. - Future updateKeyLabel(String alias, String newLabel) async { - try { - // Retrieve existing keys - final String? existingKeys = await _secureStorage.read(key: 'keys'); - if (existingKeys == null) { - throw Exception("No keys found to update."); - } - - List keysList = jsonDecode(existingKeys); - - // Find the key with the specified alias - bool keyFound = false; - for (var key in keysList) { - if (key['alias'] == alias) { - key['label'] = newLabel; - keyFound = true; - break; - } - } - - if (!keyFound) { - throw Exception("Key with alias \"$alias\" not found."); - } - - // Save the updated keys list - await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); - } catch (e) { - throw Exception("Failed to update key label: $e"); - } - } -} diff --git a/dialer/lib/services/obfuscate_service.dart b/dialer/lib/services/obfuscate_service.dart deleted file mode 100644 index 6eaf43f..0000000 --- a/dialer/lib/services/obfuscate_service.dart +++ /dev/null @@ -1,91 +0,0 @@ -// lib/services/obfuscate_service.dart -import 'package:dialer/widgets/color_darkener.dart'; - -import '../../globals.dart' as globals; -import 'dart:ui'; -import 'dart:typed_data'; -import 'package:flutter/material.dart'; - -class ObfuscateService { - // Private constructor - ObfuscateService._privateConstructor(); - - // Singleton instance - static final ObfuscateService _instance = ObfuscateService._privateConstructor(); - - // Factory constructor to return the same instance - factory ObfuscateService() { - return _instance; - } - - // Public method to obfuscate data - String obfuscateData(String data) { - if (globals.isStealthMode) { - return _obfuscateData(data); - } else { - return data; - } - } - - // Private helper method for obfuscation logic - String _obfuscateData(String data) { - if (data.isNotEmpty) { - // Ensure the string has at least two characters to obfuscate - if (data.length == 1) { - return '${data[0]}'; - } else { - return '${data[0]}...${data[data.length - 1]}'; - } - } - return ''; - } -} - - -class ObfuscatedAvatar extends StatelessWidget { - final Uint8List? imageBytes; - final double radius; - final Color backgroundColor; - final String? fallbackInitial; - - const ObfuscatedAvatar({ - Key? key, - required this.imageBytes, - this.radius = 25, - this.backgroundColor = Colors.grey, - this.fallbackInitial, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - if (imageBytes != null && imageBytes!.isNotEmpty) { - return ClipOval( - child: ImageFiltered( - imageFilter: globals.isStealthMode - ? ImageFilter.blur(sigmaX: 10, sigmaY: 10) - : ImageFilter.blur(sigmaX: 0, sigmaY: 0), - child: Image.memory( - imageBytes!, - fit: BoxFit.cover, - width: radius * 2, - height: radius * 2, - ), - ), - ); - } else { - return CircleAvatar( - radius: radius, - backgroundColor: backgroundColor, - child: Text( - fallbackInitial != null && fallbackInitial!.isNotEmpty - ? fallbackInitial![0].toUpperCase() - : '?', - style: TextStyle( - color: darken(backgroundColor), - fontSize: radius, - ), - ), - ); - } - } -} diff --git a/dialer/lib/services/qr/qr_scanner.dart b/dialer/lib/services/qr/qr_scanner.dart deleted file mode 100644 index df405f6..0000000 --- a/dialer/lib/services/qr/qr_scanner.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; - -class QRCodeScannerScreen extends StatefulWidget { - const QRCodeScannerScreen({super.key}); - - @override - _QRCodeScannerScreenState createState() => _QRCodeScannerScreenState(); -} - -class _QRCodeScannerScreenState extends State { - MobileScannerController cameraController = MobileScannerController(); - bool _flashEnabled = false; - - @override - void dispose() { - cameraController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Scan QR Code'), - actions: [ - IconButton( - icon: Icon(_flashEnabled ? Icons.flash_on : Icons.flash_off), - onPressed: () { - setState(() { - _flashEnabled = !_flashEnabled; - cameraController.toggleTorch(); - }); - }, - ), - IconButton( - icon: const Icon(Icons.flip_camera_ios), - onPressed: () => cameraController.switchCamera(), - ), - ], - ), - body: MobileScanner( - controller: cameraController, - onDetect: (capture) { - final List barcodes = capture.barcodes; - if (barcodes.isNotEmpty) { - // Return the first barcode value - final String? code = barcodes.first.rawValue; - if (code != null) { - Navigator.pop(context, code); - } - } - }, - ), - ); - } -}