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
+226 -64
+226 -64
View File
@@ -421,64 +421,84 @@ 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: 20, vertical: 16),
horizontal: 24, vertical: 12), decoration: BoxDecoration(
decoration: BoxDecoration( color: const Color(0xFF1A1A2E).withOpacity(0.9),
color: borderRadius: BorderRadius.circular(24),
const Color(0xFF1A1A2E).withOpacity(0.85), border: Border.all(
borderRadius: BorderRadius.circular(24), color: Colors.white.withOpacity(0.1)),
border: Border.all( ),
color: Colors.white.withOpacity(0.1)), child: Row(
), children: [
child: Row( // EPG Info (left side)
mainAxisSize: MainAxisSize.min, Expanded(
children: [ child: _buildInlineEpg(),
// Previous Channel ),
if (widget.channels != null &&
widget.channels!.length > 1) const SizedBox(width: 24),
// Control Buttons (right side)
Row(
mainAxisSize: MainAxisSize.min,
children: [
// Previous Channel
if (widget.channels != null &&
widget.channels!.length > 1)
_buildSimpleIconButton(
icon: Icons.skip_previous_rounded,
onTap: _previousChannel,
size: 44,
),
if (widget.channels != null &&
widget.channels!.length > 1)
const SizedBox(width: 16),
// Play/Pause (Large)
_buildSimpleIconButton( _buildSimpleIconButton(
icon: Icons.skip_previous_rounded, icon: _isPlaying
onTap: _previousChannel, ? Icons.pause_rounded
size: 48, : Icons.play_arrow_rounded,
onTap: _togglePlayPause,
size: 52,
iconSize: 28,
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),
// Play/Pause (Large) // Next Channel
_buildSimpleIconButton( if (widget.channels != null &&
icon: _isPlaying widget.channels!.length > 1)
? Icons.pause_rounded _buildSimpleIconButton(
: Icons.play_arrow_rounded, icon: Icons.skip_next_rounded,
onTap: _togglePlayPause, onTap: _nextChannel,
size: 56, size: 44,
iconSize: 32, ),
highlighted: true,
),
if (widget.channels != null && const SizedBox(width: 16),
widget.channels!.length > 1)
const SizedBox(width: 24),
// Next Channel // Mute button
if (widget.channels != null &&
widget.channels!.length > 1)
_buildSimpleIconButton( _buildSimpleIconButton(
icon: Icons.skip_next_rounded, icon: _isMuted
onTap: _nextChannel, ? Icons.volume_off_rounded
size: 48, : Icons.volume_up_rounded,
onTap: _toggleMute,
size: 44,
), ),
], ],
), ),
],
), ),
), ),
), ),
@@ -489,23 +509,6 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
), ),
), ),
), ),
// EPG Overlay (outside the main PointerInterceptor)
if (widget.streamType == StreamType.live)
AnimatedPositioned(
duration: const Duration(milliseconds: 200),
bottom: _showControls ? 24 : -140,
left: 24,
right: 0,
child: PointerInterceptor(
child: EpgOverlay(
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,
),
),
);
},
);
}
} }