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:
Cookiues committed 2026-09-26 00:37:54 +08:00
1 parent ebd1fdddaa
commit 8dda399f76
24 files changed
+1832 -134

No files matched your search

+46 -1
View File
@@ -174,6 +174,19 @@ class AppStrings {
static const unlockNeedMusicId = '请填写要解锁的歌曲 ID。';
static const unlockNeedOption = '请至少勾选一项解锁内容。';
static const unlockNotLoggedIn = '尚未登录游戏服务器,无法执行解锁操作。';
// 待提交列表 (userItemList 可累加多行)
static const listAddButton = '添加到列表';
static const listRemoveTooltip = '从列表移除';
static const listDuplicate = '该内容已在列表中。';
static const unlockPendingTitle = '待解锁列表';
static const collectiblesPendingTitle = '待获取列表';
static const unlockPendingEmpty = '列表为空,可在上方填写歌曲 ID 并勾选难度后添加。';
static const collectiblesPendingEmpty = '列表为空,可在上方选择类型与 ID 后添加。';
static const unlockNeedList = '请至少添加一首要解锁的歌曲。';
static const collectiblesNeedList = '请至少添加一项收藏品。';
static const autoLogoutAndExit = '完成后自动退出登录并返回标题页';
static const exitingToTitle = '正在退出登录并返回标题页...';
static String unlockCooldownNotice(int remaining) =>
'登录后需冷却 $ticketCooldownSeconds 秒,剩余 $remaining 秒后可执行解锁操作。';
static const unlockFetchData = '拉取数据';
@@ -181,7 +194,6 @@ class AppStrings {
static const unlockRefetch = '重新拉取';
static const unlockNoData = '⚠️ 未拉取到用户数据,请先点「拉取数据」按钮获取。';
static const unlockFetched = '数据已拉取';
static const unlockAutoLogout = '完成后自动退出登录';
static const unlockMusicRun = '执行解锁';
static const unlockRunning = '执行中...';
static const unlockStepFetch = '拉取用户数据';
@@ -225,6 +237,39 @@ class AppStrings {
}
}
// TravelPartner 旅行伙伴 (ItemKind.Character = 9,走 userCharacterList)
static const travelPartnerFeatureTitle = '旅行伙伴';
static const travelPartnerFeatureDesc =
'发放旅行伙伴并编组出战槽位。角色走 userCharacterList(不是搭档 Partner=10,也不走 userItemList)。';
static const travelPartnerNotLoggedIn = '尚未登录游戏服务器,无法操作旅行伙伴。';
static String travelPartnerCooldownNotice(int remaining) =>
'登录后需冷却 $ticketCooldownSeconds 秒,剩余 $remaining 秒后可操作旅行伙伴。';
static const travelPartnerFetchData = '拉取角色数据';
static const travelPartnerFetching = '拉取中...';
static const travelPartnerRefetch = '重新拉取';
static const travelPartnerFetched = '角色数据已拉取';
static const travelPartnerNoData = '⚠️ 尚未拉取,请先获取角色与用户数据。';
static const travelPartnerOwnedTitle = '已拥有旅行伙伴';
static const travelPartnerOwnedEmpty = '还没有任何旅行伙伴。';
static const travelPartnerGrantTitle = '发放新的旅行伙伴';
static const travelPartnerGrantEmpty = '未添加待发放角色。';
static const travelPartnerCharacterIdLabel = '旅行伙伴 ID';
static const travelPartnerCharacterIdHint = '例如 1001';
static const travelPartnerNeedCharacterId = '请填写旅行伙伴 ID。';
static const travelPartnerAlreadyOwned = '该旅行伙伴已拥有,无需重复发放。';
static const travelPartnerSlotTitle = '编组出战槽位';
static const travelPartnerSlotNone = '空';
static String travelPartnerSlotLabel(int index) =>
index == 0 ? '槽 0(队长)' : '槽 $index';
static const travelPartnerCopySlot0 = '把槽 0 复制到其他槽位';
static const travelPartnerCopyNeedSlot0 = '槽 0 还没有选择旅行伙伴。';
static const travelPartnerNotOwnedHint = '编组失败:你并不拥有该旅行伙伴。';
static const travelPartnerRun = '执行旅行伙伴操作';
static const travelPartnerRunning = '执行中...';
static const travelPartnerSuccess = '旅行伙伴数据上传完成';
static const travelPartnerStepFetch = '拉取角色与用户数据';
static const travelPartnerStepUpload = '上传旅行伙伴数据';
// HighRiskFeature hub (中转页)
static const musicRiskHubTitle = '高危功能';
static const musicRiskHubDesc = '以下功能会向服务器发送伪造数据,属于高风险操作,请谨慎使用。';