Chepuhagram/lib/presentation/screens/contacts_screen.dart

376 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ContactsScreen> createState() => _ContactsScreenState();
}
class _ContactsScreenState extends State<ContactsScreen> {
static const String _notificationLaunchKey = 'notification_launch_data';
StreamSubscription<dynamic>? _socketSubscription;
@override
void initState() {
super.initState();
print('ContactsScreen initState, targetChatId: ${widget.targetChatId}');
_setupPushNotifications();
WidgetsBinding.instance.addPostFrameCallback((_) {
final authProvider = context.read<AuthProvider>();
final contactProvider = context.read<ContactProvider>();
// Установить текущего пользователя и загрузить контакты с сообщениями
contactProvider.setCurrentUserId(authProvider.currentUserId);
contactProvider.loadContacts().then((_) {
print('Contacts loaded, checking targetChatId: ${widget.targetChatId}');
// После загрузки контактов проверить, нужно ли перейти к чату
if (widget.targetChatId != null) {
_navigateToTargetChat();
} else {
_checkSavedNotificationTarget();
}
});
});
}
Future<void> _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<String, dynamic>;
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<ContactProvider>();
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<void> _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<ContactProvider>();
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<void> _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>();
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<ContactProvider>(
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<AuthProvider>(
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: () {
/* ... */
},
),
],
),
),
);
}
}