Files
pj_supermarket_cloud/lib/pages/setting/controller.dart
T
2026-09-11 00:22:34 +08:00

778 lines
27 KiB
Dart

import 'dart:async';
import 'package:common_utils/common_utils.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:forui/forui.dart';
import 'package:get/get.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
class SettingController extends GetxController {
static const String _tag = "设置 Controller";
final appC = Get.find<AppControl>();
Rx<SysSetting?> sysSetting = Rx(null);
Rx<UserSetting?> userSetting = Rx(null);
/// 是否使用人工输入代替扫码、读卡和人脸等真实硬件输入。
final manualHardwareInput = false.obs;
/// Android Kiosk 当前能力与运行状态,应用恢复前台时刷新。
final kioskStatus = AppRuntimeTool.kioskStatus.obs;
/// Root 手动刷新是否正在执行,用于禁用重复点击并展示局部进度。
final rootRefreshing = false.obs;
/// Root 一键配置 Device Owner 是否正在执行,命令期间禁止重复提交。
final kioskProvisioning = false.obs;
/// 解除 Device Owner 是否正在执行,防止连点重复触发清除。
final kioskClearing = false.obs;
/// Kiosk 开关是否正在同步原生 Lock Task,防止连续点击形成状态反转。
final kioskSwitching = false.obs;
/// 最近一次 Root 配置 Kiosk 的失败处理建议;成功或重新加载设置后清空。
final kioskProvisionMessage = ''.obs;
// 全局文字大小
var globalFontSizeC = FRadioSelectGroupController<String>(
value: SettingGlobalFontSize.xl3.code,
);
/// 支付设置的界面排序,初始顺序来自枚举并由本地持久化覆盖。
final payTypeOrder = <OrderPayDisplayType>[
...OrderPayDisplayType.values,
].obs;
/// 允许在收银弹框内重新识别人员的支付方式集合;初始化采用业务默认值,配置加载后以持久化结果为准。
final cashierReidentifiablePayTypes =
AppSettingTool.defaultCashierReidentifiablePayTypes.toSet().obs;
/// 支付偏好串行保存中;保存期间的新变更会继续下一轮写入。
bool _payPersisting = false;
/// 支付偏好存在待写入变更;连续拖动或切换开关时合并为一次落盘。
bool _payPendingPersist = false;
/// 商品餐段时间配置;餐段校验弹框和自动切换按该时间范围判定。
final productMealPeriodSchedule = ProductMealPeriodSchedule.defaults.obs;
/// 餐段配置正在保存时禁用重复提交并展示按钮状态。
final productMealPeriodSaving = false.obs;
_initData() {
update(["setting"]);
}
RxList<SettingMenu> menuList = <SettingMenu>[
SettingMenu(
prefixIcon: FAssets.icons.bookCopy,
title: "基本信息",
type: SettingMenuType.baseInfo,
),
SettingMenu(
prefixIcon: FAssets.icons.databaseBackup,
title: "资源备份",
type: SettingMenuType.backup,
),
SettingMenu(
prefixIcon: FAssets.icons.refreshCw,
title: "定时任务",
type: SettingMenuType.syncSetting,
),
SettingMenu(
prefixIcon: FAssets.icons.japaneseYen,
title: "支付设置",
type: SettingMenuType.paySetting,
),
SettingMenu(
prefixIcon: FAssets.icons.users,
title: "离线购买人员",
type: SettingMenuType.offlineUserSetting,
),
SettingMenu(
prefixIcon: FAssets.icons.utensils,
title: "餐段时间配置",
type: SettingMenuType.mealPeriodSetting,
),
SettingMenu(
prefixIcon: FAssets.icons.scanFace,
title: "人脸管理",
type: SettingMenuType.faceSetting,
),
SettingMenu(
prefixIcon: FAssets.icons.settings2,
title: "人脸设置",
type: SettingMenuType.facePluginSetting,
),
SettingMenu(
prefixIcon: FAssets.icons.shoppingCart,
title: "商品管理",
type: SettingMenuType.productSetting,
),
SettingMenu(
prefixIcon: FAssets.icons.logs,
title: "日志预览",
type: SettingMenuType.logPreview,
),
SettingMenu(
prefixIcon: FAssets.icons.rocket,
title: "版本管理",
type: SettingMenuType.versionSetting,
),
].obs;
Rx<SettingMenuType> cuurentMenuType = SettingMenuType.baseInfo.obs;
/// 各任务尚未保存的 Cron 输入草稿,以任务类型隔离编辑状态。
final scheduledTaskCronDrafts = <ScheduledTaskType, String>{}.obs;
/// 各任务 Cron 草稿的本地校验错误,保存成功或继续输入后移除。
final scheduledTaskCronErrors = <ScheduledTaskType, String>{}.obs;
/// 正在持久化配置的任务集合,用于禁止同任务重复提交并局部禁用控件。
final savingScheduledTasks = <ScheduledTaskType>{}.obs;
late StreamSubscription mqUpdateListen;
@override
void onInit() async {
super.onInit();
await getSetting();
await _initScheduledTasks();
globalFontSizeC.addValueListener((v) {
final fontSizeType = SettingGlobalFontSize.values.fromCode(
v.first,
);
if (fontSizeType == null) return;
final appTheme = Get.find<AppThemeControl>();
appTheme.setSizeScalar(fontSizeType);
});
mqUpdateListen = appC.mqUpdateEventBus.on<MqtActionEnum>().listen(
(event) async {
if (event == MqtActionEnum.CONFIG) {
sysSetting.value = _orderedSysSetting(
await AppSettingTool.getSysSetting(),
);
}
},
);
}
@override
void onReady() {
super.onReady();
_initData();
}
@override
void onClose() {
super.onClose();
globalFontSizeC.dispose();
mqUpdateListen.cancel();
// 隐藏键盘
// FocusManager.instance.primaryFocus?.unfocus();
// SystemChannels.textInput.invokeMethod('TextInput.hide');
}
void doMenu(SettingMenu v) {
if (v.type == cuurentMenuType.value) {
return;
}
cuurentMenuType.value = v.type;
menuList.refresh();
}
/// 按菜单类型直接切换(供人脸设置页跳转人脸管理等场景使用)。
void openMenu(SettingMenuType type) {
cuurentMenuType.value = type;
menuList.refresh();
}
/// 启动统一调度器并用已保存的 Cron 规则初始化草稿。
Future<void> _initScheduledTasks() async {
try {
await SyncCheckTool.instance.startAll();
scheduledTaskCronDrafts.assignAll({
for (final entry in SyncCheckTool.instance.configs.entries)
entry.key: entry.value.cron,
});
} catch (e, stack) {
logger.e("|$_tag|定时任务初始化失败error:|$e|stack:|$stack|");
}
}
/// 更新指定任务的 Cron 草稿,输入变化后清除该任务旧校验错误。
void setScheduledTaskCronDraft(ScheduledTaskType type, String value) {
scheduledTaskCronDrafts[type] = value;
scheduledTaskCronErrors.remove(type);
}
/// 切换任务自动调度开关;关闭运行保障任务前要求操作人员确认。
Future<void> setScheduledTaskEnabled(
ScheduledTaskType type,
bool enabled,
) async {
final config = SyncCheckTool.instance.configs[type];
if (config == null || config.enabled == enabled) return;
if (!enabled && _isRuntimeGuardTask(type)) {
final confirmed = await appMessageDialog(
Get.context!,
'关闭后终端将不再自动执行该运行保障任务,是否继续?',
);
if (!confirmed) return;
}
await _saveScheduledTaskConfig(config.copyWith(enabled: enabled));
}
/// 切换任务是否仅在完整空闲窗口自动执行,并持久化到结构化任务表。
Future<void> setScheduledTaskIdleOnly(
ScheduledTaskType type,
bool idleOnly,
) async {
final config = SyncCheckTool.instance.configs[type];
if (config == null || config.idleOnly == idleOnly) return;
await _saveScheduledTaskConfig(config.copyWith(idleOnly: idleOnly));
}
/// Cron 弹窗返回经过本地解析校验的表达式,Controller 仍在持久化边界再次校验。
Future<void> setScheduledTaskCron(
ScheduledTaskType type,
String expression,
) async {
final config = SyncCheckTool.instance.configs[type];
if (config == null) return;
final cron = expression.trim();
if (!SyncCheckTool.isValidCron(cron)) {
Toast.error(msg: '请输入有效的 6 段 Cron 表达式');
return;
}
if (cron == config.cron) return;
await _saveScheduledTaskConfig(config.copyWith(cron: cron));
}
/// 串联调度重建和 SQLite 保存,负责重复提交保护、草稿回退及用户反馈。
Future<void> _saveScheduledTaskConfig(ScheduledTaskConfig config) async {
if (savingScheduledTasks.contains(config.type)) return;
final previous = SyncCheckTool.instance.configs[config.type];
savingScheduledTasks.add(config.type);
try {
await SyncCheckTool.instance.updateConfig(config);
scheduledTaskCronDrafts[config.type] = config.cron;
scheduledTaskCronErrors.remove(config.type);
Toast.success(msg: '任务配置已保存');
} catch (e, stack) {
if (previous != null) {
scheduledTaskCronDrafts[config.type] = previous.cron;
}
logger.e(
"|$_tag|保存定时任务失败type:${config.type.name}|error:$e|stack:$stack|");
Toast.error(msg: '任务配置保存失败,请重试');
} finally {
savingScheduledTasks.remove(config.type);
}
}
/// 手动触发指定任务,并根据立即执行、排队或合并结果向操作人员反馈。
Future<void> runScheduledTaskNow(ScheduledTaskType type) async {
final scheduler = SyncCheckTool.instance;
if (scheduler.runningTasks.contains(type) ||
scheduler.pendingTasks.contains(type)) {
await scheduler.trigger(type);
Toast.success(msg: '已合并到待执行任务');
return;
}
await scheduler.trigger(type);
if (scheduler.pendingTasks.contains(type)) {
Toast.success(msg: '任务已排队,将在安全窗口执行');
return;
}
final record = scheduler.records[type];
if (record?.success == true) {
Toast.success(msg: record?.message ?? '任务执行完成');
} else {
Toast.error(msg: record?.message ?? '任务执行失败');
}
}
/// 返回关闭自动调度前需要二次确认的 MQTT、打印、广播和设备状态保障任务。
bool _isRuntimeGuardTask(ScheduledTaskType type) => const {
ScheduledTaskType.mqReconnect,
ScheduledTaskType.printCheck,
ScheduledTaskType.broadcastCheck,
ScheduledTaskType.deviceStatus,
}.contains(type);
// 同步设备信息(人脸设置页等入口使用)
void syncConfig() async {
final isNetwork = AppNetworkTool.isAutoNetworkMsg();
if (!isNetwork) return;
EasyLoading.show(
status: '同步中...',
maskType: EasyLoadingMaskType.black,
);
try {
final result = await SyncConfigTool.autoSyncSysInfo();
// result.setting 是同步后最新快照,直接刷新 Rx 才能让基本信息页显示新配置。
if (result != null) {
sysSetting.value = _orderedSysSetting(result.setting);
}
Toast.success(msg: "同步设备信息成功");
EasyLoading.dismiss();
} catch (e, stack) {
Toast.error(msg: "同步设备信息错误");
logger.e("|$_tag|同步设备信息error:|$e|stack:|$stack|");
EasyLoading.dismiss();
}
}
// 备份sql
void backupsSql() async {
EasyLoading.show(
status: '备份中...',
maskType: EasyLoadingMaskType.black,
);
try {
final bool = await AppFileTool.backupSqlFile();
if (!bool) throw Error();
EasyLoading.dismiss();
Toast.success(msg: "备份中sql成功");
EasyLoading.dismiss();
} catch (e) {
Toast.error(msg: "备份中sql失败");
logger.e("|$_tag|备份中sqlerror:|$e|");
EasyLoading.dismiss();
}
}
// 备份日志
void backupsLog() async {
Toast.success(msg: "当前日志为实时备份");
}
// sql恢复
Future<void> recoverSql() async {
EasyLoading.show(
status: '恢复中...',
maskType: EasyLoadingMaskType.black,
);
try {
await AppFileTool.useLastBackDatabase();
EasyLoading.dismiss();
Toast.success(msg: "sql恢复成功");
EasyLoading.dismiss();
} catch (e) {
Toast.error(msg: "sql恢复失败");
logger.e("|$_tag|sql恢复失败error:|$e|");
EasyLoading.dismiss();
}
}
Future<void> getSetting() async {
final setting = await AppSettingTool.getSysSetting();
final storedPayTypeOrder = await AppSettingTool.getPayTypeOrder();
final reidentifiablePayTypes =
await AppSettingTool.getCashierReidentifiablePayTypes();
final byCode = {
for (final item in OrderPayDisplayType.values) item.code: item,
};
final restoredPayTypeOrder = <OrderPayDisplayType>[];
for (final code in storedPayTypeOrder) {
final item = byCode[code];
if (item != null && !restoredPayTypeOrder.contains(item)) {
restoredPayTypeOrder.add(item);
}
}
for (final item in OrderPayDisplayType.values) {
if (!restoredPayTypeOrder.contains(item)) {
restoredPayTypeOrder.add(item);
}
}
payTypeOrder.assignAll(restoredPayTypeOrder);
cashierReidentifiablePayTypes.assignAll(reidentifiablePayTypes);
sysSetting.value = _orderedSysSetting(setting);
productMealPeriodSchedule.value =
await AppSettingTool.getProductMealPeriodSchedule();
manualHardwareInput.value =
AppRuntimeTool.isEmulator || sysSetting.value!.manualHardwareInput;
AppRuntimeTool.applyManualHardwareInput(
sysSetting.value!.manualHardwareInput,
);
if (sysSetting.value!.globalFontSize != null) {
globalFontSizeC.value = {sysSetting.value!.globalFontSize!.code};
}
}
/// 将设置中的启用支付方式按本地排序重排;null 保持 null 由收银页按默认处理。
SysSetting _orderedSysSetting(SysSetting setting) {
if (setting.usePayDisplayType != null) {
setting.usePayDisplayType = payTypeOrder
.where(setting.usePayDisplayType!.contains)
.toList();
}
return setting;
}
/// 启用或停用支付方式,始终至少保留一种并修正失效的默认方式。
void togglePayType(OrderPayDisplayType type) {
final setting = sysSetting.value;
if (setting == null) return;
final enabled = List<OrderPayDisplayType>.from(
setting.usePayDisplayType ?? OrderPayDisplayType.values,
);
if (enabled.contains(type)) {
if (enabled.length == 1) {
Toast.error(msg: '至少保留一种支付方式');
return;
}
enabled.remove(type);
} else {
enabled.add(type);
}
final orderedEnabled = payTypeOrder.where(enabled.contains).toList();
// 默认支付方式必须属于启用集合,否则收银页会出现默认值不可选择的矛盾状态。
if (!orderedEnabled.contains(setting.defaultPayDisplayType)) {
setting.defaultPayDisplayType = orderedEnabled.first;
}
setting.usePayDisplayType = orderedEnabled;
sysSetting.refresh();
_schedulePayPreferencesPersist();
}
/// 切换指定支付方式能否在收银弹框中重新识别人员,并立即安排本机持久化。
void togglePayTypeReidentification(OrderPayDisplayType type) {
if (cashierReidentifiablePayTypes.contains(type)) {
cashierReidentifiablePayTypes.remove(type);
} else {
cashierReidentifiablePayTypes.add(type);
}
_schedulePayPreferencesPersist();
}
/// 调整支付方式展示顺序,同时保持启用集合与完整排序一致。
void reorderPayType(int oldIndex, int newIndex) {
if (oldIndex == newIndex ||
oldIndex < 0 ||
oldIndex >= payTypeOrder.length) {
return;
}
if (newIndex > oldIndex) newIndex -= 1;
if (newIndex < 0 || newIndex >= payTypeOrder.length) return;
final updated = payTypeOrder.toList();
final item = updated.removeAt(oldIndex);
updated.insert(newIndex, item);
payTypeOrder.assignAll(updated);
final setting = sysSetting.value;
if (setting != null) {
final visible = setting.usePayDisplayType ?? OrderPayDisplayType.values;
setting.usePayDisplayType = updated.where(visible.contains).toList();
sysSetting.refresh();
}
_schedulePayPreferencesPersist();
}
/// 恢复全部支付方式、默认枚举顺序和默认重新识别范围,并安排持久化。
void resetPayTypes() {
payTypeOrder.assignAll(OrderPayDisplayType.values);
cashierReidentifiablePayTypes.assignAll(
AppSettingTool.defaultCashierReidentifiablePayTypes,
);
final setting = sysSetting.value;
if (setting != null) {
setting.usePayDisplayType = OrderPayDisplayType.values.toList();
if (!setting.usePayDisplayType!.contains(setting.defaultPayDisplayType)) {
setting.defaultPayDisplayType = setting.usePayDisplayType!.first;
}
sysSetting.refresh();
}
_schedulePayPreferencesPersist();
}
/// 设置默认支付方式;未启用的支付方式不能成为默认值。
void setDefaultPayType(OrderPayDisplayType type) {
final setting = sysSetting.value;
if (setting == null) return;
final enabled = setting.usePayDisplayType ?? OrderPayDisplayType.values;
if (!enabled.contains(type)) {
Toast.error(msg: '请先启用该支付方式');
return;
}
setting.defaultPayDisplayType = type;
sysSetting.refresh();
_schedulePayPreferencesPersist();
}
/// 支付设置表格“是否默认”开关入口;默认方式唯一,关闭当前默认时提示改选其他方式。
void toggleDefaultPayType(OrderPayDisplayType type, bool value) {
if (value) {
setDefaultPayType(type);
return;
}
if (sysSetting.value?.defaultPayDisplayType == type) {
Toast.error(msg: '默认支付方式不能取消,请选择其他支付方式');
}
}
/// 支付方式连续拖动或切换时串行保存,始终以最后一次界面状态为准。
void _schedulePayPreferencesPersist() {
_payPendingPersist = true;
if (!_payPersisting) unawaited(_flushPayPreferences());
}
/// 串行写入支付方式偏好,保存期间的新变更会继续下一轮写入。
Future<void> _flushPayPreferences() async {
_payPersisting = true;
while (_payPendingPersist) {
_payPendingPersist = false;
final setting = sysSetting.value;
if (setting == null) continue;
try {
await AppSettingTool.setPayTypePreferences(
sysInfo: setting,
order: payTypeOrder,
reidentifiableTypes: cashierReidentifiablePayTypes,
);
} catch (e, stack) {
logger.e('|$_tag|保存支付管理配置失败error:$e|stack:$stack|');
Toast.error(msg: '支付设置保存失败,请重试');
}
}
_payPersisting = false;
}
Future<bool> checkIsBind() async {
final sysSetting = await AppSettingTool.getSysSetting();
final id = sysSetting.serviceMerchantId;
if (ObjectUtil.isEmpty(id)) {
return false;
}
return true;
}
/// 校验并保存餐段时间;空档允许保留,相邻或交叉范围由强类型模型统一处理。
Future<bool> saveProductMealPeriods(List<ProductMealPeriod> periods) async {
if (productMealPeriodSaving.value) return false;
final error = ProductMealPeriodSchedule.validate(periods);
if (error != null) {
Toast.error(msg: error);
return false;
}
productMealPeriodSaving.value = true;
try {
final schedule = ProductMealPeriodSchedule.checked(periods);
await AppSettingTool.setProductMealPeriodSchedule(schedule);
productMealPeriodSchedule.value = schedule;
Toast.success(msg: '餐段配置已保存');
return true;
} catch (e, stack) {
logger.e('|$_tag|保存餐段配置失败error:$e|stack:$stack|');
Toast.error(msg: '餐段配置保存失败,请重试');
return false;
} finally {
productMealPeriodSaving.value = false;
}
}
/// 从 Android 原生能力重新读取当前 Kiosk 状态。
Future<void> refreshKioskStatus() async {
kioskStatus.value = await AppRuntimeTool.refreshKioskStatus();
}
/// 立即执行真实 Root 命令并局部持久化结果;失败状态也作为有效检查结果保存。
Future<void> refreshRootAccess({bool showFeedback = true}) async {
if (rootRefreshing.value) return;
rootRefreshing.value = true;
try {
final result = await AppRuntimeTool.checkRootAccess();
await AppSettingTool.setRootAccessResult(result);
final setting = sysSetting.value;
if (setting != null) {
setting.rootAccessStatus = result.status;
setting.rootAccessCheckedAt = result.checkedAt;
setting.rootAccessDiagnostic = result.diagnostic;
sysSetting.refresh();
}
if (showFeedback) {
if (result.available) {
Toast.success(msg: 'Root 权限可用');
} else {
Toast.error(msg: result.diagnostic);
}
}
} catch (e, stack) {
logger.e('|$_tag|刷新Root状态失败error:|$e|stack:$stack|');
if (showFeedback) Toast.error(msg: 'Root 状态检查失败,请稍后重试');
} finally {
rootRefreshing.value = false;
}
}
/// 经用户二次确认后通过 Root 配置 Device Owner;不删除账户、不移除其他 Owner。
Future<void> enableKioskWithRoot() async {
final setting = sysSetting.value;
if (setting == null || kioskProvisioning.value) return;
if (setting.rootAccessStatus != RootAccessStatus.available) {
kioskProvisionMessage.value = 'Root 不可用,请先刷新 Root 状态并完成授权';
return;
}
final confirmed = await appMessageDialog(
Get.context!,
'将使用 Root 把当前应用配置为 Android Device Owner,并立即进入 Kiosk。该操作不会删除账户或移除其他 Device Owner,系统拒绝时只会返回原因。是否继续?',
);
if (!confirmed) return;
kioskProvisioning.value = true;
kioskProvisionMessage.value = '';
EasyLoading.show(
status: '正在启用专用设备模式...',
maskType: EasyLoadingMaskType.black,
);
try {
final result = await AppRuntimeTool.enableKioskWithRoot();
kioskStatus.value = result.status;
if (result.success) {
kioskProvisionMessage.value = '';
Toast.success(msg: 'Kiosk 已启用并自动进入');
} else {
kioskProvisionMessage.value = result.message;
Toast.error(msg: result.message);
}
} catch (e, stack) {
logger.e('|$_tag|Root启用Kiosk失败error:|$e|stack:$stack|');
kioskProvisionMessage.value = '启用失败,请稍后重试并检查系统 Device Owner 状态';
Toast.error(msg: kioskProvisionMessage.value);
} finally {
EasyLoading.dismiss();
kioskProvisioning.value = false;
}
}
/// 经用户二次确认后解除 Device Owner;解除后本应用恢复为普通应用、可正常卸载。
Future<void> clearKioskDeviceOwner() async {
if (kioskClearing.value || !kioskStatus.value.deviceOwner) return;
final confirmed = await appMessageDialog(
Get.context!,
'解除后 Kiosk 锁定与开机自启立即失效,本应用恢复为普通应用,随后即可正常卸载。是否继续?',
);
if (!confirmed) return;
kioskClearing.value = true;
EasyLoading.show(
status: '正在解除设备所有者...',
maskType: EasyLoadingMaskType.black,
);
try {
final result = await AppRuntimeTool.clearKioskDeviceOwner();
kioskStatus.value = result.status;
if (result.success) {
kioskProvisionMessage.value = '';
Toast.success(msg: '已解除设备所有者,现在可以正常卸载本应用');
} else {
kioskProvisionMessage.value = result.message;
Toast.error(msg: result.message);
}
} catch (e, stack) {
logger.e('|$_tag|解除DeviceOwner失败error:$e|stack:$stack|');
kioskProvisionMessage.value = '解除失败,请稍后重试';
Toast.error(msg: kioskProvisionMessage.value);
} finally {
EasyLoading.dismiss();
kioskClearing.value = false;
}
}
/// 切换主控手动硬件输入;虚拟机强制开启,开启前需操作员确认。
Future<void> setManualHardwareInput(bool enabled) async {
if (AppRuntimeTool.isEmulator && !enabled) {
Toast.error(msg: '虚拟机必须使用手动硬件输入模式');
manualHardwareInput.value = true;
return;
}
if (enabled && !manualHardwareInput.value) {
final confirmed = await appMessageDialog(
Get.context!,
'设置为主控后,扫码、饭卡读写和人脸识别将改为人工输入,是否继续?',
);
if (!confirmed) return;
}
final setting = sysSetting.value;
if (setting == null) return;
setting.manualHardwareInput = enabled;
await AppSettingTool.setSysSetting(setting);
AppRuntimeTool.applyManualHardwareInput(enabled);
manualHardwareInput.value = enabled;
sysSetting.refresh();
Toast.success(msg: enabled ? '已设置为主控手动输入模式' : '已恢复真实硬件模式');
}
/// 立即开启或关闭 Kiosk 自动进入,并同步原生 Lock Task 状态。
Future<void> setKioskAutoEnterEnabled(bool enabled) async {
if (!kioskStatus.value.available || kioskSwitching.value) return;
kioskSwitching.value = true;
try {
final status = await AppRuntimeTool.setKioskAutoEnterEnabled(enabled);
kioskStatus.value = status;
final applied =
status.autoEnterEnabled == enabled && status.active == enabled;
if (!applied) {
Toast.error(
msg: enabled ? 'Kiosk 未能立即开启,请检查设备策略' : 'Kiosk 未能立即关闭,请重试',
);
return;
}
Toast.success(msg: enabled ? 'Kiosk 已立即开启' : 'Kiosk 已立即关闭');
} finally {
kioskSwitching.value = false;
}
}
/// 保存全局字体倍率并立即应用到运行中主题。
Future<void> setGlobalFontSize(SettingGlobalFontSize fontSize) async {
final setting = sysSetting.value;
if (setting == null || setting.globalFontSize == fontSize) return;
setting.globalFontSize = fontSize;
await AppSettingTool.setSysSetting(setting);
Get.find<AppThemeControl>().setSizeScalar(fontSize);
sysSetting.refresh();
}
/// 保存按钮圆角并立即应用到运行中主题。
Future<void> setButtonRadius(SettingButtonRadius value) async {
final setting = sysSetting.value;
if (setting == null || setting.buttonRadius == value) return;
setting.buttonRadius = value;
await AppSettingTool.setSysSetting(setting);
Get.find<AppThemeControl>().setButtonRadius(value);
sysSetting.refresh();
}
/// 保存卡片圆角并立即应用到运行中主题。
Future<void> setCardRadius(SettingCardRadius value) async {
final setting = sysSetting.value;
if (setting == null || setting.cardRadius == value) return;
setting.cardRadius = value;
await AppSettingTool.setSysSetting(setting);
Get.find<AppThemeControl>().setCardRadius(value);
sysSetting.refresh();
}
/// 保存按钮尺寸档位并立即应用到运行中主题。
Future<void> setButtonSize(SettingButtonSize value) async {
final setting = sysSetting.value;
if (setting == null || setting.buttonSize == value) return;
setting.buttonSize = value;
await AppSettingTool.setSysSetting(setting);
Get.find<AppThemeControl>().setButtonSize(value);
sysSetting.refresh();
}
}