fix(guide): use fitWidth to prevent image stretching + add debug logs
- Change BoxFit.cover to BoxFit.fitWidth for guide page images - Add screen info logging (resolution, pixel ratio, aspect ratio) - Add detailed avatar loading logs in frontend and backend - Log avatarUrl from DB and API response during recovery 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a3c0d3948d
commit
243105f97f
|
|
@ -43,6 +43,10 @@ export class RecoverByMnemonicHandler {
|
||||||
|
|
||||||
// 如果头像为空,重新生成一个
|
// 如果头像为空,重新生成一个
|
||||||
let avatarUrl = account.avatarUrl;
|
let avatarUrl = account.avatarUrl;
|
||||||
|
this.logger.log(`Account ${command.accountSequence} avatarUrl from DB: ${avatarUrl ? `长度=${avatarUrl.length}` : 'null'}`);
|
||||||
|
if (avatarUrl) {
|
||||||
|
this.logger.log(`Account ${command.accountSequence} avatarUrl前50字符: ${avatarUrl.substring(0, 50)}`);
|
||||||
|
}
|
||||||
if (!avatarUrl) {
|
if (!avatarUrl) {
|
||||||
this.logger.log(`Account ${command.accountSequence} has no avatar, generating new one`);
|
this.logger.log(`Account ${command.accountSequence} has no avatar, generating new one`);
|
||||||
avatarUrl = generateRandomAvatarSvg();
|
avatarUrl = generateRandomAvatarSvg();
|
||||||
|
|
@ -62,7 +66,7 @@ export class RecoverByMnemonicHandler {
|
||||||
await this.eventPublisher.publishAll(account.domainEvents);
|
await this.eventPublisher.publishAll(account.domainEvents);
|
||||||
account.clearDomainEvents();
|
account.clearDomainEvents();
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
userId: account.userId.toString(),
|
userId: account.userId.toString(),
|
||||||
accountSequence: account.accountSequence.value,
|
accountSequence: account.accountSequence.value,
|
||||||
nickname: account.nickname,
|
nickname: account.nickname,
|
||||||
|
|
@ -71,5 +75,10 @@ export class RecoverByMnemonicHandler {
|
||||||
accessToken: tokens.accessToken,
|
accessToken: tokens.accessToken,
|
||||||
refreshToken: tokens.refreshToken,
|
refreshToken: tokens.refreshToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.logger.log(`RecoverByMnemonic result - accountSequence: ${result.accountSequence}, nickname: ${result.nickname}`);
|
||||||
|
this.logger.log(`RecoverByMnemonic result - avatarUrl: ${result.avatarUrl ? `长度=${result.avatarUrl.length}` : 'null'}`);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,11 +199,16 @@ class RecoverAccountResponse {
|
||||||
|
|
||||||
factory RecoverAccountResponse.fromJson(Map<String, dynamic> json) {
|
factory RecoverAccountResponse.fromJson(Map<String, dynamic> json) {
|
||||||
debugPrint('[AccountService] 解析 RecoverAccountResponse: ${json.keys.toList()}');
|
debugPrint('[AccountService] 解析 RecoverAccountResponse: ${json.keys.toList()}');
|
||||||
|
final avatarUrl = json['avatarUrl'] as String?;
|
||||||
|
debugPrint('[AccountService] RecoverAccountResponse.avatarUrl: ${avatarUrl != null ? "长度=${avatarUrl.length}" : "null"}');
|
||||||
|
if (avatarUrl != null && avatarUrl.isNotEmpty) {
|
||||||
|
debugPrint('[AccountService] RecoverAccountResponse.avatarUrl前50字符: ${avatarUrl.substring(0, avatarUrl.length > 50 ? 50 : avatarUrl.length)}');
|
||||||
|
}
|
||||||
return RecoverAccountResponse(
|
return RecoverAccountResponse(
|
||||||
userId: json['userId'] as String,
|
userId: json['userId'] as String,
|
||||||
userSerialNum: json['accountSequence'] as int,
|
userSerialNum: json['accountSequence'] as int,
|
||||||
username: json['nickname'] as String,
|
username: json['nickname'] as String,
|
||||||
avatarSvg: json['avatarUrl'] as String?,
|
avatarSvg: avatarUrl,
|
||||||
referralCode: json['referralCode'] as String,
|
referralCode: json['referralCode'] as String,
|
||||||
accessToken: json['accessToken'] as String,
|
accessToken: json['accessToken'] as String,
|
||||||
refreshToken: json['refreshToken'] as String,
|
refreshToken: json['refreshToken'] as String,
|
||||||
|
|
@ -884,12 +889,15 @@ class AccountService {
|
||||||
value: response.username,
|
value: response.username,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.avatarSvg != null) {
|
if (response.avatarSvg != null && response.avatarSvg!.isNotEmpty) {
|
||||||
debugPrint('$_tag _saveRecoverAccountData() - 保存 avatarSvg (长度: ${response.avatarSvg!.length})');
|
debugPrint('$_tag _saveRecoverAccountData() - 保存 avatarSvg (长度: ${response.avatarSvg!.length})');
|
||||||
|
debugPrint('$_tag _saveRecoverAccountData() - avatarSvg前50字符: ${response.avatarSvg!.substring(0, response.avatarSvg!.length > 50 ? 50 : response.avatarSvg!.length)}');
|
||||||
await _secureStorage.write(
|
await _secureStorage.write(
|
||||||
key: StorageKeys.avatarSvg,
|
key: StorageKeys.avatarSvg,
|
||||||
value: response.avatarSvg!,
|
value: response.avatarSvg!,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
debugPrint('$_tag _saveRecoverAccountData() - ⚠️ avatarSvg 为空! response.avatarSvg=${response.avatarSvg}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存 Token
|
// 保存 Token
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,31 @@ class _GuidePageState extends ConsumerState<GuidePage> {
|
||||||
final PageController _pageController = PageController();
|
final PageController _pageController = PageController();
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 延迟到 build 后获取屏幕信息
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_logScreenInfo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 打印屏幕信息用于调试
|
||||||
|
void _logScreenInfo() {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final screenSize = mediaQuery.size;
|
||||||
|
final devicePixelRatio = mediaQuery.devicePixelRatio;
|
||||||
|
final physicalSize = screenSize * devicePixelRatio;
|
||||||
|
|
||||||
|
debugPrint('[GuidePage] ========== 屏幕信息 ==========');
|
||||||
|
debugPrint('[GuidePage] 逻辑分辨率: ${screenSize.width.toStringAsFixed(1)} x ${screenSize.height.toStringAsFixed(1)}');
|
||||||
|
debugPrint('[GuidePage] 设备像素比: $devicePixelRatio');
|
||||||
|
debugPrint('[GuidePage] 物理分辨率: ${physicalSize.width.toStringAsFixed(0)} x ${physicalSize.height.toStringAsFixed(0)}');
|
||||||
|
debugPrint('[GuidePage] 屏幕宽高比: ${(screenSize.width / screenSize.height).toStringAsFixed(3)} (${screenSize.width.toStringAsFixed(0)}:${screenSize.height.toStringAsFixed(0)})');
|
||||||
|
debugPrint('[GuidePage] 图片设计比例: 0.5625 (1080:1920 = 9:16)');
|
||||||
|
debugPrint('[GuidePage] ================================');
|
||||||
|
}
|
||||||
|
|
||||||
// 向导页1-5的数据 (第5页为欢迎加入页)
|
// 向导页1-5的数据 (第5页为欢迎加入页)
|
||||||
// 支持 png、jpg、webp 等格式
|
// 支持 png、jpg、webp 等格式
|
||||||
final List<GuidePageData> _guidePages = const [
|
final List<GuidePageData> _guidePages = const [
|
||||||
|
|
@ -121,37 +146,50 @@ class _GuidePageState extends ConsumerState<GuidePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建向导页 (页面1-4) - 全屏背景图片,无文字
|
/// 构建向导页 (页面1-4) - 全屏背景图片,无文字
|
||||||
|
/// 使用 BoxFit.fitWidth 保持图片原始比例,不拉伸文字
|
||||||
Widget _buildGuidePage(GuidePageData data, int index) {
|
Widget _buildGuidePage(GuidePageData data, int index) {
|
||||||
return Stack(
|
debugPrint('[GuidePage] _buildGuidePage() - 页面 ${index + 1}, 图片: ${data.imagePath}');
|
||||||
fit: StackFit.expand,
|
return Container(
|
||||||
children: [
|
color: Colors.black, // 如果图片不够高,上下用黑色填充
|
||||||
// 全屏背景图片
|
child: Stack(
|
||||||
if (data.imagePath != null)
|
fit: StackFit.expand,
|
||||||
Image.asset(
|
children: [
|
||||||
data.imagePath!,
|
// 全屏背景图片 - 使用 fitWidth 保持宽高比
|
||||||
fit: BoxFit.cover,
|
if (data.imagePath != null)
|
||||||
width: double.infinity,
|
LayoutBuilder(
|
||||||
height: double.infinity,
|
builder: (context, constraints) {
|
||||||
errorBuilder: (context, error, stackTrace) {
|
debugPrint('[GuidePage] 页面 ${index + 1} 容器尺寸: ${constraints.maxWidth.toStringAsFixed(1)} x ${constraints.maxHeight.toStringAsFixed(1)}');
|
||||||
return Container(
|
debugPrint('[GuidePage] 页面 ${index + 1} 使用 BoxFit.fitWidth');
|
||||||
color: const Color(0xFFFFF8E7),
|
return Center(
|
||||||
child: _buildPlaceholderImage(index),
|
child: Image.asset(
|
||||||
);
|
data.imagePath!,
|
||||||
},
|
fit: BoxFit.fitWidth,
|
||||||
)
|
width: double.infinity,
|
||||||
else
|
errorBuilder: (context, error, stackTrace) {
|
||||||
Container(
|
debugPrint('[GuidePage] 页面 ${index + 1} 图片加载失败: $error');
|
||||||
color: const Color(0xFFFFF8E7),
|
return Container(
|
||||||
child: _buildPlaceholderImage(index),
|
color: const Color(0xFFFFF8E7),
|
||||||
|
child: _buildPlaceholderImage(index),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(
|
||||||
|
color: const Color(0xFFFFF8E7),
|
||||||
|
child: _buildPlaceholderImage(index),
|
||||||
|
),
|
||||||
|
// 底部页面指示器
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 80.h,
|
||||||
|
child: _buildPageIndicator(),
|
||||||
),
|
),
|
||||||
// 底部页面指示器
|
],
|
||||||
Positioned(
|
),
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 80.h,
|
|
||||||
child: _buildPageIndicator(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,39 +423,42 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => FocusScope.of(context).unfocus(),
|
onTap: () => FocusScope.of(context).unfocus(),
|
||||||
child: Stack(
|
child: Container(
|
||||||
fit: StackFit.expand,
|
color: Colors.black, // 如果图片不够高,上下用黑色填充
|
||||||
children: [
|
child: Stack(
|
||||||
// 全屏背景图片
|
fit: StackFit.expand,
|
||||||
if (widget.backgroundImage != null)
|
children: [
|
||||||
Image.asset(
|
// 全屏背景图片 - 使用 fitWidth 保持宽高比
|
||||||
widget.backgroundImage!,
|
if (widget.backgroundImage != null)
|
||||||
fit: BoxFit.cover,
|
Center(
|
||||||
width: double.infinity,
|
child: Image.asset(
|
||||||
height: double.infinity,
|
widget.backgroundImage!,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
fit: BoxFit.fitWidth,
|
||||||
return Container(
|
width: double.infinity,
|
||||||
color: const Color(0xFFFFF8E7),
|
errorBuilder: (context, error, stackTrace) {
|
||||||
);
|
return Container(
|
||||||
},
|
color: const Color(0xFFFFF8E7),
|
||||||
)
|
);
|
||||||
else
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(
|
||||||
|
color: const Color(0xFFFFF8E7),
|
||||||
|
),
|
||||||
|
// 半透明遮罩,让内容更清晰
|
||||||
Container(
|
Container(
|
||||||
color: const Color(0xFFFFF8E7),
|
decoration: BoxDecoration(
|
||||||
),
|
gradient: LinearGradient(
|
||||||
// 半透明遮罩,让内容更清晰
|
begin: Alignment.topCenter,
|
||||||
Container(
|
end: Alignment.bottomCenter,
|
||||||
decoration: BoxDecoration(
|
colors: [
|
||||||
gradient: LinearGradient(
|
Colors.black.withValues(alpha: 0.3),
|
||||||
begin: Alignment.topCenter,
|
Colors.black.withValues(alpha: 0.6),
|
||||||
end: Alignment.bottomCenter,
|
],
|
||||||
colors: [
|
),
|
||||||
Colors.black.withValues(alpha: 0.3),
|
|
||||||
Colors.black.withValues(alpha: 0.6),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
// 内容区域
|
// 内容区域
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
|
@ -526,6 +567,7 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue