This commit is contained in:
factory-droid[bot]
2025-10-02 19:54:32 +00:00
parent 2ba77aee3a
commit 7201d2e7dc
4 changed files with 79 additions and 26 deletions

View File

@@ -1,7 +1,7 @@
# Gradle JVM settings - optimized for limited RAM # Gradle JVM settings - optimized for limited RAM
org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=1G -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.parallel=true org.gradle.parallel=false
org.gradle.caching=true org.gradle.caching=true
org.gradle.configureondemand=true org.gradle.configureondemand=true

View File

@@ -57,8 +57,12 @@ class ApiClient {
); );
} }
Future<void> removeFavorite(String mediaId) { Future<void> removeFavorite(String mediaId, {String mediaType = 'movie'}) {
return _neoClient.removeFavorite(mediaId); return _neoClient.removeFavorite(mediaId, mediaType: mediaType);
}
Future<bool> checkIsFavorite(String mediaId, {String mediaType = 'movie'}) {
return _neoClient.checkIsFavorite(mediaId, mediaType: mediaType);
} }
// ---- Reactions ---- // ---- Reactions ----

View File

@@ -189,7 +189,12 @@ class NeoMoviesApiClient {
final response = await _client.get(uri); final response = await _client.get(uri);
if (response.statusCode == 200) { if (response.statusCode == 200) {
return Movie.fromJson(json.decode(response.body)); final apiResponse = json.decode(response.body);
// API returns: {"success": true, "data": {...}}
final movieData = (apiResponse is Map && apiResponse['data'] != null)
? apiResponse['data']
: apiResponse;
return Movie.fromJson(movieData);
} else { } else {
throw Exception('Failed to load movie: ${response.statusCode}'); throw Exception('Failed to load movie: ${response.statusCode}');
} }
@@ -225,7 +230,12 @@ class NeoMoviesApiClient {
final response = await _client.get(uri); final response = await _client.get(uri);
if (response.statusCode == 200) { if (response.statusCode == 200) {
return Movie.fromJson(json.decode(response.body)); final apiResponse = json.decode(response.body);
// API returns: {"success": true, "data": {...}}
final tvData = (apiResponse is Map && apiResponse['data'] != null)
? apiResponse['data']
: apiResponse;
return Movie.fromJson(tvData);
} else { } else {
throw Exception('Failed to load TV show: ${response.statusCode}'); throw Exception('Failed to load TV show: ${response.statusCode}');
} }
@@ -266,7 +276,11 @@ class NeoMoviesApiClient {
final response = await _client.get(uri); final response = await _client.get(uri);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body); final apiResponse = json.decode(response.body);
// API returns: {"success": true, "data": [...]}
final List<dynamic> data = (apiResponse is Map && apiResponse['data'] != null)
? (apiResponse['data'] is List ? apiResponse['data'] : [])
: (apiResponse is List ? apiResponse : []);
return data.map((json) => Favorite.fromJson(json)).toList(); return data.map((json) => Favorite.fromJson(json)).toList();
} else { } else {
throw Exception('Failed to fetch favorites: ${response.body}'); throw Exception('Failed to fetch favorites: ${response.body}');
@@ -274,23 +288,17 @@ class NeoMoviesApiClient {
} }
/// Add movie/show to favorites /// Add movie/show to favorites
/// Backend automatically fetches title and poster_path from TMDB
Future<void> addFavorite({ Future<void> addFavorite({
required String mediaId, required String mediaId,
required String mediaType, required String mediaType,
required String title, required String title,
required String posterPath, required String posterPath,
}) async { }) async {
final uri = Uri.parse('$apiUrl/favorites'); // Backend route: POST /favorites/{id}?type={mediaType}
final response = await _client.post( final uri = Uri.parse('$apiUrl/favorites/$mediaId')
uri, .replace(queryParameters: {'type': mediaType});
headers: {'Content-Type': 'application/json'}, final response = await _client.post(uri);
body: json.encode({
'mediaId': mediaId,
'mediaType': mediaType,
'title': title,
'posterPath': posterPath,
}),
);
if (response.statusCode != 200 && response.statusCode != 201) { if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception('Failed to add favorite: ${response.body}'); throw Exception('Failed to add favorite: ${response.body}');
@@ -298,8 +306,10 @@ class NeoMoviesApiClient {
} }
/// Remove movie/show from favorites /// Remove movie/show from favorites
Future<void> removeFavorite(String mediaId) async { Future<void> removeFavorite(String mediaId, {String mediaType = 'movie'}) async {
final uri = Uri.parse('$apiUrl/favorites/$mediaId'); // Backend route: DELETE /favorites/{id}?type={mediaType}
final uri = Uri.parse('$apiUrl/favorites/$mediaId')
.replace(queryParameters: {'type': mediaType});
final response = await _client.delete(uri); final response = await _client.delete(uri);
if (response.statusCode != 200 && response.statusCode != 204) { if (response.statusCode != 200 && response.statusCode != 204) {
@@ -307,6 +317,26 @@ class NeoMoviesApiClient {
} }
} }
/// Check if media is in favorites
Future<bool> checkIsFavorite(String mediaId, {String mediaType = 'movie'}) async {
// Backend route: GET /favorites/{id}/check?type={mediaType}
final uri = Uri.parse('$apiUrl/favorites/$mediaId/check')
.replace(queryParameters: {'type': mediaType});
final response = await _client.get(uri);
if (response.statusCode == 200) {
final apiResponse = json.decode(response.body);
// API returns: {"success": true, "data": {"isFavorite": true}}
if (apiResponse is Map && apiResponse['data'] != null) {
final data = apiResponse['data'];
return data['isFavorite'] ?? false;
}
return false;
} else {
throw Exception('Failed to check favorite status: ${response.body}');
}
}
// ============================================ // ============================================
// Reactions Endpoints (NEW!) // Reactions Endpoints (NEW!)
// ============================================ // ============================================
@@ -351,7 +381,11 @@ class NeoMoviesApiClient {
final response = await _client.get(uri); final response = await _client.get(uri);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body); final apiResponse = json.decode(response.body);
// API returns: {"success": true, "data": [...]}
final List<dynamic> data = (apiResponse is Map && apiResponse['data'] != null)
? (apiResponse['data'] is List ? apiResponse['data'] : [])
: (apiResponse is List ? apiResponse : []);
return data.map((json) => UserReaction.fromJson(json)).toList(); return data.map((json) => UserReaction.fromJson(json)).toList();
} else { } else {
throw Exception('Failed to get my reactions: ${response.body}'); throw Exception('Failed to get my reactions: ${response.body}');
@@ -433,8 +467,18 @@ class NeoMoviesApiClient {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final decoded = json.decode(response.body); final decoded = json.decode(response.body);
// API returns: {"success": true, "data": {"page": 1, "results": [...], ...}}
List<dynamic> results; List<dynamic> results;
if (decoded is List) { if (decoded is Map && decoded['success'] == true && decoded['data'] != null) {
final data = decoded['data'];
if (data is Map && data['results'] != null) {
results = data['results'];
} else if (data is List) {
results = data;
} else {
throw Exception('Unexpected data format in API response');
}
} else if (decoded is List) {
results = decoded; results = decoded;
} else if (decoded is Map && decoded['results'] != null) { } else if (decoded is Map && decoded['results'] != null) {
results = decoded['results']; results = decoded['results'];

View File

@@ -1,11 +1,12 @@
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
class Favorite { class Favorite {
final int id; final String id; // MongoDB ObjectID as string
final String mediaId; final String mediaId;
final String mediaType; final String mediaType; // "movie" or "tv"
final String title; final String title;
final String posterPath; final String posterPath;
final DateTime? createdAt;
Favorite({ Favorite({
required this.id, required this.id,
@@ -13,15 +14,19 @@ class Favorite {
required this.mediaType, required this.mediaType,
required this.title, required this.title,
required this.posterPath, required this.posterPath,
this.createdAt,
}); });
factory Favorite.fromJson(Map<String, dynamic> json) { factory Favorite.fromJson(Map<String, dynamic> json) {
return Favorite( return Favorite(
id: json['id'] as int? ?? 0, id: json['id'] as String? ?? '',
mediaId: json['mediaId'] as String? ?? '', mediaId: json['mediaId'] as String? ?? '',
mediaType: json['mediaType'] as String? ?? '', mediaType: json['mediaType'] as String? ?? 'movie',
title: json['title'] as String? ?? '', title: json['title'] as String? ?? '',
posterPath: json['posterPath'] as String? ?? '', posterPath: json['posterPath'] as String? ?? '',
createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'] as String)
: null,
); );
} }