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
@@ -9,6 +9,7 @@ import 'package:pointycastle/export.dart';
|
||||
|
||||
import '../config/title_server_config.dart';
|
||||
import '../models/user_data.dart';
|
||||
import '../models/user_character.dart';
|
||||
import '../models/user_preview.dart';
|
||||
import '../models/user_rating.dart';
|
||||
|
||||
@@ -557,6 +558,16 @@ class TitleApiService {
|
||||
final list = json['userCharacterList'] as List<dynamic>? ?? [];
|
||||
return list.map((e) => e as Map<String, dynamic>).toList();
|
||||
}
|
||||
|
||||
/// 旅行伙伴(角色)列表,对应 UserCharacterResponseVO。
|
||||
Future<List<UserCharacterBean>> getUserCharacters(int userId) async {
|
||||
final json = await _callApi(
|
||||
'GetUserCharacterApi',
|
||||
{'userId': userId},
|
||||
userId,
|
||||
);
|
||||
return UserCharacterBean.listFromResponse(json);
|
||||
}
|
||||
}
|
||||
|
||||
class _RandomHelper {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../config/title_server_config.dart';
|
||||
import '../models/user_character.dart';
|
||||
import 'title_api_service.dart';
|
||||
|
||||
/// Dart port of `eaquira/src/sdgb/payload.py::UserAll_payload` and
|
||||
@@ -356,27 +357,46 @@ class UserAllPayloadBuilder {
|
||||
upsert['isNewItemList'] = List.filled(items.length, '1').join();
|
||||
}
|
||||
|
||||
/// Port of `music_user_all_patcher`: append unlock items
|
||||
/// (itemKind 5=music, 6=master, 7=remaster) to the packet.
|
||||
void applyMusicUnlockPatch(
|
||||
/// 解锁歌曲/谱面时补一条 musicDetail 记录并标记为新增。
|
||||
/// 难度本身由 userItemList 的 itemKind 5/6/7 行表达(见客户端
|
||||
/// VOExtensions.ExportUserItems),不写在 musicDetail 上。
|
||||
void applyMusicDetailPatch(
|
||||
Map<String, dynamic> packet, {
|
||||
required Map<String, dynamic> musicData,
|
||||
required int musicId,
|
||||
bool unlockMusic = false,
|
||||
bool unlockMaster = false,
|
||||
bool unlockRemaster = false,
|
||||
}) {
|
||||
applyItemListPatch(packet, items: [
|
||||
if (unlockMusic)
|
||||
{'itemKind': 5, 'itemId': musicId, 'stock': 1, 'isValid': true},
|
||||
if (unlockMaster)
|
||||
{'itemKind': 6, 'itemId': musicId, 'stock': 1, 'isValid': true},
|
||||
if (unlockRemaster)
|
||||
{'itemKind': 7, 'itemId': musicId, 'stock': 1, 'isValid': true},
|
||||
]);
|
||||
|
||||
final upsert = packet['upsertUserAll'] as Map<String, dynamic>;
|
||||
upsert['userMusicDetailList'] = [musicData];
|
||||
upsert['isNewMusicDetailList'] = '1';
|
||||
}
|
||||
|
||||
/// 发放/更新旅行伙伴:写 `userCharacterList` 并按行数生成 `isNewCharacterList`。
|
||||
/// 客户端 BuildListData 只发增量、每位一个字符,所以这里只放要新增的行。
|
||||
void applyCharacterListPatch(
|
||||
Map<String, dynamic> packet, {
|
||||
required List<Map<String, dynamic>> characters,
|
||||
}) {
|
||||
final upsert = packet['upsertUserAll'] as Map<String, dynamic>;
|
||||
upsert['userCharacterList'] = characters;
|
||||
upsert['isNewCharacterList'] = List.filled(characters.length, '1').join();
|
||||
}
|
||||
|
||||
/// 编组旅行伙伴:改 `userData[0].charaSlot`(int[5],槽 0 为队长)。
|
||||
/// 客户端 ExportUserPlaylog 用同一份 CharaSlot 填 playlog 的
|
||||
/// characterId1..5,这里一起同步,避免 playlog 与 userData 互相矛盾。
|
||||
void applyCharaSlotPatch(
|
||||
Map<String, dynamic> packet, {
|
||||
required List<int> charaSlot,
|
||||
}) {
|
||||
final slots = normalizeCharaSlot(charaSlot);
|
||||
final upsert = packet['upsertUserAll'] as Map<String, dynamic>;
|
||||
final userDataList = upsert['userData'] as List;
|
||||
(userDataList.first as Map<String, dynamic>)['charaSlot'] = slots;
|
||||
|
||||
for (final raw in (packet['userPlaylogList'] as List)) {
|
||||
final playlog = raw as Map<String, dynamic>;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
playlog['characterId${i + 1}'] = slots[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user