import 'dart:convert'; import 'package:chepuhagram/domain/services/aPI_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../widgets/contact_tile.dart'; import '../screens/settings_screen.dart'; import '../screens/new_chat_screen.dart'; import '../screens/chat_screen.dart'; import '/logic/contact_provider.dart'; import '/logic/auth_provider.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:chepuhagram/domain/services/crypto_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:chepuhagram/main.dart'; import 'package:chepuhagram/data/datasources/ws_client.dart'; import 'dart:async'; class ContactsScreen extends StatefulWidget { final int? targetChatId; const ContactsScreen({super.key, this.targetChatId}); @override State createState() => _ContactsScreenState(); } class _ContactsScreenState extends State { static const String _notificationLaunchKey = 'notification_launch_data'; StreamSubscription? _socketSubscription; @override void initState() { super.initState(); print('ContactsScreen initState, targetChatId: ${widget.targetChatId}'); _setupPushNotifications(); WidgetsBinding.instance.addPostFrameCallback((_) { final authProvider = context.read(); final contactProvider = context.read(); // Установить текущего пользователя и загрузить контакты с сообщениями contactProvider.setCurrentUserId(authProvider.currentUserId); contactProvider.loadContacts().then((_) { print('Contacts loaded, checking targetChatId: ${widget.targetChatId}'); // После загрузки контактов проверить, нужно ли перейти к чату if (widget.targetChatId != null) { _navigateToTargetChat(); } else { _checkSavedNotificationTarget(); } }); }); } Future _checkSavedNotificationTarget() async { final prefs = await SharedPreferences.getInstance(); final savedData = prefs.getString(_notificationLaunchKey); if (savedData == null) { print('No saved notification data found in SharedPreferences'); return; } try { final data = jsonDecode(savedData) as Map; print('Recovered saved notification data: $data'); final senderId = int.tryParse(data['sender_id']?.toString() ?? ''); final type = data['type']?.toString(); // Поддерживаем старый payload (только sender_id) и новый (type+sender_id) if (senderId != null && (type == null || type == 'enc_message')) { print('Recovered targetChatId from saved data: $senderId'); await prefs.remove(_notificationLaunchKey); _navigateToTargetChatWithId(senderId); return; } print('Saved notification data is not a valid payload: $data'); await prefs.remove(_notificationLaunchKey); } catch (e) { print('Error parsing saved notification data: $e'); await prefs.remove(_notificationLaunchKey); } } void _navigateToTargetChat() { if (widget.targetChatId == null) return; _navigateToTargetChatWithId(widget.targetChatId!); } void _navigateToTargetChatWithId(int targetChatId) { print('_navigateToTargetChat called with targetChatId: $targetChatId'); final contactProvider = context.read(); try { final contact = contactProvider.contacts.firstWhere( (c) => c.id == targetChatId, ); print('Auto-navigating to chat with contact: ${contact.username}'); currentActiveChatContactId = targetChatId; // Устанавливаем активный чат Navigator.push( context, MaterialPageRoute(builder: (_) => ChatScreen(contact: contact)), ); } catch (e) { print('Target contact with id $targetChatId not found: $e'); } } Future _setupPushNotifications() async { // Request permissions await FirebaseMessaging.instance.requestPermission(); String? token = await FirebaseMessaging.instance.getToken(); if (token != null) { ApiService apiService = ApiService(); print(token); await apiService.updateFcmToken(token); } // Listen for token refresh FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { ApiService apiService = ApiService(); apiService.updateFcmToken(newToken); print('FCM Token refreshed: $newToken'); }); // Listen for foreground messages FirebaseMessaging.onMessage.listen((RemoteMessage message) { print('Foreground message received: ${message.data}'); if (message.data['type'] == 'enc_message') { _handleIncomingMessage(message); } }); // Handle notification tap when app was terminated/backgrounded FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { print('Notification tapped, app opened: ${message.data}'); if (message.data['type'] == 'enc_message') { final senderId = int.tryParse( message.data['sender_id']?.toString() ?? '', ); if (senderId != null) { _navigateToChatFromNotification(senderId); } else { print( 'Notification tap contains invalid sender_id: ${message.data['sender_id']}', ); } } }); } void _navigateToChatFromNotification(int senderId) { final contactProvider = context.read(); print('Navigate to chat from notification with senderId: $senderId'); // Если контакты еще не загружены, ждем их загрузки if (contactProvider.contacts.isEmpty) { print('Contacts not loaded yet, waiting...'); // Ждем немного и пробуем снова Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { _navigateToChatFromNotification(senderId); } }); return; } try { final contact = contactProvider.contacts.firstWhere( (c) => c.id == senderId, ); print('Navigating to chat from notification: ${contact.username}'); currentActiveChatContactId = senderId; // Устанавливаем активный чат Navigator.push( context, MaterialPageRoute(builder: (_) => ChatScreen(contact: contact)), ); } catch (e) { // Contact not found, stay on contacts screen print('Contact not found for notification: $senderId'); } } Future _handleIncomingMessage(RemoteMessage message) async { try { // Проверяем, не находимся ли мы уже в чате с отправителем final senderId = int.tryParse( message.data['sender_id']?.toString() ?? '', ); if (senderId != null && currentActiveChatContactId == senderId) { print('Already in chat with sender $senderId, skipping notification'); return; } // Ensure notification channel exists const AndroidNotificationChannel channel = AndroidNotificationChannel( 'chat_id', 'Messages', description: 'Chat messages notifications', importance: Importance.high, ); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin >() ?.createNotificationChannel(channel); final crypto = CryptoService(); final myPrivKey = await crypto.getPrivateKey(); if (myPrivKey == null) { print('Private key not found, cannot decrypt message'); return; } final sharedSecret = await crypto.deriveSharedSecret( myPrivKey, message.data['public_key'], ); final decryptedText = await crypto.decryptMessage( message.data['content'], sharedSecret, ); // Show local notification await flutterLocalNotificationsPlugin.show( message.hashCode, message.data['username'] ?? 'Unknown', decryptedText, const NotificationDetails( android: AndroidNotificationDetails('chat_id', 'Messages'), ), payload: jsonEncode({ 'type': 'enc_message', 'sender_id': message.data['sender_id'], 'timestamp': message.data['timestamp'] ?? DateTime.now().toIso8601String(), }), ); if (message.data['type'] == 'enc_message') { final contactProvider = context.read(); contactProvider.loadContacts(); } } catch (e) { print('Error processing foreground message: $e'); } } @override void dispose() { _socketSubscription?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( "Chepuhagram", style: TextStyle(fontWeight: FontWeight.bold), ), centerTitle: false, elevation: 0, actions: [IconButton(icon: const Icon(Icons.search), onPressed: () {})], ), body: Consumer( builder: (context, contactProvider, child) { if (contactProvider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (contactProvider.error != null) { return Center(child: Text('Error: ${contactProvider.error}')); } return ListView.separated( itemCount: contactProvider.contacts.length, separatorBuilder: (context, index) => Divider( height: 1, indent: 80, color: Theme.of(context).colorScheme.primaryContainer, ), itemBuilder: (context, index) { final contact = contactProvider.contacts[index]; return ContactTile( contact: contact, onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => ChatScreen(contact: contact), ), ); }, ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const NewChatScreen()), ); }, child: Icon(Icons.edit, color: Theme.of(context).colorScheme.onSurface), ), drawer: Drawer( child: ListView( padding: EdgeInsets.zero, children: [ // Шапка меню с данными юзера Consumer( builder: (context, authProvider, _) { final username = authProvider.username; final displayName = authProvider.displayName; final initials = (displayName.isNotEmpty ? displayName : (username ?? 'U')) .trim() .split(RegExp(r'\s+')) .where((p) => p.isNotEmpty) .take(2) .map((p) => p[0].toUpperCase()) .join(); return UserAccountsDrawerHeader( accountName: Text(displayName), accountEmail: Text( username == null || username.isEmpty ? '' : '@$username', ), currentAccountPicture: CircleAvatar( backgroundColor: Theme.of(context).colorScheme.onSurface, child: Text( initials.isEmpty ? 'U' : initials, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primaryContainer, ), ), ), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, ), ); }, ), ListTile( leading: const Icon(Icons.settings), title: const Text("Настройки"), onTap: () { // Закрываем Drawer и переходим на экран настроек Navigator.pop(context); Navigator.push( context, MaterialPageRoute(builder: (_) => const SettingsScreen()), ); }, ), ListTile( leading: const Icon(Icons.info_outline), title: const Text("О приложении"), onTap: () { /* ... */ }, ), ], ), ), ); } }