From 7201d2e7dc7fe5af1ede730fdbcfd7ad9c1db683 Mon Sep 17 00:00:00 2001 From: "factory-droid[bot]" <138933559+factory-droid[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:54:32 +0000 Subject: [PATCH] v0.0.3 --- android/gradle.properties | 4 +- lib/data/api/api_client.dart | 8 ++- lib/data/api/neomovies_api_client.dart | 80 ++++++++++++++++++++------ lib/data/models/favorite.dart | 13 +++-- 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/android/gradle.properties b/android/gradle.properties index 1903dd1..4ba6529 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,7 +1,7 @@ # 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.parallel=true +org.gradle.parallel=false org.gradle.caching=true org.gradle.configureondemand=true diff --git a/lib/data/api/api_client.dart b/lib/data/api/api_client.dart index c8c4ae8..1f5c010 100644 --- a/lib/data/api/api_client.dart +++ b/lib/data/api/api_client.dart @@ -57,8 +57,12 @@ class ApiClient { ); } - Future removeFavorite(String mediaId) { - return _neoClient.removeFavorite(mediaId); + Future removeFavorite(String mediaId, {String mediaType = 'movie'}) { + return _neoClient.removeFavorite(mediaId, mediaType: mediaType); + } + + Future checkIsFavorite(String mediaId, {String mediaType = 'movie'}) { + return _neoClient.checkIsFavorite(mediaId, mediaType: mediaType); } // ---- Reactions ---- diff --git a/lib/data/api/neomovies_api_client.dart b/lib/data/api/neomovies_api_client.dart index 7868435..7cb8a49 100644 --- a/lib/data/api/neomovies_api_client.dart +++ b/lib/data/api/neomovies_api_client.dart @@ -189,7 +189,12 @@ class NeoMoviesApiClient { final response = await _client.get(uri); 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 { throw Exception('Failed to load movie: ${response.statusCode}'); } @@ -225,7 +230,12 @@ class NeoMoviesApiClient { final response = await _client.get(uri); 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 { throw Exception('Failed to load TV show: ${response.statusCode}'); } @@ -266,7 +276,11 @@ class NeoMoviesApiClient { final response = await _client.get(uri); if (response.statusCode == 200) { - final List data = json.decode(response.body); + final apiResponse = json.decode(response.body); + // API returns: {"success": true, "data": [...]} + final List 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(); } else { throw Exception('Failed to fetch favorites: ${response.body}'); @@ -274,23 +288,17 @@ class NeoMoviesApiClient { } /// Add movie/show to favorites + /// Backend automatically fetches title and poster_path from TMDB Future addFavorite({ required String mediaId, required String mediaType, required String title, required String posterPath, }) async { - final uri = Uri.parse('$apiUrl/favorites'); - final response = await _client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: json.encode({ - 'mediaId': mediaId, - 'mediaType': mediaType, - 'title': title, - 'posterPath': posterPath, - }), - ); + // Backend route: POST /favorites/{id}?type={mediaType} + final uri = Uri.parse('$apiUrl/favorites/$mediaId') + .replace(queryParameters: {'type': mediaType}); + final response = await _client.post(uri); if (response.statusCode != 200 && response.statusCode != 201) { throw Exception('Failed to add favorite: ${response.body}'); @@ -298,8 +306,10 @@ class NeoMoviesApiClient { } /// Remove movie/show from favorites - Future removeFavorite(String mediaId) async { - final uri = Uri.parse('$apiUrl/favorites/$mediaId'); + Future removeFavorite(String mediaId, {String mediaType = 'movie'}) async { + // Backend route: DELETE /favorites/{id}?type={mediaType} + final uri = Uri.parse('$apiUrl/favorites/$mediaId') + .replace(queryParameters: {'type': mediaType}); final response = await _client.delete(uri); if (response.statusCode != 200 && response.statusCode != 204) { @@ -307,6 +317,26 @@ class NeoMoviesApiClient { } } + /// Check if media is in favorites + Future 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!) // ============================================ @@ -351,7 +381,11 @@ class NeoMoviesApiClient { final response = await _client.get(uri); if (response.statusCode == 200) { - final List data = json.decode(response.body); + final apiResponse = json.decode(response.body); + // API returns: {"success": true, "data": [...]} + final List 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(); } else { throw Exception('Failed to get my reactions: ${response.body}'); @@ -433,8 +467,18 @@ class NeoMoviesApiClient { if (response.statusCode == 200) { final decoded = json.decode(response.body); + // API returns: {"success": true, "data": {"page": 1, "results": [...], ...}} List 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; } else if (decoded is Map && decoded['results'] != null) { results = decoded['results']; diff --git a/lib/data/models/favorite.dart b/lib/data/models/favorite.dart index d20e013..adcb887 100644 --- a/lib/data/models/favorite.dart +++ b/lib/data/models/favorite.dart @@ -1,11 +1,12 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; class Favorite { - final int id; + final String id; // MongoDB ObjectID as string final String mediaId; - final String mediaType; + final String mediaType; // "movie" or "tv" final String title; final String posterPath; + final DateTime? createdAt; Favorite({ required this.id, @@ -13,15 +14,19 @@ class Favorite { required this.mediaType, required this.title, required this.posterPath, + this.createdAt, }); factory Favorite.fromJson(Map json) { return Favorite( - id: json['id'] as int? ?? 0, + id: json['id'] as String? ?? '', mediaId: json['mediaId'] as String? ?? '', - mediaType: json['mediaType'] as String? ?? '', + mediaType: json['mediaType'] as String? ?? 'movie', title: json['title'] as String? ?? '', posterPath: json['posterPath'] as String? ?? '', + createdAt: json['createdAt'] != null + ? DateTime.tryParse(json['createdAt'] as String) + : null, ); }