import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:forui/forui.dart'; import 'package:get/get.dart'; import 'package:ducafe_ui_core/ducafe_ui_core.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; /// 主屏 Flutter 应用根组件,统一承载主题、路由、生命周期和用户活动监听。 class MainApp extends StatefulWidget { /// 创建主屏应用根组件。 const MainApp({super.key}); /// 创建负责生命周期观察和根级空闲活动采集的应用状态。 @override State createState() => _MainAppState(); } /// 主屏应用生命周期与根视图状态。 class _MainAppState extends State with WidgetsBindingObserver { /// 注册应用生命周期观察者,供副屏、人脸和同步空闲状态共用。 @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } /// 注销生命周期观察者,避免应用根组件释放后继续接收回调。 @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } /// 同步前后台空闲状态,并在挂起时释放人脸会话、恢复时校验副屏窗口和补排部门人脸对账。 @override void didChangeAppLifecycleState(AppLifecycleState state) async { SyncIdleTool.instance.updateLifecycle(state); if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { if (Get.isRegistered()) { try { await Get.find().suspend(); } catch (_) {} } return; } if (state == AppLifecycleState.resumed) { // 前台恢复需要重新确认原生 Presentation;若系统回收了副屏窗口才重新创建, // 正常常驻时不会触发插件内部的“先关闭再打开”。 try { await AppSubScreenTool.show(verifyPresentation: true); } catch (_) {} SyncCheckTool.instance.queueMissedDepartmentFaceSync(); } } /// 构建主题与 GetX 根应用,并在最外层 Listener 记录所有指针活动。 @override Widget build(BuildContext context) { return ScreenUtilInit( designSize: const Size(1920, 1080), minTextAdapt: true, splitScreenMode: true, builder: (_, child) { return Obx(() { final appTheme = Get.find(); final fTheme = appTheme.theme.value; final sizeScalar = appTheme.sizeScalar.value; final buttonRadius = appTheme.buttonRadius.value.radius.w; final cardRadius = appTheme.cardRadius.value.radius.w; final buttonSizeScale = appTheme.buttonSize.value.scale; // 新 UI 保留用户字号缩放能力,只统一视觉色板与组件形态。 final colorScheme = fTheme.colorScheme.copyWith( background: XfjDesignTokens.surface, foreground: XfjDesignTokens.ink, primary: XfjDesignTokens.primary, primaryForeground: Colors.white, secondary: XfjDesignTokens.surface, secondaryForeground: XfjDesignTokens.ink, muted: XfjDesignTokens.canvas, mutedForeground: XfjDesignTokens.muted, destructive: XfjDesignTokens.danger, destructiveForeground: Colors.white, error: XfjDesignTokens.danger, errorForeground: Colors.white, border: XfjDesignTokens.line, ); final inheritedTheme = FThemeData.inherit( colorScheme: colorScheme, typography: fTheme.typography.scale(sizeScalar: sizeScalar), style: fTheme.style.copyWith( borderRadius: BorderRadius.all( Radius.circular(buttonRadius), ), borderWidth: 1, iconStyle: fTheme.style.iconStyle.copyWith( size: fTheme.typography.xl2.fontSize, ), pagePadding: EdgeInsets.symmetric(vertical: 5.h, horizontal: 1.w), ), ); final buttonPadding = EdgeInsets.symmetric( horizontal: 16.w * buttonSizeScale, vertical: 12.5.h * buttonSizeScale, ); final iconPadding = EdgeInsets.all(7.5.w * buttonSizeScale); final badgePadding = EdgeInsets.symmetric( horizontal: 14.w * buttonSizeScale, vertical: 2.h * buttonSizeScale, ); final themeData = inheritedTheme.copyWith( buttonStyles: _buttonStyles( inheritedTheme.buttonStyles, buttonPadding, iconPadding, ), badgeStyles: _badgeStyles( inheritedTheme.badgeStyles, buttonRadius, badgePadding, ), cardStyle: inheritedTheme.cardStyle.copyWith( decoration: inheritedTheme.cardStyle.decoration.copyWith( borderRadius: BorderRadius.circular(cardRadius), ), ), ); return GetMaterialApp( title: '收银机', theme: ThemeData( scaffoldBackgroundColor: XfjDesignTokens.canvas, colorScheme: ColorScheme.fromSeed( seedColor: XfjDesignTokens.primary, surface: XfjDesignTokens.surface, ), bottomSheetTheme: const BottomSheetThemeData( backgroundColor: Colors.transparent, ), ), defaultTransition: Transition.noTransition, initialRoute: RouteNames.load, getPages: RoutePages.list, enableLog: true, debugShowCheckedModeBanner: false, locale: const Locale('zh', 'CN'), localizationsDelegates: FLocalizations.localizationsDelegates, supportedLocales: FLocalizations.supportedLocales, navigatorObservers: [ GlobalRouteObserver(), ], builder: EasyLoading.init( builder: (_, childSecond) => Listener( behavior: HitTestBehavior.translucent, onPointerDown: (_) => SyncIdleTool.instance.recordUserActivity(), child: FTheme( data: themeData, child: childSecond!, ), ), ), ); }); }, ); } } FButtonStyles _buttonStyles( FButtonStyles styles, EdgeInsets padding, EdgeInsets iconPadding, ) { FButtonCustomStyle resize(FButtonCustomStyle style) => style.copyWith( contentStyle: style.contentStyle.copyWith(padding: padding), iconContentStyle: style.iconContentStyle.copyWith(padding: iconPadding), ); return styles.copyWith( primary: resize(styles.primary), secondary: resize(styles.secondary), destructive: resize(styles.destructive), outline: resize(styles.outline), ghost: resize(styles.ghost), ); } FBadgeStyles _badgeStyles( FBadgeStyles styles, double radius, EdgeInsets padding, ) { FBadgeCustomStyle resize(FBadgeCustomStyle style) => style.copyWith( borderRadius: BorderRadius.circular(radius), contentStyle: style.contentStyle.copyWith(padding: padding), ); return styles.copyWith( primary: resize(styles.primary), secondary: resize(styles.secondary), outline: resize(styles.outline), destructive: resize(styles.destructive), ); }