Files
Ichino/lib/widgets/risk/risk_run_mixin.dart
Bad0RANG3 e9357f5969 refactor: 拆分主页与公共组件,扫码登录改走 WinHTTP
- 抽出 lib/core 的日期/JSON 工具,收敛各页重复解析
- 主页与音乐风险页拆成组件,风险功能共用一套运行框架
- 修复设置页作为主页页签保存时弹掉根路由导致的黑屏
- 登录对齐 Astral aimedb 协议:84 位二维码校验、签名与请求头修正,
  补 GlobalSign 中间证书;Windows 改用 WinHTTP 绕过服务端对
  BoringSSL 的 TLS 指纹拦截(证书校验保持开启)
- 补开发预览数据与测试
2026-10-25 15:51:10 +08:00

97 lines
3.0 KiB
Dart

import 'package:flutter/widgets.dart';
import '../../config/strings.dart';
import '../../config/title_server_config.dart';
import '../../services/title_api_service.dart';
import '../step_progress.dart';
/// 高危功能共用的执行状态:发包中、自动退登、阶段与错误。
///
/// 六个页面原来各自维护一份完全相同的字段和 `_updateStep`,导致同样的
/// 「失败落到哪一步、退登时序怎么走」要在每个文件里各看一遍。
mixin RiskRunMixin<T extends StatefulWidget> on State<T> {
bool riskFetching = false;
bool riskRunning = false;
bool riskAutoLogout = true;
RiskStep riskStep = RiskStep.idle;
String riskStepMessage = '';
String? riskError;
void updateRiskStep(RiskStep step, [String? message]) {
if (!mounted) return;
setState(() {
riskStep = step;
riskStepMessage = message ?? '';
});
}
void setRiskFetching(bool value) {
if (!mounted) return;
setState(() => riskFetching = value);
}
void setRiskRunning(bool value) {
if (!mounted) return;
setState(() => riskRunning = value);
}
void setRiskAutoLogout(bool value) {
if (!mounted) return;
setState(() => riskAutoLogout = value);
}
void clearRiskError() {
if (!mounted || riskError == null) return;
setState(() => riskError = null);
}
/// 把异常写进阶段卡。`TitleApiException` 的 message 已经是给用户看的文案,
/// 其他异常保留 `toString()` 以便排查。
void reportRiskError(Object error) {
final message = describeApiError(error);
if (!mounted) return;
setState(() {
riskError = message;
riskStep = RiskStep.failed;
riskStepMessage = message;
});
}
/// 成功后的自动退登:留一点时间让阶段卡显示完成态,再由 HomePage 结算并
/// pop 回标题页。调用方负责先判断 `onExitToTitle` 是否存在。
Future<void> autoLogoutAndExit(
Future<void> Function()? onExitToTitle, {
Duration delay = const Duration(seconds: 2),
}) async {
if (!riskAutoLogout || onExitToTitle == null) return;
await Future<void>.delayed(delay);
if (!mounted) return;
updateRiskStep(RiskStep.logout, AppStrings.exitingToTitle);
await onExitToTitle();
}
}
/// 执行前的统一门禁,顺序固定为「配置 → 登录态 → 冷却」。
///
/// 返回要提示给用户的文案,`null` 表示可以继续。功能票只依赖
/// `loginDateTime`,其余 `UpsertUserAll` 页面还需要 `loginId`。
String? riskPreconditionError({
required int? loginDateTime,
required int? loginId,
required int cooldownRemaining,
required String notLoggedInMessage,
required String Function(int remaining) cooldownNotice,
bool requireLoginId = true,
}) {
if (!TitleServerConfigHolder().isConfigured) {
return AppStrings.ticketNotConfigured;
}
if (loginDateTime == null || (requireLoginId && loginId == null)) {
return notLoggedInMessage;
}
if (cooldownRemaining > 0) {
return cooldownNotice(cooldownRemaining);
}
return null;
}