| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- 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<ServiceRouteScreen> createState() =>
- _ServiceRouteScreenState();
- }
- class _ServiceRouteScreenState extends ConsumerState<ServiceRouteScreen> {
- @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;
- }
|