/// ============================================================ /// 闲言APP — 应用根组件 /// 创建时间: 2026-04-20 /// 更新时间: 2026-06-05 /// 作用: MaterialApp.router + Riverpod 主题管理 + GlassTheme + flutter_animate + AppLockOverlay /// 上次更新: 初始化AccessibilityService,build时同步系统无障碍状态 /// ============================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:go_router/go_router.dart'; import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter_quill/flutter_quill.dart' show FlutterQuillLocalizations; import 'package:flutter/services.dart'; import '../core/services/device/quick_actions_service.dart'; import '../core/services/device/macos_platform_service.dart'; import '../core/services/data/home_widget_service.dart'; import '../core/services/ui/status_bar_service.dart'; import '../core/services/accessibility/accessibility_service.dart'; import '../core/router/app_router.dart' show appRouter, rootNavigatorKey; import '../core/router/app_routes.dart'; import '../core/layout/ohos_app_shell.dart'; import '../core/router/ohos_nav_bridge.dart'; import '../core/services/device/app_lock_service.dart'; import '../core/services/performance/app_lifecycle_gate.dart'; import '../core/theme/app_theme.dart'; import '../core/utils/logger.dart'; import '../core/utils/platform/platform_utils.dart' as pu; import '../core/network/api_client.dart'; import '../core/providers/connectivity_provider.dart'; import '../core/theme/app_typography.dart'; import '../features/template/services/wallpaper_preload_service.dart'; import '../shared/widgets/adaptive/keyboard_back_handler.dart'; import '../shared/widgets/feedback/app_toast.dart'; import '../features/mine/settings/providers/theme_settings_provider.dart'; import '../features/mine/settings/providers/general_settings_provider.dart'; import '../features/mine/settings/presentation/font_management_notifier.dart'; import '../features/mine/settings/presentation/lock/app_lock_overlay.dart'; import '../core/sync/sync.dart' show disposeReadlaterRefreshController, disposeFavoriteRefreshController; import '../l10n/app_locale.dart'; import '../main.dart' show liquidGlassReady; const _localizationsDelegates = [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, DefaultCupertinoLocalizations.delegate, FlutterQuillLocalizations.delegate, ]; class AppScrollBehavior extends MaterialScrollBehavior { const AppScrollBehavior(); @override Set get dragDevices => { PointerDeviceKind.touch, PointerDeviceKind.mouse, PointerDeviceKind.stylus, PointerDeviceKind.invertedStylus, PointerDeviceKind.trackpad, }; } class XianyanApp extends ConsumerStatefulWidget { const XianyanApp({super.key}); @override ConsumerState createState() => _XianyanAppState(); } class _XianyanAppState extends ConsumerState with WidgetsBindingObserver, AppLifecycleGate { static const _dataManagementChannel = MethodChannel( 'apps.xy.xianyan/data_management', ); Duration? _lastAnimateDuration; Curve? _lastAnimateCurve; bool _pendingDataManagement = false; @override void initState() { super.initState(); initLifecycleGate(); _initQuickActions(); _initWallpaperPreload(); _initDataManagementChannel(); _initHttpCache(); _initAccessibility(); } /// 初始化无障碍服务 void _initAccessibility() { // 从设置中恢复语义调试状态 final generalSettings = ref.read(generalSettingsProvider); AccessibilityService.instance.initSemanticsDebug( generalSettings.semanticsDebugEnabled, ); } void _initQuickActions() { QuickActionsService.init( onActionCallback: (String route) { final context = rootNavigatorKey.currentContext; if (context == null) { Log.w('🚀 [QuickActions] context不可用,延迟导航'); return; } if (pu.isOhos) { OhosNavBridge.push(context, route); } else { // 使用push而非go,保留导航栈以便返回 appRouter.push(route); } }, ); } Future _initHttpCache() async { try { await ApiClient.instance.initCache(); } catch (e) { Log.e('HTTP缓存初始化失败', e); } } void _initDataManagementChannel() { if (pu.isWeb || !pu.isOhos) return; _dataManagementChannel.setMethodCallHandler((call) async { if (call.method == 'open_data_management') { _navigateToDataManagement(); } }); _checkPendingManageStorage(); } Future _checkPendingManageStorage() async { if (pu.isWeb) return; try { final isPending = await _dataManagementChannel.invokeMethod( 'checkPendingManageStorage', ); if (isPending == true) { _pendingDataManagement = true; Log.i('📦 [DataManagement] 检测到系统清除数据意图,等待导航'); } } catch (e) { Log.e('数据管理通道检查失败', e); } } void _navigateToDataManagement() { _pendingDataManagement = false; Future.delayed(const Duration(milliseconds: 300), () { final context = rootNavigatorKey.currentContext; if (context == null) { _pendingDataManagement = true; Log.w('📦 [DataManagement] context不可用,延迟导航'); return; } Log.i('📦 [DataManagement] 导航到数据管理页面'); // ignore: use_build_context_synchronously if (!context.mounted) return; if (pu.isOhos) { OhosNavBridge.push(context, AppRoutes.dataManagement); } else { appRouter.push(AppRoutes.dataManagement); } }).catchError((_) {}); } void _handlePendingDataManagement() { if (!_pendingDataManagement) return; _navigateToDataManagement(); } /// WiFi环境下预加载壁纸缩略图 void _initWallpaperPreload() { Future.microtask(() async { try { await WallpaperPreloadService.preloadIfNeeded(); } catch (e) { Log.e('壁纸预加载初始化失败', e); } }).catchError((_) {}); } @override void dispose() { disposeLifecycleGate(); disposeReadlaterRefreshController(); disposeFavoriteRefreshController(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.paused: try { ref.read(appLockProvider.notifier).onAppPaused(); } catch (e) { Log.e('应用锁暂停处理失败', e); } break; case AppLifecycleState.resumed: Future.microtask(() { try { ref.read(appLockProvider.notifier).onAppResumed(); } catch (e) { Log.e('应用锁恢复处理失败', e); } _handlePendingWidgetNavigation(); _handlePendingDataManagement(); QuickActionsService.handlePendingAction(); }); break; default: break; } } @override void didChangePlatformBrightness() { super.didChangePlatformBrightness(); final settings = ref.read(themeSettingsProvider); if (settings.themeMode == AppThemeMode.system) { final isDark = WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark; MacosPlatformService.syncTheme(isDark); } } @override void didChangeLocales(List? locales) { super.didChangeLocales(locales); if (locales != null && locales.isNotEmpty) { ref.read(systemLocaleVersionProvider.notifier).increment(); Log.i('系统语言变化: ${locales.first.toLanguageTag()}'); } } void _handlePendingWidgetNavigation() { final route = HomeWidgetService.consumePendingNavigation(); if (route == null) return; final context = rootNavigatorKey.currentContext; if (context == null) { Log.w('WidgetNavigation: rootNavigatorKey context 不可用'); return; } Log.i('WidgetNavigation: 从小组件导航到 $route'); if (pu.isOhos) { OhosNavBridge.go(context, route); } else { appRouter.push(route); } } @override Widget build(BuildContext context) { final settings = ref.watch(themeSettingsProvider); final generalSettings = ref.watch(generalSettingsProvider); final fontState = ref.watch(fontManagementProvider); final appLocale = ref.watch(appLocaleProvider); final supportedLocales = ref.watch(supportedLocalesProvider); final textDirection = ref.watch(appTextDirectionProvider); final connectivity = ref.watch(connectivityProvider); // 同步系统无障碍状态到AccessibilityService AccessibilityService.instance.updateFromContext(context); // 修复9: 仅在设置变化时更新Animate全局配置,避免每次build重复设置 final targetDuration = settings.animationEnabled ? Duration( milliseconds: (300 * settings.animationIntensity.durationMultiplier) .round(), ) : Duration.zero; final targetCurve = settings.animationIntensity.curve; if (_lastAnimateDuration != targetDuration || _lastAnimateCurve != targetCurve) { _lastAnimateDuration = targetDuration; _lastAnimateCurve = targetCurve; Animate.defaultDuration = targetDuration; Animate.defaultCurve = targetCurve; } final builtInFontFamilies = fontStyleOptions .map((opt) => opt.fontFamily) .toSet(); final effectiveFontFamily = settings.customFontFamily.isNotEmpty ? settings.customFontFamily : (!builtInFontFamilies.contains(fontState.activeFontFamily) ? fontState.activeFontFamily : settings.fontStyle.fontFamily); final theme = AppTheme.buildFromSettings( isDark: settings.isDark, isAmoled: settings.isAmoled, accent: settings.accentColor.color, accentLight: settings.accentColor.lightPrimary, fontScale: settings.fontSize.scale, fontWeight: settings.fontWeight.weight, fontFamily: effectiveFontFamily, glassBlurMultiplier: settings.glassIntensity.blurMultiplier, cardStyleId: settings.cardStyleId, cornerRadiusId: settings.cornerRadiusId, highContrast: generalSettings.highContrastEnabled, colorWeakTypeId: generalSettings.colorWeakTypeId, ); final darkTheme = AppTheme.buildFromSettings( isDark: true, isAmoled: settings.isAmoled, accent: settings.accentColor.darkPrimary, accentLight: settings.accentColor.lightPrimary, fontScale: settings.fontSize.scale, fontWeight: settings.fontWeight.weight, fontFamily: effectiveFontFamily, glassBlurMultiplier: settings.glassIntensity.blurMultiplier, cardStyleId: settings.cardStyleId, cornerRadiusId: settings.cornerRadiusId, highContrast: generalSettings.highContrastEnabled, colorWeakTypeId: generalSettings.colorWeakTypeId, ); final themeMode = _resolveThemeMode(settings.themeMode); final effectiveIsDark = switch (themeMode) { ThemeMode.dark => true, ThemeMode.light => false, ThemeMode.system => WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark, }; MacosPlatformService.syncTheme(effectiveIsDark); return Directionality( textDirection: textDirection, child: _LocaleTransitionWrapper( locale: appLocale, animationEnabled: settings.animationEnabled, child: ScreenUtilInit( designSize: const Size(390, 844), minTextAdapt: true, splitScreenMode: true, builder: (context, child) { Widget buildApp() { if (pu.isOhos) { Log.i( '🟢 [OHOS] 使用 MaterialApp(home:) + OhosAppShell (liquidGlass=$liquidGlassReady)', ); final ohosMaterialApp = MaterialApp( navigatorKey: rootNavigatorKey, title: '闲言', debugShowCheckedModeBanner: false, scrollBehavior: const AppScrollBehavior(), locale: appLocale, supportedLocales: supportedLocales, localizationsDelegates: _localizationsDelegates, theme: theme, darkTheme: darkTheme, themeMode: themeMode, home: const OhosAppShell(), navigatorObservers: [ BotToastNavigatorObserver(), ], // [BotToast] 步骤3: 注册路由观察者 builder: (context, widget) { final botToastBuilder = BotToastInit(); // [BotToast] 步骤1: 创建Toast builder AppToast.markInitialized(); // [BotToast] 步骤2: 标记初始化完成 final botWidget = botToastBuilder(context, widget); return DefaultTextStyle( style: const TextStyle(decoration: TextDecoration.none), child: StatusBarStyleRegion( isDark: settings.isDark, child: botWidget, ), ); }, ); return InheritedGoRouter( goRouter: appRouter, child: liquidGlassReady ? GlassTheme( data: GlassThemeData( light: GlassThemeVariant( settings: GlassThemeSettings( thickness: 20.0, blur: settings.glassEnabled ? 2.0 : 0.0, refractiveIndex: 1.4, lightIntensity: 0.8, ambientStrength: 0.4, saturation: 1.0, ), ), dark: GlassThemeVariant( settings: GlassThemeSettings( thickness: 28.0, blur: settings.glassEnabled ? 3.0 : 0.0, lightIntensity: 1.0, refractiveIndex: 1.2, saturation: 1.0, ), ), ), child: ohosMaterialApp, ) : ohosMaterialApp, ); } final materialApp = MaterialApp.router( title: '闲言', debugShowCheckedModeBanner: false, scrollBehavior: const AppScrollBehavior(), locale: appLocale, supportedLocales: supportedLocales, localizationsDelegates: _localizationsDelegates, theme: theme, darkTheme: darkTheme, themeMode: themeMode, routerConfig: appRouter, builder: (context, widget) { final botToastBuilder = BotToastInit(); // [BotToast] 步骤1: 创建Toast builder AppToast.markInitialized(); // [BotToast] 步骤2: 标记初始化完成 final botWidget = botToastBuilder(context, widget); return DefaultTextStyle( style: const TextStyle(decoration: TextDecoration.none), child: StatusBarStyleRegion( isDark: settings.isDark, child: botWidget, ), ); }, ); return GlassTheme( data: GlassThemeData( light: GlassThemeVariant( settings: GlassThemeSettings( thickness: 20.0, blur: settings.glassEnabled ? 2.0 : 0.0, refractiveIndex: 1.4, lightIntensity: 0.8, ambientStrength: 0.4, saturation: 1.0, ), ), dark: GlassThemeVariant( settings: GlassThemeSettings( thickness: 28.0, blur: settings.glassEnabled ? 3.0 : 0.0, lightIntensity: 1.0, refractiveIndex: 1.2, saturation: 1.0, ), ), ), child: materialApp, ); } final isLocked = ref.watch( appLockProvider.select((s) => s.isLocked), ); return KeyboardBackHandler( child: Stack( children: [ buildApp(), if (isLocked) const Positioned.fill(child: AppLockOverlay()), if (!connectivity.isConnected) Positioned( top: 0, left: 0, right: 0, child: SafeArea( child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), color: CupertinoColors.systemGrey6.withValues( alpha: 0.95, ), child: Row( children: [ const Icon( CupertinoIcons.wifi_exclamationmark, size: 16, color: CupertinoColors.systemGrey, ), const SizedBox(width: 8), Text( '网络已断开,部分功能可能不可用', style: AppTypography.footnote.copyWith( color: CupertinoColors.systemGrey, ), ), ], ), ), ), ), ], ), ); }, ), ), ); } ThemeMode _resolveThemeMode(AppThemeMode mode) { return switch (mode) { AppThemeMode.light => ThemeMode.light, AppThemeMode.dark => ThemeMode.dark, AppThemeMode.amoled => ThemeMode.dark, AppThemeMode.system => ThemeMode.system, }; } } class _LocaleTransitionWrapper extends StatefulWidget { const _LocaleTransitionWrapper({ required this.locale, required this.animationEnabled, required this.child, }); final Locale locale; final bool animationEnabled; final Widget child; @override State<_LocaleTransitionWrapper> createState() => _LocaleTransitionWrapperState(); } class _LocaleTransitionWrapperState extends State<_LocaleTransitionWrapper> with SingleTickerProviderStateMixin { late final AnimationController _controller; late Animation _opacity; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _opacity = Tween(begin: 1.0, end: 1.0).animate(_controller); _controller.value = 1.0; } @override void didUpdateWidget(_LocaleTransitionWrapper oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.locale != widget.locale) { if (widget.animationEnabled) { _triggerTransition(); } } } void _triggerTransition() { _opacity = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); _controller.forward(from: 0.0); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Opacity(opacity: _opacity.value, child: child); }, child: widget.child, ); } }