fix(app): X 상세 화면 프로필 사진/표시 이름 누락 수정

- ScheduleDetail에 profileDisplayName, profileAvatarUrl 필드 추가
- X 섹션: profile.displayName으로 표시 이름, profile.avatarUrl로 프로필 사진 표시
- 아바타 URL이 없을 때 이니셜 폴백 유지
- @username 표시 수정 (displayName이 아닌 실제 username 사용)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-03-29 14:21:24 +09:00
parent fc38678fbd
commit 998125333b
2 changed files with 55 additions and 24 deletions

View file

@ -55,6 +55,9 @@ class ScheduleDetail {
final String? content; final String? content;
final List<String> imageUrls; final List<String> imageUrls;
final String? postUrl; final String? postUrl;
// X
final String? profileDisplayName;
final String? profileAvatarUrl;
ScheduleDetail({ ScheduleDetail({
required this.id, required this.id,
@ -75,6 +78,8 @@ class ScheduleDetail {
this.content, this.content,
this.imageUrls = const [], this.imageUrls = const [],
this.postUrl, this.postUrl,
this.profileDisplayName,
this.profileAvatarUrl,
}); });
factory ScheduleDetail.fromJson(Map<String, dynamic> json) { factory ScheduleDetail.fromJson(Map<String, dynamic> json) {
@ -103,6 +108,8 @@ class ScheduleDetail {
content: json['content'] as String?, content: json['content'] as String?,
imageUrls: (json['imageUrls'] as List<dynamic>?)?.cast<String>() ?? [], imageUrls: (json['imageUrls'] as List<dynamic>?)?.cast<String>() ?? [],
postUrl: json['postUrl'] as String?, postUrl: json['postUrl'] as String?,
profileDisplayName: (json['profile'] as Map<String, dynamic>?)?['displayName'] as String?,
profileAvatarUrl: (json['profile'] as Map<String, dynamic>?)?['avatarUrl'] as String?,
); );
} }

View file

@ -423,7 +423,9 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
/// X /// X
Widget _buildXSection(ScheduleDetail schedule) { Widget _buildXSection(ScheduleDetail schedule) {
final displayName = schedule.username ?? 'Unknown'; final username = schedule.username ?? 'Unknown';
final displayName = schedule.profileDisplayName ?? username;
final avatarUrl = schedule.profileAvatarUrl;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -440,28 +442,24 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
child: Row( child: Row(
children: [ children: [
// //
Container( avatarUrl != null
width: 40, ? ClipOval(
height: 40, child: CachedNetworkImage(
decoration: BoxDecoration( imageUrl: avatarUrl,
gradient: LinearGradient( width: 40,
begin: Alignment.topLeft, height: 40,
end: Alignment.bottomRight, fit: BoxFit.cover,
colors: [Colors.grey[700]!, Colors.grey[900]!], placeholder: (_, _) => Container(
), width: 40, height: 40,
shape: BoxShape.circle, decoration: BoxDecoration(
), color: Colors.grey[300],
child: Center( shape: BoxShape.circle,
child: Text( ),
displayName[0].toUpperCase(), ),
style: const TextStyle( errorWidget: (_, _, _) => _buildAvatarFallback(displayName),
color: Colors.white, ),
fontWeight: FontWeight.bold, )
fontSize: 16, : _buildAvatarFallback(displayName),
),
),
),
),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
@ -484,7 +482,7 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
], ],
), ),
Text( Text(
'@$displayName', '@$username',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: Colors.grey[500], color: Colors.grey[500],
@ -691,6 +689,32 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
); );
} }
/// ()
Widget _buildAvatarFallback(String name) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.grey[700]!, Colors.grey[900]!],
),
shape: BoxShape.circle,
),
child: Center(
child: Text(
name[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
);
}
/// ///
Widget _buildVerifiedBadge() { Widget _buildVerifiedBadge() {
return SizedBox( return SizedBox(