// nodes_provider.dart — 节点清单 + 当前选择 // // 从 GET /v1/nodes 拉取节点列表;认证后自动刷新。 // 未登录 / 加载中时退回演示节点(kDemoNodes)保证 UI 正常显示。 import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import '../models/node.dart'; import 'auth_provider.dart'; // ── API base URL(由 --dart-define 注入)────────────────────────── const _kApiUrl = String.fromEnvironment( 'PANGOLIN_API_URL', defaultValue: 'http://localhost:8080', ); // ── 节点列表 AsyncNotifier ──────────────────────────────────────── class NodesNotifier extends AsyncNotifier> { @override Future> build() async { final auth = ref.watch(authProvider); if (!auth.isLoggedIn) return kDemoNodes; return _fetchNodes(auth.accessToken!); } Future refresh() async { state = const AsyncLoading(); final auth = ref.read(authProvider); if (!auth.isLoggedIn) { state = AsyncData(kDemoNodes); return; } state = await AsyncValue.guard(() => _fetchNodes(auth.accessToken!)); } static Future> _fetchNodes(String accessToken) async { final uri = Uri.parse('$_kApiUrl/v1/nodes'); final resp = await http.get(uri, headers: { 'Authorization': 'Bearer $accessToken', }).timeout(const Duration(seconds: 10)); if (resp.statusCode != 200) return kDemoNodes; final body = jsonDecode(resp.body) as Map; final rawList = body['nodes'] as List? ?? []; return rawList.map((e) { final m = e as Map; return Node( uuid: m['id'] as String? ?? '', code: m['region'] as String? ?? '??', nameZh: m['name_zh'] as String? ?? '', nameEn: m['name_en'] as String? ?? '', tier: m['tier'] as String? ?? 'free', ping: 0, // 延迟由探针数据填充;MVP 默认 0 ); }).toList(); } } final nodesProvider = AsyncNotifierProvider>(NodesNotifier.new); // ── 当前选中的节点 UUID;'AUTO' 表示智能选择(默认)───────────────── final selectedNodeCodeProvider = StateProvider((ref) => kSmartNodeCode); /// 是否处于智能选择。 final isSmartSelectProvider = Provider( (ref) => ref.watch(selectedNodeCodeProvider) == kSmartNodeCode, ); /// 实际生效的节点:同步拉取 AsyncValue;未就绪时取 kDemoNodes 第一条。 final effectiveNodeProvider = Provider((ref) { final nodesAsync = ref.watch(nodesProvider); final nodes = nodesAsync.valueOrNull ?? kDemoNodes; if (nodes.isEmpty) return kDemoNodes.first; final code = ref.watch(selectedNodeCodeProvider); if (code == kSmartNodeCode) { // 延迟最小者;MVP 无真实延迟时取第一条 return nodes.reduce((a, b) => a.ping <= b.ping ? a : b); } return nodes.firstWhere((n) => n.code == code, orElse: () => nodes.first); });