Chepuhagram/lib/presentation/screens/splash_screen.dart

335 lines
12 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:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../logic/auth_provider.dart';
import '../../logic/contact_provider.dart';
import 'login_screen.dart';
import 'contacts_screen.dart';
import 'account_setup_screen.dart';
import 'key_recovery_screen.dart';
import 'chat_screen.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:chepuhagram/main.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'package:chepuhagram/domain/services/crypto_service.dart';
import 'package:cryptography/cryptography.dart';
import 'package:flutter/foundation.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
int? _targetChatId;
String? connectError;
// Ключ для SharedPreferences
static const String _notificationLaunchKey = 'notification_launch_data';
static const String _contactPublicKey = 'contact_public_key_';
static const String _contactSharedKey = 'contact_shared_key_';
@override
void initState() {
super.initState();
print('SplashScreen initState');
_setupNotificationHandler();
_initializeApp();
}
void _setupNotificationHandler() {
print('Setting up notification handler');
// Обработка открытия приложения из уведомления
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('App opened from notification: ${message.data}');
if (message.data['type'] == 'enc_message') {
final senderId = int.tryParse(
message.data['sender_id']?.toString() ?? '',
);
if (senderId != null) {
setState(() {
_targetChatId = senderId;
});
print('Set target chat from opened app: $senderId');
}
}
});
}
Future<void> _initializeApp() async {
// 1. Искусственная задержка в 2 секунды для демонстрации splash
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
// 2. Пытаемся выполнить автологин
final authProvider = context.read<AuthProvider>();
bool? isLoggedIn;
try {
isLoggedIn = await authProvider.tryAutoLogin();
} catch (e) {
setState(() {
connectError =
'$e+_sps_init_1'.replaceAll('Exception: ', '');
});
return;
}
if (!mounted) return;
bool connected = false;
int connectAttempt = 0;
// 3. Навигация в зависимости от результата и статуса аккаунта
if (isLoggedIn) {
while (!connected) {
try {
await authProvider.initRealtime();
connected = true;
} catch (e) {
connectAttempt++;
if (e.toString().contains('timeout')) {
setState(() {
connectError =
'Превышено время ожидания.\n Проверьте интернет соеденение.\n Попытка соеденения: $connectAttempt';
});
} else if (e.toString().contains('Failed host lookup')) {
setState(() {
connectError =
'Сервер недоступен. Проверьте интернет соеденение.\n Попытка соеденения: $connectAttempt';
});
} else {
setState(() {
connectError = e.toString().replaceAll('Exception: ', '');
});
}
await Future.delayed(Duration(seconds: 2));
}
}
await authProvider.refreshMe();
// Определяем путь пользователя
if (authProvider.needsSetup) {
// Путь А: Первичная настройка
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const AccountSetupScreen()),
);
} else if (authProvider.needsKeyRecovery) {
// Путь В: Восстановление ключей
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const KeyRecoveryScreen()),
);
} else {
// Путь Б: Нормальный вход в контакты
// Проверяем, было ли приложение запущено из уведомления
int? targetChatId =
_targetChatId; // Сначала проверяем из onMessageOpenedApp
if (targetChatId == null) {
final prefs = await SharedPreferences.getInstance();
final savedData = prefs.getString(_notificationLaunchKey);
try {
final contactProvider = context.read<ContactProvider>();
contactProvider.setCurrentUserId(authProvider.currentUserId);
await contactProvider.loadContacts(enrichContacts: false);
final myPrivKeyBase64 = await context
.read<CryptoService>()
.getPrivateKey();
if (myPrivKeyBase64 != null) {
final Map<int, String> keysToCompute = {};
for (var c in contactProvider.contacts) {
final savedKeyHex = prefs.getString(
'$_contactSharedKey${c.id}',
);
final savedPubKey = prefs.getString(
'$_contactPublicKey${c.id}',
);
if (savedKeyHex != null && savedPubKey == c.publicKey) {
final bytes = base64Decode(savedKeyHex);
contactProvider.setSharedKey(c.id, SecretKey(bytes));
} else if (c.publicKey != null) {
keysToCompute[c.id] = c.publicKey!;
}
}
print(
'Contacts with keys for isolate: ${keysToCompute.keys.toList()}',
);
final String privKey = myPrivKeyBase64;
final computedKeys = await compute(
CryptoService.computeSharedKeysTask,
{'keysMap': keysToCompute, 'privKey': privKey},
);
computedKeys.forEach((id, bytes) {
contactProvider.setSharedKey(id, SecretKey(bytes));
prefs.setString('$_contactSharedKey$id', base64Encode(bytes));
prefs.setString('$_contactPublicKey$id', keysToCompute[id]!);
});
}
} catch (e) {
print("Ошибка при загрузке контактов или вычислении ключей: $e");
}
// Если не установлено, проверяем SharedPreferences
if (savedData != null) {
try {
final data = jsonDecode(savedData) as Map<String, dynamic>;
print('Found 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')) {
targetChatId = senderId;
print(
'App launched from saved notification, target chat: $targetChatId',
);
}
// Очищаем сохраненные данные после использования
await prefs.remove(_notificationLaunchKey);
} catch (e) {
print('Error parsing saved notification data: $e');
await prefs.remove(_notificationLaunchKey);
}
}
// Также проверяем initialMessage как fallback
if (targetChatId == null) {
print('Checking initialMessage: $initialMessage');
if (initialMessage != null) {
print('Initial message data: ${initialMessage!.data}');
if (initialMessage!.data['type'] == 'enc_message') {
targetChatId = int.tryParse(
initialMessage!.data['sender_id']?.toString() ?? '',
);
print('Set target chat from initialMessage: $targetChatId');
} else {
print(
'Initial message type is not enc_message: ${initialMessage!.data['type']}',
);
}
} else {
print('No initial message found');
}
}
} else {
print('Using targetChatId from onMessageOpenedApp: $targetChatId');
}
if (targetChatId != null) {
print(
'Notification targetChatId resolved: $targetChatId, trying to open chat directly',
);
try {
final contactProvider = context.read<ContactProvider>();
contactProvider.setCurrentUserId(authProvider.currentUserId);
await contactProvider.loadContacts(enrichContacts: false);
final contact = contactProvider.contacts.firstWhere(
(c) => c.id == targetChatId,
);
currentActiveChatContactId = targetChatId;
print(
'Directly navigating to ChatScreen for contact: ${contact.username}',
);
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_notificationLaunchKey);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => ChatScreen(contact: contact)),
);
return;
} catch (e) {
print(
'Failed to open chat directly, falling back to ContactsScreen: $e',
);
}
}
print('Navigating to ContactsScreen, targetChatId: $targetChatId');
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_notificationLaunchKey);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => ContactsScreen(targetChatId: targetChatId),
),
);
}
} else {
// Нет токена - переходим на экран входа
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const LoginScreen()),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Icon(
Icons.messenger_outline,
size: 80,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 24),
Text(
"Chepuhagram",
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 40),
// Мягкий индикатор загрузки снизу
CircularProgressIndicator(
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 40),
Text(
connectError ?? '',
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 14,
),
textAlign: TextAlign.center,
),
const Spacer(),
Text(
'Made by ArturKarasevich',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 12,
),
),
const SizedBox(height: 80),
],
),
),
);
}
}