| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import '../../presentation/screens/home/home_screen.dart';
- import '../../presentation/screens/market/futures_market_detail_screen.dart';
- import '../../presentation/screens/market/spot_market_detail_screen.dart';
- import '../../presentation/screens/market/market_screen.dart';
- import '../../presentation/screens/copy_trading/copy_trading_screen.dart';
- import '../../presentation/screens/copy_trading/trader_apply_screen.dart';
- import '../../presentation/screens/copy_trading/my_trades_screen.dart';
- import '../../presentation/screens/copy_trading/trader_settings_screen.dart';
- import '../../presentation/screens/copy_trading/my_copy_trading_screen.dart';
- import '../../presentation/screens/copy_trading/trader_detail_screen.dart';
- import '../../presentation/screens/copy_trading/follow_setting_screen.dart';
- import '../../presentation/screens/placeholder_screen.dart';
- import '../../presentation/screens/network_error_screen.dart';
- import '../../presentation/screens/asset/asset_screen.dart';
- import '../../presentation/screens/asset/deposit_screen.dart';
- import '../../presentation/screens/asset/deposit_history_screen.dart';
- import '../../presentation/screens/asset/withdraw_screen.dart';
- import '../../presentation/screens/asset/withdraw_history_screen.dart';
- import '../../presentation/screens/asset/withdraw_detail_screen.dart';
- import '../../presentation/screens/asset/deposit_detail_screen.dart';
- import '../../presentation/screens/asset/transfer_screen.dart';
- import '../../presentation/screens/asset/transfer_history_screen.dart';
- import '../../presentation/screens/asset/asset_history_screen.dart';
- import '../../data/models/asset/withdraw_record.dart';
- import '../../data/models/asset/recharge_record.dart';
- import '../../presentation/screens/futures/futures_screen.dart';
- import '../../presentation/screens/futures/futures_history_screen.dart';
- import '../../presentation/screens/futures/position_detail_screen.dart';
- import '../../presentation/screens/futures/order_detail_screen.dart';
- import '../../presentation/screens/spot/spot_screen.dart';
- import '../../presentation/screens/spot/spot_history_screen.dart';
- import '../../presentation/screens/spot/spot_account_records_screen.dart';
- import '../../providers/futures_provider.dart'
- show FuturesPosition, FuturesOrder, futuresProvider;
- import '../../presentation/screens/auth/login_screen.dart';
- import '../../presentation/screens/auth/register_screen.dart';
- import '../../presentation/screens/auth/forgot_password_screen.dart';
- import '../../presentation/screens/auth/two_factor_screen.dart';
- import '../../presentation/screens/auth/verify_code_screen.dart';
- import '../../presentation/screens/user/change_password_screen.dart';
- import '../../presentation/screens/user/fund_password_screen.dart';
- import '../../presentation/screens/user/google_auth_screen.dart';
- import '../../presentation/screens/user/google_auth_bind_screen.dart';
- import '../../presentation/screens/user/notification_detail_screen.dart';
- import '../../presentation/screens/user/notifications_screen.dart';
- import '../../presentation/screens/user/profile_screen.dart';
- import '../../presentation/screens/user/security_screen.dart';
- import '../../presentation/screens/user/service_route_screen.dart';
- import '../../presentation/screens/invite/invite_friends_screen.dart';
- import '../../presentation/screens/broker/broker_apply_screen.dart';
- import '../../presentation/screens/broker/broker_screen.dart';
- import '../../presentation/screens/broker/my_invitations_screen.dart';
- import '../../presentation/screens/broker/team_detail_screen.dart';
- import '../../presentation/screens/user/help_center_screen.dart';
- import '../../presentation/screens/user/help_detail_screen.dart';
- import '../../presentation/screens/user/language_screen.dart';
- import '../../presentation/screens/user/protocol_screen.dart';
- import '../../presentation/screens/finance/finance_hub_screen.dart';
- import '../../presentation/screens/finance/staking_screen.dart';
- import '../../presentation/screens/common/qr_scanner_screen.dart';
- import '../../presentation/widgets/common/bottom_nav_shell.dart';
- import '../../providers/auth_provider.dart';
- import '../../providers/asset_provider.dart';
- import '../../providers/profile_provider.dart';
- import '../../providers/home_provider.dart';
- import '../l10n/app_localizations.dart';
- import '../network/dio_client.dart' show sessionExpiredProvider;
- /// 需要登录的路由前缀(startsWith 匹配)
- const _authRequiredPrefixes = [
- '/asset',
- '/asset/deposit',
- '/asset/withdraw',
- '/asset/transfer',
- '/user/account',
- '/user/security',
- '/user/kyc',
- '/user/api-keys',
- '/user/referral',
- '/user/messages',
- '/broker',
- '/broker/apply',
- '/broker/my-invitations',
- '/broker/team-detail',
- ];
- /// 全局 Navigator Key,用于在非 Widget 上下文中弹出对话框
- final rootNavigatorKey = GlobalKey<NavigatorState>();
- // 各 Tab 分支固定 NavigatorKey,避免 StatefulShellRoute 热重载/重建时 GlobalKey 冲突
- final _homeTabKey = GlobalKey<NavigatorState>(debugLabel: 'homeTab');
- final _marketTabKey = GlobalKey<NavigatorState>(debugLabel: 'marketTab');
- final _futuresTabKey = GlobalKey<NavigatorState>(debugLabel: 'futuresTab');
- final _copyTabKey = GlobalKey<NavigatorState>(debugLabel: 'copyTab');
- final _assetTabKey = GlobalKey<NavigatorState>(debugLabel: 'assetTab');
- /// 路由 Provider — 监听 isLoggedInProvider 实现登录状态自动刷新路由
- final appRouterProvider = Provider<GoRouter>((ref) {
- final notifier = _RouterNotifier(ref);
- return GoRouter(
- navigatorKey: rootNavigatorKey,
- initialLocation: '/',
- debugLogDiagnostics: true,
- refreshListenable: notifier,
- redirect: (context, state) {
- final isLoggedIn = ref.read(isLoggedInProvider);
- final path = state.matchedLocation;
- // 未登录访问受保护路由 → 跳转登录
- final needsAuth = _authRequiredPrefixes.any((p) => path.startsWith(p));
- if (!isLoggedIn && needsAuth) {
- return '/login';
- }
- // 已登录访问登录/注册页 → 跳转首页
- if (isLoggedIn && (path == '/login' || path == '/register')) {
- return '/';
- }
- return null;
- },
- routes: [
- // ── 带底部导航的 Tab 路由 ─────────────────────────
- StatefulShellRoute.indexedStack(
- pageBuilder: (context, state, navigationShell) {
- return NoTransitionPage(
- child: BottomNavShell(navigationShell: navigationShell),
- );
- },
- branches: [
- // Tab 0: 首页
- StatefulShellBranch(
- navigatorKey: _homeTabKey,
- routes: [
- GoRoute(
- path: '/',
- builder: (context, state) => const HomeScreen(),
- ),
- ],
- ),
- // Tab 1: 行情
- StatefulShellBranch(
- navigatorKey: _marketTabKey,
- routes: [
- GoRoute(
- path: '/market',
- builder: (context, state) => const MarketScreen(),
- ),
- ],
- ),
- // Tab 2: 合约 / 现货
- StatefulShellBranch(
- navigatorKey: _futuresTabKey,
- initialLocation: '/spot/BTCUSDT',
- routes: [
- GoRoute(
- path: '/spot/:symbol',
- builder: (context, state) => SpotScreen(
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- ),
- routes: [
- GoRoute(
- path: 'history',
- redirect: (context, state) {
- final loggedIn = ref.read(isLoggedInProvider);
- return loggedIn ? null : '/login';
- },
- builder: (context, state) => SpotHistoryScreen(
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- ),
- ),
- GoRoute(
- path: 'records',
- redirect: (context, state) {
- final loggedIn = ref.read(isLoggedInProvider);
- return loggedIn ? null : '/login';
- },
- builder: (context, state) => SpotAccountRecordsScreen(
- initialSymbol: state.uri.queryParameters['symbol'],
- ),
- ),
- ],
- ),
- GoRoute(
- path: '/futures/:symbol',
- builder: (context, state) => FuturesScreen(
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- ),
- routes: [
- GoRoute(
- path: 'history',
- redirect: (context, state) {
- final loggedIn = ref.read(isLoggedInProvider);
- return loggedIn ? null : '/login';
- },
- builder: (context, state) => const FuturesHistoryScreen(),
- routes: [
- GoRoute(
- path: 'position-detail',
- builder: (context, state) =>
- PositionHistoryDetailScreen(
- data: state.extra! as Map<String, dynamic>,
- ),
- ),
- GoRoute(
- path: 'order-detail',
- builder: (context, state) => OrderHistoryDetailScreen(
- data: state.extra! as Map<String, dynamic>,
- ),
- ),
- ],
- ),
- GoRoute(
- path: 'position-detail',
- builder: (context, state) => PositionDetailScreen(
- position: state.extra! as FuturesPosition,
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- ),
- ),
- GoRoute(
- path: 'order-detail',
- builder: (context, state) => OrderDetailScreen(
- order: state.extra! as FuturesOrder,
- ),
- ),
- ],
- ),
- ],
- ),
- // Tab 3: 合约
- StatefulShellBranch(
- initialLocation: '/contracts',
- routes: [
- GoRoute(
- path: '/contracts',
- builder: (context, state) => const FuturesScreen(
- symbol: 'BTCUSDT',
- showSpotSwitcher: false,
- ),
- routes: [
- GoRoute(
- path: ':symbol',
- builder: (context, state) => FuturesScreen(
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- showSpotSwitcher: false,
- ),
- ),
- ],
- ),
- ],
- ),
- // Tab 4: 跟单
- StatefulShellBranch(
- navigatorKey: _copyTabKey,
- routes: [
- GoRoute(
- path: '/copy-trading',
- builder: (context, state) => const CopyTradingScreen(),
- ),
- ],
- ),
- // Tab 5: 资产
- StatefulShellBranch(
- navigatorKey: _assetTabKey,
- routes: [
- GoRoute(
- path: '/asset',
- builder: (context, state) => const AssetScreen(),
- ),
- ],
- ),
- ],
- ),
- // ── 认证流程(无底部导航,使用右滑入/左滑出转场)───
- GoRoute(
- path: '/login',
- pageBuilder: (context, state) => CustomTransitionPage(
- key: state.pageKey,
- child: const LoginScreen(),
- transitionsBuilder: (context, animation, secondaryAnimation, child) {
- const begin = Offset(1, 0);
- const end = Offset.zero;
- final tween = Tween(begin: begin, end: end)
- .chain(CurveTween(curve: Curves.easeInOut));
- return SlideTransition(
- position: animation.drive(tween), child: child);
- },
- ),
- ),
- GoRoute(
- path: '/register',
- pageBuilder: (context, state) => CustomTransitionPage(
- key: state.pageKey,
- child: const RegisterScreen(),
- transitionsBuilder: (context, animation, secondaryAnimation, child) {
- const begin = Offset(1, 0);
- const end = Offset.zero;
- final tween = Tween(begin: begin, end: end)
- .chain(CurveTween(curve: Curves.easeInOut));
- return SlideTransition(
- position: animation.drive(tween), child: child);
- },
- ),
- ),
- GoRoute(
- path: '/two-factor',
- pageBuilder: (context, state) => CustomTransitionPage(
- key: state.pageKey,
- child: const TwoFactorScreen(),
- transitionsBuilder: (context, animation, secondaryAnimation, child) {
- const begin = Offset(1, 0);
- const end = Offset.zero;
- final tween = Tween(begin: begin, end: end)
- .chain(CurveTween(curve: Curves.easeInOut));
- return SlideTransition(
- position: animation.drive(tween), child: child);
- },
- ),
- ),
- GoRoute(
- path: '/verify-code',
- pageBuilder: (context, state) => CustomTransitionPage(
- key: state.pageKey,
- child: VerifyCodeScreen(args: state.extra! as VerifyCodeArgs),
- transitionsBuilder: (context, animation, secondaryAnimation, child) {
- const begin = Offset(1, 0);
- const end = Offset.zero;
- final tween = Tween(begin: begin, end: end)
- .chain(CurveTween(curve: Curves.easeInOut));
- return SlideTransition(
- position: animation.drive(tween), child: child);
- },
- ),
- ),
- GoRoute(
- path: '/forgot-password',
- pageBuilder: (context, state) => CustomTransitionPage(
- key: state.pageKey,
- child: const ForgotPasswordScreen(),
- transitionsBuilder: (context, animation, secondaryAnimation, child) {
- const begin = Offset(1, 0);
- const end = Offset.zero;
- final tween = Tween(begin: begin, end: end)
- .chain(CurveTween(curve: Curves.easeInOut));
- return SlideTransition(
- position: animation.drive(tween), child: child);
- },
- ),
- ),
- // ── 行情详情(无底部导航)──────────────────────────
- GoRoute(
- path: '/market/futures/:symbol',
- builder: (context, state) => FuturesMarketDetailScreen(
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- ),
- ),
- GoRoute(
- path: '/market/spot/:symbol',
- builder: (context, state) => SpotMarketDetailScreen(
- symbol: state.pathParameters['symbol'] ?? 'BTCUSDT',
- ),
- ),
- // ── 我的跟单(无底部导航)──────────────────────────
- GoRoute(
- path: '/my-copy-trading',
- builder: (context, state) => const MyCopyTradingScreen(),
- ),
- GoRoute(
- path: '/trader-apply',
- builder: (context, state) => const TraderApplyScreen(),
- ),
- // ── 我的带单(带单员专用)────────────────────────────
- GoRoute(
- path: '/my-trades',
- builder: (context, state) => const MyTradesScreen(),
- ),
- GoRoute(
- path: '/trader-settings',
- builder: (context, state) => const TraderSettingsScreen(),
- ),
- GoRoute(
- path: '/trader-detail/:traderId',
- builder: (context, state) => TraderDetailScreen(
- traderId: state.pathParameters['traderId'] ?? '',
- ),
- ),
- GoRoute(
- path: '/follow-setting',
- builder: (context, state) => FollowSettingScreen(
- trader: state.extra! as Map<String, dynamic>,
- ),
- ),
- // ── 资产操作(无底部导航)──────────────────────────
- GoRoute(
- path: '/asset/deposit',
- builder: (context, state) => const DepositScreen(),
- ),
- GoRoute(
- path: '/asset/deposit/history',
- builder: (context, state) => const DepositHistoryScreen(),
- ),
- GoRoute(
- path: '/asset/deposit/detail',
- builder: (context, state) {
- final record = state.extra as RechargeRecord;
- return DepositDetailScreen(record: record);
- },
- ),
- GoRoute(
- path: '/asset/withdraw',
- builder: (context, state) => const WithdrawScreen(),
- ),
- GoRoute(
- path: '/asset/withdraw/history',
- builder: (context, state) => const WithdrawHistoryScreen(),
- ),
- GoRoute(
- path: '/asset/withdraw/detail',
- builder: (context, state) {
- final record = state.extra as WithdrawRecord;
- final isTransfer = state.uri.queryParameters['isTransfer'] == 'true';
- return WithdrawDetailScreen(record: record, isTransfer: isTransfer);
- },
- ),
- GoRoute(
- path: '/asset/transfer',
- builder: (context, state) {
- final qp = state.uri.queryParameters;
- return TransferScreen(
- initialFrom: qp['from'],
- initialTo: qp['to'],
- initialSymbol: qp['symbol'],
- preferDefaultSymbol: qp['preferSymbol'] == '1',
- spotTradingBridgeOnly: qp['bridgeOnly'] == '1',
- );
- },
- ),
- GoRoute(
- path: '/asset/transfer/history',
- builder: (context, state) => const TransferHistoryScreen(),
- ),
- GoRoute(
- path: '/asset/history',
- builder: (context, state) => const AssetHistoryScreen(),
- ),
- GoRoute(
- path: '/qr-scanner',
- builder: (context, state) => const QrScannerScreen(),
- ),
- // ── 个人中心(无底部导航)──────────────────────────
- GoRoute(
- path: '/user',
- builder: (context, state) => const ProfileScreen(),
- ),
- GoRoute(
- path: '/user/account',
- builder: (context, state) =>
- const PlaceholderScreen(title: '账户设置', icon: Icons.settings),
- ),
- GoRoute(
- path: '/user/security',
- builder: (context, state) => const SecurityScreen(),
- ),
- GoRoute(
- path: '/user/security/google-auth',
- builder: (context, state) => const GoogleAuthScreen(),
- ),
- GoRoute(
- path: '/user/security/google-auth/bind',
- builder: (context, state) => const GoogleAuthBindScreen(),
- ),
- GoRoute(
- path: '/user/security/fund-password',
- builder: (context, state) {
- final isReset = state.extra as bool? ?? false;
- return FundPasswordScreen(isResetMode: isReset);
- },
- ),
- GoRoute(
- path: '/user/security/change-password',
- builder: (context, state) => const ChangePasswordScreen(),
- ),
- GoRoute(
- path: '/user/kyc',
- builder: (context, state) =>
- const PlaceholderScreen(title: '身份认证', icon: Icons.verified_user),
- ),
- GoRoute(
- path: '/user/api-keys',
- builder: (context, state) =>
- const PlaceholderScreen(title: 'API 管理', icon: Icons.key),
- ),
- GoRoute(
- path: '/user/referral',
- builder: (context, state) => const InviteFriendsScreen(),
- ),
- GoRoute(
- path: '/user/messages',
- builder: (context, state) => const NotificationsScreen(),
- ),
- GoRoute(
- path: '/user/messages/:id',
- builder: (context, state) => NotificationDetailScreen(
- id: state.pathParameters['id'] ?? '',
- ),
- ),
- GoRoute(
- path: '/user/service-route',
- builder: (context, state) => const ServiceRouteScreen(),
- ),
- GoRoute(
- path: '/user/language',
- builder: (context, state) => const LanguageScreen(),
- ),
- GoRoute(
- path: '/user/help',
- builder: (context, state) => const HelpCenterScreen(),
- ),
- GoRoute(
- path: '/user/help/:id',
- builder: (context, state) => HelpDetailScreen(
- id: state.pathParameters['id'] ?? '',
- ),
- ),
- GoRoute(
- path: '/protocol',
- builder: (context, state) {
- final args = state.extra as ProtocolArgs?;
- return ProtocolScreen(
- title: args?.title ?? '',
- categoryCode: args?.categoryCode ?? 'PROTOCOL',
- );
- },
- ),
- GoRoute(
- path: '/finance/ido',
- builder: (context, state) {
- final tab = state.uri.queryParameters['tab'];
- final initialTab = tab == 'airdrop' ? 1 : 0;
- return FinanceHubScreen(initialTab: initialTab);
- },
- ),
- GoRoute(
- path: '/finance/stake/:configId',
- builder: (context, state) => StakingScreen(
- configId: state.pathParameters['configId'],
- ),
- ),
- GoRoute(
- path: '/finance/airdrop',
- builder: (context, state) => const FinanceHubScreen(initialTab: 1),
- ),
- GoRoute(
- path: '/user/about',
- builder: (context, state) =>
- const PlaceholderScreen(title: '关于', icon: Icons.info),
- ),
- // ── 经纪商 ────────────────────────────────────────
- GoRoute(
- path: '/broker',
- builder: (context, state) => const BrokerScreen(),
- ),
- GoRoute(
- path: '/broker/apply',
- builder: (context, state) => const BrokerApplyScreen(),
- ),
- GoRoute(
- path: '/broker/my-invitations',
- builder: (context, state) => const MyInvitationsScreen(),
- ),
- GoRoute(
- path: '/broker/team-detail',
- builder: (context, state) => const TeamDetailScreen(),
- ),
- // ── 网络加载失败页 ────────────────────────────────
- GoRoute(
- path: '/network-error',
- builder: (context, state) {
- final extra = state.extra as Map<String, dynamic>?;
- return NetworkErrorScreen(
- title: extra?['title'] as String? ?? '行情',
- errorCode: extra?['errorCode'] as String?,
- onRetry: extra?['onRetry'] as VoidCallback?,
- );
- },
- ),
- // ── Deep Link 路径映射 ────────────────────────────
- GoRoute(
- path: '/f/:symbol',
- redirect: (context, state) {
- final symbol = state.pathParameters['symbol'] ?? 'BTCUSDT';
- return '/futures/$symbol';
- },
- ),
- ],
- );
- });
- /// 桥接 Riverpod → GoRouter refreshListenable
- class _RouterNotifier extends ChangeNotifier {
- /// true 时跳过 isLoggedInProvider 变化引发的 notifyListeners,
- /// 用于 session 过期场景:由 dialog 按钮统一执行导航,避免双重导航断言。
- bool _suppressLoggedOutNotify = false;
- /// 保存 ref,供 dialog 按钮回调中延迟 invalidate 使用
- late final Ref _ref;
- _RouterNotifier(Ref ref) {
- _ref = ref;
- ref.listen(isLoggedInProvider, (prev, next) {
- if (prev == true && next == false) {
- if (_suppressLoggedOutNotify) return;
- // 主动登出:先触发 redirect 导航,下一帧再 invalidate provider,
- // 避免 invalidate rebuild 与 GoRouter navigation 并发导致 Duplicate GlobalKey 崩溃
- notifyListeners();
- // 下一帧再 invalidate:profile 由 isLoggedIn 监听同步降为访客,
- // 另行 invalidate 易与_uc/member/my-info 等在途请求收尾并发,触发 element 断言。
- WidgetsBinding.instance.addPostFrameCallback((_) {
- ref.invalidate(assetProvider);
- ref.invalidate(homeProvider);
- ref.invalidate(futuresProvider);
- });
- }
- // 登录:导航由页面显式 context.go('/') 处理,不在此触发 notifyListeners,
- // 避免与 context.go 并发导致 element._lifecycleState 断言失败。
- });
- ref.listen(sessionExpiredProvider, (_, expired) {
- if (expired) {
- ref.read(sessionExpiredProvider.notifier).state = false;
- // 阻止 isLoggedInProvider 监听器触发 notifyListeners(→ redirect),
- // 导航由 dialog 按钮的 context.go('/login') 统一处理,避免双重导航。
- _suppressLoggedOutNotify = true;
- // 只清本地登录态,不请求 logout 接口(token 已失效,请求也会报 4000)
- ref.read(authProvider.notifier).clearLocalSession();
- _suppressLoggedOutNotify = false;
- // 下一帧再弹 dialog,此时 clearLocalSession 的 rebuild 已完成;
- // provider invalidate 推迟到用户点击确认后导航完成再执行,
- // 确保 navigation 与 invalidate 完全不在同一帧
- WidgetsBinding.instance.addPostFrameCallback((_) {
- _showSessionExpiredDialog();
- });
- }
- });
- }
- void _showSessionExpiredDialog() {
- final ctx = rootNavigatorKey.currentContext;
- if (ctx == null) return;
- final cs = Theme.of(ctx).colorScheme;
- final l10n = AppLocalizations.of(ctx)!;
- showDialog(
- context: ctx,
- barrierDismissible: false,
- builder: (dialogCtx) => AlertDialog(
- backgroundColor: cs.surface,
- title: Text(l10n.sessionExpiredTitle, style: TextStyle(color: cs.onSurface)),
- content: Text(
- l10n.sessionExpiredContent,
- style: TextStyle(color: cs.onSurface.withAlpha(153)),
- ),
- actions: [
- TextButton(
- onPressed: () {
- Navigator.of(dialogCtx).pop();
- rootNavigatorKey.currentContext?.go('/login');
- // 导航完成后下一帧再 invalidate,避免与 navigation 并发
- WidgetsBinding.instance.addPostFrameCallback((_) {
- _ref.invalidate(assetProvider);
- _ref.invalidate(profileProvider);
- _ref.invalidate(homeProvider);
- _ref.invalidate(futuresProvider);
- });
- },
- child: Text(l10n.relogin, style: TextStyle(color: cs.onSurface)),
- ),
- ],
- ),
- );
- }
- }
|