Chepuhagram/lib/logic/auth_provider.dart

377 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 'package:chepuhagram/data/datasources/local_db_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'dart:io';
import '/core/constants.dart';
import 'package:http/http.dart' as http;
import 'package:chepuhagram/domain/services/api_service.dart';
import 'package:chepuhagram/data/datasources/ws_client.dart';
import 'package:chepuhagram/domain/services/crypto_service.dart';
class AuthProvider extends ChangeNotifier {
bool _isLoading = false;
bool get isLoading => _isLoading;
int? _currentUserId;
int? get currentUserId => _currentUserId;
String? _username;
String? get username => _username;
String? _firstName;
String? get firstName => _firstName;
String? _lastName;
String? get lastName => _lastName;
String? _phone;
String? get phone => _phone;
String? _email;
String? get email => _email;
String? _about;
String? get about => _about;
String? _avatarPath;
String? get avatarPath => _avatarPath;
String? _avatarUrl;
String? get avatarUrl => _avatarUrl;
// Privacy settings
bool? _showEmail;
bool? get showEmail => _showEmail;
bool? _showPhone;
bool? get showPhone => _showPhone;
bool? _showAvatar;
bool? get showAvatar => _showAvatar;
bool? _showAbout;
bool? get showAbout => _showAbout;
bool? _showUsername;
bool? get showUsername => _showUsername;
String get displayName {
final full = '${_firstName ?? ''} ${_lastName ?? ''}'.trim();
if (full.isNotEmpty) return full;
if ((_username ?? '').isNotEmpty) return _username!;
return 'User';
}
// Флаги для определения пути пользователя
bool _needsSetup = false;
bool get needsSetup => _needsSetup;
bool _needsKeyRecovery = false;
bool get needsKeyRecovery => _needsKeyRecovery;
bool _hasPublicKeyOnServer = false;
bool get hasPublicKeyOnServer => _hasPublicKeyOnServer;
final _storage = const FlutterSecureStorage();
final _client = http.Client();
final ApiService _apiService = ApiService();
final SocketService _socketService = SocketService();
final CryptoService _cryptoService = CryptoService();
Future<void> initRealtime() async {
try {
await _socketService.connect(_apiService);
} catch (e) {
throw Exception(e);
}
}
void closeRealtime() {
_socketService.disconnect();
}
SocketService get socketService => _socketService;
Future<bool> login(String username, String password, {String? totpCode}) async {
_isLoading = true;
notifyListeners();
try {
final body = {'username': username, 'password': password};
if (totpCode != null) {
body['totp_code'] = totpCode;
}
final response = await _client.post(
Uri.parse('${AppConstants.baseUrl}/auth/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
);
final decodedResponse =
jsonDecode(utf8.decode(response.bodyBytes)) as Map;
if (response.statusCode == 200) {
await _storage.write(
key: 'access_token',
value: decodedResponse['access_token'],
);
await _storage.write(
key: 'refresh_token',
value: decodedResponse['refresh_token'],
);
await _storage.write(
key: 'user_id',
value: decodedResponse['user_id'].toString(),
);
_currentUserId = decodedResponse['user_id'];
// Проверяем статус аккаунта (нужна ли настройка или восстановление)
await _checkAccountStatus();
_isLoading = false;
notifyListeners();
return true;
} else {
_isLoading = false;
notifyListeners();
final error = decodedResponse['detail'] ?? 'Ошибка запроса';
throw Exception(error);
}
} catch (e) {
_isLoading = false;
notifyListeners();
rethrow;
}
}
Future<void> logout() async {
final mode = await _storage.read(key: 'theme_mode');
final color = await _storage.read(key: 'accent_color');
final wallpaper = await _storage.read(key: 'wallpaper_path');
final avatar = await _storage.read(key: 'avatar_path');
await _storage.deleteAll();
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
await LocalDbService().clearDatabase();
_currentUserId = null;
_username = null;
_firstName = null;
_lastName = null;
_phone = null;
_email = null;
_about = null;
_avatarPath = null;
_avatarUrl = null;
if (mode != null) {
await _storage.write(key: 'theme_mode', value: mode);
}
if (color != null) {
await _storage.write(key: 'accent_color', value: color);
}
if (wallpaper != null) {
await _storage.write(key: 'wallpaper_path', value: wallpaper);
}
if (avatar != null) {
await _storage.write(key: 'avatar_path', value: avatar);
}
notifyListeners();
}
Future<bool> tryAutoLogin() async {
final token = await _apiService.getAccessToken();
if (token == null) return false;
// Загружаем currentUserId из хранилища
/*final userIdStr = await _storage.read(key: 'user_id');
if (userIdStr != null) {
_currentUserId = int.tryParse(userIdStr);
}
try {
final response = await _client
.get(
Uri.parse('${AppConstants.baseUrl}/users/me'),
headers: {'Authorization': 'Bearer $token'},
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
// Проверяем статус аккаунта для определения дальнейшего пути
await _checkAccountStatus();
return true;
} else if (response.statusCode == 401) {
bool isUpdated = await _apiService.refreshToken();
if (isUpdated) {
// После обновления токена проверяем статус
await _checkAccountStatus();
}
return isUpdated;
} else {
return false;
}
} catch (e) {
// Если сервер недоступен, позволяем offline mode
return true;
}*/
return true;
}
Future<bool> updateProfileAndSecurity({
required String firstName,
String? lastName,
required String masterPassword,
}) async {
notifyListeners();
try {
final token = await _apiService.getAccessToken();
// Генерируем ключи и шифруем приватный
final keys = await _cryptoService.initAccountSecurity(masterPassword);
final response = await _client.post(
Uri.parse('${AppConstants.baseUrl}/auth/setup-account'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({
'first_name': firstName,
'last_name': lastName,
'public_key': keys['public_key'],
'encrypted_private_key': keys['encrypted_private_key'],
}),
);
if (response.statusCode == 200) {
_needsSetup = false;
notifyListeners();
return true;
} else {
print("Ошибка настройки профиля: ${response.body}");
return false;
}
} catch (e) {
print("Ошибка сети: $e");
return false;
} finally {
notifyListeners();
}
}
// Приватный метод для проверки статуса аккаунта
Future<void> _checkAccountStatus() async {
try {
final token = await _apiService.getAccessToken();
final response = await _client.get(
Uri.parse('${AppConstants.baseUrl}/users/me'),
headers: {'Authorization': 'Bearer $token'},
);
if (response.statusCode == 200) {
final data = jsonDecode(utf8.decode(response.bodyBytes)) as Map;
_currentUserId = data['id'] as int?;
_username = data['username']?.toString();
_firstName = data['first_name']?.toString();
_lastName = data['last_name']?.toString();
_phone = data['phone']?.toString();
_email = data['email']?.toString();
_about = data['about']?.toString();
final avatarFileId = data['avatar_file_id']?.toString();
_avatarUrl = avatarFileId != null ? '${AppConstants.baseUrl}/media/$avatarFileId' : null;
// Загружаем локальные настройки
_avatarPath = await _storage.read(key: 'avatar_path');
// Проверяем наличие публичного ключа на сервере
_hasPublicKeyOnServer =
data['public_key'] != null && data['public_key'].isNotEmpty;
// Проверяем наличие приватного ключа локально
final hasLocalPrivateKey =
await _storage.read(key: 'private_key') != null;
if (!_hasPublicKeyOnServer) {
// Путь А: Первая настройка - нужно создать ключи и профиль
_needsSetup = true;
_needsKeyRecovery = false;
} else if (!hasLocalPrivateKey) {
// Путь В: Переустановка - ключ на сервере, но его нет локально
_needsKeyRecovery = true;
_needsSetup = false;
} else {
// Путь Б: Нормальный вход - все в порядке
_needsSetup = false;
_needsKeyRecovery = false;
}
}
// Загружаем настройки конфиденциальности
try {
final privacyData = await _apiService.getPrivacySettings();
_showEmail = privacyData['show_email'] as bool?;
_showPhone = privacyData['show_phone'] as bool?;
_showAvatar = privacyData['show_avatar'] as bool?;
_showAbout = privacyData['show_about'] as bool?;
_showUsername = privacyData['show_username'] as bool?;
} catch (e) {
print("Ошибка загрузки настроек конфиденциальности: $e");
// Устанавливаем значения по умолчанию
_showEmail = true;
_showPhone = true;
_showAvatar = true;
_showAbout = true;
_showUsername = true;
}
} catch (e) {
print("Ошибка проверки статуса: $e");
_needsSetup = false;
_needsKeyRecovery = false;
}
notifyListeners();
}
Future<void> refreshMe() async {
await _checkAccountStatus();
}
// Метод для начала с чистого листа (новые ключи)
Future<void> resetKeys() async {
await _storage.delete(key: 'private_key');
_needsKeyRecovery = false;
notifyListeners();
}
void updateAvatarPath(String? path) {
_avatarPath = path;
if (path != null) {
_storage.write(key: 'avatar_path', value: path);
} else {
_storage.delete(key: 'avatar_path');
}
notifyListeners();
}
Future<bool> updateAvatar(String path) async {
try {
final bytes = await File(path).readAsBytes();
final fileId = await _apiService.uploadFile(bytes, purpose: 'avatar');
if (fileId != null) {
final success = await _apiService.updateAvatar(fileId);
if (success) {
updateAvatarPath(path);
await refreshMe(); // Обновить данные профиля, включая avatarUrl
return true;
}
}
return false;
} catch (e) {
print('Ошибка обновления аватарки: $e');
return false;
}
}
}