import 'dart:async'; import 'package:flutter/material.dart'; mixin BannerCarouselMixin on State, WidgetsBindingObserver { final PageController pageController = PageController(); Timer? _autoPlayTimer; Timer? _resumeTimer; int currentIndex = 0; bool hovering = false; bool _manualPaused = false; bool _appPaused = false; bool _autoPageChangeInProgress = false; double? lastBannerWidth; bool _tickerVisible = true; static const autoInterval = Duration(seconds: 5); static const _manualPauseDuration = Duration(seconds: 8); static const slideDuration = Duration(milliseconds: 450); static const slideCurve = Curves.easeInOutCubic; int get itemCount; bool get isActive; void emitActiveItem(); void precacheAdjacentSlides(); @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; emitActiveItem(); syncAutoPlay(); }); } @override void dispose() { _autoPlayTimer?.cancel(); _resumeTimer?.cancel(); pageController.dispose(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); final visible = TickerMode.of(context); if (visible == _tickerVisible) return; _tickerVisible = visible; if (visible) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted || !_tickerVisible) return; emitActiveItem(); precacheAdjacentSlides(); }); syncAutoPlay(); } else { stopAutoPlay(); _resumeTimer?.cancel(); _manualPaused = false; } } @override void didChangeAppLifecycleState(AppLifecycleState state) { final paused = state == AppLifecycleState.paused || state == AppLifecycleState.inactive || state == AppLifecycleState.hidden; if (paused == _appPaused) return; _appPaused = paused; paused ? stopAutoPlay() : syncAutoPlay(); } bool get shouldAutoPlay => mounted && isActive && _tickerVisible && !_appPaused && !hovering && !_manualPaused && itemCount > 1; void syncAutoPlay() { if (!shouldAutoPlay) { stopAutoPlay(); return; } _autoPlayTimer ??= Timer.periodic( autoInterval, (_) => nextPage(manual: false), ); } void stopAutoPlay() { _autoPlayTimer?.cancel(); _autoPlayTimer = null; } void setHovering(bool value) { hovering = value; value ? stopAutoPlay() : syncAutoPlay(); if (mounted) setState(() {}); } void pauseForManual() { if (!_manualPaused) setState(() => _manualPaused = true); stopAutoPlay(); _resumeTimer?.cancel(); _resumeTimer = Timer(_manualPauseDuration, () { if (!mounted) return; setState(() => _manualPaused = false); syncAutoPlay(); }); } void onPageChanged(int index) { setState(() => currentIndex = index); emitActiveItem(); precacheAdjacentSlides(); if (_autoPageChangeInProgress) { _autoPageChangeInProgress = false; return; } pauseForManual(); } void goToPage(int index) { if (index < 0 || index >= itemCount) return; if (!pageController.hasClients) return; pageController.animateToPage( index, duration: slideDuration, curve: slideCurve, ); pauseForManual(); } void nextPage({bool manual = true}) { if (itemCount <= 1) return; if (!pageController.hasClients) return; final nextIndex = (currentIndex + 1) % itemCount; if (!manual) _autoPageChangeInProgress = true; pageController.animateToPage( nextIndex, duration: slideDuration, curve: slideCurve, ); if (manual) pauseForManual(); } void prevPage() { if (itemCount <= 1) return; if (!pageController.hasClients) return; final previousIndex = (currentIndex - 1 + itemCount) % itemCount; pageController.animateToPage( previousIndex, duration: slideDuration, curve: slideCurve, ); pauseForManual(); } int? bannerCacheWidth() { final width = lastBannerWidth; if (width == null || !width.isFinite || width <= 0) return null; return (width * MediaQuery.devicePixelRatioOf(context)).ceil(); } void resetCarouselItems() { stopAutoPlay(); _resumeTimer?.cancel(); _manualPaused = false; if (itemCount > 0 && pageController.hasClients) { pageController.jumpToPage(0); } currentIndex = 0; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; emitActiveItem(); precacheAdjacentSlides(); syncAutoPlay(); }); } void activateCarousel() { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; emitActiveItem(); precacheAdjacentSlides(); syncAutoPlay(); }); } void deactivateCarousel() { stopAutoPlay(); _resumeTimer?.cancel(); _manualPaused = false; } }