Merge pull request #84 from R0m1k3/antigravity

feat: add mobile movies and series tabs for IPTV content display
This commit is contained in:
LogiFlow authored and GitHub committed 2025-12-07 23:00:33 +01:00
commit 124d2b432f
7 files changed
+67 -31

No files matched your search

+4 -2
View File
@@ -27,11 +27,13 @@ High-performance, containerized IPTV Web Application using Flutter Web and Xtrea
✅ **VOD & Series**
- Movies and Series organized by categories
- Grid layout with posters
- Ratings display
- Optimized ratings display (1 decimal place)
✅ **Docker Deployment**
- Multi-stage build with Flutter and Dart
- `dhttpd` static server (minimal footprint)
- Custom Dart Server (`bin/server.dart`)
- **FFmpeg Transcoding** for mobile compatibility
- **Cache Management** system for temporary files
- External network support (`nginx_default`)
## Tech Stack
@@ -61,6 +61,15 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
}
}
String? _formatRating(String? rating) {
if (rating == null || rating.isEmpty) return null;
final value = double.tryParse(rating);
if (value != null) {
return value.toStringAsFixed(1);
}
return rating;
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -144,7 +153,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
Icon(Icons.star, size: 16, color: Colors.amber.shade700),
const SizedBox(width: 4),
Text(
_seriesInfo!.rating!,
_formatRating(_seriesInfo!.rating!)!,
style: GoogleFonts.roboto(fontSize: 13, color: Colors.white70),
),
],
+13 -3
View File
@@ -131,6 +131,15 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
}
}
String? _formatRating(String? rating) {
if (rating == null || rating.isEmpty) return null;
final value = double.tryParse(rating);
if (value != null) {
return value.toStringAsFixed(1);
}
return rating;
}
void _playMovie(Movie movie) {
ref.read(watchHistoryProvider.notifier).markMovieWatched(movie.streamId);
@@ -167,7 +176,7 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
id: m.streamId,
title: m.name,
imageUrl: m.streamIcon ?? '',
subtitle: m.rating != null ? '${m.rating} ★' : null,
subtitle: m.rating != null ? '${_formatRating(m.rating)} ★' : null,
onMoreInfo: () {
_playMovie(m);
},
@@ -283,8 +292,9 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
return MediaCard(
title: movie.name,
imageUrl: movie.streamIcon,
subtitle: movie.rating != null ? '${movie.rating} ★' : null,
rating: movie.rating,
imageUrl: movie.streamIcon,
subtitle: movie.rating != null ? '${_formatRating(movie.rating)} ★' : null,
rating: _formatRating(movie.rating),
isWatched: isWatched,
onTap: () => _playMovie(movie),
onLongPress: () {
+13 -3
View File
@@ -131,6 +131,15 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
}
}
String? _formatRating(String? rating) {
if (rating == null || rating.isEmpty) return null;
final value = double.tryParse(rating);
if (value != null) {
return value.toStringAsFixed(1);
}
return rating;
}
void _openSeries(Series series) {
Navigator.push(
context,
@@ -165,7 +174,7 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
id: s.seriesId.toString(), // ID logic might differ for Series
title: s.name,
imageUrl: s.cover ?? '',
subtitle: s.rating != null ? '${s.rating} ★' : null,
subtitle: s.rating != null ? '${_formatRating(s.rating)} ★' : null,
onMoreInfo: () => _openSeries(s),
)).toList();
@@ -273,8 +282,9 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
return MediaCard(
title: serie.name,
imageUrl: serie.cover,
subtitle: serie.rating != null ? '${serie.rating} ★' : null,
rating: serie.rating,
imageUrl: serie.cover,
subtitle: serie.rating != null ? '${_formatRating(serie.rating)} ★' : null,
rating: _formatRating(serie.rating),
placeholderIcon: Icons.tv,
onTap: () => _openSeries(serie),
);
@@ -24,21 +24,6 @@ class MobileSeriesDetailScreen extends ConsumerStatefulWidget {
}
class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScreen> {
SeriesInfo? _seriesInfo;
bool _isLoading = true;
String? _error;
int _selectedSeason = 1;
@override
void initState() {
super.initState();
_loadSeriesInfo();
}
Future<void> _loadSeriesInfo() async {
try {
setState(() {
_isLoading = true;
_error = null;
});
@@ -159,7 +144,7 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
const Icon(Icons.star, size: 16, color: Colors.amber),
const SizedBox(width: 4),
Text(
_seriesInfo!.rating!,
_formatRating(_seriesInfo!.rating!)!,
style: const TextStyle(color: AppColors.textSecondary, fontSize: 13),
),
const SizedBox(width: 16),
@@ -116,6 +116,15 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
}
}
String? _formatRating(String? rating) {
if (rating == null || rating.isEmpty) return null;
final value = double.tryParse(rating);
if (value != null) {
return value.toStringAsFixed(1);
}
return rating;
}
void _playMovie(Movie movie) {
ref.read(watchHistoryProvider.notifier).markMovieWatched(movie.streamId);
@@ -152,7 +161,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
id: m.streamId,
title: m.name,
imageUrl: m.streamIcon ?? '',
subtitle: m.rating != null ? '${m.rating} ★' : null,
subtitle: m.rating != null ? '${_formatRating(m.rating)} ★' : null,
onMoreInfo: () => _playMovie(m),
)).toList();
@@ -243,8 +252,9 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
return MediaCard(
title: movie.name,
imageUrl: movie.streamIcon,
subtitle: movie.rating != null ? '${movie.rating} ★' : null,
rating: movie.rating,
imageUrl: movie.streamIcon,
subtitle: movie.rating != null ? '${_formatRating(movie.rating)} ★' : null,
rating: _formatRating(movie.rating),
isWatched: isWatched,
onTap: () => _playMovie(movie),
onLongPress: () {
@@ -115,6 +115,15 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
}
}
String? _formatRating(String? rating) {
if (rating == null || rating.isEmpty) return null;
final value = double.tryParse(rating);
if (value != null) {
return value.toStringAsFixed(1);
}
return rating;
}
void _openSeries(Series series) {
Navigator.push(
context,
@@ -145,7 +154,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
id: s.seriesId.toString(),
title: s.name,
imageUrl: s.cover ?? '',
subtitle: s.rating != null ? '${s.rating} ★' : null,
subtitle: s.rating != null ? '${_formatRating(s.rating)} ★' : null,
onMoreInfo: () => _openSeries(s),
)).toList();
@@ -243,8 +252,9 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
return MediaCard(
title: series.name,
imageUrl: series.cover,
subtitle: series.rating != null ? '${series.rating} ★' : null,
rating: series.rating,
imageUrl: series.cover,
subtitle: series.rating != null ? '${_formatRating(series.rating)} ★' : null,
rating: _formatRating(series.rating),
placeholderIcon: Icons.tv,
onTap: () => _openSeries(series),
);