Unified Live TV control bar - EPG + buttons in single bar

This commit is contained in:
Michael committed 2026-01-02 09:14:57 +01:00
1 parent 8670e42cf8
commit d93d638b1b
1 file changed
+200 -38
+200 -38
View File
@@ -421,24 +421,32 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
), ),
), ),
// Bottom Controls - styled like Native player // Unified Bottom Bar - EPG + Controls in one bar
AnimatedPositioned( AnimatedPositioned(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
bottom: _showControls ? 24 : -100, bottom: _showControls ? 24 : -100,
left: 0, left: 24,
right: 0, right: 24,
child: Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12), horizontal: 20, vertical: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: const Color(0xFF1A1A2E).withOpacity(0.9),
const Color(0xFF1A1A2E).withOpacity(0.85),
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all( border: Border.all(
color: Colors.white.withOpacity(0.1)), color: Colors.white.withOpacity(0.1)),
), ),
child: Row( child: Row(
children: [
// EPG Info (left side)
Expanded(
child: _buildInlineEpg(),
),
const SizedBox(width: 24),
// Control Buttons (right side)
Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Previous Channel // Previous Channel
@@ -447,12 +455,12 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
_buildSimpleIconButton( _buildSimpleIconButton(
icon: Icons.skip_previous_rounded, icon: Icons.skip_previous_rounded,
onTap: _previousChannel, onTap: _previousChannel,
size: 48, size: 44,
), ),
if (widget.channels != null && if (widget.channels != null &&
widget.channels!.length > 1) widget.channels!.length > 1)
const SizedBox(width: 24), const SizedBox(width: 16),
// Play/Pause (Large) // Play/Pause (Large)
_buildSimpleIconButton( _buildSimpleIconButton(
@@ -460,14 +468,14 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
? Icons.pause_rounded ? Icons.pause_rounded
: Icons.play_arrow_rounded, : Icons.play_arrow_rounded,
onTap: _togglePlayPause, onTap: _togglePlayPause,
size: 56, size: 52,
iconSize: 32, iconSize: 28,
highlighted: true, highlighted: true,
), ),
if (widget.channels != null && if (widget.channels != null &&
widget.channels!.length > 1) widget.channels!.length > 1)
const SizedBox(width: 24), const SizedBox(width: 16),
// Next Channel // Next Channel
if (widget.channels != null && if (widget.channels != null &&
@@ -475,34 +483,29 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
_buildSimpleIconButton( _buildSimpleIconButton(
icon: Icons.skip_next_rounded, icon: Icons.skip_next_rounded,
onTap: _nextChannel, onTap: _nextChannel,
size: 48, size: 44,
),
],
),
),
),
),
],
),
),
),
),
),
), ),
// EPG Overlay (outside the main PointerInterceptor) const SizedBox(width: 16),
if (widget.streamType == StreamType.live)
AnimatedPositioned( // Mute button
duration: const Duration(milliseconds: 200), _buildSimpleIconButton(
bottom: _showControls ? 24 : -140, icon: _isMuted
left: 24, ? Icons.volume_off_rounded
right: 0, : Icons.volume_up_rounded,
child: PointerInterceptor( onTap: _toggleMute,
child: EpgOverlay( size: 44,
playlist: widget.playlist, ),
streamId: widget.channels != null ],
? widget.channels![_currentIndex].streamId ),
: widget.streamId, ],
),
),
),
],
),
),
),
), ),
), ),
), ),
@@ -795,4 +798,163 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
), ),
); );
} }
/// Build inline EPG info for the unified control bar
Widget _buildInlineEpg() {
final currentStreamId = widget.channels != null
? widget.channels![_currentIndex].streamId
: widget.streamId;
return Consumer(
builder: (context, ref, child) {
final epgAsync = ref.watch(epgByPlaylistProvider(
EpgRequestKey(playlist: widget.playlist, streamId: currentStreamId),
));
return epgAsync.when(
data: (epgEntries) {
if (epgEntries.isEmpty) {
// No EPG data - show channel name only
return Row(
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'LIVE',
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.channels != null
? widget.channels![_currentIndex].name
: widget.title,
style: GoogleFonts.outfit(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
);
}
// Get current program
final now = DateTime.now();
final currentProgram = epgEntries.firstWhere(
(entry) {
final start = DateTime.parse(entry.start);
final end = DateTime.parse(entry.end);
return now.isAfter(start) && now.isBefore(end);
},
orElse: () => epgEntries.first,
);
return Row(
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'LIVE',
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
currentProgram.title,
style: GoogleFonts.outfit(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (currentProgram.description.isNotEmpty)
Text(
currentProgram.description,
style: GoogleFonts.inter(
color: Colors.white60,
fontSize: 12,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
);
},
loading: () => Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'LIVE',
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 12),
Text(
widget.channels != null
? widget.channels![_currentIndex].name
: widget.title,
style: GoogleFonts.outfit(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
error: (_, __) => Text(
widget.channels != null
? widget.channels![_currentIndex].name
: widget.title,
style: GoogleFonts.outfit(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
);
},
);
}
} }