refactor: 拆分主页与公共组件,扫码登录改走 WinHTTP
- 抽出 lib/core 的日期/JSON 工具,收敛各页重复解析 - 主页与音乐风险页拆成组件,风险功能共用一套运行框架 - 修复设置页作为主页页签保存时弹掉根路由导致的黑屏 - 登录对齐 Astral aimedb 协议:84 位二维码校验、签名与请求头修正, 补 GlobalSign 中间证书;Windows 改用 WinHTTP 绕过服务端对 BoringSSL 的 TLS 指纹拦截(证书校验保持开启) - 补开发预览数据与测试
This commit is contained in:
1 parent
0cb5320290
commit
e9357f5969
55 files changed
+4387
-3392
No files matched your search
@@ -0,0 +1,96 @@
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user