118 lines
4.3 KiB
Dart
118 lines
4.3 KiB
Dart
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/services/face_recognition_session_service.dart';
|
|
|
|
/// 同步调度与顶栏共用的空闲状态快照,避免界面自行拼装支付、弹窗和重型任务规则。
|
|
class SyncIdleSnapshot {
|
|
/// 创建一次不可变的终端空闲判断快照。
|
|
const SyncIdleSnapshot({
|
|
required this.foreground,
|
|
required this.safeWindow,
|
|
required this.idle,
|
|
required this.inactiveFor,
|
|
required this.reason,
|
|
});
|
|
|
|
/// 应用当前是否处于 resumed 前台状态。
|
|
final bool foreground;
|
|
|
|
/// 当前是否避开了支付遮罩、人脸会话、业务弹窗和其他重型任务。
|
|
final bool safeWindow;
|
|
|
|
/// 当前是否同时满足安全窗口和连续 60 秒无人操作。
|
|
final bool idle;
|
|
|
|
/// 距离最近一次指针操作或应用恢复前台经过的时长。
|
|
final Duration inactiveFor;
|
|
|
|
/// 当前未达到空闲状态的首要中文原因;空闲时为空字符串。
|
|
final String reason;
|
|
|
|
/// 返回顶栏使用的简短空闲状态文案。
|
|
String get label => idle ? '空闲' : '忙碌';
|
|
}
|
|
|
|
/// 统一记录终端用户活动和重型后台任务,用于选择不会干扰支付、人脸和弹框的安全执行窗口。
|
|
class SyncIdleTool {
|
|
/// 隐藏构造器,空闲判断必须通过全局单例维护同一份活动时间和任务计数。
|
|
SyncIdleTool._();
|
|
|
|
/// 应用级空闲检测单例。
|
|
static final SyncIdleTool instance = SyncIdleTool._();
|
|
|
|
/// 自动空闲任务要求的最短连续无人操作时间。
|
|
static const Duration idleDuration = Duration(seconds: 60);
|
|
|
|
/// 最近一次用户指针操作或恢复前台的本地时间。
|
|
DateTime _lastActivityAt = DateTime.now();
|
|
|
|
/// 应用是否处于可安全执行终端任务的前台生命周期。
|
|
bool _foreground = true;
|
|
|
|
/// 当前并行执行的重型同步任务数量,用计数支持嵌套开始和结束。
|
|
int _heavyTaskCount = 0;
|
|
|
|
/// 记录一次终端触控活动并重新开始 60 秒空闲计时。
|
|
void recordUserActivity() {
|
|
_lastActivityAt = DateTime.now();
|
|
}
|
|
|
|
/// 根据应用生命周期更新前台状态,恢复前台时视为一次用户活动。
|
|
void updateLifecycle(AppLifecycleState state) {
|
|
_foreground = state == AppLifecycleState.resumed;
|
|
if (_foreground) recordUserActivity();
|
|
}
|
|
|
|
/// 标记一个重型任务开始,阻止其他需要安全窗口的任务并发执行。
|
|
void beginHeavyTask() => _heavyTaskCount++;
|
|
|
|
/// 标记一个重型任务结束;计数最低保持为零以容忍重复清理。
|
|
void endHeavyTask() {
|
|
if (_heavyTaskCount > 0) _heavyTaskCount--;
|
|
}
|
|
|
|
/// 手动任务和后台拉取只等待业务安全窗口,不额外要求 60 秒无人操作。
|
|
SyncIdleSnapshot get snapshot {
|
|
final faceActive = Get.isRegistered<FaceRecognitionSessionService>() &&
|
|
Get.find<FaceRecognitionSessionService>().isActive;
|
|
final route = Get.context == null ? null : ModalRoute.of(Get.context!);
|
|
final dialogOpen = Get.isDialogOpen == true ||
|
|
Get.isBottomSheetOpen == true ||
|
|
route?.isCurrent == false;
|
|
final inactiveFor = DateTime.now().difference(_lastActivityAt);
|
|
final reason = !_foreground
|
|
? '应用不在前台'
|
|
: EasyLoading.isShow
|
|
? '前台操作进行中'
|
|
: faceActive
|
|
? '人脸识别进行中'
|
|
: dialogOpen
|
|
? '业务弹窗处理中'
|
|
: _heavyTaskCount > 0
|
|
? '后台同步进行中'
|
|
: inactiveFor < idleDuration
|
|
? '最近有终端操作'
|
|
: '';
|
|
final safeWindow = _foreground &&
|
|
!EasyLoading.isShow &&
|
|
!faceActive &&
|
|
!dialogOpen &&
|
|
_heavyTaskCount == 0;
|
|
return SyncIdleSnapshot(
|
|
foreground: _foreground,
|
|
safeWindow: safeWindow,
|
|
idle: safeWindow && inactiveFor >= idleDuration,
|
|
inactiveFor: inactiveFor,
|
|
reason: reason,
|
|
);
|
|
}
|
|
|
|
/// 当前是否满足手动执行和 MQTT 拉取所需的业务安全窗口。
|
|
bool get isSafeWindow => snapshot.safeWindow;
|
|
|
|
/// 当前是否满足自动空闲任务的完整空闲条件。
|
|
bool get isIdle => snapshot.idle;
|
|
}
|