Files
biz_xfj_cloud/lib/common/utils/app/control/app_control.dart
T
2026-07-24 15:27:20 +08:00

361 lines
11 KiB
Dart

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 = <AppStartupStepState>[
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<void> 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<void> retryStartup({String destination = RouteNames.login}) async {
if (_initializing) return;
await initAsync(destination: destination, force: true);
}
/// Floor 已完成建库后才允许原生读取共享数据库;这里只恢复已激活设备的内存库,不自动联网激活。
Future<void> _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,
);
}
}