import 'dart:async'; import 'package:event_bus/event_bus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:get/get.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; enum AppStartupStepStatus { waiting, running, success, warning, failure, } class AppStartupStepState { const AppStartupStepState({ required this.title, required this.detail, this.status = AppStartupStepStatus.waiting, }); final String title; final String detail; final AppStartupStepStatus status; AppStartupStepState copyWith({ String? detail, AppStartupStepStatus? status, }) { return AppStartupStepState( title: title, detail: detail ?? this.detail, status: status ?? this.status, ); } } class AppControl extends GetxController { static const String _tag = "AppControl"; final startupSteps = [ const AppStartupStepState( title: '加载终端配置', detail: '等待读取设备模式与本地设置', ), const AppStartupStepState( title: '打开业务数据库', detail: '等待准备订单、商品与用户数据', ), const AppStartupStepState( title: '初始化设备服务', detail: '等待准备打印、读卡与电子秤服务', ), ].obs; final startupFailureMessage = ''.obs; final startupFinished = false.obs; final startupEnvironmentDescription = '正在识别终端运行环境'.obs; final startupEnvironmentDetail = '双屏、虚拟机与硬件输入模式将按设备能力自动适配'.obs; final startupStatusLabel = '正在启动'.obs; bool _initializing = false; bool _databaseInitialized = false; int get startupCompletedCount => startupSteps .where((item) => item.status == AppStartupStepStatus.success || item.status == AppStartupStepStatus.warning) .length; double get startupProgress => startupSteps.isEmpty ? 0 : startupCompletedCount / startupSteps.length; /// 打印机 late PrintManage printManage; /// 读卡器串口 late SerialCardManage serialCardManage; /// 称串口 late SerialSteelyardManage srialSteelyardManage; /// mq StreamSubscription? mqListen; EventBus mqUpdateEventBus = EventBus(); /// 数据库 late final AppDatabase dataBase; // 是否有网络 RxBool isNetwork = RxBool(false); @override void onClose() { super.onClose(); if (printManageInitialized) printManage.close(); if (serialCardManageInitialized) serialCardManage.close(); if (steelyardManageInitialized) srialSteelyardManage.close(); AppNetworkTool.closeListenNetwork(); TtsManage.dispose(); SyncCheckTool.instance.dispose(); } bool get printManageInitialized { try { printManage; return true; } catch (_) { return false; } } bool get serialCardManageInitialized { try { serialCardManage; return true; } catch (_) { return false; } } bool get steelyardManageInitialized { try { srialSteelyardManage; return true; } catch (_) { return false; } } Future initAsync({ String destination = RouteNames.login, bool force = false, }) async { if (_initializing || (startupFinished.value && !force)) return; _initializing = true; startupFinished.value = false; startupFailureMessage.value = ''; startupStatusLabel.value = '正在启动'; _resetStartupSteps(); try { _setStartupStep( 0, status: AppStartupStepStatus.running, detail: '正在读取运行环境与本地终端配置', ); await AppRuntimeTool.initializePlatform(); await AppSettingTool.removeSettingCache(); _setStartupStep( 1, status: AppStartupStepStatus.running, detail: '正在打开本地业务数据库', ); if (!_databaseInitialized) { dataBase = await AppDatabase.getDataBase(); _databaseInitialized = true; } var storageGranted = await Permission.manageExternalStorage.request().isGranted; if (!storageGranted) { storageGranted = await Permission.storage.request().isGranted; } final setting = await AppSettingTool.getSysSetting(); AppRuntimeTool.applyManualHardwareInput(setting.manualHardwareInput); unawaited(_preloadFaceLibrary()); final environment = _resolveEnvironment(setting); startupEnvironmentDescription.value = environment.title; startupEnvironmentDetail.value = environment.detail; _setStartupStep( 0, status: AppStartupStepStatus.success, detail: environment.stepDetail, ); _setStartupStep( 1, status: storageGranted ? AppStartupStepStatus.success : AppStartupStepStatus.warning, detail: storageGranted ? '数据库已打开,本地备份目录权限可用' : '数据库已打开;未获存储权限,本地备份可能不可用', ); _setStartupStep( 2, status: AppStartupStepStatus.running, detail: '正在创建打印、读卡与电子秤管理服务', ); if (!printManageInitialized) printManage = PrintManage(); if (!serialCardManageInitialized) serialCardManage = SerialCardManage(); if (!steelyardManageInitialized) { srialSteelyardManage = SerialSteelyardManage(); } await AppDeviceStatusTool.loadCachedStatus(); // 硬件检查失败只能影响状态展示,不能阻塞收银终端进入业务页面。 unawaited( AppDeviceStatusTool.refreshAll() .catchError((Object error, StackTrace stack) { logger.w('|$_tag|首次检查设备状态失败error:|$error|stack:|$stack|'); return AppDeviceStatusTool.status.value; }), ); await TtsManage.initialize(); _setStartupStep( 2, status: environment.manualHardwareInput ? AppStartupStepStatus.warning : AppStartupStepStatus.success, detail: environment.manualHardwareInput ? '当前为手动硬件输入模式,实体设备将在业务使用时按需连接' : '设备管理服务已就绪,实体设备将在业务使用时按需连接', ); final backupSucceeded = storageGranted ? await AppFileTool.backupSqlFile() : false; if (storageGranted && !backupSucceeded) { _setStartupStep( 1, status: AppStartupStepStatus.warning, detail: '数据库已打开;本次自动备份未完成,可稍后在设置中重试', ); } // 网络、MQTT 与后台同步属于运行期能力,不参与加载页检查或阻塞进入系统。 if (AppNetworkTool.subscription == null) { AppNetworkTool.listenNetwork(); } SyncCheckTool.instance.startAll(); startupFinished.value = true; startupStatusLabel.value = startupSteps .any((item) => item.status == AppStartupStepStatus.warning) ? '受限启动完成' : '启动完成'; await Future.delayed(const Duration(milliseconds: 650)); if (Get.currentRoute == RouteNames.load) { Get.offAllNamed(destination); } } catch (e, stack) { final currentIndex = startupSteps.indexWhere( (item) => item.status == AppStartupStepStatus.running, ); if (currentIndex >= 0) { _setStartupStep( currentIndex, status: AppStartupStepStatus.failure, detail: '启动失败,请检查终端配置或本地设备后重试', ); } startupStatusLabel.value = '启动失败'; startupFailureMessage.value = '终端初始化未完成,请检查设备状态后重试。'; _closePartiallyInitializedResources(); logger.e('|$_tag|初始化失败error:|$e|stack:|$stack|'); } finally { _initializing = false; } } Future retryStartup({String destination = RouteNames.login}) async { if (_initializing) return; await initAsync(destination: destination, force: true); } /// Floor 已完成建库后才允许原生读取共享数据库;这里只恢复已激活设备的内存库,不自动联网激活。 Future _preloadFaceLibrary() async { if (AppRuntimeTool.useManualHardwareInput) return; try { final status = await AppFaceTool.getStatus(); if (status?.activated != true || status!.registrationInProgress) return; if (status.databaseCount > 0 && !status.libraryConsistent) { await FaceDiscernPlugs.rebuildFaceLibrary(); } } catch (e, stack) { logger.w('|$_tag|预加载人脸库失败error:|$e|stack:|$stack|'); } } void _closePartiallyInitializedResources() { if (printManageInitialized) printManage.close(); if (serialCardManageInitialized) serialCardManage.close(); if (steelyardManageInitialized) srialSteelyardManage.close(); AppNetworkTool.closeListenNetwork(); SyncCheckTool.instance.dispose(); } void _resetStartupSteps() { for (var index = 0; index < startupSteps.length; index++) { final item = startupSteps[index]; startupSteps[index] = item.copyWith( status: AppStartupStepStatus.waiting, detail: switch (index) { 0 => '等待读取设备模式与本地设置', 1 => '等待准备订单、商品与用户数据', _ => '等待准备打印、读卡与电子秤服务', }, ); } } void _setStartupStep( int index, { required AppStartupStepStatus status, required String detail, }) { startupSteps[index] = startupSteps[index].copyWith( status: status, detail: detail, ); } ({ String title, String detail, String stepDetail, bool manualHardwareInput, }) _resolveEnvironment(SysSetting setting) { final manualHardwareInput = AppRuntimeTool.isEmulator || setting.manualHardwareInput; if (AppRuntimeTool.isEmulator) { return ( title: '虚拟机调试模式', detail: '已切换为手动硬件输入,不启动真实副屏和实体串口设备。', stepDetail: '已识别虚拟机,终端配置读取完成', manualHardwareInput: true, ); } if (manualHardwareInput) { return ( title: '手动硬件输入模式', detail: '终端配置为手动录入硬件结果,真实副屏仍按设备能力启用。', stepDetail: '终端配置读取完成,已启用手动硬件输入', manualHardwareInput: true, ); } if (AppRuntimeTool.hasMultipleDisplays) { return ( title: '双屏终端模式', detail: '已检测到 ${AppRuntimeTool.displayCount} 个显示设备,副屏将在业务页面按需启动。', stepDetail: '终端配置读取完成,已检测到可用双屏环境', manualHardwareInput: false, ); } return ( title: '单屏终端模式', detail: '当前仅检测到主屏,收银核心流程可继续使用,副屏功能不会启动。', stepDetail: '终端配置读取完成,当前为单屏环境', manualHardwareInput: false, ); } }