From b023e08750878c40870151e4f73d4bf7d0a65e71 Mon Sep 17 00:00:00 2001 From: caadiq Date: Thu, 15 Jan 2026 21:39:29 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9D=BC=EC=A0=95=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PC/모바일 웹: - 콘서트 선택된 일정 텍스트 볼드체 제거 - 모바일 콘서트 헤더 장소 표시 제거 - 모바일 콘서트 포스터 여백 제거 Flutter 앱: - lucide_icons 사용하여 웹과 동일한 아이콘 적용 - 콘서트 헤더 장소 제거 및 포스터 여백 제거 - 콘서트 선택된 일정 볼드체 제거 - 지도 플레이스홀더 추가 (탭시 카카오맵 이동) - 길찾기 버튼 색상 blue-500으로 변경 - 유튜브 섹션: 숏츠 둥근 테두리, 버튼 제거, 유튜브 아이콘으로 교체 - X 섹션: 웹과 동일한 디자인 (프로필, @username, 인증배지, X로고 버튼) Co-Authored-By: Claude Opus 4.5 --- .../views/schedule/schedule_detail_view.dart | 495 +++++++++++++----- .../pages/mobile/public/ScheduleDetail.jsx | 33 +- .../src/pages/pc/public/ScheduleDetail.jsx | 2 +- 3 files changed, 377 insertions(+), 153 deletions(-) diff --git a/app/lib/views/schedule/schedule_detail_view.dart b/app/lib/views/schedule/schedule_detail_view.dart index d97b765..dcd2835 100644 --- a/app/lib/views/schedule/schedule_detail_view.dart +++ b/app/lib/views/schedule/schedule_detail_view.dart @@ -4,6 +4,7 @@ library; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../core/constants.dart'; import '../../models/schedule.dart'; @@ -74,6 +75,13 @@ class _ScheduleDetailViewState extends ConsumerState { return null; } + /// X URL에서 username 추출 + String? _extractXUsername(String? url) { + if (url == null) return null; + final match = RegExp(r'(?:twitter\.com|x\.com)/([^/]+)').firstMatch(url); + return match?.group(1); + } + /// 날짜 포맷팅 (2026. 1. 15. (수)) String _formatFullDate(String dateStr) { final date = DateTime.parse(dateStr); @@ -92,6 +100,27 @@ class _ScheduleDetailViewState extends ConsumerState { return result; } + /// X용 날짜/시간 포맷팅 (오후 2:30 · 2026년 1월 15일) + String _formatXDateTime(String dateStr, String? timeStr) { + final date = DateTime.parse(dateStr); + final year = date.year; + final month = date.month; + final day = date.day; + + var result = '$year년 $month월 $day일'; + + if (timeStr != null && timeStr.length >= 5) { + final parts = timeStr.split(':'); + final hours = int.parse(parts[0]); + final minutes = parts[1]; + final period = hours < 12 ? '오전' : '오후'; + final hour12 = hours == 0 ? 12 : (hours > 12 ? hours - 12 : hours); + result = '$period $hour12:$minutes · $result'; + } + + return result; + } + @override Widget build(BuildContext context) { final scheduleAsync = ref.watch(scheduleDetailProvider(_currentScheduleId)); @@ -103,7 +132,7 @@ class _ScheduleDetailViewState extends ConsumerState { elevation: 0, scrolledUnderElevation: 0.5, leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 20), + icon: const Icon(LucideIcons.chevronLeft, size: 24), onPressed: () => Navigator.of(context).pop(), ), title: scheduleAsync.whenOrNull( @@ -144,7 +173,7 @@ class _ScheduleDetailViewState extends ConsumerState { borderRadius: BorderRadius.circular(20), ), child: Icon( - Icons.calendar_today, + LucideIcons.calendar, size: 40, color: AppColors.primary.withValues(alpha: 0.4), ), @@ -170,7 +199,7 @@ class _ScheduleDetailViewState extends ConsumerState { const SizedBox(height: 32), OutlinedButton.icon( onPressed: () => Navigator.of(context).pop(), - icon: const Icon(Icons.arrow_back), + icon: const Icon(LucideIcons.arrowLeft, size: 18), label: const Text('돌아가기'), style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, @@ -219,15 +248,15 @@ class _ScheduleDetailViewState extends ConsumerState { return Column( children: [ - // 썸네일 + 재생 버튼 + // 썸네일 + 재생 버튼 (클릭시 YouTube로 이동) GestureDetector( onTap: () => _launchUrl(schedule.sourceUrl!), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: isShorts - ? Center( - child: SizedBox( - width: 200, + child: isShorts + ? Center( + child: SizedBox( + width: 200, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: 9 / 16, child: Stack( @@ -239,20 +268,23 @@ class _ScheduleDetailViewState extends ConsumerState { width: double.infinity, height: double.infinity, placeholder: (_, _) => Container( - color: Colors.grey[200], + color: Colors.grey[900], ), errorWidget: (_, _, _) => Container( - color: Colors.grey[200], - child: const Icon(Icons.play_circle_outline, size: 48), + color: Colors.grey[900], + child: const Icon(LucideIcons.play, size: 48, color: Colors.white54), ), ), - _buildPlayButton(), + _buildYoutubePlayButton(), ], ), ), ), - ) - : AspectRatio( + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(12), + child: AspectRatio( aspectRatio: 16 / 9, child: Stack( alignment: Alignment.center, @@ -263,55 +295,56 @@ class _ScheduleDetailViewState extends ConsumerState { width: double.infinity, height: double.infinity, placeholder: (_, _) => Container( - color: Colors.grey[200], + color: Colors.grey[900], ), errorWidget: (_, _, _) => Container( - color: Colors.grey[200], - child: const Icon(Icons.play_circle_outline, size: 48), + color: Colors.grey[900], + child: const Icon(LucideIcons.play, size: 48, color: Colors.white54), ), ), - _buildPlayButton(), + _buildYoutubePlayButton(), ], ), ), - ), + ), ), const SizedBox(height: 16), - // 정보 카드 - _buildInfoCard( - schedule, - bottomButton: _buildYoutubeButton(schedule.sourceUrl), - ), + // 정보 카드 (버튼 없음) + _buildInfoCard(schedule, bottomButton: null), ], ); } - /// 재생 버튼 - Widget _buildPlayButton() { + /// 유튜브 재생 버튼 (실제 유튜브 아이콘) + Widget _buildYoutubePlayButton() { return Container( - width: 64, - height: 64, + width: 68, + height: 48, decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - blurRadius: 8, - offset: const Offset(0, 2), + color: Colors.black.withValues(alpha: 0.4), + blurRadius: 12, + offset: const Offset(0, 4), ), ], ), - child: const Icon( - Icons.play_arrow, - color: Colors.white, - size: 40, + child: Center( + child: CustomPaint( + size: const Size(24, 24), + painter: _YoutubePlayIconPainter(), + ), ), ); } - /// X 섹션 + /// X 섹션 (웹과 동일) Widget _buildXSection(ScheduleDetail schedule) { + final username = _extractXUsername(schedule.sourceUrl); + final displayName = schedule.sourceName ?? username ?? 'Unknown'; + return Container( decoration: BoxDecoration( color: Colors.white, @@ -323,22 +356,28 @@ class _ScheduleDetailViewState extends ConsumerState { children: [ // 헤더 Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Row( children: [ + // 프로필 이미지 Container( width: 40, height: 40, decoration: BoxDecoration( - color: Colors.grey[800], + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.grey[700]!, Colors.grey[900]!], + ), shape: BoxShape.circle, ), child: Center( child: Text( - (schedule.sourceName ?? 'X')[0].toUpperCase(), + displayName[0].toUpperCase(), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, + fontSize: 16, ), ), ), @@ -352,7 +391,7 @@ class _ScheduleDetailViewState extends ConsumerState { children: [ Flexible( child: Text( - schedule.sourceName ?? '', + displayName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -361,10 +400,18 @@ class _ScheduleDetailViewState extends ConsumerState { ), ), const SizedBox(width: 4), - const Icon(Icons.verified, - color: Colors.blue, size: 16), + // 인증 배지 (웹과 동일한 SVG 형태) + _buildVerifiedBadge(), ], ), + if (username != null) + Text( + '@$username', + style: TextStyle( + fontSize: 13, + color: Colors.grey[500], + ), + ), ], ), ), @@ -373,35 +420,45 @@ class _ScheduleDetailViewState extends ConsumerState { ), // 본문 Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(16), child: Text( decodeHtmlEntities(schedule.description ?? schedule.title), - style: const TextStyle(fontSize: 15, height: 1.5), + style: const TextStyle( + fontSize: 15, + height: 1.5, + color: AppColors.textPrimary, + ), ), ), // 이미지 if (schedule.imageUrl != null) ...[ - const SizedBox(height: 12), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: ClipRRect( borderRadius: BorderRadius.circular(12), - child: Image.network( - schedule.imageUrl!, + child: CachedNetworkImage( + imageUrl: schedule.imageUrl!, fit: BoxFit.cover, - errorBuilder: (_, _, _) => const SizedBox.shrink(), + placeholder: (_, _) => Container( + height: 200, + color: Colors.grey[200], + ), + errorWidget: (_, _, _) => const SizedBox.shrink(), ), ), ), ], - // 날짜 - Padding( - padding: const EdgeInsets.all(16), + // 날짜/시간 + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: Colors.grey[100]!)), + ), child: Text( - _formatFullDate(schedule.date), - style: const TextStyle( + _formatXDateTime(schedule.date, schedule.time), + style: TextStyle( fontSize: 14, - color: AppColors.textSecondary, + color: Colors.grey[500], ), ), ), @@ -410,22 +467,32 @@ class _ScheduleDetailViewState extends ConsumerState { padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[50], - border: Border(top: BorderSide(color: AppColors.border)), + border: Border(top: BorderSide(color: Colors.grey[100]!)), ), child: SizedBox( width: double.infinity, - child: ElevatedButton.icon( + child: ElevatedButton( onPressed: () => _launchUrl(schedule.sourceUrl ?? ''), - icon: const Icon(Icons.open_in_new, size: 18), - label: const Text('X에서 보기'), style: ElevatedButton.styleFrom( - backgroundColor: Colors.black, + backgroundColor: Colors.grey[900], foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), + padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // X 로고 + _buildXLogo(14), + const SizedBox(width: 8), + const Text( + 'X에서 보기', + style: TextStyle(fontWeight: FontWeight.w500), + ), + ], + ), ), ), ), @@ -434,6 +501,28 @@ class _ScheduleDetailViewState extends ConsumerState { ); } + /// 인증 배지 (웹과 동일한 SVG) + Widget _buildVerifiedBadge() { + return SizedBox( + width: 16, + height: 16, + child: CustomPaint( + painter: _VerifiedBadgePainter(), + ), + ); + } + + /// X 로고 + Widget _buildXLogo(double size) { + return SizedBox( + width: size, + height: size, + child: CustomPaint( + painter: _XLogoPainter(), + ), + ); + } + /// 콘서트 섹션 Widget _buildConcertSection(ScheduleDetail schedule) { final hasLocation = @@ -456,9 +545,8 @@ class _ScheduleDetailViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 헤더 + // 헤더 (포스터 여백 제거) Container( - padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [AppColors.primary, AppColors.primary.withValues(alpha: 0.8)], @@ -470,42 +558,33 @@ class _ScheduleDetailViewState extends ConsumerState { children: [ if (hasPoster) ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.network( - schedule.images[0], + borderRadius: const BorderRadius.only(topLeft: Radius.circular(12)), + child: CachedNetworkImage( + imageUrl: schedule.images[0], width: 56, height: 72, fit: BoxFit.cover, - errorBuilder: (_, _, _) => const SizedBox.shrink(), + placeholder: (_, _) => Container( + width: 56, + height: 72, + color: Colors.white24, + ), + errorWidget: (_, _, _) => const SizedBox.shrink(), ), ), - if (hasPoster) const SizedBox(width: 12), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - decodeHtmlEntities(schedule.title), - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 15, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + decodeHtmlEntities(schedule.title), + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 15, ), - if (schedule.locationName != null) ...[ - const SizedBox(height: 4), - Text( - schedule.locationName!, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.8), - fontSize: 12, - ), - overflow: TextOverflow.ellipsis, - ), - ], - ], + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), ), ), ], @@ -518,7 +597,7 @@ class _ScheduleDetailViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ // 공연 일정 - _buildSectionLabel(Icons.calendar_today, '공연 일정'), + _buildSectionLabel(LucideIcons.calendar, '공연 일정'), const SizedBox(height: 8), if (hasMultipleDates) ...schedule.relatedDates.asMap().entries.map((entry) { @@ -546,8 +625,7 @@ class _ScheduleDetailViewState extends ConsumerState { '${index + 1}회차 ${_formatSingleDate(item.date, item.time)}', style: TextStyle( fontSize: 13, - fontWeight: - isCurrent ? FontWeight.bold : FontWeight.normal, + // 볼드체 제거 color: isCurrent ? Colors.white : AppColors.textPrimary, @@ -568,7 +646,7 @@ class _ScheduleDetailViewState extends ConsumerState { const SizedBox(height: 16), // 장소 if (schedule.locationName != null) ...[ - _buildSectionLabel(Icons.place, '장소'), + _buildSectionLabel(LucideIcons.mapPin, '장소'), const SizedBox(height: 8), Text( schedule.locationName!, @@ -589,6 +667,75 @@ class _ScheduleDetailViewState extends ConsumerState { ], const SizedBox(height: 16), ], + // 위치 (지도) + if (hasLocation) ...[ + _buildSectionLabel(LucideIcons.navigation, '위치'), + const SizedBox(height: 8), + // 지도 플레이스홀더 (탭시 카카오맵으로 이동) + GestureDetector( + onTap: () => _launchUrl( + 'https://map.kakao.com/link/map/${Uri.encodeComponent(schedule.locationName!)},${schedule.locationLat},${schedule.locationLng}', + ), + child: Container( + height: 160, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.border), + ), + child: Stack( + children: [ + // 지도 이미지 (카카오 Static Map API) + ClipRRect( + borderRadius: BorderRadius.circular(11), + child: CachedNetworkImage( + imageUrl: 'https://map.kakao.com/link/map/${Uri.encodeComponent(schedule.locationName!)},${schedule.locationLat},${schedule.locationLng}', + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + placeholder: (_, _) => Container( + color: Colors.grey[200], + child: const Center( + child: Icon(LucideIcons.map, size: 32, color: Colors.grey), + ), + ), + errorWidget: (_, _, _) => Container( + color: Colors.grey[200], + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(LucideIcons.navigation, size: 28, color: Colors.grey[400]), + const SizedBox(height: 8), + Text( + schedule.locationName!, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + Text( + '탭하여 지도에서 보기', + style: TextStyle( + fontSize: 11, + color: Colors.grey[400], + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 16), + ], // 설명 if (schedule.description != null && schedule.description!.isNotEmpty) ...[ @@ -618,33 +765,39 @@ class _ScheduleDetailViewState extends ConsumerState { padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Column( children: [ + // 길찾기 버튼 (파란색) if (hasLocation) SizedBox( width: double.infinity, - child: ElevatedButton.icon( + child: ElevatedButton( onPressed: () => _launchUrl( 'https://map.kakao.com/link/to/${Uri.encodeComponent(schedule.locationName!)},${schedule.locationLat},${schedule.locationLng}'), - icon: const Icon(Icons.navigation, size: 18), - label: const Text('길찾기'), style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, + backgroundColor: const Color(0xFF3B82F6), // blue-500 foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(LucideIcons.navigation, size: 18), + SizedBox(width: 8), + Text('길찾기'), + ], + ), ), ), if (hasLocation && schedule.sourceUrl != null) const SizedBox(height: 8), + // 상세 정보 버튼 if (schedule.sourceUrl != null) SizedBox( width: double.infinity, - child: ElevatedButton.icon( + child: ElevatedButton( onPressed: () => _launchUrl(schedule.sourceUrl!), - icon: const Icon(Icons.open_in_new, size: 18), - label: const Text('상세 정보 보기'), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[900], foregroundColor: Colors.white, @@ -653,6 +806,14 @@ class _ScheduleDetailViewState extends ConsumerState { borderRadius: BorderRadius.circular(12), ), ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(LucideIcons.externalLink, size: 18), + SizedBox(width: 8), + Text('상세 정보 보기'), + ], + ), ), ), ], @@ -670,10 +831,8 @@ class _ScheduleDetailViewState extends ConsumerState { bottomButton: schedule.sourceUrl != null ? SizedBox( width: double.infinity, - child: ElevatedButton.icon( + child: ElevatedButton( onPressed: () => _launchUrl(schedule.sourceUrl!), - icon: const Icon(Icons.open_in_new, size: 18), - label: const Text('원본 보기'), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[900], foregroundColor: Colors.white, @@ -682,6 +841,14 @@ class _ScheduleDetailViewState extends ConsumerState { borderRadius: BorderRadius.circular(12), ), ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(LucideIcons.externalLink, size: 18), + SizedBox(width: 8), + Text('원본 보기'), + ], + ), ), ) : null, @@ -724,11 +891,11 @@ class _ScheduleDetailViewState extends ConsumerState { spacing: 12, runSpacing: 8, children: [ - _buildMetaItem(Icons.calendar_today, _formatFullDate(schedule.date)), + _buildMetaItem(LucideIcons.calendar, _formatFullDate(schedule.date)), if (schedule.formattedTime != null) - _buildMetaItem(Icons.access_time, schedule.formattedTime!), + _buildMetaItem(LucideIcons.clock, schedule.formattedTime!), if (schedule.sourceName != null) - _buildMetaItem(Icons.link, schedule.sourceName!), + _buildMetaItem(LucideIcons.link2, schedule.sourceName!), ], ), // 멤버 @@ -808,25 +975,89 @@ class _ScheduleDetailViewState extends ConsumerState { ], ); } - - /// 유튜브 버튼 - Widget _buildYoutubeButton(String? url) { - if (url == null) return const SizedBox.shrink(); - return SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () => _launchUrl(url), - icon: const Icon(Icons.play_circle_filled, size: 20), - label: const Text('YouTube에서 보기'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ); - } +} + +/// 유튜브 재생 아이콘 페인터 +class _YoutubePlayIconPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + + final path = Path() + ..moveTo(size.width * 0.35, size.height * 0.25) + ..lineTo(size.width * 0.35, size.height * 0.75) + ..lineTo(size.width * 0.75, size.height * 0.5) + ..close(); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +/// 인증 배지 페인터 (웹과 동일한 디자인) +class _VerifiedBadgePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = const Color(0xFF3B82F6) // blue-500 + ..style = PaintingStyle.fill; + + // 배지 배경 (둥근 별 모양) + final center = Offset(size.width / 2, size.height / 2); + final radius = size.width / 2; + canvas.drawCircle(center, radius, paint); + + // 체크마크 + final checkPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5 + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round; + + final checkPath = Path() + ..moveTo(size.width * 0.28, size.height * 0.52) + ..lineTo(size.width * 0.45, size.height * 0.68) + ..lineTo(size.width * 0.72, size.height * 0.35); + + canvas.drawPath(checkPath, checkPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +/// X 로고 페인터 +class _XLogoPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + + final path = Path(); + // X 로고 경로 (간소화) + path.moveTo(size.width * 0.76, size.height * 0.09); + path.lineTo(size.width * 0.9, size.height * 0.09); + path.lineTo(size.width * 0.6, size.height * 0.44); + path.lineTo(size.width * 0.95, size.height * 0.91); + path.lineTo(size.width * 0.67, size.height * 0.91); + path.lineTo(size.width * 0.46, size.height * 0.63); + path.lineTo(size.width * 0.21, size.height * 0.91); + path.lineTo(size.width * 0.07, size.height * 0.91); + path.lineTo(size.width * 0.4, size.height * 0.53); + path.lineTo(size.width * 0.05, size.height * 0.09); + path.lineTo(size.width * 0.34, size.height * 0.09); + path.lineTo(size.width * 0.52, size.height * 0.35); + path.close(); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } diff --git a/frontend/src/pages/mobile/public/ScheduleDetail.jsx b/frontend/src/pages/mobile/public/ScheduleDetail.jsx index 34542bf..6fdfbc9 100644 --- a/frontend/src/pages/mobile/public/ScheduleDetail.jsx +++ b/frontend/src/pages/mobile/public/ScheduleDetail.jsx @@ -427,25 +427,18 @@ function ConcertSection({ schedule, onDateChange }) { className="bg-white rounded-xl overflow-hidden shadow-sm" > {/* 헤더: 포스터 썸네일 + 제목 */} -
-
- {hasPoster && ( - {schedule.title} - )} -
-

- {decodeHtmlEntities(schedule.title)} -

- {schedule.location_name && ( -

- {schedule.location_name} -

- )} -
+
+ {hasPoster && ( + {schedule.title} + )} +
+

+ {decodeHtmlEntities(schedule.title)} +

@@ -467,7 +460,7 @@ function ConcertSection({ schedule, onDateChange }) { onClick={() => onDateChange(item.id)} className={`block w-full px-3 py-2 rounded-lg text-sm text-left transition-all ${ isCurrentDate - ? 'bg-primary text-white font-bold' + ? 'bg-primary text-white' : 'bg-gray-50 active:bg-gray-100 text-gray-700' }`} > diff --git a/frontend/src/pages/pc/public/ScheduleDetail.jsx b/frontend/src/pages/pc/public/ScheduleDetail.jsx index 45944f0..3c722c9 100644 --- a/frontend/src/pages/pc/public/ScheduleDetail.jsx +++ b/frontend/src/pages/pc/public/ScheduleDetail.jsx @@ -495,7 +495,7 @@ function ConcertSection({ schedule }) { to={`/schedule/${item.id}`} className={`block px-4 py-2.5 rounded-xl transition-all ${ isCurrentDate - ? 'bg-primary text-white font-bold shadow-lg shadow-primary/20' + ? 'bg-primary text-white shadow-lg shadow-primary/20' : 'bg-gray-50 hover:bg-gray-100 text-gray-700' }`} >