import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/common/route_aware_state.dart'; import 'package:qhd_prevention/common/route_service.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/pages/home/scan_page.dart'; import 'package:qhd_prevention/pages/home/unit/unit_tab_page.dart'; import 'package:qhd_prevention/pages/main_tab.dart'; import 'package:qhd_prevention/pages/user/firm_list_page.dart'; import 'package:qhd_prevention/tools/h_colors.dart'; import 'package:qhd_prevention/tools/tools.dart'; import 'package:shared_preferences/shared_preferences.dart'; class HomePage extends StatefulWidget { const HomePage({Key? key, required this.isChooseFirm}) : super(key: key); final bool isChooseFirm; @override HomePageState createState() => HomePageState(); } class HomePageState extends RouteAwareState with WidgetsBindingObserver, SingleTickerProviderStateMixin { final PageController _pageController = PageController(); final ScrollController _scrollController = ScrollController(); int _currentPage = 0; bool _isMobileSelected = true; // 切换按钮状态 void startScan() { Navigator.push( context, MaterialPageRoute(builder: (_) => ScanPage(type: ScanType.Onboarding)), ); } // 缓存 key static const String _hiddenCacheKey = 'hidden_roll_cache'; // 上面按钮显示状态 List _buttonVisibility = []; // 我的工作子项显示状态 List _workItemVisibility = []; List totalList = []; // 工作统计数据 Map workStats = {'total': 36, 'processed': 30, 'pending': 6}; // 检查清单数据 List> checkLists = [ { "title": "电工班车间清单", "type": "隐患排查", "time": "2025-11-17", "color": Color(0xFF4CAF50), }, { "title": "工地区消防点检清单", "type": "消防点检", "time": "2025-11-17", "color": Color(0xFF2196F3), }, { "title": "消防专项检查清单", "type": "安环检查", "time": "2025-11-17", "color": Color(0xFFFF9800), }, { "title": "二车间防护网", "type": "隐患处理", "time": "2025-11-17", "color": Color(0xFFF44336), }, { "title": "二车间防护网", "type": "隐患处理", "time": "2025-11-17", "color": Color(0xFFF44336), }, { "title": "二车间防护网", "type": "隐患处理", "time": "2025-11-17", "color": Color(0xFFF44336), }, { "title": "二车间防护网", "type": "隐患处理", "time": "2025-11-17", "color": Color(0xFFF44336), }, { "title": "二车间防护网", "type": "隐患处理", "time": "2025-11-17", "color": Color(0xFFF44336), }, ]; // 通知相关 final List _notifications = [ "系统通知:今晚20:00 将进行系统维护,请提前保存数据。", "安全提示:施工区请佩戴安全帽并系好安全带。", "公告:本周五集团例会在多功能厅召开,9:00准时开始。", "提醒:请尽快完成隐患整改清单中的待办项。", ]; int _notifIndex = 0; late final PageController _notifPageController; Timer? _notifTimer; List> buttonInfos = [ {"icon": "assets/images/ico1.png", "title": "单位管理", "unreadCount": 0}, {"icon": "assets/images/ico2.png", "title": "现场监管", "unreadCount": 0}, {"icon": "assets/images/ico3.png", "title": "危险作业", "unreadCount": 0}, {"icon": "assets/images/ico4.png", "title": "隐患处理", "unreadCount": 0}, {"icon": "assets/images/ico5.png", "title": "安环检查", "unreadCount": 0}, {"icon": "assets/images/ico6.png", "title": "口门门禁", "unreadCount": 0}, {"icon": "assets/images/ico7.png", "title": "入港培训", "unreadCount": 0}, ]; // 浮动 AppBar(滚动触发) bool _showFloatingAppBar = false; static const double _triggerOffset = 30.0; // 滚动触发距离 static const double _floatingBarHeight = 56.0; // 更新模块和按钮显示状态的方法 void _updateModuleAndButtonVisibility() { final routeService = RouteService(); final homeRoutes = routeService.mainTabs.isNotEmpty ? routeService.getRoutesForTab(routeService.mainTabs[0]) : []; setState(() { _buttonVisibility = List.filled(buttonInfos.length, false); // 根据路由标题匹配并设置显示状态(目前保留) for (final route in homeRoutes) { final routeTitle = route.title ?? ''; } }); } /// 隐患播报列表及状态 List> hiddenList = []; bool _initialLoadingHidden = true; bool _firstLoad = false; @override void initState() { super.initState(); // _getNeedSafetyCommitment(); _buttonVisibility = List.filled(buttonInfos.length, true); // 通知滚动 PageController _notifPageController = PageController(initialPage: 0); // 启动定时器:每 3 秒切换一条通知 _notifTimer = Timer.periodic(const Duration(seconds: 3), (timer) { if (!mounted) return; final next = (_notifIndex + 1) % _notifications.length; _notifIndex = next; // animateToPage 可能抛异常(在 dispose 中),所以包在 try/catch try { _notifPageController.animateToPage( next, duration: const Duration(milliseconds: 400), curve: Curves.easeInOut, ); } catch (_) {} setState(() {}); }); // 监听主列表滚动以控制浮动 AppBar 显示 _scrollController.addListener(_onScroll); WidgetsBinding.instance.addObserver(this); Future.delayed(const Duration(seconds: 1), () { _firstLoad = true; }); } void _onScroll() { final offset = _scrollController.hasClients ? _scrollController.offset : 0.0; final shouldShow = offset >= _triggerOffset; if (shouldShow != _showFloatingAppBar) { setState(() { _showFloatingAppBar = shouldShow; }); } } @override void dispose() { _pageController.dispose(); _notifTimer?.cancel(); _notifPageController.dispose(); _scrollController.removeListener(_onScroll); _scrollController.dispose(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Future onVisible() async { final current = CurrentTabNotifier.of(context)?.currentIndex ?? 0; const myIndex = 0; if (current != myIndex) { return; } if (_firstLoad) {} } void onRouteConfigLoaded() { if (mounted) { setState(() { _updateModuleAndButtonVisibility(); }); } } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { // App 回到前台时刷新数据 } } void _onBadgeUpdated() { if (mounted) { setState(() {}); } } /// 更新按钮角标 void _updateButtonBadges() { setState(() { // 可以在这里更新角标数据 }); } /// 从 SharedPreferences 读取缓存 Future _loadHiddenCache() async { try { final prefs = await SharedPreferences.getInstance(); final jsonStr = prefs.getString(_hiddenCacheKey); if (jsonStr != null && jsonStr.isNotEmpty) { final parsed = jsonDecode(jsonStr) as List; final list = parsed.map((e) => Map.from(e as Map)).toList(); setState(() { hiddenList = list; _initialLoadingHidden = false; }); } else { setState(() { _initialLoadingHidden = true; }); } } catch (e) { debugPrint('加载 hidden cache 失败: $e'); setState(() { _initialLoadingHidden = true; }); } } Future _onRefresh() async { // 刷新数据 await Future.delayed(const Duration(seconds: 1)); } @override Widget build(BuildContext context) { const double notificationHeight = 60.0; // 通知栏高度 double bannerHeight = 738 / 1125 * screenWidth(context); const double iconSectionHeight = 220.0; const double iconOverlapBanner = 90.0; // 图标区覆盖 banner 的高度 const double iconOverlapNotification = -10.0; // 图标区覆盖通知栏的高度 final double stackBottom = bannerHeight - iconOverlapBanner + iconSectionHeight + 60; final double statusBar = MediaQuery.of(context).padding.top; return PopScope( canPop: false, child: Scaffold( extendBodyBehindAppBar: true, body: Stack( children: [ RefreshIndicator( onRefresh: _onRefresh, child: ListView( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(0), children: [ SizedBox( height: stackBottom, child: Stack( clipBehavior: Clip.none, children: [ // Banner(顶部) Positioned( top: 0, left: 0, right: 0, height: bannerHeight, child: _buildBannerSection(bannerHeight), ), // 通知栏( Positioned( left: 10, right: 10, top: (bannerHeight - iconOverlapBanner) + iconSectionHeight - iconOverlapNotification, height: notificationHeight, child: _buildNotificationBar(notificationHeight - 2), ), // 图标区(覆盖 banner 底部 overlap) Positioned( left: 10, right: 10, top: bannerHeight - iconOverlapBanner, height: iconSectionHeight, child: _buildIconSection(context), ), ], ), ), if (widget.isChooseFirm) ...[ // 工作统计区域 Padding( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 20, ), child: _buildWorkStatsSection(), ), // 检查清单区域 Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: _buildCheckListSection(), ), const SizedBox(height: 20), ] ], ), ), // 浮动 AppBar(平滑出现/隐藏),放在 ListView 之上 Positioned( top: 0, left: 0, right: 0, child: AnimatedContainer( duration: const Duration(milliseconds: 0), height: _showFloatingAppBar ? (statusBar + _floatingBarHeight) : 0, decoration: BoxDecoration( color: _showFloatingAppBar ? h_AppBarColor() : Colors.transparent, boxShadow: _showFloatingAppBar ? [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 6, ), ] : [], ), child: SafeArea( bottom: false, child: Opacity( opacity: _showFloatingAppBar ? 1.0 : 0.0, child: SizedBox( height: _floatingBarHeight, child: const SizedBox() ), ), ), ), ), Positioned( top: statusBar + 14, width: screenWidth(context), child: Center(child: Text( '秦港-相关方安全管理', style: TextStyle( fontSize: 19, fontWeight: FontWeight.w600, color: Colors.white, ), ),) ), // 固定在最上层的图标(位于 AppBar 之上),保证它们不会随滚动移动 _buildFixedTopIcons(context), ], ), ), ); } // 固定在屏幕右上角的图标(不会随页面滚动) Widget _buildFixedTopIcons(BuildContext context) { final double statusBar = MediaQuery.of(context).padding.top; // 固定图标距离顶部的偏移(在 banner 内时可调小) final double topOffset = statusBar + 12; return Positioned( top: topOffset, right: 12, child: Row( children: [ GestureDetector( onTap: startScan, child: Container( width: 38, height: 38, alignment: Alignment.center, child: Image.asset( "assets/icon-apps/home_saoyisao.png", width: 22, height: 22, ), ), ), ], ), ); } Widget _buildNotificationBar(double notificationHeight) { return Material( color: Colors.transparent, child: Container( height: notificationHeight, decoration: BoxDecoration( color: const Color(0xFFE6F5FF), // 浅蓝 borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.white, width: 1), // 白色边框 boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // const SizedBox(height: 40), Row( children: [ const SizedBox(width: 12), // 左侧图标 Image.asset('assets/images/ico8.png', width: 30, height: 25), const SizedBox(width: 12), // 中间可滚动的文本区域(使用垂直 PageView) Expanded( child: SizedBox( height: notificationHeight, child: PageView.builder( controller: _notifPageController, scrollDirection: Axis.vertical, physics: const NeverScrollableScrollPhysics(), itemCount: _notifications.length, itemBuilder: (context, index) { final text = _notifications[index]; return Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(right: 8.0), child: Text( text, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.black87, fontSize: 14, ), ), ), ); }, ), ), ), // 右侧箭头 const Icon(Icons.chevron_right, color: Colors.black26), const SizedBox(width: 8), ], ), ], ), ), ); } // 构建顶部 Banner Widget _buildBannerSection(double bannerHeight) { return Stack( children: [ // 背景图片 Image.asset( "assets/images/banner.png", width: MediaQuery.of(context).size.width, height: bannerHeight, fit: BoxFit.cover, ), // 这里保留 banner 内的额外装饰(如果需要) ], ); } Widget _buildIconSection(BuildContext context) { // 判断是否有第二行 final hasSecondRow = buttonInfos.length > 4; return Container( padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 5), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)), ], ), child: widget.isChooseFirm ? Column( children: [ // 第一行(4 列等分) Row( children: List.generate(4, (i) { final idx = i; if (idx < buttonInfos.length) { return Expanded( child: Center(child: _buildIconButton(buttonInfos[idx], context)), ); } else { return const Expanded(child: SizedBox()); } }), ), if (hasSecondRow) const SizedBox(height: 20), // 第二行(仍然 4 列等分;不足的用占位填充) if (hasSecondRow) Row( children: List.generate(4, (i) { final idx = 4 + i; // 第二行从索引4开始 if (idx < buttonInfos.length) { return Expanded( child: Center(child: _buildIconButton(buttonInfos[idx], context)), ); } else { return const Expanded(child: SizedBox()); } }), ), ], ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset('assets/images/ico1.png', width: 100, height: 100), const SizedBox(height: 10), CustomButton( text: '点击入职企业', onPressed: () { pushPage(FirmListPage(), context); }, ) ], ), ), ); } // 构建单个图标按钮(保持原样) Widget _buildIconButton(Map info, BuildContext context) { return GestureDetector( onTap: () { _handleIconTap(info['title']); }, child: Column( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( color: const Color(0xFFE8F4FD), borderRadius: BorderRadius.circular(25), ), child: Center( child: Image.asset(info['icon'], width: 30, height: 30), ), ), const SizedBox(height: 6), SizedBox( width: 70, child: Text( info['title'], style: const TextStyle(fontSize: 12, color: Colors.black87), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ); } // 处理图标点击 void _handleIconTap(String title) { switch (title) { case "单位管理": pushPage(UnitTabPage(), context); break; case "现场监管": break; case "危险作业": break; case "隐患处理": break; case "安环检查": break; case "口门门禁": break; case "入港培训": break; default: break; } } // 构建工作统计区域 - 新版设计 Widget _buildWorkStatsSection() { double buttonHeight = 45.0; return Container( padding: const EdgeInsets.all(1), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 切换按钮 Container( width: screenWidth(context), height: buttonHeight, decoration: BoxDecoration( // color: Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // 手机端按钮 Expanded( child: GestureDetector( onTap: () { setState(() { _isMobileSelected = true; }); }, child: Container( decoration: BoxDecoration( color: _isMobileSelected ? const Color(0xFFE9F4FE) : Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), ), ), child: Center( child: Text( "手机端", style: TextStyle( fontSize: 14, color: _isMobileSelected ? Colors.black : Colors.blue, fontWeight: FontWeight.w500, ), ), ), ), ), ), Image.asset( 'assets/images/${_isMobileSelected ? 'img2.png' : 'img1.png'}', fit: BoxFit.cover, height: buttonHeight, width: 30, ), // 电脑端按钮 Expanded( child: GestureDetector( onTap: () { setState(() { _isMobileSelected = false; }); }, child: Container( decoration: BoxDecoration( color: !_isMobileSelected ? const Color(0xFFE9F4FE) : Colors.white, borderRadius: const BorderRadius.only( topRight: Radius.circular(12), ), ), child: Center( child: Text( "电脑端", style: TextStyle( fontSize: 15, color: !_isMobileSelected ? Colors.black : Colors.blue, fontWeight: FontWeight.w500, ), ), ), ), ), ), ], ), ), Padding( padding: EdgeInsets.all(15), child: Column( children: [ // 今日工作项 Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 0), child: RichText( text: TextSpan( children: [ TextSpan( text: "今日有工作项", style: TextStyle(fontSize: 16, color: Colors.black87), ), TextSpan( text: " ${workStats['total']}", style: const TextStyle( fontSize: 16, color: Color(0xFF2A75F8), fontWeight: FontWeight.bold, ), ), TextSpan( text: "个", style: TextStyle(fontSize: 16, color: Colors.black87), ), ], ), ), ), const SizedBox(height: 15), // 第三行:已处理和待处理工作项 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ RichText( text: TextSpan( children: [ TextSpan( text: "已处理工作项:", style: TextStyle( fontSize: 14, color: Colors.black87, ), ), TextSpan( text: " ${workStats['processed']}", style: TextStyle( fontSize: 14, color: Colors.greenAccent, fontWeight: FontWeight.bold, ), ), TextSpan( text: "个", style: TextStyle( fontSize: 14, color: Colors.black87, ), ), ], ), ), RichText( text: TextSpan( children: [ TextSpan( text: "待处理工作项:", style: TextStyle( fontSize: 14, color: Colors.black87, ), ), TextSpan( text: " ${workStats['processed']}", style: TextStyle( fontSize: 14, color: Colors.orange, fontWeight: FontWeight.bold, ), ), TextSpan( text: "个", style: TextStyle( fontSize: 14, color: Colors.black87, ), ), ], ), ), ], ), ], ), ), ], ), ); } // 构建检查清单区域 Widget _buildCheckListSection() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( ' 待办清单', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(), ], ), SizedBox(height: 8), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 6, offset: Offset(0, 2), ), ], ), child: Column( children: [...checkLists.map((item) => _buildCheckListItem(item))], ), ), ], ); } // 构建检查清单项 Widget _buildCheckListItem(Map item) { return Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.grey[200]!, width: 1)), ), child: Row( children: [ // 内容区域 - 使用Expanded确保不溢出 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 标题使用Flexible防止溢出 Flexible( child: Text( item['title'], style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black87, ), overflow: TextOverflow.ellipsis, // 添加省略号 maxLines: 1, // 限制为1行 ), ), const SizedBox(width: 8), // 添加间距 Text( item['type'], style: TextStyle(fontSize: 12, color: Colors.grey[500]), ), ], ), const SizedBox(height: 8), // 替代Divider的间距 // 底部信息行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 类型标签使用Flexible Flexible( child: Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(4), ), child: Text( "类型:${item['type']}", style: const TextStyle( fontSize: 12, color: Colors.blue, ), overflow: TextOverflow.ellipsis, ), ), ), const SizedBox(width: 8), // 添加间距 // 时间使用Text Flexible( child: Text( "时间:${item['time']}", style: const TextStyle( fontSize: 12, color: Colors.grey, ), overflow: TextOverflow.ellipsis, ), ), ], ), ], ), ), ], ), ); } }