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,116 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ichino/config/title_server_config.dart';
|
||||
import 'package:ichino/models/user_character.dart';
|
||||
import 'package:ichino/services/user_all_payload_builder.dart';
|
||||
|
||||
const _config = TitleServerConfig(
|
||||
titleServerUrl: 'http://127.0.0.1:9999',
|
||||
aesKey: '0123456789abcdef',
|
||||
aesIv: '0123456789abcdef',
|
||||
clientId: 'A63E01C2805',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _buildPacket(UserAllPayloadBuilder builder) =>
|
||||
builder.build(
|
||||
userId: 42,
|
||||
loginId: 7,
|
||||
loginDateTime: 1700000000,
|
||||
musicData: const {
|
||||
'musicId': 11538,
|
||||
'level': 0,
|
||||
'playCount': 1,
|
||||
'achievement': 0,
|
||||
'comboStatus': 0,
|
||||
'syncStatus': 0,
|
||||
'deluxscoreMax': 0,
|
||||
'scoreRank': 0,
|
||||
'extNum1': 0,
|
||||
},
|
||||
generalUserInfo: const {},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _upsert(Map<String, dynamic> packet) =>
|
||||
packet['upsertUserAll'] as Map<String, dynamic>;
|
||||
|
||||
void main() {
|
||||
group('旅行伙伴 userCharacterList patch', () {
|
||||
test('写入 4 字段行并按行数生成 isNewCharacterList', () {
|
||||
final builder = UserAllPayloadBuilder(_config);
|
||||
final packet = _buildPacket(builder);
|
||||
|
||||
builder.applyCharacterListPatch(packet, characters: [
|
||||
{'characterId': 1001, 'level': 1, 'awakening': 0, 'useCount': 0},
|
||||
{'characterId': 1002, 'level': 3, 'awakening': 1, 'useCount': 9},
|
||||
]);
|
||||
|
||||
final upsert = _upsert(packet);
|
||||
expect(upsert['userCharacterList'], hasLength(2));
|
||||
expect(upsert['isNewCharacterList'], '11');
|
||||
final first = (upsert['userCharacterList'] as List).first
|
||||
as Map<String, dynamic>;
|
||||
// 客户端 UserCharacter 结构只有这 4 个字段,多一个都可能被拒。
|
||||
expect(first.keys.toSet(), {
|
||||
'characterId',
|
||||
'level',
|
||||
'awakening',
|
||||
'useCount',
|
||||
});
|
||||
});
|
||||
|
||||
test('空列表时 isNewCharacterList 为空串', () {
|
||||
final builder = UserAllPayloadBuilder(_config);
|
||||
final packet = _buildPacket(builder);
|
||||
builder.applyCharacterListPatch(packet, characters: []);
|
||||
expect(_upsert(packet)['isNewCharacterList'], '');
|
||||
});
|
||||
});
|
||||
|
||||
group('charaSlot 编组 patch', () {
|
||||
test('槽位补齐到 5 并同步 playlog 的 characterId1..5', () {
|
||||
final builder = UserAllPayloadBuilder(_config);
|
||||
final packet = _buildPacket(builder);
|
||||
|
||||
builder.applyCharaSlotPatch(packet, charaSlot: const [1001]);
|
||||
|
||||
final userData = (_upsert(packet)['userData'] as List).first
|
||||
as Map<String, dynamic>;
|
||||
expect(userData['charaSlot'], [1001, 0, 0, 0, 0]);
|
||||
|
||||
final playlog = (packet['userPlaylogList'] as List).first
|
||||
as Map<String, dynamic>;
|
||||
expect(playlog['characterId1'], 1001);
|
||||
expect(playlog['characterId2'], 0);
|
||||
});
|
||||
|
||||
test('超过 5 个槽位被截断', () {
|
||||
expect(
|
||||
normalizeCharaSlot(const [1, 2, 3, 4, 5, 6, 7]),
|
||||
[1, 2, 3, 4, 5],
|
||||
);
|
||||
});
|
||||
|
||||
test('一键复制槽 0 到其余槽位', () {
|
||||
final slots = normalizeCharaSlot(const [1001, 0, 0, 0, 0]);
|
||||
final copied = normalizeCharaSlot([
|
||||
for (var i = 0; i < 5; i++) slots[0],
|
||||
]);
|
||||
expect(copied, [1001, 1001, 1001, 1001, 1001]);
|
||||
});
|
||||
});
|
||||
|
||||
group('收藏品 userItemList patch', () {
|
||||
test('多行道具与 isNewItemList 位数一致', () {
|
||||
final builder = UserAllPayloadBuilder(_config);
|
||||
final packet = _buildPacket(builder);
|
||||
|
||||
builder.applyItemListPatch(packet, items: [
|
||||
{'itemKind': 9, 'itemId': 1001, 'stock': 1, 'isValid': true},
|
||||
{'itemKind': 10, 'itemId': 2002, 'stock': 1, 'isValid': true},
|
||||
]);
|
||||
|
||||
final upsert = _upsert(packet);
|
||||
expect(upsert['userItemList'], hasLength(2));
|
||||
expect(upsert['isNewItemList'], '11');
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user