269 lines
11 KiB
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(),
|
|
);
|
|
}
|
|
}
|