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

160 lines
5.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import '../core/json_value.dart';
/// `Net.VO.Mai2.UserKaleidxScope`(万花筒 / 宿命之门),走
/// `upsertUserAll.userKaleidxScopeList`,主键是 `gateId`。
///
/// 依据反编译客户端:
/// - 「发现门」与「获取钥匙」都在这同一张表里,分别是 `isGateFound` 与
/// `isKeyFound`。钥匙**不**走 `userItemList`——`ExportUserItems` 从不输出
/// itemKind 15,下行也没有 `GetUserItem(KaleidxScopeKey)` 这一步。
/// - 状态机在 `KaleidxScopeGateListController.cs:144-159`,四个组合里:
/// `!found && key` -> AnimState.None(**隐形**,不是锁着的门)
/// ` found && !key` -> Close(可见、未解锁)
/// ` found && key && !clear` -> Open(可挑战)
/// ` found && key && clear` -> Clear
/// 所以只给钥匙不给门等于什么都没给。
class UserKaleidxScopeBean {
final int gateId;
final bool isGateFound;
final bool isKeyFound;
final bool isClear;
final int totalRestLife;
final int totalAchievement;
/// 原样拼写是 `Deluxscore`(不是 `DeluxeScore`),改名服务器就认不出了。
final int totalDeluxscore;
final int bestAchievement;
final int bestDeluxscore;
final String bestAchievementDate;
final String bestDeluxscoreDate;
final int playCount;
final String clearDate;
final String lastPlayDate;
final bool isInfoWatched;
const UserKaleidxScopeBean({
required this.gateId,
this.isGateFound = false,
this.isKeyFound = false,
this.isClear = false,
this.totalRestLife = 0,
this.totalAchievement = 0,
this.totalDeluxscore = 0,
this.bestAchievement = 0,
this.bestDeluxscore = 0,
this.bestAchievementDate = '',
this.bestDeluxscoreDate = '',
this.playCount = 0,
this.clearDate = '',
this.lastPlayDate = '',
this.isInfoWatched = false,
});
/// wire 上的完整 15 个字段。发出去的是整行替换,所以没列在这里的字段
/// 必须从服务器原值带回来,否则会被清零。
Map<String, dynamic> toWireJson() => {
'gateId': gateId,
'isGateFound': isGateFound,
'isKeyFound': isKeyFound,
'isClear': isClear,
'totalRestLife': totalRestLife,
'totalAchievement': totalAchievement,
'totalDeluxscore': totalDeluxscore,
'bestAchievement': bestAchievement,
'bestDeluxscore': bestDeluxscore,
'bestAchievementDate': bestAchievementDate,
'bestDeluxscoreDate': bestDeluxscoreDate,
'playCount': playCount,
'clearDate': clearDate,
'lastPlayDate': lastPlayDate,
'isInfoWatched': isInfoWatched,
};
UserKaleidxScopeBean copyWith({
bool? isGateFound,
bool? isKeyFound,
bool? isClear,
String? clearDate,
}) => UserKaleidxScopeBean(
gateId: gateId,
isGateFound: isGateFound ?? this.isGateFound,
isKeyFound: isKeyFound ?? this.isKeyFound,
isClear: isClear ?? this.isClear,
totalRestLife: totalRestLife,
totalAchievement: totalAchievement,
totalDeluxscore: totalDeluxscore,
bestAchievement: bestAchievement,
bestDeluxscore: bestDeluxscore,
bestAchievementDate: bestAchievementDate,
bestDeluxscoreDate: bestDeluxscoreDate,
playCount: playCount,
clearDate: clearDate ?? this.clearDate,
lastPlayDate: lastPlayDate,
isInfoWatched: isInfoWatched,
);
/// 服务器上还没有这扇门时的新行。`isGateFound` 恒为 true——不存在「只给钥匙
/// 不给门」这种可用状态(见类注释的状态机),新行其余字段取 struct 默认值。
static UserKaleidxScopeBean discovered(
int gateId, {
required bool giveKey,
bool? clear,
String timestamp = '',
}) => UserKaleidxScopeBean(
gateId: gateId,
isGateFound: true,
isKeyFound: giveKey,
isClear: clear ?? false,
clearDate: (clear ?? false) ? timestamp : '',
);
/// 在服务器原行上叠加本次动作。**必须**走这里而不是新建一行:整行替换会把
/// best 成绩、playCount、clearDate 这些字段清零。
///
/// [clear] 传 null 表示不改动通关状态。给钥匙必须连带发现门,否则这行在
/// 客户端是隐形的。
UserKaleidxScopeBean withActions({
required bool discover,
required bool giveKey,
bool? clear,
String timestamp = '',
}) => copyWith(
isGateFound: isGateFound || discover || giveKey,
isKeyFound: isKeyFound || giveKey,
isClear: clear,
// 首次通关日期只在刚转成通关时落一个时间戳(客户端就是这么写的:
// `clearDate = TimeManager.GetNowDateString()`);撤销通关时一起清空,
// 留着一个日期比缺日期更容易让人误读。
clearDate: clear == null
? null
: (clear
? (clearDate.isNotEmpty ? clearDate : timestamp)
: ''),
);
factory UserKaleidxScopeBean.fromJson(Map<String, dynamic> json) =>
UserKaleidxScopeBean(
gateId: jsonInt(json['gateId']),
isGateFound: jsonBool(json['isGateFound']),
isKeyFound: jsonBool(json['isKeyFound']),
isClear: jsonBool(json['isClear']),
totalRestLife: jsonInt(json['totalRestLife']),
totalAchievement: jsonInt(json['totalAchievement']),
totalDeluxscore: jsonInt(json['totalDeluxscore']),
bestAchievement: jsonInt(json['bestAchievement']),
bestDeluxscore: jsonInt(json['bestDeluxscore']),
bestAchievementDate: jsonString(json['bestAchievementDate']),
bestDeluxscoreDate: jsonString(json['bestDeluxscoreDate']),
playCount: jsonInt(json['playCount']),
clearDate: jsonString(json['clearDate']),
lastPlayDate: jsonString(json['lastPlayDate']),
isInfoWatched: jsonBool(json['isInfoWatched']),
);
static List<UserKaleidxScopeBean> listFromResponse(
Map<String, dynamic> json,
) => jsonMapList(json['userKaleidxScopeList'])
.map(UserKaleidxScopeBean.fromJson)
.toList();
}