From 20f8904e785b40a5e209bb91797fbeab56ae641c Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Dec 2025 23:13:12 +0100 Subject: [PATCH] feat: add HeroCarousel widget for displaying interactive, auto-playing content. --- .../widgets/components/hero_carousel.dart | 8 -------- .../iptv/widgets/mobile_movies_tab.dart | 19 +++++++++++++------ .../iptv/widgets/mobile_series_tab.dart | 19 +++++++++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/core/widgets/components/hero_carousel.dart b/lib/core/widgets/components/hero_carousel.dart index af80b0f..81c8cc1 100644 --- a/lib/core/widgets/components/hero_carousel.dart +++ b/lib/core/widgets/components/hero_carousel.dart @@ -169,14 +169,6 @@ class _HeroCarouselState extends State { onPressed: () => widget.onTap(item), isPrimary: true, ), - const SizedBox(width: 12), - if (item.onMoreInfo != null) - _HeroButton( - icon: Icons.info_outline, - label: 'Plus d\'infos', - onPressed: item.onMoreInfo!, - isPrimary: false, - ), ], ), ], diff --git a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart index e5bbf47..afe245b 100644 --- a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart @@ -49,7 +49,9 @@ class _MobileMoviesTabState extends ConsumerState { } 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) { _loadMoreMovies(); } @@ -92,9 +94,12 @@ class _MobileMoviesTabState extends ConsumerState { } Future _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 { final service = ref.read(xtreamServiceProvider(widget.playlist)); @@ -112,7 +117,9 @@ class _MobileMoviesTabState extends ConsumerState { }); } } catch (e) { - if (mounted) setState(() => _isLoading = false); + if (mounted) { + setState(() => _isLoading = false); + } } } @@ -165,8 +172,8 @@ class _MobileMoviesTabState extends ConsumerState { : _movies.where((m) => settings.matchesMoviesFilter(m.categoryName)).toList(); } - // Hero Items - final heroItems = _movies.take(3).map((m) => HeroItem( + // Hero Items (use filtered movies) + final heroItems = displayMovies.take(3).map((m) => HeroItem( id: m.streamId, title: m.name, imageUrl: _getProxiedImageUrl(m.streamIcon), diff --git a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart index 29505db..f25290f 100644 --- a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart @@ -48,7 +48,9 @@ class _MobileSeriesTabState extends ConsumerState { } 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) { _loadMoreSeries(); } @@ -91,9 +93,12 @@ class _MobileSeriesTabState extends ConsumerState { } Future _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 { final service = ref.read(xtreamServiceProvider(widget.playlist)); @@ -111,7 +116,9 @@ class _MobileSeriesTabState extends ConsumerState { }); } } catch (e) { - if (mounted) setState(() => _isLoading = false); + if (mounted) { + setState(() => _isLoading = false); + } } } @@ -158,8 +165,8 @@ class _MobileSeriesTabState extends ConsumerState { : _series.where((s) => settings.matchesSeriesFilter(s.categoryName)).toList(); } - // Hero Items - final heroItems = _series.take(3).map((s) => HeroItem( + // Hero Items (use filtered series) + final heroItems = displaySeries.take(3).map((s) => HeroItem( id: s.seriesId.toString(), title: s.name, imageUrl: _getProxiedImageUrl(s.cover),