377 lines
12 KiB
Dart
377 lines
12 KiB
Dart
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;
|
||
}
|
||
}
|
||
}
|