Chepuhagram/lib/main.dart

407 lines
14 KiB
Dart
Raw Permalink 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 '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>();
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
int? currentActiveChatContactId;
RemoteMessage? initialMessage;
// Ключ для SharedPreferences
const String _notificationLaunchKey = 'notification_launch_data';
const String _lastHandledNotificationLaunchPayloadKey =
'notification_last_handled_payload';
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();
final canonicalPayload = jsonEncode(data);
if (context == null) {
final lastHandled = prefs.getString(
_lastHandledNotificationLaunchPayloadKey,
);
if (lastHandled != canonicalPayload) {
await prefs.setString(_notificationLaunchKey, canonicalPayload);
await prefs.setString(
_lastHandledNotificationLaunchPayloadKey,
canonicalPayload,
);
}
print(
'Navigator context is null, saved notification payload to SharedPreferences',
);
} else {
await prefs.remove(_notificationLaunchKey);
}
_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();
try {
await Firebase.initializeApp();
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']}');
final payloadString = jsonEncode(initialMessage!.data);
final lastHandled = prefs.getString(
_lastHandledNotificationLaunchPayloadKey,
);
if (lastHandled != payloadString) {
// Сохраняем данные уведомления
await prefs.setString(_notificationLaunchKey, payloadString);
await prefs.setString(
_lastHandledNotificationLaunchPayloadKey,
payloadString,
);
print('Saved notification data to SharedPreferences');
} else {
print('InitialMessage payload already handled earlier, skipping');
}
} 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 lastHandled = prefs.getString(
_lastHandledNotificationLaunchPayloadKey,
);
if (lastHandled != payload) {
final data = jsonDecode(payload);
await prefs.setString(_notificationLaunchKey, jsonEncode(data));
await prefs.setString(
_lastHandledNotificationLaunchPayloadKey,
payload,
);
print(
'Saved local notification launch payload to SharedPreferences',
);
} else {
print(
'Local notification payload already handled earlier, skipping',
);
}
} 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);
} catch (e) {
print('Уведосления не были инициальзированы: $e');
}
runApp(
MultiProvider(
providers: [
Provider(create: (_) => CryptoService()),
Provider(create: (_) => SocketService()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider(create: (_) => ThemeProvider()),
Provider(create: (_) => SocketService()),
ChangeNotifierProvider(
create: (context) => ContactProvider(context.read<CryptoService>()),
),
],
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()}';
}
final senderId = int.tryParse(
message.data['sender_id']?.toString() ?? '',
);
// 4. Показываем локальное уведомление
final String groupKey = 'ru.chepuhagram.app.$senderId';
await flutterLocalNotificationsPlugin.show(
senderId!,
'',
'',
NotificationDetails(
android: AndroidNotificationDetails(
'Messages',
'Новые сообщения',
groupKey: groupKey,
setAsGroupSummary: true,
importance: Importance.high,
priority: Priority.high,
groupAlertBehavior: GroupAlertBehavior.all,
),
),
);
await flutterLocalNotificationsPlugin.show(
message.hashCode,
message.data['username'] ?? 'Unknown',
notificationText,
NotificationDetails(
android: AndroidNotificationDetails(
'chat_id',
'Messages',
groupKey: groupKey,
importance: Importance.high,
priority: Priority.high,
showWhen: true,
),
),
payload: jsonEncode({
'type': 'enc_message',
'sender_id': message.data['sender_id'],
'timestamp':
message.data['timestamp'] ?? DateTime.now().toIso8601String(),
}),
);
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 StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Закрываем сокет, как только приложение сворачивается.
if (state == AppLifecycleState.paused ||
state == AppLifecycleState.inactive ||
state == AppLifecycleState.detached) {
try {
context.read<AuthProvider>().closeRealtime();
} catch (_) {}
return;
}
// На возврате в приложение — пробуем переподключиться (если есть токен).
if (state == AppLifecycleState.resumed) {
try {
context.read<AuthProvider>().initRealtime();
} catch (_) {}
}
}
@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,
navigatorObservers: [routeObserver],
// Начальный экран
home: const SplashScreen(),
);
}
}