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
+69
-44
@@ -3,6 +3,7 @@ import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../config/responsive.dart';
|
||||
import '../config/strings.dart';
|
||||
import '../config/title_server_config.dart';
|
||||
import '../models/user_rating.dart';
|
||||
@@ -189,56 +190,80 @@ class _Best50PageState extends State<Best50Page> {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
if (!_musicReady)
|
||||
_NoticeBanner(theme: theme, text: AppStrings.best50NoMusicData),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: _exporting ? null : _export,
|
||||
icon: _exporting
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.download, size: 20),
|
||||
label: Text(
|
||||
_exporting
|
||||
? AppStrings.best50Exporting
|
||||
: AppStrings.best50Export,
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
responsiveBody(
|
||||
context,
|
||||
child: Column(
|
||||
children: [
|
||||
if (!_musicReady)
|
||||
_NoticeBanner(
|
||||
theme: theme,
|
||||
text: AppStrings.best50NoMusicData,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: _exporting ? null : _export,
|
||||
icon: _exporting
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.download, size: 20),
|
||||
label: Text(
|
||||
_exporting
|
||||
? AppStrings.best50Exporting
|
||||
: AppStrings.best50Export,
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: InteractiveViewer(
|
||||
maxScale: 6,
|
||||
boundaryMargin: const EdgeInsets.all(24),
|
||||
// FittedBox 会按视口把固定尺寸的海报缩出来;它给子节点无限约束,
|
||||
// 所以海报仍保持 1762x1850,RepaintBoundary 录到的也是原始尺寸
|
||||
// (祖先变换不会被烘进边界里),导出不会糊。
|
||||
child: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
alignment: Alignment.topCenter,
|
||||
child: RepaintBoundary(
|
||||
key: _posterKey,
|
||||
child: Best50Poster(
|
||||
userName: widget.userName,
|
||||
iconId: widget.iconId,
|
||||
playerRating: widget.playerRating,
|
||||
best35: best35,
|
||||
best15: best15,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
// 按视口宽度适配:横屏/桌面下海报占满宽度、竖向拖动看全图,
|
||||
// 而不是被 contain 缩成中间一小块、两侧大片空白。
|
||||
// minScale 再允许缩到整张可见,导出仍是原始 1762x1850。
|
||||
final fitWidth =
|
||||
constraints.maxWidth / Best50Poster.canvasWidth;
|
||||
final scaledHeight = Best50Poster.canvasHeight * fitWidth;
|
||||
final containScale = constraints.maxHeight / scaledHeight;
|
||||
final minScale = containScale < 1 ? containScale : 1.0;
|
||||
|
||||
return InteractiveViewer(
|
||||
constrained: false,
|
||||
minScale: minScale,
|
||||
maxScale: minScale * 6,
|
||||
boundaryMargin: const EdgeInsets.all(24),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
child: RepaintBoundary(
|
||||
key: _posterKey,
|
||||
child: Best50Poster(
|
||||
userName: widget.userName,
|
||||
iconId: widget.iconId,
|
||||
playerRating: widget.playerRating,
|
||||
best35: best35,
|
||||
best15: best15,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user