Chepuhagram/lib/presentation/screens/splash_screen.dart

296 lines
9.9 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:chepuhagram/logic/auth_provider.dart';
import 'package:chepuhagram/logic/contact_provider.dart';
import 'package:chepuhagram/presentation/screens/login_screen.dart';
import 'package:chepuhagram/presentation/screens/contacts_screen.dart';
import 'package:chepuhagram/presentation/screens/account_setup_screen.dart';
import 'package:chepuhagram/presentation/screens/key_recovery_screen.dart';
import 'package:chepuhagram/presentation/screens/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';
import 'package:chepuhagram/core/theme_manager.dart';
import 'dart:ui';
import 'dart:io';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen>
with TickerProviderStateMixin {
int? _targetChatId;
String? _statusMessage;
late AnimationController _fadeController;
late AnimationController _pulseController;
late Animation<double> _fadeAnimation;
late Animation<double> _pulseAnimation;
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();
_fadeController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 800));
_pulseController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 1200));
_fadeAnimation =
Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: _fadeController,
curve: Curves.easeIn,
));
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.15)
.animate(CurvedAnimation(
parent: _pulseController,
curve: Curves.easeInOut,
));
_pulseController.repeat(reverse: true);
_fadeController.forward();
_setupNotificationHandler();
_initializeApp();
}
void _setupNotificationHandler() {
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data['type'] == 'enc_message') {
final senderId = int.tryParse(message.data['sender_id']?.toString() ?? '');
if (senderId != null) {
setState(() => _targetChatId = senderId);
}
}
});
}
Future<void> _initializeApp() async {
if (!mounted) return;
setState(() => _statusMessage = "Подключение...");
final authProvider = context.read<AuthProvider>();
bool? isLoggedIn;
try {
isLoggedIn = await authProvider.tryAutoLogin();
} catch (e) {
setState(() => _statusMessage = 'Ошибка входа: ${e.toString().replaceAll('Exception: ', '')}');
await Future.delayed(const Duration(seconds: 3));
if (mounted) _navigateTo(const LoginScreen());
return;
}
if (!mounted) return;
if (isLoggedIn) {
setState(() => _statusMessage = "Аутентификация...");
bool connected = false;
int connectAttempt = 1;
while (!connected) {
try {
await authProvider.initRealtime();
connected = true;
} catch (e) {
setState(() => _statusMessage = 'Соединение... (попытка $connectAttempt)');
connectAttempt++;
await Future.delayed(const Duration(seconds: 2));
}
}
setState(() => _statusMessage = "Загрузка профиля...");
await authProvider.refreshMe();
if (authProvider.needsSetup) {
_navigateTo(const AccountSetupScreen());
} else if (authProvider.needsKeyRecovery) {
_navigateTo(const KeyRecoveryScreen());
} else {
setState(() => _statusMessage = "Загрузка контактов...");
_loadContactsAndNavigate(authProvider.currentUserId);
}
} else {
_navigateTo(const LoginScreen());
}
}
Future<void> _loadContactsAndNavigate(int? currentUserId) async {
// Navigate to ContactsScreen while contacts are loading in the background
_navigateTo(ContactsScreen(targetChatId: await _getTargetChatId()));
try {
final contactProvider = context.read<ContactProvider>();
contactProvider.setCurrentUserId(currentUserId);
await contactProvider.loadContacts(enrichContacts: false);
final prefs = await SharedPreferences.getInstance();
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) {
contactProvider.setSharedKey(c.id, SecretKey(base64Decode(savedKeyHex)));
} else if (c.publicKey != null) {
keysToCompute[c.id] = c.publicKey!;
}
}
final computedKeys = await compute(
CryptoService.computeSharedKeysTask,
{'keysMap': keysToCompute, 'privKey': myPrivKeyBase64},
);
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");
}
}
Future<int?> _getTargetChatId() async {
int? targetChatId = _targetChatId;
final prefs = await SharedPreferences.getInstance();
if (targetChatId == null) {
final savedData = prefs.getString(_notificationLaunchKey);
if (savedData != null) {
try {
final data = jsonDecode(savedData) as Map<String, dynamic>;
targetChatId = int.tryParse(data['sender_id']?.toString() ?? '');
} catch (e) {
print('Error parsing saved notification data: $e');
}
}
}
if (targetChatId == null && initialMessage != null) {
if (initialMessage!.data['type'] == 'enc_message') {
targetChatId = int.tryParse(initialMessage!.data['sender_id']?.toString() ?? '');
}
}
await prefs.remove(_notificationLaunchKey);
return targetChatId;
}
void _navigateTo(Widget screen) {
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => screen),
);
}
}
@override
void dispose() {
_fadeController.dispose();
_pulseController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final themeProv = context.watch<ThemeProvider>();
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
body: Stack(
children: [
if (themeProv.wallpaperPath != null)
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(File(themeProv.wallpaperPath!)),
fit: BoxFit.cover,
),
),
),
if (themeProv.wallpaperPath != null)
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: Container(color: Colors.black.withOpacity(0.1)),
),
Center(
child: FadeTransition(
opacity: _fadeAnimation,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
ScaleTransition(
scale: _pulseAnimation,
child: Icon(
Icons.messenger_outline,
size: 80,
color: colorScheme.primary,
),
),
const SizedBox(height: 24),
Text(
"Chepuhagram",
style: TextStyle(
color: colorScheme.primary,
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 80),
SizedBox(
height: 40,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _statusMessage != null
? Text(
_statusMessage!,
key: ValueKey(_statusMessage),
style: TextStyle(
color: colorScheme.outline,
fontSize: 14,
),
textAlign: TextAlign.center,
)
: const SizedBox.shrink(),
),
),
const CircularProgressIndicator(),
const Spacer(),
Text(
'Made by ArturKarasevich',
style: TextStyle(
color: colorScheme.outline,
fontSize: 12,
),
),
const SizedBox(height: 40),
],
),
),
),
],
),
);
}
}