| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- import 'package:flutter/material.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'package:flutter_sticky_header/flutter_sticky_header.dart';
- import 'package:flutter_svg/flutter_svg.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 '../../../constants/assets.dart';
- import '../controllers/node_controller.dart';
- class NodeList extends StatefulWidget {
- final int tabIndex;
- const NodeList({super.key, required this.tabIndex});
- @override
- State<NodeList> createState() {
- return _NodeListState();
- }
- }
- class _NodeListState extends State<NodeList>
- with AutomaticKeepAliveClientMixin {
- @override
- bool get wantKeepAlive => true;
- // 获取 Controller
- NodeController get controller => Get.find<NodeController>();
- /// 获取国家的展开状态
- bool _getExpandedState(String countryCode) {
- return controller.getExpandedState(widget.tabIndex, countryCode);
- }
- /// 设置国家的展开状态
- void _setExpandedState(String countryCode, bool expanded) {
- setState(() {
- controller.setExpandedState(widget.tabIndex, countryCode, expanded);
- });
- }
- @override
- Widget build(BuildContext context) {
- super.build(context);
- final data = {
- 'Europe': [
- {
- 'title': 'United Kingdom',
- 'code': 'GB',
- 'cities': [
- 'London',
- 'Edinburgh',
- 'Cardiff',
- 'Liverpool',
- 'Manchester',
- ],
- },
- {
- 'title': 'Italy',
- 'code': 'IT',
- 'cities': ['Rome', 'Milan', 'Naples', 'Turin', 'Genoa'],
- },
- {
- 'title': 'Germany',
- 'code': 'DE',
- 'cities': ['Berlin', 'Hamburg', 'Munich', 'Frankfurt', 'Cologne'],
- },
- ],
- 'Asia': [
- {
- 'title': 'China',
- 'code': 'CN',
- 'cities': ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Chengdu'],
- },
- {
- 'title': 'Japan',
- 'code': 'JP',
- 'cities': ['Tokyo', 'Osaka', 'Nagoya', 'Sapporo', 'Fukuoka'],
- },
- {
- 'title': 'Korea',
- 'code': 'KR',
- 'cities': ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Gwangju'],
- },
- {
- 'title': 'India',
- 'code': 'IN',
- 'cities': ['Mumbai', 'Delhi', 'Bangalore', 'Chennai', 'Hyderabad'],
- },
- ],
- 'America': [
- {
- 'title': 'United States',
- 'code': 'US',
- 'cities': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Miami'],
- },
- {
- 'title': 'Canada',
- 'code': 'CA',
- 'cities': ['Toronto', 'Montreal', 'Vancouver', 'Calgary', 'Edmonton'],
- },
- {
- 'title': 'Mexico',
- 'code': 'MX',
- 'cities': [
- 'Mexico City',
- 'Guadalajara',
- 'Monterrey',
- 'Tijuana',
- 'Puebla',
- ],
- },
- {
- 'title': 'Brazil',
- 'code': 'BR',
- 'cities': [
- 'Sao Paulo',
- 'Rio de Janeiro',
- 'Brasilia',
- 'Salvador',
- 'Fortaleza',
- ],
- },
- ],
- };
- return CustomScrollView(
- slivers: [
- for (var region in data.entries)
- SliverStickyHeader(
- header: Container(
- color: Get.reactiveTheme.scaffoldBackgroundColor,
- padding: const EdgeInsets.all(16),
- child: Text(
- region.key,
- style: TextStyle(
- color: Get.reactiveTheme.hintColor,
- fontSize: 16,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- sliver: SliverList(
- delegate: SliverChildBuilderDelegate((context, i) {
- final country = region.value[i];
- final countryCode = country['code'] as String;
- final cities = country['cities'] as List<dynamic>;
- return _CountrySection(
- title: country['title'] as String,
- code: countryCode,
- cities: cities,
- // 传递展开状态
- expanded: _getExpandedState(countryCode),
- // 展开状态变化回调
- onExpandedChanged: (expanded) {
- _setExpandedState(countryCode, expanded);
- },
- );
- }, childCount: region.value.length),
- ),
- ),
- ],
- );
- }
- }
- class _CountrySection extends StatelessWidget {
- final String title;
- final String code;
- final List<dynamic> cities;
- final bool expanded;
- final ValueChanged<bool> onExpandedChanged;
- const _CountrySection({
- required this.title,
- required this.code,
- required this.cities,
- required this.expanded,
- required this.onExpandedChanged,
- });
- @override
- Widget build(BuildContext context) {
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- decoration: BoxDecoration(
- color: Get.reactiveTheme.highlightColor,
- borderRadius: BorderRadius.circular(12),
- ),
- child: Column(
- children: [
- ClickOpacity(
- onTap: () => onExpandedChanged(!expanded),
- child: Padding(
- padding: EdgeInsets.all(14.w),
- child: Row(
- children: [
- // 国旗图标
- ClipRRect(
- borderRadius: BorderRadius.circular(4.r), // 设置圆角
- child: SvgPicture.asset(
- Assets.getCountryFlagImage(code),
- width: 32.w,
- height: 24.w,
- fit: BoxFit.cover,
- // placeholderBuilder: (context) => Container(
- // width: 32.w,
- // height: 24.w,
- // decoration: BoxDecoration(
- // borderRadius: BorderRadius.circular(4.r),
- // color: Colors.grey[200],
- // ),
- // alignment: Alignment.center,
- // child: Icon(
- // Icons.flag,
- // size: 16.w,
- // color: Colors.grey[400],
- // ),
- // ),
- ),
- ),
- 10.horizontalSpace,
- Text(
- title,
- style: TextStyle(
- fontSize: 16.sp,
- height: 1.5,
- fontWeight: FontWeight.w500,
- color: Get.reactiveTheme.textTheme.bodyLarge!.color,
- ),
- ),
- const Spacer(),
- // 箭头图标
- AnimatedRotation(
- turns: expanded ? 0.25 : 0.0,
- duration: const Duration(milliseconds: 300),
- child: Icon(
- Icons.keyboard_arrow_right,
- 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: cities.map((city) {
- final cityName = city as String;
- return ClickOpacity(
- onTap: () {},
- 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: [
- ClipRRect(
- borderRadius: BorderRadius.circular(
- 4.r,
- ), // 设置圆角
- child: SvgPicture.asset(
- Assets.getCountryFlagImage(code),
- width: 32.w,
- height: 24.w,
- fit: BoxFit.cover,
- // placeholderBuilder: (context) => Container(
- // width: 32.w,
- // height: 24.w,
- // decoration: BoxDecoration(
- // borderRadius: BorderRadius.circular(4.r),
- // color: Colors.grey[200],
- // ),
- // alignment: Alignment.center,
- // child: Icon(
- // Icons.flag,
- // size: 16.w,
- // color: Colors.grey[400],
- // ),
- // ),
- ),
- ),
- 10.horizontalSpace,
- Text(
- cityName,
- style: TextStyle(
- fontSize: 14.sp,
- fontWeight: FontWeight.w500,
- color: Get.reactiveTheme.hintColor,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- );
- }).toList(),
- ),
- ),
- ),
- ),
- ],
- ),
- );
- }
- }
|