mirror of
https://gitlab.com/foxixus/neomovies_mobile.git
synced 2025-10-27 17:58:50 +05:00
1. Fix Downloads screen gray screen issue: - Add DownloadsProvider to main.dart providers list - Remove @RoutePage() decorator from DownloadsScreen - Downloads screen now displays torrent list correctly 2. Fix movie detail screen gray screen issue: - Improve Movie.fromJson() with better error handling - Safe parsing of genres field (handles both Map and String formats) - Add fallback 'Untitled' for movies without title - Add detailed logging in MovieDetailProvider - Better error messages with stack traces 3. Add automatic version update from CI/CD tags: - GitLab CI: Update pubspec.yaml version from CI_COMMIT_TAG before build - GitHub Actions: Update pubspec.yaml version from GITHUB_REF before build - Version format: tag v0.0.18 becomes version 0.0.18+18 - Applies to all build jobs (arm64, arm32, x64) How versioning works: - When you create tag v0.0.18, CI automatically updates pubspec.yaml - Build uses version 0.0.18+18 (version+buildNumber) - APK shows correct version in About screen and Google Play - No manual pubspec.yaml updates needed Example: - Create tag: git tag v0.0.18 && git push origin v0.0.18 - CI reads tag, extracts '0.0.18' - Updates: version: 0.0.18+18 in pubspec.yaml - Builds APK with this version - Release created with proper version number Changes: - lib/main.dart: Add DownloadsProvider - lib/presentation/screens/downloads/downloads_screen.dart: Remove @RoutePage - lib/data/models/movie.dart: Safe JSON parsing with error handling - lib/presentation/providers/movie_detail_provider.dart: Add detailed logging - .gitlab-ci.yml: Add version update script in all build jobs - .github/workflows/release.yml: Add version update step in all build jobs Result: ✅ Downloads screen displays properly ✅ Movie details screen loads correctly ✅ Automatic versioning from tags (0.0.18, 0.0.19, etc.) ✅ No more gray screens!
139 lines
4.2 KiB
Dart
139 lines
4.2 KiB
Dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import 'package:hive/hive.dart';
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
part 'movie.g.dart';
|
|
|
|
@HiveType(typeId: 0)
|
|
@JsonSerializable()
|
|
class Movie extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String title;
|
|
|
|
@HiveField(2)
|
|
final String? posterPath;
|
|
|
|
final String? backdropPath;
|
|
|
|
@HiveField(3)
|
|
final String? overview;
|
|
|
|
@HiveField(4)
|
|
final DateTime? releaseDate;
|
|
|
|
@HiveField(5)
|
|
final List<String>? genres;
|
|
|
|
@HiveField(6)
|
|
final double? voteAverage;
|
|
|
|
// Поле популярности из API (TMDB-style)
|
|
@HiveField(9)
|
|
final double popularity;
|
|
|
|
@HiveField(7)
|
|
final int? runtime;
|
|
|
|
// TV specific
|
|
@HiveField(10)
|
|
final int? seasonsCount;
|
|
@HiveField(11)
|
|
final int? episodesCount;
|
|
|
|
@HiveField(8)
|
|
final String? tagline;
|
|
|
|
// not stored in Hive, runtime-only field
|
|
final String mediaType;
|
|
|
|
Movie({
|
|
required this.id,
|
|
required this.title,
|
|
this.posterPath,
|
|
this.backdropPath,
|
|
this.overview,
|
|
this.releaseDate,
|
|
this.genres,
|
|
this.voteAverage,
|
|
this.popularity = 0.0,
|
|
this.runtime,
|
|
this.seasonsCount,
|
|
this.episodesCount,
|
|
this.tagline,
|
|
this.mediaType = 'movie',
|
|
});
|
|
|
|
factory Movie.fromJson(Map<String, dynamic> json) {
|
|
try {
|
|
// Parse genres safely
|
|
List<String> genresList = [];
|
|
if (json['genres'] != null) {
|
|
if (json['genres'] is List) {
|
|
genresList = (json['genres'] as List)
|
|
.map((g) => g is Map ? (g['name'] as String?) ?? '' : g.toString())
|
|
.where((name) => name.isNotEmpty)
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
return Movie(
|
|
id: (json['id'] as num).toString(), // Ensure id is a string
|
|
title: (json['title'] ?? json['name'] ?? 'Untitled') as String,
|
|
posterPath: json['poster_path'] as String?,
|
|
backdropPath: json['backdrop_path'] as String?,
|
|
overview: json['overview'] as String?,
|
|
releaseDate: json['release_date'] != null && json['release_date'].toString().isNotEmpty
|
|
? DateTime.tryParse(json['release_date'].toString())
|
|
: json['first_air_date'] != null && json['first_air_date'].toString().isNotEmpty
|
|
? DateTime.tryParse(json['first_air_date'].toString())
|
|
: null,
|
|
genres: genresList,
|
|
voteAverage: (json['vote_average'] as num?)?.toDouble() ?? 0.0,
|
|
popularity: (json['popularity'] as num?)?.toDouble() ?? 0.0,
|
|
runtime: json['runtime'] is num
|
|
? (json['runtime'] as num).toInt()
|
|
: (json['episode_run_time'] is List && (json['episode_run_time'] as List).isNotEmpty)
|
|
? ((json['episode_run_time'] as List).first as num).toInt()
|
|
: null,
|
|
seasonsCount: json['number_of_seasons'] as int?,
|
|
episodesCount: json['number_of_episodes'] as int?,
|
|
tagline: json['tagline'] as String?,
|
|
mediaType: (json['media_type'] ?? (json['title'] != null ? 'movie' : 'tv')) as String,
|
|
);
|
|
} catch (e) {
|
|
print('Error parsing Movie from JSON: $e');
|
|
print('JSON data: $json');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => _$MovieToJson(this);
|
|
|
|
String get fullPosterUrl {
|
|
if (posterPath == null || posterPath!.isEmpty) {
|
|
// Use API placeholder
|
|
final apiUrl = dotenv.env['API_URL'] ?? 'https://api.neomovies.ru';
|
|
return '$apiUrl/api/v1/images/w500/placeholder.jpg';
|
|
}
|
|
// Use NeoMovies API images endpoint instead of TMDB directly
|
|
final apiUrl = dotenv.env['API_URL'] ?? 'https://api.neomovies.ru';
|
|
final cleanPath = posterPath!.startsWith('/') ? posterPath!.substring(1) : posterPath!;
|
|
return '$apiUrl/api/v1/images/w500/$cleanPath';
|
|
}
|
|
|
|
String get fullBackdropUrl {
|
|
if (backdropPath == null || backdropPath!.isEmpty) {
|
|
// Use API placeholder
|
|
final apiUrl = dotenv.env['API_URL'] ?? 'https://api.neomovies.ru';
|
|
return '$apiUrl/api/v1/images/w780/placeholder.jpg';
|
|
}
|
|
// Use NeoMovies API images endpoint instead of TMDB directly
|
|
final apiUrl = dotenv.env['API_URL'] ?? 'https://api.neomovies.ru';
|
|
final cleanPath = backdropPath!.startsWith('/') ? backdropPath!.substring(1) : backdropPath!;
|
|
return '$apiUrl/api/v1/images/w780/$cleanPath';
|
|
}
|
|
}
|