feat: add HeroCarousel widget for displaying interactive, auto-playing content.

This commit is contained in:
Michael committed 2025-12-08 23:13:12 +01:00
1 parent e0cdf69674
commit 20f8904e78
3 files changed
+26 -20

No files matched your search

@@ -169,14 +169,6 @@ class _HeroCarouselState extends State<HeroCarousel> {
onPressed: () => widget.onTap(item), onPressed: () => widget.onTap(item),
isPrimary: true, isPrimary: true,
), ),
const SizedBox(width: 12),
if (item.onMoreInfo != null)
_HeroButton(
icon: Icons.info_outline,
label: 'Plus d\'infos',
onPressed: item.onMoreInfo!,
isPrimary: false,
),
], ],
), ),
], ],
@@ -49,7 +49,9 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
} }
void _onScroll() { void _onScroll() {
if (_scrollController.position.pixels >= // Only trigger load when near bottom and not already loading
if (!_isLoading && _hasMore &&
_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent * 0.8) { _scrollController.position.maxScrollExtent * 0.8) {
_loadMoreMovies(); _loadMoreMovies();
} }
@@ -92,9 +94,12 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
} }
Future<void> _loadMoreMovies() async { Future<void> _loadMoreMovies() async {
if (_isLoading || !_hasMore) return; // Double-check conditions to prevent race conditions
if (_isLoading || !_hasMore || !mounted) return;
setState(() => _isLoading = true); // Set loading flag immediately before any async operation
_isLoading = true;
if (mounted) setState(() {});
try { try {
final service = ref.read(xtreamServiceProvider(widget.playlist)); final service = ref.read(xtreamServiceProvider(widget.playlist));
@@ -112,7 +117,9 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
}); });
} }
} catch (e) { } catch (e) {
if (mounted) setState(() => _isLoading = false); if (mounted) {
setState(() => _isLoading = false);
}
} }
} }
@@ -165,8 +172,8 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> {
: _movies.where((m) => settings.matchesMoviesFilter(m.categoryName)).toList(); : _movies.where((m) => settings.matchesMoviesFilter(m.categoryName)).toList();
} }
// Hero Items // Hero Items (use filtered movies)
final heroItems = _movies.take(3).map((m) => HeroItem( final heroItems = displayMovies.take(3).map((m) => HeroItem(
id: m.streamId, id: m.streamId,
title: m.name, title: m.name,
imageUrl: _getProxiedImageUrl(m.streamIcon), imageUrl: _getProxiedImageUrl(m.streamIcon),
@@ -48,7 +48,9 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
} }
void _onScroll() { void _onScroll() {
if (_scrollController.position.pixels >= // Only trigger load when near bottom and not already loading
if (!_isLoading && _hasMore &&
_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent * 0.8) { _scrollController.position.maxScrollExtent * 0.8) {
_loadMoreSeries(); _loadMoreSeries();
} }
@@ -91,9 +93,12 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
} }
Future<void> _loadMoreSeries() async { Future<void> _loadMoreSeries() async {
if (_isLoading || !_hasMore) return; // Double-check conditions to prevent race conditions
if (_isLoading || !_hasMore || !mounted) return;
setState(() => _isLoading = true); // Set loading flag immediately before any async operation
_isLoading = true;
if (mounted) setState(() {});
try { try {
final service = ref.read(xtreamServiceProvider(widget.playlist)); final service = ref.read(xtreamServiceProvider(widget.playlist));
@@ -111,7 +116,9 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
}); });
} }
} catch (e) { } catch (e) {
if (mounted) setState(() => _isLoading = false); if (mounted) {
setState(() => _isLoading = false);
}
} }
} }
@@ -158,8 +165,8 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> {
: _series.where((s) => settings.matchesSeriesFilter(s.categoryName)).toList(); : _series.where((s) => settings.matchesSeriesFilter(s.categoryName)).toList();
} }
// Hero Items // Hero Items (use filtered series)
final heroItems = _series.take(3).map((s) => HeroItem( final heroItems = displaySeries.take(3).map((s) => HeroItem(
id: s.seriesId.toString(), id: s.seriesId.toString(),
title: s.name, title: s.name,
imageUrl: _getProxiedImageUrl(s.cover), imageUrl: _getProxiedImageUrl(s.cover),