419 lines
12 KiB
Dart
419 lines
12 KiB
Dart
import 'package:jwt_decoder/jwt_decoder.dart';
|
||
import 'dart:convert';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||
import 'package:chepuhagram/core/constants.dart';
|
||
import 'package:http/http.dart' as http;
|
||
|
||
class ApiService extends ChangeNotifier {
|
||
final _client = http.Client();
|
||
final _storage = const FlutterSecureStorage();
|
||
bool _isRefreshing = false;
|
||
|
||
Future<String?> uploadMedia(List<int> bytes, {String purpose = 'media'}) async {
|
||
try {
|
||
final token = await getAccessToken();
|
||
var request = http.MultipartRequest(
|
||
'POST',
|
||
Uri.parse('${AppConstants.baseUrl}/media/v2/upload'),
|
||
);
|
||
request.headers.addAll({
|
||
'Authorization': 'Bearer $token',
|
||
});
|
||
// Добавляем файл в запрос
|
||
request.files.add(
|
||
http.MultipartFile.fromBytes(
|
||
'file',
|
||
bytes,
|
||
filename: 'media.enc', // Имя файла для сервера
|
||
),
|
||
);
|
||
// Добавляем purpose
|
||
request.fields['purpose'] = purpose;
|
||
|
||
var streamedResponse = await request.send().timeout(Duration(seconds: 30));
|
||
var response = await http.Response.fromStream(streamedResponse);
|
||
|
||
if (response.statusCode == 200) {
|
||
// Предполагаем, что сервер возвращает JSON {"file_id": "..."}
|
||
final data = jsonDecode(response.body);
|
||
return data['file_id'];
|
||
}
|
||
return null;
|
||
} catch (e) {
|
||
print("Ошибка API при загрузке: $e");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
Future<bool> refreshToken() async {
|
||
if (_isRefreshing) {
|
||
// Already refreshing, wait for completion or return true assuming it will succeed
|
||
return true;
|
||
}
|
||
_isRefreshing = true;
|
||
notifyListeners();
|
||
|
||
try {
|
||
final refreshToken = await _storage.read(key: 'refresh_token');
|
||
final response = await _client.post(
|
||
Uri.parse('${AppConstants.baseUrl}/auth/refresh'),
|
||
body: jsonEncode({'refresh_token': refreshToken}),
|
||
headers: {'Content-Type': 'application/json'},
|
||
).timeout(Duration(seconds: 30));
|
||
|
||
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'],
|
||
);
|
||
notifyListeners();
|
||
return true;
|
||
} else {
|
||
notifyListeners();
|
||
return false;
|
||
}
|
||
} catch (e) {
|
||
notifyListeners();
|
||
return false;
|
||
} finally {
|
||
_isRefreshing = false;
|
||
}
|
||
}
|
||
|
||
Future<String?> getAccessToken() async {
|
||
String? token = await _storage.read(key: 'access_token');
|
||
|
||
if (token != null) {
|
||
bool isExpiredSoon =
|
||
JwtDecoder.isExpired(token) ||
|
||
JwtDecoder.getRemainingTime(token).inMinutes < 2;
|
||
|
||
if (isExpiredSoon) {
|
||
bool refreshed = await refreshToken();
|
||
if (refreshed) {
|
||
token = await _storage.read(key: 'access_token');
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
return token;
|
||
}
|
||
|
||
Future<bool> updateFcmToken(String fcmtoken) async {
|
||
notifyListeners();
|
||
|
||
try {
|
||
final token = await getAccessToken();
|
||
final response = await _client.post(
|
||
Uri.parse('${AppConstants.baseUrl}/auth/update-fcm?token=$fcmtoken'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
return true;
|
||
} else {
|
||
print("Ошибка установки ключа: ${response.statusCode}");
|
||
return false;
|
||
}
|
||
} catch (e) {
|
||
rethrow;
|
||
} finally {
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
Future<bool> setPublicKey(String publicKey) async {
|
||
notifyListeners();
|
||
|
||
try {
|
||
final token = await getAccessToken();
|
||
final response = await _client.post(
|
||
Uri.parse('${AppConstants.baseUrl}/auth/set-public-key'),
|
||
body: jsonEncode({'public_key': publicKey}),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
return true;
|
||
} else {
|
||
print("Ошибка установки ключа: ${response.statusCode}");
|
||
return false;
|
||
}
|
||
} catch (e) {
|
||
rethrow;
|
||
} finally {
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
Future<Map<String, dynamic>> getMe() async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.get(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
return jsonDecode(utf8.decode(response.bodyBytes))
|
||
as Map<String, dynamic>;
|
||
}
|
||
throw Exception('Не удалось получить данные пользователя');
|
||
}
|
||
|
||
Future<bool> updateEncryptedPrivateKey(String encryptedPrivateKey) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.put(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me/encryption-key'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
body: jsonEncode({'encrypted_private_key': encryptedPrivateKey}),
|
||
);
|
||
|
||
return response.statusCode == 200;
|
||
}
|
||
|
||
Future<bool> changePassword(
|
||
String currentPassword,
|
||
String newPassword,
|
||
) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.put(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me/password'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
body: jsonEncode({
|
||
'current_password': currentPassword,
|
||
'new_password': newPassword,
|
||
}),
|
||
);
|
||
|
||
return response.statusCode == 200;
|
||
}
|
||
|
||
Future<List<dynamic>> getChatHistory(int contactId) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.get(
|
||
Uri.parse(
|
||
'${AppConstants.baseUrl}/messages/history/${contactId.toString()}',
|
||
),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
"Authorization": "Bearer $token",
|
||
},
|
||
);
|
||
return jsonDecode(response.body) as List<dynamic>;
|
||
}
|
||
|
||
Future<Uint8List?> downloadMedia(String fileId) async {
|
||
try {
|
||
final token = await getAccessToken();
|
||
final response = await _client.get(
|
||
Uri.parse('${AppConstants.baseUrl}/media/$fileId'),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
if (response.statusCode == 200) {
|
||
return response.bodyBytes;
|
||
}
|
||
print('Ошибка загрузки медиа: ${response.statusCode}');
|
||
return null;
|
||
} catch (e) {
|
||
print('Ошибка downloadMedia: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
Future<Map<String, dynamic>> updateMe({
|
||
required String username,
|
||
required String firstName,
|
||
required String lastName,
|
||
String? phone,
|
||
String? email,
|
||
String? about,
|
||
}) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.put(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
body: jsonEncode({
|
||
'username': username,
|
||
'first_name': firstName,
|
||
'last_name': lastName,
|
||
'phone': (phone == null || phone.trim().isEmpty) ? null : phone.trim(),
|
||
'email': (email == null || email.trim().isEmpty) ? null : email.trim(),
|
||
'about': (about == null || about.trim().isEmpty) ? null : about.trim(),
|
||
}),
|
||
);
|
||
|
||
final decoded = jsonDecode(utf8.decode(response.bodyBytes));
|
||
if (response.statusCode == 200) {
|
||
return decoded as Map<String, dynamic>;
|
||
}
|
||
throw Exception(
|
||
(decoded is Map && decoded['detail'] != null)
|
||
? decoded['detail']
|
||
: 'Failed to update profile',
|
||
);
|
||
}
|
||
|
||
Future<Map<String, dynamic>> getUserById(int userId) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.get(
|
||
Uri.parse('${AppConstants.baseUrl}/users/$userId'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
return jsonDecode(utf8.decode(response.bodyBytes))
|
||
as Map<String, dynamic>;
|
||
}
|
||
throw Exception('Не удалось получить информацию о пользователе');
|
||
}
|
||
|
||
Future<bool> updatePrivacySettings({
|
||
bool? showEmail,
|
||
bool? showPhone,
|
||
bool? showAvatar,
|
||
bool? showAbout,
|
||
bool? showUsername,
|
||
bool? showLastOnline,
|
||
}) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.put(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me/privacy'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
body: jsonEncode({
|
||
if (showEmail != null) 'show_email': showEmail,
|
||
if (showPhone != null) 'show_phone': showPhone,
|
||
if (showAvatar != null) 'show_avatar': showAvatar,
|
||
if (showAbout != null) 'show_about': showAbout,
|
||
if (showUsername != null) 'show_username': showUsername,
|
||
if (showLastOnline != null) 'show_last_online': showLastOnline,
|
||
}),
|
||
);
|
||
|
||
return response.statusCode == 200;
|
||
}
|
||
|
||
Future<Map<String, dynamic>> getPrivacySettings() async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.get(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me/privacy'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
return jsonDecode(utf8.decode(response.bodyBytes))
|
||
as Map<String, dynamic>;
|
||
}
|
||
throw Exception('Не удалось получить настройки конфиденциальности');
|
||
}
|
||
|
||
Future<bool> updateAvatar(String fileId) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.put(
|
||
Uri.parse('${AppConstants.baseUrl}/users/me/avatar'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
body: jsonEncode({'avatar_file_id': fileId}),
|
||
);
|
||
return response.statusCode == 200;
|
||
}
|
||
|
||
Future<Map<String, dynamic>> enableTotp() async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.post(
|
||
Uri.parse('${AppConstants.baseUrl}/auth/totp/enable'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
return jsonDecode(utf8.decode(response.bodyBytes))
|
||
as Map<String, dynamic>;
|
||
}
|
||
throw Exception(
|
||
(jsonDecode(response.body) as Map<String, dynamic>)['detail'] ??
|
||
'Failed to enable TOTP',
|
||
);
|
||
}
|
||
|
||
Future<bool> verifyTotp(String code) async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.post(
|
||
Uri.parse('${AppConstants.baseUrl}/auth/totp/verify'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
body: jsonEncode({'code': code}),
|
||
);
|
||
return response.statusCode == 200;
|
||
}
|
||
|
||
Future<bool> disableTotp() async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.post(
|
||
Uri.parse('${AppConstants.baseUrl}/auth/totp/disable'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
return response.statusCode == 200;
|
||
}
|
||
|
||
Future<bool> deleteAllMessages() async {
|
||
final token = await getAccessToken();
|
||
final response = await _client.delete(
|
||
Uri.parse('${AppConstants.baseUrl}/messages/all'),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
return response.statusCode == 200;
|
||
}
|
||
}
|