import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:get/get.dart'; import 'package:nomo/app/widgets/click_opacity.dart'; import 'package:nomo/config/theme/theme_extensions/theme_extension.dart'; import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart'; import '../../../../config/translations/strings_enum.dart'; import '../../../constants/iconfont/iconfont.dart'; import '../../../controllers/api_controller.dart'; import '../../../data/models/launch/groups.dart'; import '../../../widgets/country_icon.dart'; import '../../home/controllers/home_controller.dart'; import '../controllers/node_controller.dart'; class NodeList extends StatefulWidget { final int tabIndex; const NodeList({super.key, required this.tabIndex}); @override State createState() { return _NodeListState(); } } class _NodeListState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; // 获取 Controller final controller = Get.find(); final apiController = Get.find(); /// 获取国家的展开状态 bool _getExpandedState(String countryCode) { return controller.getExpandedState(widget.tabIndex, countryCode); } /// 设置国家的展开状态 void _setExpandedState(String countryCode, bool expanded) { setState(() { controller.setExpandedState(widget.tabIndex, countryCode, expanded); }); } final RefreshController _refreshController = RefreshController( initialRefresh: false, ); void _onRefresh() async { try { final groups = await apiController.getLocations(); controller.updateGroups(groups); _refreshController.refreshCompleted(); } catch (e) { _refreshController.refreshFailed(); } } @override Widget build(BuildContext context) { super.build(context); // 获取当前 tab 的数据 final data = controller.getDataByTabIndex(widget.tabIndex); if (data == null || data.tags == null || data.list == null) { return Center( child: Text( Strings.noData.tr, style: TextStyle(fontSize: 16.sp, color: Get.reactiveTheme.hintColor), ), ); } // 按 tag 分组数据 final groupedData = >{}; for (var location in data.list!) { if (location.tag != null) { groupedData.putIfAbsent(location.tag!, () => []).add(location); } } // 创建 tag 名称映射 final tagNameMap = {}; for (var tag in data.tags!) { if (tag.id != null && tag.name != null) { tagNameMap[tag.id!] = tag.name!; } } // 按 sort 排序 tags final sortedTags = data.tags!.toList() ..sort((a, b) => (a.sort ?? 0).compareTo(b.sort ?? 0)); return SmartRefresher( enablePullDown: true, enablePullUp: false, controller: _refreshController, onRefresh: _onRefresh, child: CustomScrollView( slivers: [ for (var tag in sortedTags) if (groupedData.containsKey(tag.id) && groupedData[tag.id]!.isNotEmpty) SliverStickyHeader( header: Container( color: Get.reactiveTheme.scaffoldBackgroundColor, padding: const EdgeInsets.all(16), child: Text( tag.name ?? '', style: TextStyle( color: Get.reactiveTheme.hintColor, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, i) { final locationList = groupedData[tag.id]![i]; final countryCode = locationList.icon ?? ''; return _CountrySection( locationList: locationList, // 传递展开状态 expanded: _getExpandedState(countryCode), // 展开状态变化回调 onExpandedChanged: (expanded) { _setExpandedState(countryCode, expanded); }, ); }, childCount: groupedData[tag.id]!.length), ), ), SliverSafeArea(sliver: SliverToBoxAdapter(child: 0.verticalSpace)), ], ), ); } } class _CountrySection extends StatelessWidget { final LocationList locationList; final bool expanded; final ValueChanged onExpandedChanged; const _CountrySection({ required this.locationList, required this.expanded, required this.onExpandedChanged, }); @override Widget build(BuildContext context) { final countryIcon = locationList.icon ?? ''; final countryName = locationList.name ?? ''; final locations = locationList.locations ?? []; // 获取 HomeController 并判断当前国家是否有选中的节点 final homeController = Get.find(); final hasSelectedLocation = locations.any( (loc) => loc.id == homeController.selectedLocation?.id, ); // 根据展开状态和是否选中设置背景色 final backgroundColor = hasSelectedLocation ? (expanded ? Get.reactiveTheme.cardColor : Get.reactiveTheme.highlightColor) : Get.reactiveTheme.highlightColor; return AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(12.r), ), child: Column( children: [ ClickOpacity( onTap: () => onExpandedChanged(!expanded), child: Padding( padding: EdgeInsets.all(14.w), child: Row( children: [ // 国旗图标 CountryIcon( countryCode: countryIcon, width: 32.w, height: 24.w, borderRadius: 4.r, ), 10.horizontalSpace, Text( countryName, style: TextStyle( fontSize: 16.sp, height: 1.5, fontWeight: FontWeight.w500, color: hasSelectedLocation ? Get.reactiveTheme.primaryColor : Get.reactiveTheme.textTheme.bodyLarge!.color, ), ), const Spacer(), // 箭头图标 AnimatedRotation( turns: expanded ? 0.25 : 0.0, duration: const Duration(milliseconds: 300), child: Icon( IconFont.icon02, size: 20.w, color: Get.reactiveTheme.hintColor, ), ), ], ), ), ), ClipRect( child: AnimatedAlign( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, heightFactor: expanded ? 1.0 : 0.0, alignment: Alignment.topLeft, child: AnimatedOpacity( opacity: expanded ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), child: Column( children: locations.map((location) { final locationName = location.name ?? ''; final locationIcon = location.icon ?? countryIcon; // 判断当前节点是否被选中 final isSelected = location.id == homeController.selectedLocation?.id; return ClickOpacity( onTap: () { // 获取 HomeController final homeController = Get.find(); homeController.selectLocation(location); homeController.handleConnect(delay: true); // 返回上一页 Get.back(); }, child: Column( children: [ Divider( height: 1, color: Get.reactiveTheme.dividerColor, ), Container( alignment: Alignment.centerLeft, margin: EdgeInsets.only( top: 14.w, bottom: 14.w, left: 24.w, right: 14.w, ), child: Row( children: [ CountryIcon( countryCode: locationIcon, width: 32.w, height: 24.w, borderRadius: 4.r, ), 10.horizontalSpace, Expanded( child: Text( locationName, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, color: isSelected ? Get.reactiveTheme.primaryColor : Get .reactiveTheme .textTheme .bodyLarge! .color, ), ), ), // 显示延迟 if (isSelected) Icon( IconFont.icon27, size: 16.w, color: Get.reactiveTheme.primaryColor, ), ], ), ), ], ), ); }).toList(), ), ), ), ), ], ), ); } }