// account_providers.dart — 账户域 Riverpod 装配。 // // 此处只做依赖装配:把 authProvider 的令牌/刷新接到统一 ApiClient, // 再产出 AccountApi。具体数据 provider(meProvider/devicesProvider/usage…) // 在后续阶段加入本文件。 import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/device.dart'; import '../models/device_usage.dart'; import '../models/me.dart'; import '../models/plan.dart'; import '../models/usage_point.dart'; import '../services/account_api.dart'; import '../services/api_client.dart'; import '../services/api_config.dart'; import 'auth_provider.dart'; /// 统一鉴权 HTTP 客户端:令牌取自 authProvider,401 时触发 AuthNotifier.refresh。 final apiClientProvider = Provider((ref) { return ApiClient( baseUrl: kApiBaseUrl, getToken: () => ref.read(authProvider).accessToken, refresh: () => ref.read(authProvider.notifier).refresh(), ); }); /// 账户域端点封装。 final accountApiProvider = Provider( (ref) => AccountApi(ref.watch(apiClientProvider)), ); // ── 数据 provider ─────────────────────────────────────────────────────────── /// 当前账户聚合视图(GET /v1/me)。登录态变化自动重取;未登录返回默认免费档。 class MeNotifier extends AsyncNotifier { @override Future build() async { final auth = ref.watch(authProvider); if (!auth.isLoggedIn) return const Me(email: '', plan: 'free'); return ref.read(accountApiProvider).me(); } /// 手动刷新(如兑换后 / 下拉刷新)。会短暂进 loading 态(触发整页转圈)。 Future refresh() async { state = const AsyncLoading(); state = await AsyncValue.guard(() => ref.read(accountApiProvider).me()); } /// 静默刷新:不进 loading 态、成功才替换。供连接期/断开后刷新「今日剩余」用, /// 避免 refresh() 的 AsyncLoading 让连接页整页闪 spinner(meLoadingProvider)。 Future silentRefresh() async { if (!ref.read(authProvider).isLoggedIn) return; final next = await AsyncValue.guard(() => ref.read(accountApiProvider).me()); if (next is AsyncData) state = next; } } final meProvider = AsyncNotifierProvider(MeNotifier.new); /// 套餐定义(GET /v1/plans)。未登录返回空。 final plansProvider = FutureProvider>((ref) async { if (!ref.watch(authProvider).isLoggedIn) return const []; return ref.read(accountApiProvider).plans(); }); /// 最近 N 天用量(GET /v1/usage?days=N)。未登录返回空。 /// autoDispose:离开统计页即销毁,重进自动重取(配合下拉刷新/连接态变化 invalidate), /// 根治「本月流量取一次就冻住」。后端每 60s 才更新,无需更勤刷新。 /// 用量曲线 key:days + 可选设备 uuid(null = 全部/账户级)。 typedef UsageKey = ({int days, String? device}); final usageProvider = FutureProvider.autoDispose.family, UsageKey>((ref, key) async { if (!ref.watch(authProvider).isLoggedIn) return const []; return ref.read(accountApiProvider).usage(days: key.days, device: key.device); }); /// 最近 N 天按设备用量(GET /v1/usage/devices?days=N)。统计页设备下拉/明细用。 /// 未登录返回空。autoDispose 同 usageProvider。 final deviceUsageProvider = FutureProvider.autoDispose.family, int>((ref, days) async { if (!ref.watch(authProvider).isLoggedIn) return const []; return ref.read(accountApiProvider).deviceUsage(days: days); }); /// 已登录设备列表(GET /v1/me/devices)。 class DevicesNotifier extends AsyncNotifier> { @override Future> build() async { if (!ref.watch(authProvider).isLoggedIn) return const []; return ref.read(accountApiProvider).devices(); } /// 清除登录信息(DELETE:移除设备 + 吊销会话/凭证)后刷新列表。 Future remove(String uuid) async { await ref.read(accountApiProvider).removeDevice(uuid); ref.invalidateSelf(); await future; } /// 静默刷新(不闪 loading):重新拉列表、成功才替换。供设备页 ~15s 轮询,让其他设备的 /// 在线/状态变化 ~15s 内可见(配合服务端会话轮询刷 last_seen)。 Future refresh() async { if (!ref.read(authProvider).isLoggedIn) return; try { final list = await ref.read(accountApiProvider).devices(); state = AsyncData(list); } catch (_) { // 失败保留旧列表,不闪错。 } } /// 强制退出(吊销该设备会话,设备保留)后刷新列表。 Future forceLogout(String uuid) async { await ref.read(accountApiProvider).forceLogout(uuid); ref.invalidateSelf(); await future; } /// 重命名设备后刷新列表。 Future rename(String uuid, String name) async { await ref.read(accountApiProvider).renameDevice(uuid, name); ref.invalidateSelf(); await future; } } final devicesProvider = AsyncNotifierProvider>(DevicesNotifier.new);