Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,209 @@
import 'dart:async';
import 'package:flutter/material.dart';
mixin BannerCarouselMixin<T extends StatefulWidget>
on State<T>, 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;
}
}