Files
photo-live/apps/mobile/lib/app/app.dart
T
2026-09-19 05:00:11 +08:00

91 lines
3.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api_client.dart';
import '../features/uploads/engine_scope.dart';
import '../l10n/app_localizations.dart';
import '../pages/home_page.dart';
import '../pages/login_page.dart';
import '../pages/password_change_page.dart';
import '../pages/server_history_page.dart';
import 'locale_controller.dart';
import 'theme.dart';
/// 根导航键:[ApiClient.onUnauthorized](非 UI 上下文触发)跳转 /login 用。
final GlobalKey<NavigatorState> rootNavigatorKey =
GlobalKey<NavigatorState>();
/// App 根组件:主题 + l10n 委托 + 命名路由 + 401 驱逐接线 + 引擎生命周期。
///
/// 基线路由为命名路由(/login → /home → /password-change);随页面增长再
/// 评估是否迁移 go_router,当前保持零额外依赖。
/// initState 注册 [ApiClient.onUnauthorized]:任意业务端点 401(会话过期)
/// 即清空栈回到 /login(登录/修改密码端点自身的 401 不触发,见 ApiClient),
/// 并同步刷新会话账号(引擎 stop,任务保留库内)。
/// build watch [uploadEngineScopeProvider]——App 存续期间引擎随登录态
/// start/stop(Task 9 接线,见 engine_scope.dart)。
class PhotoLiveApp extends ConsumerStatefulWidget {
const PhotoLiveApp({super.key});
@override
ConsumerState<PhotoLiveApp> createState() => _PhotoLiveAppState();
}
class _PhotoLiveAppState extends ConsumerState<PhotoLiveApp> {
@override
void initState() {
super.initState();
ref.read(apiClientProvider).onUnauthorized = () {
rootNavigatorKey.currentState
?.pushNamedAndRemoveUntil('/login', (route) => false);
// 401 已清会话(ApiClient):刷新账号观察 → 引擎 stop。
ref.read(sessionAccountProvider.notifier).refresh();
};
}
/// 语言解析(P3 Task 10):
/// - 显式选择(locale 非 null)优先,按 languageCode 匹配;
/// - system 档位下用系统 locale 匹配(zh_* → zh、en_* → en);
/// - 均不匹配(如 fr)回退模板语言 zh——不依赖生成代码 supportedLocales
/// 的排序(gen-l10n 按字母序 en 在前,默认解析会回退 en,与模板不符)。
static Locale _resolveLocale(
Locale? deviceLocale,
Iterable<Locale> supportedLocales,
) {
Locale? requested;
for (final supported in supportedLocales) {
if (supported.languageCode == deviceLocale?.languageCode) {
requested = supported;
break;
}
}
// 语言码不匹配 → 模板语言(l10n.yaml template-arb-file=app_zh.arb)。
return requested ?? const Locale('zh');
}
@override
Widget build(BuildContext context) {
// select 只取 Locale?:未引起 locale 变化的状态更新不重建 MaterialApp。
final locale = ref.watch(localeSettingsProvider.select((c) => c.locale));
// 引擎生命周期绑定(常驻 watch;账号在场 start / 离场 stop)。
ref.watch(uploadEngineScopeProvider);
return MaterialApp(
navigatorKey: rootNavigatorKey,
onGenerateTitle: (context) => AppLocalizations.of(context).appName,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
locale: locale,
localeResolutionCallback: _resolveLocale,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
routes: {
'/login': (context) => const LoginPage(),
'/home': (context) => const HomePage(),
'/password-change': (context) => const PasswordChangePage(),
'/server-history': (context) => const ServerHistoryPage(),
},
home: const LoginPage(),
);
}
}