feat: 旅行伙伴发放与编组、修复退登卡死、优化宽屏布局、加 CI
自动退出登录卡死:_performLogoutAndExit 只 pop() 一层,而解锁/收藏品页是 push
在 HomePage 之上的,退登后被弹回「风险」hub,同时 _loggingOut 仍为 true,
PopScope(canPop:false) + 返回键转圈形成死局。改为 popUntil(isFirst)。
旅行伙伴发不了:它是 ItemKind.Character = 9,与搭档 Partner = 10 是两种东西,
原先收藏品的类型列表 [1,2,3,10,11,12] 里根本没有 9。按客户端实现,角色不走
userItemList(ExportUserItems 不导出 itemKind 9),而是
upsertUserAll.userCharacterList 的 {characterId, level, awakening, useCount},
配 isNewCharacterList 一行一字符。新增发放与 5 槽 charaSlot 编组页,
含「把槽 0 复制到其他槽位」,并校验所选角色确为已拥有或本次待发放。
宽屏布局:各页只有 ConstrainedBox 限宽、没有居中容器,1440 窗口下内容贴左、
右侧大片空白。抽出 responsiveBody 统一居中;B50 预览改用按宽度适配
(InteractiveViewer constrained:false + FittedBox fitWidth),横屏下不再被
contain 缩成中间一小块,导出仍是原始 1762x1850。
CI:.github/workflows/build.yml 先跑 analyze+test 作为门槛,再产出
Windows exe、Android apk、iOS 未签名 ipa(macOS runner,无证书故不可安装)。
README 从 Flutter 模板改写为实际文档,含 path_provider/jni 与 JBR jlink
两个构建环境坑的说明。
This commit is contained in:
1 parent
ebd1fdddaa
commit
8dda399f76
24 files changed
+1832
-134
No files matched your search
@@ -0,0 +1,68 @@
|
||||
/// GetUserRatingApi 之外的角色(旅行伙伴)数据。
|
||||
///
|
||||
/// 对应客户端 `Net.VO.Mai2.UserCharacter`:只有 4 个字段,走
|
||||
/// `UpsertUserAllApi` 的 `upsertUserAll.userCharacterList`,主键是
|
||||
/// `characterId`(见 VOExtensions.cs:265 的 BuildListData 比较器)。
|
||||
///
|
||||
/// 注意:旅行伙伴不是「搭档」。搭档是 ItemKind.Partner = 10,走 userItemList;
|
||||
/// 角色是 ItemKind.Character = 9,客户端的 ExportUserItems() 并不导出它,
|
||||
/// 角色的拥有状态由 userCharacterList 表达。
|
||||
class UserCharacterBean {
|
||||
final int characterId;
|
||||
final int level;
|
||||
final int awakening;
|
||||
final int useCount;
|
||||
|
||||
const UserCharacterBean({
|
||||
required this.characterId,
|
||||
required this.level,
|
||||
required this.awakening,
|
||||
required this.useCount,
|
||||
});
|
||||
|
||||
/// wire 上的 UserCharacter,字段与顺序与客户端声明一致。
|
||||
Map<String, dynamic> toWireJson() => {
|
||||
'characterId': characterId,
|
||||
'level': level,
|
||||
'awakening': awakening,
|
||||
'useCount': useCount,
|
||||
};
|
||||
|
||||
UserCharacterBean copyWith({int? level, int? awakening, int? useCount}) =>
|
||||
UserCharacterBean(
|
||||
characterId: characterId,
|
||||
level: level ?? this.level,
|
||||
awakening: awakening ?? this.awakening,
|
||||
useCount: useCount ?? this.useCount,
|
||||
);
|
||||
|
||||
static int _toInt(dynamic v) {
|
||||
if (v == null) return 0;
|
||||
if (v is int) return v;
|
||||
if (v is num) return v.toInt();
|
||||
if (v is String) return int.tryParse(v) ?? 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
factory UserCharacterBean.fromJson(Map<String, dynamic> json) =>
|
||||
UserCharacterBean(
|
||||
characterId: _toInt(json['characterId']),
|
||||
level: _toInt(json['level']),
|
||||
awakening: _toInt(json['awakening']),
|
||||
useCount: _toInt(json['useCount']),
|
||||
);
|
||||
|
||||
static List<UserCharacterBean> listFromResponse(Map<String, dynamic> json) {
|
||||
final raw = json['userCharacterList'];
|
||||
if (raw is! List) return const [];
|
||||
return raw
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(UserCharacterBean.fromJson)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// `UserDetail.charaSlot` 固定 5 个槽,槽 0 是队长
|
||||
/// (客户端 CharactorSlotController.cs:27 对 i==0 用 leaderCharaSlotPrefab)。
|
||||
List<int> normalizeCharaSlot(List<int> slots) =>
|
||||
List<int>.generate(5, (i) => i < slots.length ? slots[i] : 0);
|
||||
Reference in new issue
Block a user