import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import '../../../core/contracts/tmdb.dart'; import '../../../shared/mappers/tmdb_image_url.dart'; import '../../../shared/widgets/banner_carousel_mixin.dart'; import '../../../shared/widgets/banner_carousel_scaffold.dart'; import '../discover_helpers.dart'; class TmdbBannerCarousel extends StatefulWidget { final List items; final String imageBaseUrl; final ValueChanged onItemTap; final ValueChanged? onActiveItemChanged; const TmdbBannerCarousel({ super.key, required this.items, required this.imageBaseUrl, required this.onItemTap, this.onActiveItemChanged, }); @override State createState() => _TmdbBannerCarouselState(); } class _TmdbBannerCarouselState extends State with WidgetsBindingObserver, BannerCarouselMixin { @override int get itemCount => widget.items.length; @override bool get isActive => true; @override void emitActiveItem() { if (widget.items.isEmpty) return; widget.onActiveItemChanged?.call(widget.items[currentIndex]); } @override void precacheAdjacentSlides() { if (!mounted || widget.items.length <= 1) return; final next = (currentIndex + 1) % widget.items.length; final nextUrl = TmdbImageUrl.backdrop( widget.imageBaseUrl, widget.items[next].backdropPath, ); if (nextUrl == null) return; final cacheWidth = bannerCacheWidth(); unawaited( precacheImage( ResizeImage.resizeIfNeeded( cacheWidth, null, CachedNetworkImageProvider(nextUrl), ), context, onError: (_, _) {}, ), ); } @override void didUpdateWidget(covariant TmdbBannerCarousel oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.items != widget.items) { resetCarouselItems(); } } BannerSlideData _slideDataFor(TmdbRecommendation item) { final typeLabel = _mediaTypeLabel(item.mediaType); return BannerSlideData( imageUrl: TmdbImageUrl.backdrop(widget.imageBaseUrl, item.backdropPath), title: item.title, overview: item.overview, rating: (item.voteAverage != null && item.voteAverage! > 0) ? item.voteAverage!.toStringAsFixed(1) : null, primaryMetaLabels: [if (typeLabel != null) typeLabel], ); } @override Widget build(BuildContext context) { return BannerCarouselScaffold( controller: this, slides: [for (final item in widget.items) _slideDataFor(item)], onSlideTap: (index) => widget.onItemTap(widget.items[index]), ); } static String? _mediaTypeLabel(String? type) => mediaTypeLabel(type); }