import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:another_telephony/telephony.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../utils/secure_messaging_service.dart'; import '../utils/database_helper.dart'; import '../utils/contact_helper.dart'; import '../utils/secure_crypto_helper.dart'; import '../utils/app_theme.dart'; import 'chat_screen.dart'; class ComposeScreen extends StatefulWidget { const ComposeScreen({super.key}); @override State createState() => _ComposeScreenState(); } class _ComposeScreenState extends State { final Telephony telephony = Telephony.instance; static const platform = MethodChannel('com.example.saba/sim_cards'); static const asymmetricModeLabel = 'رمزنگاری غیر متقارن (طولانی‌تر و امن‌تر)'; final _phoneController = TextEditingController(); final _msgController = TextEditingController(); final _keyController = TextEditingController(); final _groupNameController = TextEditingController(); final List> _selectedContacts = []; // --- کش داخلی (لیست سبک مخاطبین) --- // به جای Map، از کلاس Contact استفاده می‌کنیم که فقط ID و Name دارد List _contactsCache = []; List> _simCards = []; Map? _selectedSim; bool _loadingSims = true; bool isSending = false; String _selectedSecurityLevel = 'normal'; Color get primaryColor => Theme.of(context).primaryColor; final Color backgroundColor = const Color(0xFFF5F7FA); @override void initState() { super.initState(); _fetchSimCards(); // لود کردن لیست سبک در شروع _loadLightContacts(); } // دریافت لیست سبک (نام و آیدی) - بسیار سریع Future _loadLightContacts() async { try { final contacts = await ContactHelper.getContactsLight(); if (mounted) { setState(() { _contactsCache = contacts; }); } } catch (e) { print("Error loading light contacts: $e"); } } // دکمه آپدیت: هم لیست جستجو را آپدیت می‌کند، هم دیتابیس را Future _forceSyncContacts({bool silent = false}) async { if (!silent) { showDialog( context: context, barrierDismissible: false, builder: (_) => const Center(child: CircularProgressIndicator()), ); } // 1. آپدیت لیست جستجو (سریع) await _loadLightContacts(); // 2. آپدیت دیتابیس برای نمایش نام در صفحه اصلی (سنگین) await ContactHelper.syncWithDevice(); if (!silent && mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("✅ مخاطبین بروزرسانی شدند"))); } } Future _fetchSimCards() async { if (!await Permission.phone.request().isGranted) { setState(() => _loadingSims = false); return; } try { final List result = await platform.invokeMethod('getSimCards'); List> cleanList = result.map((e) { final Map rawMap = e as Map; return rawMap.map((key, value) => MapEntry(key.toString(), value)); }).toList(); setState(() { _simCards = cleanList; if (_simCards.isNotEmpty) _selectedSim = _simCards[0]; _loadingSims = false; }); } catch (e) { setState(() => _loadingSims = false); } } void _openContactSearch() async { if (!await FlutterContacts.requestPermission(readonly: true)) return; // اگر لیست خالی بود، سعی کن دوباره بگیری if (_contactsCache.isEmpty) { await _loadLightContacts(); } if (_contactsCache.isEmpty) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text("مخاطبی یافت نشد"))); } return; } // باز کردن سرچ با لیست سبک final result = await showSearch?>( context: context, delegate: ContactsSearchDelegate(_contactsCache), ); if (result != null) { setState(() { // نرمال‌سازی شماره قبل از افزودن final normalizedPhone = ContactHelper.normalizePhone(result['phone']!); final exists = _selectedContacts.any((c) => ContactHelper.normalizePhone(c['phone']!) == normalizedPhone); if (!exists) { result['phone'] = normalizedPhone; _selectedContacts.add(result); } if (_selectedContacts.length == 1) { _phoneController.text = _selectedContacts[0]['phone']!; } else { _phoneController.clear(); } }); } } void _removeContact(int index) { setState(() { _selectedContacts.removeAt(index); if (_selectedContacts.length == 1) { _phoneController.text = _selectedContacts[0]['phone']!; } else if (_selectedContacts.isEmpty) { _phoneController.clear(); } }); } String _securityLabel(String level) { switch (level) { case 'symmetric': return 'رمزنگاری متقارن'; case 'asymmetric': return asymmetricModeLabel; default: return 'ارسال عادی'; } } Widget _buildSecurityModeCard() { const options = ['normal', 'symmetric', 'asymmetric']; return AppTheme.glassWrapper( radius: 18, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.03), borderRadius: BorderRadius.circular(18), border: Border.all(color: Colors.white.withValues(alpha: 0.1), width: 0.5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'سطح امنیت', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: options.map((level) { return ChoiceChip( label: Text(_securityLabel(level)), selected: _selectedSecurityLevel == level, onSelected: (_) { setState(() { _selectedSecurityLevel = level; }); }, selectedColor: primaryColor, backgroundColor: Colors.white.withValues(alpha: 0.05), labelStyle: TextStyle( color: _selectedSecurityLevel == level ? Colors.white : Colors.white70, fontWeight: FontWeight.w600, ), ); }).toList(), ), const SizedBox(height: 12), Text( _selectedSecurityLevel == 'asymmetric' ? 'در حالت $asymmetricModeLabel برنامه در صورت نیاز ابتدا تبادل کلید را از طریق پیامک انجام می‌دهد و سپس پیام اصلی را می‌فرستد.' : _selectedSecurityLevel == 'symmetric' ? 'در این حالت باید کلید مشترک یکسانی در دو طرف وارد شده باشد.' : 'در این حالت پیام بدون رمزنگاری اضافه ارسال می‌شود.', style: const TextStyle( color: Colors.white60, fontSize: 12, height: 1.5, ), ), ], ), ), ); } Future _sendMessage() async { bool isGroupMode = _selectedContacts.length > 1; if (isGroupMode) { if (_groupNameController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("لطفاً نام گروه را وارد کنید"))); return; } if (_msgController.text.isEmpty) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text("متن پیام خالی است"))); return; } int groupId = await DatabaseHelper.instance .createGroup(_groupNameController.text, _selectedContacts); String finalMsg = _msgController.text; if (_keyController.text.isNotEmpty) { final crypto = SecureCryptoHelper(); finalMsg = await crypto.encryptSymmetric(_msgController.text, _keyController.text); finalMsg = "@G:SYM|" + finalMsg; // Prefix for Group Symmetric } setState(() => isSending = true); int? subId = _selectedSim != null ? _selectedSim!['subscriptionId'] as int : null; for (var member in _selectedContacts) { try { String phone = ContactHelper.normalizePhone(member['phone']!); if (subId != null) { await telephony.sendSms( to: phone, message: finalMsg, subscriptionId: subId); } else { await telephony.sendSms(to: phone, message: finalMsg); } } catch (_) {} } await DatabaseHelper.instance.saveGroupMessage( groupId, finalMsg, DateTime.now().millisecondsSinceEpoch); setState(() => isSending = false); if (mounted) Navigator.pop(context, true); return; } String rawPhone = _phoneController.text; if (rawPhone.isEmpty && _selectedContacts.isNotEmpty) { rawPhone = _selectedContacts[0]['phone']!; } if (rawPhone.isEmpty || _msgController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("شماره و متن الزامی است"))); return; } final normalizedPhone = ContactHelper.normalizePhone(rawPhone); final messageText = _msgController.text.trim(); final symmetricKey = _keyController.text.trim(); if (_selectedSecurityLevel == 'symmetric' && symmetricKey.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('برای رمزنگاری متقارن، کلید لازم است')), ); return; } setState(() => isSending = true); try { final result = await SecureMessagingService.instance.sendMessage( normalizedPhone, messageText, securityLevel: _selectedSecurityLevel, symmetricKey: _selectedSecurityLevel == 'symmetric' ? symmetricKey : null, ); if (!mounted) return; final sentText = result['sentText'] == true; final notice = result['notice'] as String? ?? ''; final snackText = sentText ? (notice == 'sent_fragmented' ? 'پیام چندبخشی ارسال شد' : 'پیام ارسال شد') : notice; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(snackText))); setState(() => isSending = false); if (sentText) { Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => ChatScreen(address: normalizedPhone), ), result: true, ); } }); } return; } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('خطا: $e')), ); setState(() => isSending = false); return; } } @override Widget build(BuildContext context) { bool isGroupMode = _selectedContacts.length > 1; return Scaffold( backgroundColor: AppTheme.darkBg, extendBodyBehindAppBar: true, appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight + 8), child: AppTheme.glassWrapper( radius: 0, sigma: 18, child: AppBar( backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, title: Text(isGroupMode ? "ساخت گروه جدید" : "پیام جدید", style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white)), foregroundColor: Colors.white, ), ), ), body: Stack( children: [ Positioned.fill(child: CustomPaint(painter: MeshBackgroundPainter())), SingleChildScrollView( padding: EdgeInsets.fromLTRB(20, MediaQuery.of(context).padding.top + kToolbarHeight + 20, 20, 40), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (!_loadingSims && _simCards.length > 1) Container( margin: const EdgeInsets.only(bottom: 20), height: 40, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _simCards.length, itemBuilder: (context, index) { final sim = _simCards[index]; final isSelected = _selectedSim == sim; return Padding( padding: const EdgeInsets.only(right: 10), child: ChoiceChip( label: Text("${sim['carrierName']} (Slot ${sim['slotIndex'] + 1})"), selected: isSelected, onSelected: (selected) => setState(() => _selectedSim = sim), selectedColor: primaryColor, labelStyle: TextStyle( color: isSelected ? Colors.white : Colors.white70, fontSize: 13), backgroundColor: Colors.white.withValues(alpha: 0.05), ), ); }, ), ), // --- بخش گیرندگان --- AppTheme.glassWrapper( radius: 20, sigma: 10, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.03), border: Border.all(color: Colors.white.withValues(alpha: 0.1), width: 0.5), borderRadius: BorderRadius.circular(20), ), child: Column( children: [ if (_selectedContacts.isNotEmpty) Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), child: Wrap( spacing: 8, runSpacing: 8, children: _selectedContacts.asMap().entries.map((entry) { return Chip( avatar: CircleAvatar( backgroundColor: primaryColor.withValues(alpha: 0.8), child: Text( entry.value['name']!.isNotEmpty ? entry.value['name']![0] : "?", style: const TextStyle(color: Colors.white, fontSize: 12)), ), label: Text(entry.value['name']!, style: const TextStyle(color: Colors.white, fontSize: 13)), deleteIcon: const Icon(Icons.close, size: 16, color: Colors.white70), onDeleted: () => _removeContact(entry.key), backgroundColor: Colors.white.withValues(alpha: 0.1), side: BorderSide.none, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ); }).toList(), ), ), if (isGroupMode) Padding( padding: const EdgeInsets.only(bottom: 12), child: TextField( controller: _groupNameController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: "نام گروه", labelStyle: const TextStyle(color: Colors.white60), prefixIcon: const Icon(Icons.groups_rounded, color: Colors.white70), filled: true, fillColor: Colors.white.withValues(alpha: 0.05), border: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), ), ), Row( children: [ Expanded( child: TextField( controller: _phoneController, keyboardType: TextInputType.phone, enabled: !isGroupMode && _selectedContacts.isEmpty, style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: "شماره گیرنده", labelStyle: const TextStyle(color: Colors.white60), prefixIcon: const Icon(Icons.phone_iphone_rounded, color: Colors.white70), filled: true, fillColor: Colors.white.withValues(alpha: 0.05), border: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), ), ), const SizedBox(width: 8), _buildSmallButton(Icons.sync_rounded, Colors.orangeAccent, () => _forceSyncContacts(silent: false)), const SizedBox(width: 8), _buildSmallButton(Icons.person_add_rounded, primaryColor, _openContactSearch), ], ), ], ), ), ), const SizedBox(height: 18), // --- بخش تنظیمات امنیتی --- if (!isGroupMode) ...[ _buildSecurityModeCard(), const SizedBox(height: 18), ], if (isGroupMode || _selectedSecurityLevel == 'symmetric') AppTheme.glassWrapper( radius: 18, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.03), borderRadius: BorderRadius.circular(18), border: Border.all(color: Colors.white.withValues(alpha: 0.1), width: 0.5), ), child: TextField( controller: _keyController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( hintText: isGroupMode ? 'کلید گروه (اختیاری)' : 'کلید رمزنگاری متقارن', hintStyle: const TextStyle(color: Colors.white30), icon: const Icon(Icons.vpn_key_rounded, color: Colors.orangeAccent), border: InputBorder.none, helperText: isGroupMode ? 'پیام به‌صورت متقارن برای همه اعضا رمز می‌شود.' : 'کلید باید در هر دو سمت یکسان باشد.', helperStyle: const TextStyle(color: Colors.white38, fontSize: 11), ), ), ), ), if (_selectedSecurityLevel == 'asymmetric' && !isGroupMode) Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.indigo.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(18), border: Border.all(color: Colors.indigo.withValues(alpha: 0.2)), ), child: const Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.shield_rounded, color: Colors.indigoAccent, size: 20), SizedBox(width: 12), Expanded( child: Text( 'در حالت رمزنگاری غیر متقارن، تبادل کلید خودکار انجام می‌شود. پیام‌های طولانی به‌صورت چندبخشی ارسال می‌شوند.', style: TextStyle(color: Colors.white70, fontSize: 12, height: 1.5), ), ), ], ), ), const SizedBox(height: 18), // --- بخش متن پیام --- AppTheme.glassWrapper( radius: 20, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.03), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white.withValues(alpha: 0.1), width: 0.5), ), child: TextField( controller: _msgController, maxLength: 600, maxLines: 6, minLines: 4, style: const TextStyle(color: Colors.white), decoration: const InputDecoration( hintText: "متن پیام خود را بنویسید...", hintStyle: TextStyle(color: Colors.white30), border: InputBorder.none, counterStyle: TextStyle(color: Colors.white38), helperText: "استفاده از پروتکل چندبخشی در صورت نیاز.", helperStyle: TextStyle(color: Colors.white38, fontSize: 11), ), ), ), ), const SizedBox(height: 32), // --- دکمه ارسال --- Container( height: 56, decoration: BoxDecoration( borderRadius: BorderRadius.circular(18), gradient: LinearGradient( colors: [primaryColor, primaryColor.withValues(alpha: 0.8)], ), boxShadow: [ BoxShadow( color: primaryColor.withValues(alpha: 0.3), blurRadius: 15, offset: const Offset(0, 5), ), ], ), child: ElevatedButton.icon( icon: isSending ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) : const Icon(Icons.send_rounded, size: 20), label: Text(isSending ? "در حال ارسال..." : "ارسال پیام", style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold)), onPressed: isSending ? null : _sendMessage, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, foregroundColor: Colors.white, shadowColor: Colors.transparent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), ), ), ), ], ), ), ], ), ); } Widget _buildSmallButton(IconData icon, Color color, VoidCallback onTap) { return Container( decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), ), child: IconButton( icon: Icon(icon, color: color, size: 22), onPressed: onTap, splashRadius: 24, ), ); } } // --- Delegate جدید: ورودی List --- class ContactsSearchDelegate extends SearchDelegate?> { final List contacts; // لیست سبک ContactsSearchDelegate(this.contacts); @override String? get searchFieldLabel => 'جستجو نام مخاطب...'; @override List? buildActions(BuildContext context) { return [ if (query.isNotEmpty) IconButton(icon: const Icon(Icons.clear), onPressed: () => query = ''), ]; } @override Widget? buildLeading(BuildContext context) { return IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => close(context, null), ); } @override Widget buildResults(BuildContext context) => _buildList(context); @override Widget buildSuggestions(BuildContext context) => _buildList(context); Widget _buildList(BuildContext context) { if (contacts.isEmpty) return const Center(child: Text("مخاطبی یافت نشد.")); final filtered = query.isEmpty ? contacts : contacts.where((c) { final name = c.displayName.toLowerCase(); final q = query.toLowerCase(); return name.contains(q); }).toList(); // بررسی اینکه آیا کوئری شبیه شماره تلفن است final bool isQueryNumeric = query.length >= 3 && RegExp(r'^[0-9+\-*#]+$').hasMatch(query); if (filtered.isEmpty && !isQueryNumeric) return const Center(child: Text("نتیجه‌ای یافت نشد.")); return ListView.separated( itemCount: filtered.length + (isQueryNumeric ? 1 : 0), separatorBuilder: (ctx, i) => const Divider(height: 1), itemBuilder: (context, index) { if (isQueryNumeric && index == 0) { return ListTile( leading: const CircleAvatar( backgroundColor: Colors.green, child: Icon(Icons.phone, color: Colors.white), ), title: Text("افزودن شماره دستی: $query", style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.green)), subtitle: const Text("استفاده از این شماره"), onTap: () => close(context, {'name': query, 'phone': query}), ); } final contact = filtered[isQueryNumeric ? index - 1 : index]; return ListTile( leading: CircleAvatar( backgroundColor: Colors.indigo.shade100, child: Text( contact.displayName.isNotEmpty ? contact.displayName[0] : "?", style: TextStyle(color: Colors.indigo.shade900)), ), title: Text(contact.displayName, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: const Text("برای انتخاب کلیک کنید"), // شماره هنوز معلوم نیست onTap: () => _onContactSelect(context, contact.id), ); }, ); } // --- دریافت شماره فقط زمان کلیک --- Future _onContactSelect(BuildContext context, String contactId) async { showDialog( context: context, barrierDismissible: false, builder: (_) => const Center(child: CircularProgressIndicator())); try { // اینجا شماره را از گوشی می‌گیریم final fullContact = await ContactHelper.getFullContact(contactId); if (context.mounted) Navigator.pop(context); if (fullContact == null || fullContact.phones.isEmpty) { if (context.mounted) ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("این مخاطب شماره تلفن ندارد"))); return; } if (fullContact.phones.length == 1) { close(context, { 'name': fullContact.displayName, 'phone': fullContact.phones.first.number }); } else { _showPhoneSelection(context, fullContact); } } catch (e) { if (context.mounted) Navigator.pop(context); } } void _showPhoneSelection(BuildContext context, Contact contact) { showDialog( context: context, builder: (_) => SimpleDialog( title: Text("انتخاب شماره ${contact.displayName}"), children: contact.phones .map((p) => SimpleDialogOption( onPressed: () { Navigator.pop(context); close(context, {'name': contact.displayName, 'phone': p.number}); }, child: Text(p.number), )) .toList(), ), ); } }