Initial commit

This commit is contained in:
2025-07-13 14:01:29 +03:00
commit 0eaf91561a
188 changed files with 11616 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
import 'package:neomovies_mobile/data/models/user.dart';
class AuthResponse {
final String token;
final User user;
final bool verified;
AuthResponse({required this.token, required this.user, required this.verified});
factory AuthResponse.fromJson(Map<String, dynamic> json) {
return AuthResponse(
token: json['token'] as String,
user: User.fromJson(json['user'] as Map<String, dynamic>),
verified: (json['verified'] as bool?) ?? (json['user']?['verified'] as bool? ?? true),
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
class Favorite {
final int id;
final String mediaId;
final String mediaType;
final String title;
final String posterPath;
Favorite({
required this.id,
required this.mediaId,
required this.mediaType,
required this.title,
required this.posterPath,
});
factory Favorite.fromJson(Map<String, dynamic> json) {
return Favorite(
id: json['id'] as int? ?? 0,
mediaId: json['mediaId'] as String? ?? '',
mediaType: json['mediaType'] as String? ?? '',
title: json['title'] as String? ?? '',
posterPath: json['posterPath'] as String? ?? '',
);
}
String get fullPosterUrl {
final baseUrl = dotenv.env['API_URL']!;
if (posterPath.isEmpty) {
return '$baseUrl/images/w500/placeholder.jpg';
}
final cleanPath = posterPath.startsWith('/') ? posterPath.substring(1) : posterPath;
return '$baseUrl/images/w500/$cleanPath';
}
}

View File

@@ -0,0 +1,57 @@
class LibraryLicense {
final String name;
final String version;
final String license;
final String url;
final String description;
final String? licenseText;
const LibraryLicense({
required this.name,
required this.version,
required this.license,
required this.url,
required this.description,
this.licenseText,
});
Map<String, dynamic> toMap() {
return {
'name': name,
'version': version,
'license': license,
'url': url,
'description': description,
'licenseText': licenseText,
};
}
LibraryLicense copyWith({
String? name,
String? version,
String? license,
String? url,
String? description,
String? licenseText,
}) {
return LibraryLicense(
name: name ?? this.name,
version: version ?? this.version,
license: license ?? this.license,
url: url ?? this.url,
description: description ?? this.description,
licenseText: licenseText ?? this.licenseText,
);
}
factory LibraryLicense.fromMap(Map<String, dynamic> map) {
return LibraryLicense(
name: map['name'] as String? ?? '',
version: map['version'] as String? ?? '',
license: map['license'] as String? ?? '',
url: map['url'] as String? ?? '',
description: map['description'] as String? ?? '',
licenseText: map['licenseText'] as String?,
);
}
}

100
lib/data/models/movie.dart Normal file
View File

@@ -0,0 +1,100 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:hive/hive.dart';
part 'movie.g.dart';
@HiveType(typeId: 0)
class Movie extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String title;
@HiveField(2)
final String? posterPath;
@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.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) {
return Movie(
id: (json['id'] as num).toString(), // Ensure id is a string
title: (json['title'] ?? json['name'] ?? '') as String,
posterPath: json['poster_path'] as String?,
overview: json['overview'] as String?,
releaseDate: json['release_date'] != null && json['release_date'].isNotEmpty
? DateTime.tryParse(json['release_date'] as String)
: json['first_air_date'] != null && json['first_air_date'].isNotEmpty
? DateTime.tryParse(json['first_air_date'] as String)
: null,
genres: List<String>.from(json['genres']?.map((g) => g['name']) ?? []),
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,
);
}
String get fullPosterUrl {
final baseUrl = dotenv.env['API_URL']!;
if (posterPath == null || posterPath!.isEmpty) {
// Use the placeholder from our own backend
return '$baseUrl/images/w500/placeholder.jpg';
}
// Null check is already performed above, so we can use `!`
final cleanPath = posterPath!.startsWith('/') ? posterPath!.substring(1) : posterPath!;
return '$baseUrl/images/w500/$cleanPath';
}
}

View File

@@ -0,0 +1,59 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'movie.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class MovieAdapter extends TypeAdapter<Movie> {
@override
final int typeId = 0;
@override
Movie read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Movie(
id: fields[0] as String,
title: fields[1] as String,
posterPath: fields[2] as String?,
overview: fields[3] as String?,
releaseDate: fields[4] as DateTime?,
genres: (fields[5] as List?)?.cast<String>(),
voteAverage: fields[6] as double?,
);
}
@override
void write(BinaryWriter writer, Movie obj) {
writer
..writeByte(7)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.title)
..writeByte(2)
..write(obj.posterPath)
..writeByte(3)
..write(obj.overview)
..writeByte(4)
..write(obj.releaseDate)
..writeByte(5)
..write(obj.genres)
..writeByte(6)
..write(obj.voteAverage);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MovieAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,21 @@
import 'package:hive/hive.dart';
part 'movie_preview.g.dart';
@HiveType(typeId: 1) // Use a new typeId to avoid conflicts with Movie
class MoviePreview extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String title;
@HiveField(2)
final String? posterPath;
MoviePreview({
required this.id,
required this.title,
this.posterPath,
});
}

View File

@@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'movie_preview.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class MoviePreviewAdapter extends TypeAdapter<MoviePreview> {
@override
final int typeId = 1;
@override
MoviePreview read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return MoviePreview(
id: fields[0] as String,
title: fields[1] as String,
posterPath: fields[2] as String?,
);
}
@override
void write(BinaryWriter writer, MoviePreview obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.title)
..writeByte(2)
..write(obj.posterPath);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MoviePreviewAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,80 @@
import 'package:equatable/equatable.dart';
enum VideoSourceType {
lumex,
alloha,
}
class VideoSource extends Equatable {
final String id;
final String name;
final VideoSourceType type;
final bool isActive;
const VideoSource({
required this.id,
required this.name,
required this.type,
this.isActive = true,
});
// Default sources
static final List<VideoSource> defaultSources = [
const VideoSource(
id: 'alloha',
name: 'Alloha',
type: VideoSourceType.alloha,
isActive: true,
),
const VideoSource(
id: 'lumex',
name: 'Lumex',
type: VideoSourceType.lumex,
isActive: false,
),
];
@override
List<Object?> get props => [id, name, type, isActive];
@override
bool get stringify => true;
// Convert to map for serialization
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'type': type.name,
'isActive': isActive,
};
}
// Create from map for deserialization
factory VideoSource.fromMap(Map<String, dynamic> map) {
return VideoSource(
id: map['id'] as String? ?? 'unknown',
name: map['name'] as String? ?? 'Unknown',
type: VideoSourceType.values.firstWhere(
(e) => e.name == map['type'],
orElse: () => VideoSourceType.lumex,
),
isActive: map['isActive'] as bool? ?? true,
);
}
// Copy with method for immutability
VideoSource copyWith({
String? id,
String? name,
VideoSourceType? type,
bool? isActive,
}) {
return VideoSource(
id: id ?? this.id,
name: name ?? this.name,
type: type ?? this.type,
isActive: isActive ?? this.isActive,
);
}
}

View File

@@ -0,0 +1,25 @@
class Reaction {
final String type;
final int count;
Reaction({required this.type, required this.count});
factory Reaction.fromJson(Map<String, dynamic> json) {
return Reaction(
type: json['type'] as String? ?? '',
count: json['count'] as int? ?? 0,
);
}
}
class UserReaction {
final String? reactionType;
UserReaction({this.reactionType});
factory UserReaction.fromJson(Map<String, dynamic> json) {
return UserReaction(
reactionType: json['type'] as String?,
);
}
}

15
lib/data/models/user.dart Normal file
View File

@@ -0,0 +1,15 @@
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['_id'] as String? ?? '',
name: json['name'] as String? ?? '',
email: json['email'] as String? ?? '',
);
}
}