feat: implement mobile dashboard screen with live TV tab and channel filtering functionality

This commit is contained in:
Michael committed 2026-03-29 12:46:32 +02:00
1 parent ddb8110c40
commit 77dc9053bb
2 files changed
+27 -71

No files matched your search

@@ -34,16 +34,23 @@ class _MobileDashboardScreenState extends ConsumerState<MobileDashboardScreen> {
_currentIndex = index; _currentIndex = index;
}); });
}, },
child: IndexedStack( child: _buildActiveTab(),
index: _currentIndex,
children: [
MobileLiveTVTab(playlist: widget.playlist),
MobileMoviesTab(playlist: widget.playlist),
MobileSeriesTab(playlist: widget.playlist),
const MobileSettingsTab(),
],
),
), ),
); );
} }
Widget _buildActiveTab() {
switch (_currentIndex) {
case 0:
return MobileLiveTVTab(playlist: widget.playlist);
case 1:
return MobileMoviesTab(playlist: widget.playlist);
case 2:
return MobileSeriesTab(playlist: widget.playlist);
case 3:
return const MobileSettingsTab();
default:
return MobileLiveTVTab(playlist: widget.playlist);
}
}
} }
@@ -292,7 +292,6 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> {
final channel = displayedChannels[index]; final channel = displayedChannels[index];
return _MobileChannelTile( return _MobileChannelTile(
channel: channel, channel: channel,
playlist: widget.playlist,
onTap: () => _playChannel( onTap: () => _playChannel(
context, context,
channel, channel,
@@ -329,31 +328,22 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> {
} }
} }
class _MobileChannelTile extends ConsumerWidget { class _MobileChannelTile extends StatelessWidget {
final Channel channel; final Channel channel;
final PlaylistConfig playlist;
final VoidCallback onTap; final VoidCallback onTap;
const _MobileChannelTile({ const _MobileChannelTile({
required this.channel, required this.channel,
required this.playlist,
required this.onTap, required this.onTap,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context) {
final iconUrl = final iconUrl =
channel.streamIcon.isNotEmpty && channel.streamIcon.startsWith('http') channel.streamIcon.isNotEmpty && channel.streamIcon.startsWith('http')
? '/api/xtream/${channel.streamIcon}' ? '/api/xtream/${channel.streamIcon}'
: null; : null;
// Fetch EPG
final epgAsync = ref.watch(
epgByPlaylistProvider(
EpgRequestKey(playlist: playlist, streamId: channel.streamId),
),
);
return GlassContainer( return GlassContainer(
borderRadius: 12, borderRadius: 12,
opacity: 0.15, opacity: 0.15,
@@ -384,56 +374,15 @@ class _MobileChannelTile extends ConsumerWidget {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Text(
crossAxisAlignment: CrossAxisAlignment.start, channel.name,
children: [ style: GoogleFonts.inter(
Text( color: Colors.white,
channel.name, fontWeight: FontWeight.w500,
style: GoogleFonts.inter( fontSize: 14,
color: Colors.white, ),
fontWeight: FontWeight.w500, maxLines: 1,
fontSize: 14, overflow: TextOverflow.ellipsis,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// EPG Only (Replaces Channel Number)
epgAsync.when(
data: (epgList) {
if (epgList.isEmpty) {
return Text(
'No Info',
style: GoogleFonts.inter(
color: Colors.white38,
fontSize: 12,
),
);
}
final current = epgList.first;
return Text(
current.title,
style: GoogleFonts.inter(
color: const Color(
0xFFFFD700,
), // Gold/Amber for visibility check
fontSize: 12,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
},
loading: () => const Text(
'...',
style: TextStyle(color: Colors.white38, fontSize: 12),
),
error: (err, stack) => const Text(
'Err',
style: TextStyle(color: Colors.red, fontSize: 10),
),
),
],
), ),
), ),
Container( Container(