mirror of
https://gitlab.com/foxixus/neomovies_mobile.git
synced 2025-10-28 01:18:50 +05:00
Fix API auth flow and poster URLs
- Fix authorization issues by improving error handling for unverified accounts - Enable auto-login after successful email verification - Fix poster fetching to use NeoMovies API instead of TMDB directly - Add missing video player models (VideoQuality, AudioTrack, Subtitle, PlayerSettings) - Add video_player and chewie dependencies for native video playback - Update Movie model to use API images endpoint for better CDN control Resolves authentication and image loading issues.
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:neomovies_mobile/data/models/reaction.dart';
|
||||
import 'package:neomovies_mobile/data/models/auth_response.dart';
|
||||
import 'package:neomovies_mobile/data/models/user.dart';
|
||||
import 'package:neomovies_mobile/data/api/neomovies_api_client.dart'; // новый клиент
|
||||
import 'package:neomovies_mobile/data/exceptions/auth_exceptions.dart';
|
||||
|
||||
class ApiClient {
|
||||
final NeoMoviesApiClient _neoClient;
|
||||
@@ -116,12 +117,22 @@ class ApiClient {
|
||||
).then((_) {}); // старый код ничего не возвращал
|
||||
}
|
||||
|
||||
Future<AuthResponse> login(String email, String password) {
|
||||
return _neoClient.login(email: email, password: password);
|
||||
Future<AuthResponse> login(String email, String password) async {
|
||||
try {
|
||||
return await _neoClient.login(email: email, password: password);
|
||||
} catch (e) {
|
||||
final errorMessage = e.toString();
|
||||
if (errorMessage.contains('Account not activated') ||
|
||||
errorMessage.contains('not verified') ||
|
||||
errorMessage.contains('Please verify your email')) {
|
||||
throw UnverifiedAccountException(email, message: errorMessage);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> verify(String email, String code) {
|
||||
return _neoClient.verifyEmail(email: email, code: code).then((_) {});
|
||||
Future<AuthResponse> verify(String email, String code) {
|
||||
return _neoClient.verifyEmail(email: email, code: code);
|
||||
}
|
||||
|
||||
Future<void> resendCode(String email) {
|
||||
|
||||
@@ -97,23 +97,25 @@ class Movie extends HiveObject {
|
||||
|
||||
String get fullPosterUrl {
|
||||
if (posterPath == null || posterPath!.isEmpty) {
|
||||
// Use a generic placeholder
|
||||
return 'https://via.placeholder.com/500x750.png?text=No+Poster';
|
||||
// Use API placeholder
|
||||
final apiUrl = dotenv.env['API_URL'] ?? 'https://api.neomovies.ru';
|
||||
return '$apiUrl/api/v1/images/w500/placeholder.jpg';
|
||||
}
|
||||
// TMDB CDN base URL
|
||||
const tmdbBaseUrl = 'https://image.tmdb.org/t/p';
|
||||
// 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 '$tmdbBaseUrl/w500/$cleanPath';
|
||||
return '$apiUrl/api/v1/images/w500/$cleanPath';
|
||||
}
|
||||
|
||||
String get fullBackdropUrl {
|
||||
if (backdropPath == null || backdropPath!.isEmpty) {
|
||||
// Use a generic placeholder
|
||||
return 'https://via.placeholder.com/1280x720.png?text=No+Backdrop';
|
||||
// Use API placeholder
|
||||
final apiUrl = dotenv.env['API_URL'] ?? 'https://api.neomovies.ru';
|
||||
return '$apiUrl/api/v1/images/w780/placeholder.jpg';
|
||||
}
|
||||
// TMDB CDN base URL
|
||||
const tmdbBaseUrl = 'https://image.tmdb.org/t/p';
|
||||
// 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 '$tmdbBaseUrl/w780/$cleanPath';
|
||||
return '$apiUrl/api/v1/images/w780/$cleanPath';
|
||||
}
|
||||
}
|
||||
|
||||
34
lib/data/models/player/audio_track.dart
Normal file
34
lib/data/models/player/audio_track.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
class AudioTrack {
|
||||
final String name;
|
||||
final String language;
|
||||
final String url;
|
||||
final bool isDefault;
|
||||
|
||||
AudioTrack({
|
||||
required this.name,
|
||||
required this.language,
|
||||
required this.url,
|
||||
this.isDefault = false,
|
||||
});
|
||||
|
||||
factory AudioTrack.fromJson(Map<String, dynamic> json) {
|
||||
return AudioTrack(
|
||||
name: json['name'] ?? '',
|
||||
language: json['language'] ?? '',
|
||||
url: json['url'] ?? '',
|
||||
isDefault: json['isDefault'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'language': language,
|
||||
'url': url,
|
||||
'isDefault': isDefault,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
73
lib/data/models/player/player_settings.dart
Normal file
73
lib/data/models/player/player_settings.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:neomovies_mobile/data/models/player/video_quality.dart';
|
||||
import 'package:neomovies_mobile/data/models/player/audio_track.dart';
|
||||
import 'package:neomovies_mobile/data/models/player/subtitle.dart';
|
||||
|
||||
class PlayerSettings {
|
||||
final VideoQuality? selectedQuality;
|
||||
final AudioTrack? selectedAudioTrack;
|
||||
final Subtitle? selectedSubtitle;
|
||||
final double volume;
|
||||
final double playbackSpeed;
|
||||
final bool autoPlay;
|
||||
final bool muted;
|
||||
|
||||
PlayerSettings({
|
||||
this.selectedQuality,
|
||||
this.selectedAudioTrack,
|
||||
this.selectedSubtitle,
|
||||
this.volume = 1.0,
|
||||
this.playbackSpeed = 1.0,
|
||||
this.autoPlay = true,
|
||||
this.muted = false,
|
||||
});
|
||||
|
||||
PlayerSettings copyWith({
|
||||
VideoQuality? selectedQuality,
|
||||
AudioTrack? selectedAudioTrack,
|
||||
Subtitle? selectedSubtitle,
|
||||
double? volume,
|
||||
double? playbackSpeed,
|
||||
bool? autoPlay,
|
||||
bool? muted,
|
||||
}) {
|
||||
return PlayerSettings(
|
||||
selectedQuality: selectedQuality ?? this.selectedQuality,
|
||||
selectedAudioTrack: selectedAudioTrack ?? this.selectedAudioTrack,
|
||||
selectedSubtitle: selectedSubtitle ?? this.selectedSubtitle,
|
||||
volume: volume ?? this.volume,
|
||||
playbackSpeed: playbackSpeed ?? this.playbackSpeed,
|
||||
autoPlay: autoPlay ?? this.autoPlay,
|
||||
muted: muted ?? this.muted,
|
||||
);
|
||||
}
|
||||
|
||||
factory PlayerSettings.fromJson(Map<String, dynamic> json) {
|
||||
return PlayerSettings(
|
||||
selectedQuality: json['selectedQuality'] != null
|
||||
? VideoQuality.fromJson(json['selectedQuality'])
|
||||
: null,
|
||||
selectedAudioTrack: json['selectedAudioTrack'] != null
|
||||
? AudioTrack.fromJson(json['selectedAudioTrack'])
|
||||
: null,
|
||||
selectedSubtitle: json['selectedSubtitle'] != null
|
||||
? Subtitle.fromJson(json['selectedSubtitle'])
|
||||
: null,
|
||||
volume: json['volume']?.toDouble() ?? 1.0,
|
||||
playbackSpeed: json['playbackSpeed']?.toDouble() ?? 1.0,
|
||||
autoPlay: json['autoPlay'] ?? true,
|
||||
muted: json['muted'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'selectedQuality': selectedQuality?.toJson(),
|
||||
'selectedAudioTrack': selectedAudioTrack?.toJson(),
|
||||
'selectedSubtitle': selectedSubtitle?.toJson(),
|
||||
'volume': volume,
|
||||
'playbackSpeed': playbackSpeed,
|
||||
'autoPlay': autoPlay,
|
||||
'muted': muted,
|
||||
};
|
||||
}
|
||||
}
|
||||
34
lib/data/models/player/subtitle.dart
Normal file
34
lib/data/models/player/subtitle.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
class Subtitle {
|
||||
final String name;
|
||||
final String language;
|
||||
final String url;
|
||||
final bool isDefault;
|
||||
|
||||
Subtitle({
|
||||
required this.name,
|
||||
required this.language,
|
||||
required this.url,
|
||||
this.isDefault = false,
|
||||
});
|
||||
|
||||
factory Subtitle.fromJson(Map<String, dynamic> json) {
|
||||
return Subtitle(
|
||||
name: json['name'] ?? '',
|
||||
language: json['language'] ?? '',
|
||||
url: json['url'] ?? '',
|
||||
isDefault: json['isDefault'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'language': language,
|
||||
'url': url,
|
||||
'isDefault': isDefault,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
38
lib/data/models/player/video_quality.dart
Normal file
38
lib/data/models/player/video_quality.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
class VideoQuality {
|
||||
final String quality;
|
||||
final String url;
|
||||
final int bandwidth;
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
VideoQuality({
|
||||
required this.quality,
|
||||
required this.url,
|
||||
required this.bandwidth,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
factory VideoQuality.fromJson(Map<String, dynamic> json) {
|
||||
return VideoQuality(
|
||||
quality: json['quality'] ?? '',
|
||||
url: json['url'] ?? '',
|
||||
bandwidth: json['bandwidth'] ?? 0,
|
||||
width: json['width'] ?? 0,
|
||||
height: json['height'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'quality': quality,
|
||||
'url': url,
|
||||
'bandwidth': bandwidth,
|
||||
'width': width,
|
||||
'height': height,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => quality;
|
||||
}
|
||||
@@ -33,8 +33,13 @@ class AuthRepository {
|
||||
}
|
||||
|
||||
Future<void> verifyEmail(String email, String code) async {
|
||||
await _apiClient.verify(email, code);
|
||||
// After successful verification, the user should log in.
|
||||
final response = await _apiClient.verify(email, code);
|
||||
// Auto-login user after successful verification
|
||||
await _storageService.saveToken(response.token);
|
||||
await _storageService.saveUserData(
|
||||
name: response.user.name,
|
||||
email: response.user.email,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> resendVerificationCode(String email) async {
|
||||
|
||||
Reference in New Issue
Block a user