import 'dart:async'; import 'package:flutter/material.dart'; import 'package:another_telephony/telephony.dart'; import '../utils/contact_helper.dart'; import '../utils/database_helper.dart'; import 'chat_screen.dart'; import 'group_chat_screen.dart'; import 'compose_screen.dart'; import 'settings_screen.dart'; import '../utils/secure_messaging_service.dart'; import '../utils/protocol_helper.dart'; import '../utils/notification_helper.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '../utils/app_theme.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; List> conversations = []; List> groups = []; List contacts = []; final TextEditingController _contactSearchController = TextEditingController(); bool isLoading = true; bool isLoadingContacts = false; final Telephony telephony = Telephony.instance; Map unreadCounts = {}; Map groupUnreadCounts = {}; // رنگ‌های تم حرفه‌ای Color get primaryColor => Theme.of(context).primaryColor; Color get secondaryColor => Theme.of(context).colorScheme.secondary; final Color backgroundColor = AppTheme.darkBg; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _tabController.addListener(() { if (mounted) setState(() {}); if (_tabController.index == 2 && contacts.isEmpty) { _loadContacts(); } }); // Delay heavy initialization until transition finishes (800ms) // This makes the logo transition buttery smooth (60fps) Future.delayed(const Duration(milliseconds: 900), () { if (mounted) initApp(); }); _initMessageStreamListener(); } StreamSubscription? _messageSubscription; StreamSubscription? _notificationSubscription; void _initMessageStreamListener() { _messageSubscription = SecureMessagingService.instance.messageStream.listen((data) { Future.delayed(const Duration(milliseconds: 500), () { if (mounted) loadData(); }); }); _notificationSubscription = NotificationHelper.instance.notificationStream.listen((payload) { if (!mounted) return; if (payload.startsWith('group_')) { final groupId = int.tryParse(payload.replaceFirst('group_', '')); if (groupId != null) { // Find group name String name = "گروه"; for (var g in groups) { if (g['id'] == groupId) { name = g['name']; break; } } Navigator.push( context, MaterialPageRoute( builder: (_) => GroupChatScreen(groupId: groupId, groupName: name), ), ).then((_) => loadData()); } } else { Navigator.push( context, MaterialPageRoute( builder: (_) => ChatScreen(address: payload), ), ).then((_) => loadData()); } }); } @override void dispose() { _tabController.dispose(); _contactSearchController.dispose(); _messageSubscription?.cancel(); _notificationSubscription?.cancel(); super.dispose(); } void initApp() { loadMessages(); // همگام‌سازی نام مخاطبین در پس‌زمینه Future.delayed(const Duration(seconds: 1), () async { // ابتدا از کش محلی لود میکنیم برای سرعت await ContactHelper.loadFromLocalCache(); if (mounted) setState(() {}); // سپس با دستگاه سینک میکنیم برای آپدیت نام‌های جدید await ContactHelper.syncWithDevice(); if (mounted) setState(() {}); }); } void loadData() async { setState(() => isLoading = true); await loadMessages(); final g = await DatabaseHelper.instance.getGroups(); final counts = {}; for (final group in g) { counts[group['id']] = await DatabaseHelper.instance.getGroupUnreadCount(group['id']); } if (_tabController.index == 2 || contacts.isNotEmpty) { await _loadContacts(); } if (mounted) { setState(() { groups = g; groupUnreadCounts = counts; isLoading = false; }); } } Future _loadContacts() async { if (isLoadingContacts) return; setState(() => isLoadingContacts = true); try { final c = await ContactHelper.getContactsLight(); if (mounted) { setState(() { contacts = c; isLoadingContacts = false; }); } } catch (e) { if (mounted) setState(() => isLoadingContacts = false); } } Future _startChatFromContact(Contact contact) async { showDialog( context: context, barrierDismissible: false, builder: (_) => const Center(child: CircularProgressIndicator()), ); try { final full = await ContactHelper.getFullContact(contact.id); if (mounted) Navigator.pop(context); if (full == null || full.phones.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("این مخاطب شماره تلفن ندارد")), ); } return; } String phone = full.phones.first.number; if (full.phones.length > 1) { if (!mounted) return; final selected = await showDialog( context: context, builder: (ctx) => SimpleDialog( title: Text("انتخاب شماره ${full.displayName}"), children: full.phones .map((p) => SimpleDialogOption( onPressed: () => Navigator.pop(ctx, p.number), child: Text(p.number), )) .toList(), ), ); if (selected == null) return; phone = selected; } if (mounted) { Navigator.push( context, MaterialPageRoute( builder: (_) => ChatScreen(address: phone), ), ).then((_) => loadData()); } } catch (e) { if (mounted) Navigator.pop(context); } } Future loadMessages() async { try { // Fetch from local cache for instant updates final localRows = await DatabaseHelper.instance.getConversations(); final installDate = await SecureMessagingService.instance.getInstallDate(); // Filter by install date List> filtered = localRows.where((m) { if (installDate == null) return true; final date = m['date'] as int? ?? 0; return date >= installDate; }).toList(); // Fetch unread counts final counts = {}; for (final msg in filtered) { final addr = msg['address'] as String?; if (addr != null) { counts[addr] = await DatabaseHelper.instance.getUnreadCount(addr); } } if (mounted) { setState(() { conversations = filtered; unreadCounts = counts; isLoading = false; }); } } catch (e) { debugPrint("[SABA] Error in local loadMessages: $e"); if (mounted) setState(() => isLoading = false); } } void _deleteGroup(int groupId) { showDialog( context: context, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: const Text("حذف گروه"), content: const Text("آیا از حذف این گروه مطمئنید؟"), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text("لغو")), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white), onPressed: () async { Navigator.pop(ctx); await DatabaseHelper.instance.deleteGroup(groupId); if (!mounted) return; loadData(); ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text("گروه حذف شد"))); }, child: const Text("حذف"), ), ], ), ); } void _deleteIndividualChat(String address) { showDialog( context: context, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: const Text("حذف گفتگو"), content: const Text( "به دلیل محدودیت‌های اندروید، حذف پیامک‌های سیستمی فقط توسط برنامه پیش‌فرض پیامک امکان‌پذیر است."), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text("باشه")), ], ), ); } Future _addContactToDevice(String phone) async { if (!await FlutterContacts.requestPermission()) return; try { final contact = Contact(phones: [Phone(phone)]); await FlutterContacts.openExternalInsert(contact); if (!mounted) return; loadData(); // همگام‌سازی مجدد بعد از افزودن } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("خطا در افزودن مخاطب: $e")), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: backgroundColor, appBar: PreferredSize( preferredSize: const Size.fromHeight(85), child: AppTheme.glassWrapper( radius: 0, sigma: 18, child: AppBar( backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, title: Padding( padding: const EdgeInsets.only(top: 15), child: Hero( tag: 'app_logo', child: GestureDetector( onTap: () { if (mounted) setState(() {}); }, child: const Image( image: AssetImage('صبا بالا.png'), height: 42, fit: BoxFit.contain, ), ), ), ), actions: [ IconButton( icon: const Icon(Icons.settings_outlined, color: Colors.white, size: 22), onPressed: () => Navigator.push( context, MaterialPageRoute(builder: (_) => const SettingsScreen()), ).then((_) => loadData()), ), IconButton( icon: const Icon(Icons.refresh_rounded, color: Colors.white, size: 22), onPressed: loadData, ), const SizedBox(width: 8), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(50), child: Container( color: Colors.transparent, // Let glass show through child: TabBar( controller: _tabController, labelColor: primaryColor, unselectedLabelColor: Colors.grey, indicatorColor: primaryColor, indicatorWeight: 3, labelStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16), dividerColor: Colors.transparent, tabs: const [ Tab(text: "گفتگوها"), Tab(text: "گروه‌ها"), Tab(text: "مخاطبین"), ], ), ), ), ), ), ), body: TabBarView( controller: _tabController, children: [ // --- لیست گفتگوها --- isLoading ? Center(child: CircularProgressIndicator(color: primaryColor)) : conversations.isEmpty ? _buildEmptyState("هیچ گفتگویی وجود ندارد") : ListView.builder( padding: const EdgeInsets.only(top: 10, bottom: 80), itemCount: conversations.length, itemBuilder: (context, index) { final msg = conversations[index]; final addr = msg['address'] as String? ?? ""; final displayName = ContactHelper.getName(addr); final body = msg['body'] as String? ?? ""; final parsed = ProtocolHelper.parseMessage(body); final isSecure = parsed['type'] != 'plain'; final displayText = isSecure ? "🔒 پیام امن (رمزگذاری شده)" : body; return _buildChatCard( index: index, title: displayName, subtitle: displayText, isEncrypted: isSecure, unreadCount: unreadCounts[addr] ?? 0, avatarText: displayName.isNotEmpty ? displayName[0] : "?", colorSeed: index, showAddButton: ContactHelper.isRawNumber(displayName), onAddContact: () => _addContactToDevice(addr), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (_) => ChatScreen(address: addr))) .then((_) => loadData()), onLongPress: () => _deleteIndividualChat(addr), ); }, ), // --- لیست گروه‌ها --- groups.isEmpty ? _buildEmptyState("گروهی ساخته نشده است") : ListView.builder( padding: const EdgeInsets.only(top: 10, bottom: 80), itemCount: groups.length, itemBuilder: (context, index) { final group = groups[index]; return _buildChatCard( index: index, title: group['name'], subtitle: "پیام گروهی", isEncrypted: false, isGroup: true, unreadCount: groupUnreadCounts[group['id']] ?? 0, avatarText: "#", colorSeed: index + 5, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (_) => GroupChatScreen( groupId: group['id'], groupName: group['name']))), onLongPress: () => _deleteGroup(group['id']), ); }, ), // --- لیست مخاطبین --- Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: AppTheme.glassWrapper( radius: 12, sigma: 5, child: TextField( controller: _contactSearchController, onChanged: (val) => setState(() {}), style: const TextStyle(color: Colors.white), decoration: InputDecoration( hintText: "جستجو در مخاطبین...", hintStyle: const TextStyle(color: Colors.white60), prefixIcon: const Icon(Icons.search, color: Colors.white70), filled: true, fillColor: Colors.white.withValues(alpha: 0.05), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(vertical: 0), ), ), ), ), Expanded( child: isLoadingContacts && contacts.isEmpty ? Center( child: CircularProgressIndicator(color: primaryColor)) : contacts.isEmpty ? _buildEmptyState("مخاطبی یافت نشد") : Builder(builder: (context) { final query = _contactSearchController.text .trim() .toLowerCase(); final filtered = query.isEmpty ? contacts : contacts.where((c) { final name = c.displayName.toLowerCase(); return name.contains(query); }).toList(); if (filtered.isEmpty) { return _buildEmptyState("نتیجه‌ای یافت نشد"); } return ListView.builder( padding: const EdgeInsets.only(bottom: 80), itemCount: filtered.length, itemBuilder: (context, index) { final contact = filtered[index]; final displayName = contact.displayName; return _buildChatCard( index: index, title: displayName, subtitle: "شروع گفتگوی جدید", isEncrypted: false, avatarText: displayName.isNotEmpty ? displayName[0] : "?", colorSeed: index, onTap: () => _startChatFromContact(contact), onLongPress: () {}, ); }, ); }), ), ], ), ], ), floatingActionButton: FloatingActionButton.extended( backgroundColor: primaryColor, icon: Icon(_tabController.index == 0 ? Icons.edit : Icons.group_add, color: Colors.white), label: Text(_tabController.index == 0 ? "پیام جدید" : "گروه جدید", style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold)), onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ComposeScreen())) .then((res) { if (res == true) loadData(); }), ), ); } Widget _buildEmptyState(String text) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.chat_bubble_outline, size: 80, color: Colors.grey[400]), const SizedBox(height: 16), Text(text, style: TextStyle(color: Colors.grey[600], fontSize: 18)), ], ), ); } Widget _buildChatCard({ required int index, required String title, required String subtitle, required bool isEncrypted, required String avatarText, required int colorSeed, int unreadCount = 0, bool isGroup = false, bool showAddButton = false, VoidCallback? onAddContact, required VoidCallback onTap, required VoidCallback onLongPress, }) { final List avatarColors = [ Colors.blueAccent, Colors.teal, Colors.deepPurple, Colors.indigo, Colors.orangeAccent, Colors.pinkAccent, Colors.cyan ]; final avatarBg = isGroup ? Colors.orange : avatarColors[colorSeed % avatarColors.length]; // Slide and Fade animation for that premium staggered feel return TweenAnimationBuilder( duration: const Duration(milliseconds: 450), tween: Tween(begin: 0.0, end: 1.0), curve: Curves.easeOutQuart, // Delay based on index for the staggered effect builder: (context, value, child) { return Opacity( opacity: value, child: Transform.translate( offset: Offset(30 * (1 - value), 0), child: child, ), ); }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: AppTheme.darkCard, borderRadius: BorderRadius.circular(18), border: Border.all( color: Colors.white.withValues(alpha: 0.05), width: 0.5), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.4), spreadRadius: 1, blurRadius: 12, offset: const Offset(0, 6), ), ], ), child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(18), child: InkWell( borderRadius: BorderRadius.circular(18), onTap: onTap, onLongPress: onLongPress, child: Padding( padding: const EdgeInsets.all(14.0), child: Row( children: [ Hero( tag: 'avatar_${title}_${isGroup ? 'group' : 'ind'}', child: Stack( clipBehavior: Clip.none, children: [ Container( width: 52, height: 52, decoration: BoxDecoration( gradient: LinearGradient( colors: [ avatarBg, avatarBg.withValues(alpha: 0.6), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: avatarBg.withValues(alpha: 0.3), blurRadius: 8, spreadRadius: 1, ), ], ), child: Center( child: isGroup ? const Icon(Icons.groups, color: Colors.white, size: 28) : Text( avatarText, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold), ), ), ), if (unreadCount > 0) Positioned( top: -4, left: -4, child: Container( constraints: const BoxConstraints( minWidth: 22, minHeight: 22, ), padding: const EdgeInsets.symmetric(horizontal: 6), decoration: BoxDecoration( color: const Color(0xFFFF5A5F), borderRadius: BorderRadius.circular(12), border: Border.all( color: AppTheme.darkCard, width: 2, ), boxShadow: [ BoxShadow( color: const Color(0xFFFF5A5F) .withValues(alpha: 0.35), blurRadius: 10, spreadRadius: 1, ), ], ), child: Center( child: Text( unreadCount > 99 ? '99+' : unreadCount.toString(), style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800, ), textAlign: TextAlign.center, ), ), ), ), ], ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontWeight: unreadCount > 0 ? FontWeight.w900 : FontWeight.bold, fontSize: 17, color: Colors.white, ), maxLines: 1, overflow: TextOverflow.ellipsis, textDirection: TextDirection.ltr, textAlign: TextAlign.right, ), const SizedBox(height: 4), Row( children: [ if (isEncrypted) Padding( padding: const EdgeInsets.only(left: 4), child: Icon(Icons.lock, size: 14, color: primaryColor), ), Expanded( child: Text( subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: unreadCount > 0 ? Colors.white.withValues(alpha: 0.9) : Colors.white.withValues(alpha: 0.6), fontSize: 13, fontWeight: unreadCount > 0 ? FontWeight.w600 : FontWeight.w400, ), ), ), ], ), ], ), ), if (showAddButton) IconButton( icon: Icon(Icons.person_add_outlined, color: primaryColor), onPressed: onAddContact, tooltip: "افزودن به مخاطبین", ), Icon(Icons.chevron_right, color: Colors.grey[300]), ], ), ), ), ), ), ); } }