import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../data/models/node/service_node.dart'; import '../../../providers/node_provider.dart'; import '../../widgets/common/app_refresh_indicator.dart'; class ServiceRouteScreen extends ConsumerStatefulWidget { const ServiceRouteScreen({super.key}); @override ConsumerState createState() => _ServiceRouteScreenState(); } class _ServiceRouteScreenState extends ConsumerState { @override void initState() { super.initState(); // 进入页面时自动测速 Future.microtask(() => ref.read(nodeProvider.notifier).speedTestAll()); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; final state = ref.watch(nodeProvider); return Scaffold( appBar: AppBar( elevation: 0, leading: IconButton( icon: const Icon(Icons.chevron_left, size: 28), onPressed: () => context.pop(), ), title: Text( AppLocalizations.of(context)!.serviceRoute, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), centerTitle: true, ), body: state.isLoading && state.nodes.isEmpty ? const Center(child: CircularProgressIndicator()) : state.nodes.isEmpty ? _EmptyView( onRetry: () => ref.read(nodeProvider.notifier).fetchNodeList(), ) : AppRefreshIndicator( onRefresh: () async { await ref.read(nodeProvider.notifier).fetchNodeList(); await ref.read(nodeProvider.notifier).speedTestAll(); }, child: ListView( children: [ const SizedBox(height: 16), Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ for (int i = 0; i < state.nodes.length; i++) ...[ _NodeItem( node: state.nodes[i], latency: state.latencyMap[state.nodes[i].id], isSelected: state.currentNode?.id == state.nodes[i].id, isTesting: state.isSpeedTesting, isFirst: i == 0, isLast: i == state.nodes.length - 1, onTap: () => ref .read(nodeProvider.notifier) .selectNode(state.nodes[i]), ), if (i < state.nodes.length - 1) Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40), ), ], ], ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Text( AppLocalizations.of(context)!.serviceRouteHint, style: TextStyle( color: cs.onSurface.withAlpha(102), fontSize: 12, ), ), ), const SizedBox(height: 40), ], ), ), ); } } // ── 节点行 ────────────────────────────────────────────────── class _NodeItem extends StatelessWidget { const _NodeItem({ required this.node, required this.latency, required this.isSelected, required this.isTesting, required this.isFirst, required this.isLast, required this.onTap, }); final ServiceNode node; final int? latency; final bool isSelected; final bool isTesting; final bool isFirst; final bool isLast; final VoidCallback onTap; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return InkWell( onTap: onTap, borderRadius: BorderRadius.vertical( top: isFirst ? const Radius.circular(12) : Radius.zero, bottom: isLast ? const Radius.circular(12) : Radius.zero, ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Row( children: [ // 线路名称 Expanded( child: Text( node.name, style: TextStyle( color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w500, ), ), ), // 信号图标 _SignalBars(latency: latency), const SizedBox(width: 12), // 延迟数值 SizedBox( width: 60, child: isTesting && latency == null ? SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: cs.onSurface.withAlpha(102), ), ) : Text( latency != null ? '${latency}ms' : '--', style: TextStyle( color: _latencyColor(latency), fontSize: 14, fontWeight: FontWeight.w500, ), ), ), // 选中标记 SizedBox( width: 24, child: isSelected ? const Icon(Icons.check, color: AppColors.rise, size: 20) : null, ), ], ), ), ); } } // ── 信号柱状图标 ──────────────────────────────────────────── class _SignalBars extends StatelessWidget { const _SignalBars({required this.latency}); final int? latency; @override Widget build(BuildContext context) { final level = _signalLevel(latency); final color = _latencyColor(latency); return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ _bar(6, level >= 1 ? color : color.withAlpha(60)), const SizedBox(width: 2), _bar(10, level >= 2 ? color : color.withAlpha(60)), const SizedBox(width: 2), _bar(14, level >= 3 ? color : color.withAlpha(60)), const SizedBox(width: 2), _bar(18, level >= 4 ? color : color.withAlpha(60)), ], ); } Widget _bar(double height, Color color) { return Container( width: 4, height: height, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(1), ), ); } } // ── 空状态 ────────────────────────────────────────────────── class _EmptyView extends StatelessWidget { const _EmptyView({required this.onRetry}); final VoidCallback onRetry; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.cloud_off, size: 48, color: cs.onSurface.withAlpha(80)), const SizedBox(height: 12), Text( AppLocalizations.of(context)!.noAvailableRoute, style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 14), ), const SizedBox(height: 16), ElevatedButton( onPressed: onRetry, child: Text(AppLocalizations.of(context)!.retry), ), ], ), ); } } // ── 工具函数 ──────────────────────────────────────────────── /// 信号等级:4=优秀 3=良好 2=一般 1=差 0=不可达 int _signalLevel(int? latency) { if (latency == null) return 0; if (latency < 150) return 4; if (latency < 300) return 3; if (latency < 500) return 2; return 1; } Color _latencyColor(int? latency) { if (latency == null) return AppColors.lightTextDisabled; if (latency < 200) return AppColors.rise; if (latency < 400) return const Color(0xFFf0b90b); return AppColors.fall; }