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:
hailin 2025-12-09 18:36:43 -08:00
parent a3c0d3948d
commit 243105f97f
3 changed files with 121 additions and 62 deletions

View File

@ -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;
} }
} }

View File

@ -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

View File

@ -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)
// pngjpgwebp // pngjpgwebp
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> {
), ),
), ),
], ],
),
), ),
); );
} }