|
|
@@ -6,10 +6,15 @@ import 'package:flutter/services.dart';
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
import 'package:news_app/widget/my_txt.dart';
|
|
|
+import 'package:flutter_swiper_view/flutter_swiper_view.dart';
|
|
|
|
|
|
+import '../../constant/api_const.dart';
|
|
|
import '../../constant/color_res.dart';
|
|
|
import '../../gen/assets.gen.dart';
|
|
|
+import '../../http/http_util.dart';
|
|
|
+import '../../util/log.util.dart';
|
|
|
import '../../util/shared_prefs_instance_util.dart';
|
|
|
+import '../../widget/load_image.dart';
|
|
|
|
|
|
/// @author: bo.zeng
|
|
|
/// @email: cnhbwds@gmail.com
|
|
|
@@ -22,15 +27,99 @@ class SplashPage extends StatefulWidget {
|
|
|
State<SplashPage> createState() => _SplashPageState();
|
|
|
}
|
|
|
|
|
|
-class _SplashPageState extends State<SplashPage> {
|
|
|
+class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateMixin {
|
|
|
TapGestureRecognizer? aa;
|
|
|
+ List<String> _splashImages = [];
|
|
|
+ int _currentIndex = 0;
|
|
|
+ late Timer _sliderTimer;
|
|
|
+ Timer? _navigateTimer;
|
|
|
+ bool _hasStartedNavigation = false;
|
|
|
+
|
|
|
+ // 本地默认开屏图片(当网络不可用时使用)
|
|
|
+ static const List<String> _defaultSplashImages = [
|
|
|
+ 'assets/images/splash_bg.png', // 本地默认图片
|
|
|
+ ];
|
|
|
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
+ // 初始化使用本地默认图片
|
|
|
+ _splashImages = _defaultSplashImages;
|
|
|
+ // 启动轮播
|
|
|
+ _startSlider();
|
|
|
+ // 从后台获取开屏页配置
|
|
|
+ _fetchSplashConfig();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 开始轮播
|
|
|
+ void _startSlider() {
|
|
|
+ _sliderTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
|
|
|
+ if (mounted && _splashImages.isNotEmpty) {
|
|
|
+ setState(() {
|
|
|
+ _currentIndex = (_currentIndex + 1) % _splashImages.length;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 从后台获取开屏页配置
|
|
|
+ Future<void> _fetchSplashConfig() async {
|
|
|
+ try {
|
|
|
+ final response = await HttpUtil().get(apiSplashConfig);
|
|
|
+ if (response != null && response['code'] == 200) {
|
|
|
+ final data = response['data'];
|
|
|
+ if (data != null) {
|
|
|
+ // 支持多种数据格式
|
|
|
+ List<String> fetchedImages = [];
|
|
|
+
|
|
|
+ // 格式1: imageUrl 为单个图片
|
|
|
+ if (data['imageUrl'] != null && data['imageUrl'].toString().isNotEmpty) {
|
|
|
+ fetchedImages.add(data['imageUrl']);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式2: images 为图片数组
|
|
|
+ if (data is List) {
|
|
|
+ for (var img in data) {
|
|
|
+ if (img.toString().isNotEmpty) {
|
|
|
+ fetchedImages.add(img['url']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 延迟 3 秒执行跳转逻辑
|
|
|
- Future.delayed(const Duration(seconds: 3), () async {
|
|
|
+ // 格式3: imageList 为图片数组
|
|
|
+ if (data['imageList'] is List) {
|
|
|
+ final images = data['imageList'] as List;
|
|
|
+ for (var img in images) {
|
|
|
+ if (img.toString().isNotEmpty) {
|
|
|
+ fetchedImages.add(img.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果获取到了图片,更新显示
|
|
|
+ if (fetchedImages.isNotEmpty && mounted) {
|
|
|
+ setState(() {
|
|
|
+ _splashImages = fetchedImages;
|
|
|
+ _currentIndex = 0;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ // 请求失败或无网络权限,继续使用本地默认图片,不做任何处理
|
|
|
+ consoleLog('Splash fetch error (will use default): $e');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 延迟 3-5 秒后执行跳转逻辑
|
|
|
+ _startNavigationTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 启动导航计时器
|
|
|
+ void _startNavigationTimer() {
|
|
|
+ if (_hasStartedNavigation) return;
|
|
|
+ _hasStartedNavigation = true;
|
|
|
+
|
|
|
+ _navigateTimer = Timer(const Duration(seconds: 4), () async {
|
|
|
bool? first = await getIsFirst();
|
|
|
// 确保页面未被销毁
|
|
|
if (!mounted) return;
|
|
|
@@ -44,6 +133,8 @@ class _SplashPageState extends State<SplashPage> {
|
|
|
|
|
|
@override
|
|
|
void dispose() {
|
|
|
+ _sliderTimer.cancel();
|
|
|
+ _navigateTimer?.cancel();
|
|
|
aa?.dispose();
|
|
|
super.dispose();
|
|
|
}
|
|
|
@@ -153,20 +244,124 @@ class _SplashPageState extends State<SplashPage> {
|
|
|
Widget build(BuildContext context) {
|
|
|
double w = MediaQuery.of(context).size.width;
|
|
|
double h = MediaQuery.of(context).size.height;
|
|
|
+
|
|
|
return Scaffold(
|
|
|
- body: Center(
|
|
|
+ body: SizedBox(
|
|
|
+ width: w,
|
|
|
+ height: h,
|
|
|
child: Stack(
|
|
|
- alignment: Alignment.center,
|
|
|
children: [
|
|
|
- Image.asset(
|
|
|
- Assets.images.splashBg.path,
|
|
|
- fit: BoxFit.cover,
|
|
|
- width: w,
|
|
|
- height: h,
|
|
|
+ // 轮播图片
|
|
|
+ if (_splashImages.length == 1)
|
|
|
+ // 只有一张图片时直接显示
|
|
|
+ _buildSingleImage()
|
|
|
+ else
|
|
|
+ // 多张图片时使用轮播
|
|
|
+ _buildCarousel(w, h),
|
|
|
+
|
|
|
+ // 指示器(多张图片时显示)
|
|
|
+ if (_splashImages.length > 1)
|
|
|
+ Positioned(
|
|
|
+ bottom: 50.h,
|
|
|
+ left: 0,
|
|
|
+ right: 0,
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ children: List.generate(
|
|
|
+ _splashImages.length,
|
|
|
+ (index) => AnimatedContainer(
|
|
|
+ duration: const Duration(milliseconds: 300),
|
|
|
+ margin: EdgeInsets.symmetric(horizontal: 4.w),
|
|
|
+ width: _currentIndex == index ? 20.w : 8.w,
|
|
|
+ height: 8.h,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: _currentIndex == index
|
|
|
+ ? Colors.white
|
|
|
+ : Colors.white.withOpacity(0.5),
|
|
|
+ borderRadius: BorderRadius.circular(4.r),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+
|
|
|
+ // 跳过按钮
|
|
|
+ Positioned(
|
|
|
+ top: 54.h,
|
|
|
+ right: 20.w,
|
|
|
+ child: GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ _navigateTimer?.cancel();
|
|
|
+ _navigateToMain();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: Colors.black.withOpacity(0.3),
|
|
|
+ borderRadius: BorderRadius.circular(20.r),
|
|
|
+ ),
|
|
|
+ child: myTxt(
|
|
|
+ text: "跳过",
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: 14.sp,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ /// 构建单张图片
|
|
|
+ Widget _buildSingleImage() {
|
|
|
+ final imageUrl = _splashImages.first;
|
|
|
+ if (imageUrl.startsWith('http') || imageUrl.startsWith('https')) {
|
|
|
+ return LoadImage(imageUrl, fit: BoxFit.cover);
|
|
|
+ } else {
|
|
|
+ return Image.asset(imageUrl.startsWith('assets') ? imageUrl : 'assets/$imageUrl', fit: BoxFit.cover);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 构建轮播
|
|
|
+ Widget _buildCarousel(double w, double h) {
|
|
|
+ return Swiper(
|
|
|
+ itemCount: _splashImages.length,
|
|
|
+ index: _currentIndex,
|
|
|
+ onIndexChanged: (index) {
|
|
|
+ if (mounted) {
|
|
|
+ setState(() {
|
|
|
+ _currentIndex = index;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ itemBuilder: (BuildContext context, int index) {
|
|
|
+ final imageUrl = _splashImages[index];
|
|
|
+ if (imageUrl.startsWith('http') || imageUrl.startsWith('https')) {
|
|
|
+ return LoadImage(imageUrl, fit: BoxFit.cover);
|
|
|
+ } else {
|
|
|
+ return Image.asset(
|
|
|
+ imageUrl.startsWith('assets') ? imageUrl : 'assets/$imageUrl',
|
|
|
+ fit: BoxFit.cover,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ autoplay: false, // 使用自定义定时器控制
|
|
|
+ loop: true,
|
|
|
+ viewportFraction: 1.0,
|
|
|
+ scale: 1.0,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 导航到主页
|
|
|
+ void _navigateToMain() async {
|
|
|
+ bool? first = await getIsFirst();
|
|
|
+ if (!mounted) return;
|
|
|
+ if (first == true) {
|
|
|
+ context.go('/main');
|
|
|
+ } else {
|
|
|
+ _showFirstDialog(context);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|