/// Widget: Promotion Slider /// /// Auto-sliding carousel of promotional banners. /// Displays promotion images, titles, and descriptions. /// Auto-advances every 4 seconds with smooth animation. library; import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; import 'package:worker/core/router/app_router.dart'; import 'package:worker/core/widgets/loading_indicator.dart'; import 'package:worker/features/home/domain/entities/promotion.dart'; /// Promotion Slider Widget /// /// Displays an auto-sliding carousel of promotion cards. /// Each card shows an image, title, and brief description. /// Auto-advances every 4 seconds with page indicators. class PromotionSlider extends StatefulWidget { const PromotionSlider({ super.key, required this.promotions, this.onPromotionTap, this.autoSlideDuration = const Duration(seconds: 4), }); /// List of promotions to display final List promotions; /// Callback when a promotion is tapped final void Function(Promotion promotion)? onPromotionTap; /// Duration between auto-slides final Duration autoSlideDuration; @override State createState() => _PromotionSliderState(); } class _PromotionSliderState extends State { late final ScrollController _scrollController; Timer? _autoSlideTimer; int _currentIndex = 0; static const double _cardWidth = 280; static const double _cardMargin = 12; static const double _scrollOffset = _cardWidth + _cardMargin; @override void initState() { super.initState(); _scrollController = ScrollController(); _startAutoSlide(); } @override void dispose() { _autoSlideTimer?.cancel(); _scrollController.dispose(); super.dispose(); } void _startAutoSlide() { if (widget.promotions.length <= 1) return; _autoSlideTimer = Timer.periodic(widget.autoSlideDuration, (_) { if (!mounted) return; _currentIndex = (_currentIndex + 1) % widget.promotions.length; final targetOffset = _currentIndex * _scrollOffset; _scrollController.animateTo( targetOffset, duration: const Duration(milliseconds: 400), curve: Curves.easeInOut, ); }); } @override Widget build(BuildContext context) { if (widget.promotions.isEmpty) { return const SizedBox.shrink(); } final colorScheme = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.only(bottom: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( 'Tin tức nổi bật', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), ), const SizedBox(height: 12), SizedBox( height: 210, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: widget.promotions.length, itemBuilder: (context, index) { return _PromotionCard( promotion: widget.promotions[index], onTap: () { if (widget.onPromotionTap != null) { widget.onPromotionTap!(widget.promotions[index]); } else { context.pushNamed( RouteNames.promotionDetail, extra: widget.promotions[index], ); } }, ); }, ), ), ], ), ); } } /// Individual Promotion Card class _PromotionCard extends StatelessWidget { const _PromotionCard({required this.promotion, this.onTap}); final Promotion promotion; final VoidCallback? onTap; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Container( width: 280, margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Promotion Image ClipRRect( borderRadius: const BorderRadius.vertical( top: Radius.circular(12), ), child: CachedNetworkImage( imageUrl: promotion.imageUrl, height: 140, width: 280, fit: BoxFit.cover, placeholder: (context, url) => Container( height: 140, color: colorScheme.surfaceContainerHighest, child: const CustomLoadingIndicator(), ), errorWidget: (context, url, error) => Container( height: 140, color: colorScheme.surfaceContainerHighest, child: FaIcon( FontAwesomeIcons.image, size: 48, color: colorScheme.onSurfaceVariant, ), ), ), ), // Promotion Info Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: const BorderRadius.vertical( bottom: Radius.circular(12), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( promotion.title, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: colorScheme.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( promotion.description, style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ), ); } }