Chepuhagram/lib/main.dart

269 lines
11 KiB
Dart

import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'data/datasources/ws_client.dart';
import 'logic/auth_provider.dart';
import 'logic/contact_provider.dart';
import 'core/theme_manager.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:chepuhagram/domain/services/crypto_service.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'dart:convert';
import 'package:chepuhagram/presentation/screens/chat_screen.dart';
import 'package:chepuhagram/presentation/screens/contacts_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'presentation/screens/splash_screen.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Глобальная переменная для отслеживания текущего активного контакта в чате
int? currentActiveChatContactId;
// Глобальная переменная для хранения начального сообщения (при запуске из уведомления)
RemoteMessage? initialMessage;
// Ключ для SharedPreferences
const String _notificationLaunchKey = 'notification_launch_data';
Future<void> _onSelectNotification(NotificationResponse notificationResponse) async {
final payload = notificationResponse.payload;
if (payload != null) {
try {
final data = jsonDecode(payload);
final senderId = int.tryParse(data['sender_id']?.toString() ?? '');
if (senderId != null) {
print('Notification selected, payload sender_id=$senderId');
final context = navigatorKey.currentContext;
final prefs = await SharedPreferences.getInstance();
// Важно: не сохраняем payload в SharedPreferences, если можем сразу перейти в чат.
// Иначе при следующем обычном запуске (по иконке) останется "хвост" и приложение
// будет снова автопереходить в чат.
if (context == null) {
await prefs.setString(_notificationLaunchKey, jsonEncode(data));
print('Navigator context is null, saved notification payload to SharedPreferences');
} else {
await prefs.remove(_notificationLaunchKey);
}
// Navigate to chat with this contact (if context is ready)
_navigateToChat(senderId);
} else {
print('Notification payload has invalid sender_id: ${data['sender_id']}');
}
} catch (e) {
print('Error parsing notification payload: $e');
}
}
}
void _navigateToChat(int senderId) {
print('Navigating to chat with senderId: $senderId');
final context = navigatorKey.currentContext;
if (context != null) {
final contactProvider = Provider.of<ContactProvider>(context, listen: false);
// Check if contacts are loaded
if (contactProvider.contacts.isEmpty) {
print('Contacts not loaded yet, navigating to contacts screen first');
// Navigate to contacts screen and pass the senderId to navigate to chat later
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ContactsScreen(targetChatId: senderId),
),
);
return;
}
try {
final contact = contactProvider.contacts.firstWhere(
(c) => c.id == senderId,
orElse: () => throw Exception('Contact not found'),
);
print('Found contact: ${contact.username}, navigating to chat');
currentActiveChatContactId = senderId; // Устанавливаем активный чат
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChatScreen(contact: contact),
),
);
} catch (e) {
print('Contact with id $senderId not found, navigating to contacts screen');
// Contact not found, go to contacts screen
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ContactsScreen(),
),
);
}
} else {
print('Navigator context is null');
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Проверяем, было ли приложение запущено из уведомления
// Добавляем небольшую задержку, чтобы Firebase полностью инициализировался
await Future.delayed(const Duration(milliseconds: 500));
initialMessage = await FirebaseMessaging.instance.getInitialMessage();
print('Initial message from main() after delay: $initialMessage');
// Сохраняем информацию в SharedPreferences для надежности
final prefs = await SharedPreferences.getInstance();
if (initialMessage != null) {
print('App launched from notification: ${initialMessage!.data}');
print('Message type: ${initialMessage!.data['type']}');
print('Sender ID: ${initialMessage!.data['sender_id']}');
// Сохраняем данные уведомления
await prefs.setString(_notificationLaunchKey, jsonEncode(initialMessage!.data));
print('Saved notification data to SharedPreferences');
} else {
print('No initial message - app launched normally');
// Очищаем сохраненные данные, если приложение запущено нормально
await prefs.remove(_notificationLaunchKey);
}
// Initialize local notifications
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
final InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onSelectNotification,
);
// Если приложение было запущено из локального уведомления, сохраним payload
final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
final payload = notificationAppLaunchDetails?.notificationResponse?.payload;
print('App launched from local notification, payload: $payload');
if (payload != null && payload.isNotEmpty) {
try {
final data = jsonDecode(payload);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_notificationLaunchKey, jsonEncode(data));
print('Saved local notification launch payload to SharedPreferences');
} catch (e) {
print('Failed to save notification launch payload: $e');
}
}
}
// Create notification channel for Android 8+
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'chat_id', // id
'Messages', // title
description: 'Chat messages notifications', // description
importance: Importance.high,
);
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider(create: (_) => ThemeProvider()),
ChangeNotifierProvider(create: (_) => ContactProvider()),
Provider(create: (_) => SocketService()),
],
child: const MyApp(),
),
);
}
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print("Фоновый пуш получен: ${message.data}");
if (message.data['type'] == 'enc_message') {
try {
// Initialize notifications for background
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
// Create notification channel
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'chat_id',
'Messages',
description: 'Chat messages notifications',
importance: Importance.high,
);
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
// Try to decrypt
String notificationText = 'New encrypted message';
try {
// 1. Инициализируем крипто-сервис
final crypto = CryptoService();
// 2. Достаем ключи (они должны быть в SecureStorage)
final myPrivKey = await crypto.getPrivateKey();
print('Private key retrieved: ${myPrivKey != null}');
if (myPrivKey == null) {
print('Private key not found, showing encrypted message');
notificationText = 'Encrypted message: ${message.data['content']?.substring(0, 50) ?? 'N/A'}...';
} else {
// 3. Расшифровываем
final sharedSecret = await crypto.deriveSharedSecret(myPrivKey, message.data['public_key']);
final decryptedText = await crypto.decryptMessage(message.data['content'], sharedSecret);
notificationText = decryptedText;
}
} catch (e) {
print('Decryption failed: $e');
notificationText = 'Failed to decrypt: ${e.toString()}';
}
// 4. Показываем локальное уведомление
await flutterLocalNotificationsPlugin.show(
message.hashCode,
message.data['username'] ?? 'Unknown',
notificationText,
const NotificationDetails(android: AndroidNotificationDetails('chat_id', 'Messages')),
payload: jsonEncode({
'type': 'enc_message',
'sender_id': message.data['sender_id'],
}),
);
print('Notification shown successfully');
} catch (e) {
print('Error processing background message: $e');
}
} else {
print('Message type is not enc_message: ${message.data['type']}');
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final themeProvider = context.watch<ThemeProvider>();
return MaterialApp(
title: 'Chepuhagram',
debugShowCheckedModeBanner: false,
themeAnimationDuration: const Duration(milliseconds: 300),
themeAnimationCurve: Curves.easeInOut,
theme: themeProvider.themeData,
themeMode: themeProvider.themeMode,
navigatorKey: navigatorKey,
// Начальный экран
home: const SplashScreen(),
);
}
}