feat: Implement a web-based IPTV player with HLS/MPEG-TS support and Flutter communication.

This commit is contained in:
Michael committed 2025-12-17 23:45:56 +01:00
1 parent 2ca92d8425
commit 55acbbc135
28 files changed
+2075 -1448

No files matched your search

+45 -23
View File
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../core/models/playlist_config.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/widgets/digital_clock.dart';
import '../widgets/live_tv_tab.dart';
import '../widgets/movies_tab.dart';
@@ -11,7 +9,7 @@ import '../widgets/series_tab.dart';
import '../widgets/settings_tab.dart';
/// Apple TV Style Dashboard
///
///
/// Uses a floating Top Bar for navigation instead of a sidebar.
/// Content flows underneath the top bar.
class DashboardScreen extends ConsumerStatefulWidget {
@@ -26,11 +24,17 @@ class DashboardScreen extends ConsumerStatefulWidget {
ConsumerState<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTickerProviderStateMixin {
class _DashboardScreenState extends ConsumerState<DashboardScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = ['Live TV', 'Films', 'Séries', 'Réglages'];
final List<IconData> _icons = [Icons.live_tv, Icons.movie, Icons.tv, Icons.settings];
final List<IconData> _icons = [
Icons.live_tv,
Icons.movie,
Icons.tv,
Icons.settings
];
@override
void initState() {
@@ -74,13 +78,14 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
child: _buildLogo(),
),
),
// 2. Centered Navigation Tabs (Expanded to fill space, then Centered)
Expanded(
child: Center(
child: Container(
height: 50,
constraints: const BoxConstraints(maxWidth: 600), // Prevent taking too much space
constraints: const BoxConstraints(
maxWidth: 600), // Prevent taking too much space
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
@@ -97,8 +102,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
indicatorSize: TabBarIndicatorSize.tab,
indicatorPadding: const EdgeInsets.all(4),
labelColor: Colors.black,
unselectedLabelColor: Colors.white70, // Fixed: Improved readability
labelStyle: GoogleFonts.inter(fontWeight: FontWeight.w700, fontSize: 13),
unselectedLabelColor:
Colors.white70, // Fixed: Improved readability
labelStyle: GoogleFonts.inter(
fontWeight: FontWeight.w700, fontSize: 13),
overlayColor: WidgetStateProperty.all(Colors.transparent),
tabs: List.generate(_tabs.length, (index) {
return Tab(
@@ -118,7 +125,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
),
),
),
// 3. Search / Profile (Fixed Width for centering balance)
SizedBox(
width: 200,
@@ -130,7 +137,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
IconButton(
icon: const Icon(Icons.search, color: Colors.white),
onPressed: () {
// Global search trigger
// Global search trigger
},
),
const SizedBox(width: 16),
@@ -139,7 +146,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
const CircleAvatar(
radius: 16,
backgroundColor: Colors.white24,
child: Icon(Icons.person, size: 18, color: Colors.white),
child:
Icon(Icons.person, size: 18, color: Colors.white),
),
],
),
@@ -165,17 +173,25 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
),
),
),
// Content
TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(), // Disable swipe to avoid gesture conflicts
physics:
const NeverScrollableScrollPhysics(), // Disable swipe to avoid gesture conflicts
children: [
// Padding top to account for the transparent AppBar
Padding(padding: const EdgeInsets.only(top: 80), child: LiveTVTab(playlist: widget.playlist)),
Padding(padding: const EdgeInsets.only(top: 80), child: MoviesTab(playlist: widget.playlist)),
Padding(padding: const EdgeInsets.only(top: 80), child: SeriesTab(playlist: widget.playlist)),
const Padding(padding: EdgeInsets.only(top: 80), child: SettingsTab()),
Padding(
padding: const EdgeInsets.only(top: 80),
child: LiveTVTab(playlist: widget.playlist)),
Padding(
padding: const EdgeInsets.only(top: 80),
child: MoviesTab(playlist: widget.playlist)),
Padding(
padding: const EdgeInsets.only(top: 80),
child: SeriesTab(playlist: widget.playlist)),
const Padding(
padding: EdgeInsets.only(top: 80), child: SettingsTab()),
],
),
],
@@ -190,7 +206,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
width: 36,
height: 36,
decoration: BoxDecoration(
gradient: const LinearGradient(colors: [Color(0xFF43cea2), Color(0xFF185a9d)]), // Green to Blue
gradient: const LinearGradient(colors: [
Color(0xFF43cea2),
Color(0xFF185a9d)
]), // Green to Blue
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.play_arrow_rounded, color: Colors.white),
@@ -202,13 +221,16 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> with SingleTi
children: [
Text(
'Xtrem',
style: GoogleFonts.inter(fontWeight: FontWeight.w800, fontSize: 16, color: Colors.white),
style: GoogleFonts.inter(
fontWeight: FontWeight.w800,
fontSize: 16,
color: Colors.white),
),
Text(
'Flow',
style: GoogleFonts.inter(
fontWeight: FontWeight.w300,
fontSize: 16,
fontWeight: FontWeight.w300,
fontSize: 16,
color: Colors.white.withOpacity(0.8),
height: 0.8,
),
File diff suppressed because it is too large. Load diff
@@ -26,14 +26,16 @@ class PlaylistSelectionScreen extends ConsumerWidget {
appBar: AppBar(
title: Text(
'Select Playlist',
style: GoogleFonts.roboto(fontWeight: FontWeight.w600, color: Colors.white),
style: GoogleFonts.roboto(
fontWeight: FontWeight.w600, color: Colors.white),
),
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
if (currentUser?.isAdmin ?? false)
IconButton(
icon: const Icon(Icons.admin_panel_settings, color: Colors.white70),
icon:
const Icon(Icons.admin_panel_settings, color: Colors.white70),
onPressed: () => context.go('/admin'),
tooltip: 'Admin Panel',
),
@@ -69,7 +71,9 @@ class PlaylistSelectionScreen extends ConsumerWidget {
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red.shade400),
const SizedBox(height: 16),
Text('Error loading playlists', style: GoogleFonts.roboto(fontSize: 18, color: Colors.white70)),
Text('Error loading playlists',
style: GoogleFonts.roboto(
fontSize: 18, color: Colors.white70)),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => ref.refresh(playlistsProvider),
@@ -84,7 +88,7 @@ class PlaylistSelectionScreen extends ConsumerWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
const Icon(
Icons.playlist_remove,
size: 64,
color: Colors.white24,
@@ -113,7 +117,8 @@ class PlaylistSelectionScreen extends ConsumerWidget {
}
return GridView.builder(
padding: const EdgeInsets.fromLTRB(24, 100, 24, 24), // Top padding for transparent AppBar
padding: const EdgeInsets.fromLTRB(
24, 100, 24, 24), // Top padding for transparent AppBar
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 24,
@@ -165,23 +170,25 @@ class _PlaylistCardState extends State<_PlaylistCard> {
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
color: _isHovered
? Colors.white.withOpacity(0.15)
color: _isHovered
? Colors.white.withOpacity(0.15)
: Colors.white.withOpacity(0.05), // Glass effect
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: _isHovered
? Colors.white.withOpacity(0.5)
color: _isHovered
? Colors.white.withOpacity(0.5)
: Colors.white.withOpacity(0.1),
width: 1.5,
),
boxShadow: _isHovered ? [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
] : [],
boxShadow: _isHovered
? [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
]
: [],
),
padding: const EdgeInsets.all(24),
child: Column(
@@ -204,7 +211,8 @@ class _PlaylistCardState extends State<_PlaylistCard> {
const SizedBox(height: 16),
Text(
widget.playlist.name,
style: GoogleFonts.outfit( // Using Outfit for modern look
style: GoogleFonts.outfit(
// Using Outfit for modern look
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
@@ -229,4 +237,3 @@ class _PlaylistCardState extends State<_PlaylistCard> {
);
}
}