generated from ttipo/project-base
feat(flutter): 축제·행사 칸을 공연·행사로 바꾸고 카드에 종류와 세로 포스터를 보임
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Waiting to run
CI (Flutter) / Flutter (analyze · test) (pull_request) Failing after 22m20s
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 12m37s
CI / Frontend (lint · type-check · test) (pull_request) Has started running
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Waiting to run
CI (Flutter) / Flutter (analyze · test) (pull_request) Failing after 22m20s
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 12m37s
CI / Frontend (lint · type-check · test) (pull_request) Has started running
KOPIS 공연이 들어온 뒤 홈 '이번 주말' 칸의 대부분이 공연이 되었다. '축제·행사'라는 이름이면 축제 목록으로 오해하기 쉬워 '공연·행사'로 바꿨다 (이번 주말 · 다가오는 · 장소 상세 근처 · 빈 문구 · 홈 카테고리 칸, 영어 Shows & events). - 카드 아래 줄 앞에 종류(공연 · 축제 · 행사 · 전시)를 굵게 보인다. 배지는 쓰지 않는다 - 포스터가 대부분 세로(A판)라 썸네일을 5:7 로 보인다(홈 120×168, 세로 목록 60×84). 공공누리 3유형 사진은 그대로 자르지 않는다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ abstract final class AppColors {
|
||||
/// 사진이 없을 때의 자리 표시
|
||||
static const Color placeholder = Color(0xFFF5F6FA);
|
||||
|
||||
/// 축제·행사 날짜 글자
|
||||
/// 공연·행사 날짜 글자
|
||||
static const Color eventDate = Color(0xFF8A5A00);
|
||||
|
||||
/// 반려동물 동반 글자(발바닥 아이콘과 함께)
|
||||
|
||||
@@ -30,11 +30,11 @@ class HomeEvents {
|
||||
/// 조회한 기간 — '전체 보기' 가 같은 기간을 연다
|
||||
final DateRange range;
|
||||
|
||||
/// 주말이 비어 '다가오는 축제·행사' 로 바꿔 보이는지
|
||||
/// 주말이 비어 '다가오는 공연·행사' 로 바꿔 보이는지
|
||||
final bool isUpcoming;
|
||||
}
|
||||
|
||||
/// 홈 '이번 주말 축제·행사' — 기준 위치 반경 50km 거리순, 비면 같은 반경 30일
|
||||
/// 홈 '이번 주말 공연·행사' — 기준 위치 반경 50km 거리순, 비면 같은 반경 30일
|
||||
final homeEventsProvider = FutureProvider<HomeEvents>((ref) async {
|
||||
final base = await ref.watch(basePointProvider.future);
|
||||
final repo = ref.watch(eventRepositoryProvider);
|
||||
|
||||
@@ -6,11 +6,21 @@ import 'package:wandergo/core/utils/format.dart';
|
||||
import 'package:wandergo/features/event/data/models/event_models.dart';
|
||||
import 'package:wandergo/features/event/presentation/widgets/event_visuals.dart';
|
||||
|
||||
/// 홈 가로 목록 카드 너비·사진 높이 (디자인 캔버스 148×110)
|
||||
const double kEventCardWidth = 148;
|
||||
const double kEventCardImageHeight = 110;
|
||||
/// 홈 가로 목록 카드 너비·사진 높이 — 세로 포스터 비율(5:7)
|
||||
const double kEventCardWidth = 120;
|
||||
const double kEventCardImageHeight = kEventCardWidth / kEventPosterRatio;
|
||||
|
||||
/// 홈 가로 목록의 한 칸 — 사진 · 기간(갈색) · 이름 · 지역·거리 (테두리 없음)
|
||||
/// 사진 아래 여백 · 기간 · 이름 · 분류·지역 세 줄의 높이
|
||||
const double _kEventCardTextHeight = 72;
|
||||
|
||||
/// 홈 가로 목록 높이 — 사진 + 글자 세 줄
|
||||
const double kEventCardHeight = kEventCardImageHeight + _kEventCardTextHeight;
|
||||
|
||||
/// 세로 목록의 작은 사진 — 같은 포스터 비율
|
||||
const double _kEventRowImageWidth = 60;
|
||||
const double _kEventRowImageHeight = _kEventRowImageWidth / kEventPosterRatio;
|
||||
|
||||
/// 홈 가로 목록의 한 칸 — 포스터 · 기간(갈색) · 이름 · 분류·지역·거리 (테두리 없음)
|
||||
class EventCard extends StatelessWidget {
|
||||
const EventCard({super.key, required this.item, required this.onTap});
|
||||
|
||||
@@ -55,12 +65,7 @@ class EventCard extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
eventMeta(item),
|
||||
style: textTheme.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
EventMetaText(item: item),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -68,7 +73,7 @@ class EventCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 세로 목록의 한 줄 — 작은 사진 + 기간 · 이름 · 분류·지역·거리, 아래 구분선
|
||||
/// 세로 목록의 한 줄 — 작은 포스터 + 기간 · 이름 · 분류·지역·거리, 아래 구분선
|
||||
class EventRow extends StatelessWidget {
|
||||
const EventRow({super.key, required this.item, required this.onTap});
|
||||
|
||||
@@ -79,11 +84,6 @@ class EventRow extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final kind = item.kind?.label(l10n);
|
||||
final meta = [
|
||||
?kind,
|
||||
eventMeta(item),
|
||||
].where((s) => s.isNotEmpty).join(' · ');
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
@@ -94,8 +94,8 @@ class EventRow extends StatelessWidget {
|
||||
url: item.imageThumbUrl,
|
||||
kind: item.kind,
|
||||
license: item.imageLicense,
|
||||
width: 64,
|
||||
height: 64,
|
||||
width: _kEventRowImageWidth,
|
||||
height: _kEventRowImageHeight,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
@@ -123,12 +123,7 @@ class EventRow extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
meta,
|
||||
style: textTheme.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
EventMetaText(item: item),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:wandergo/core/l10n/l10n.dart';
|
||||
import 'package:wandergo/core/theme/app_colors.dart';
|
||||
import 'package:wandergo/core/theme/app_theme.dart';
|
||||
import 'package:wandergo/core/utils/format.dart';
|
||||
import 'package:wandergo/features/event/data/models/event_models.dart';
|
||||
@@ -48,6 +49,42 @@ String eventMeta(EventItem item) {
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
/// 포스터 비율 — 축제·공연·행사 포스터가 대부분 세로(A판 1:1.41)라 5:7 로 보인다(2026-11-12)
|
||||
const double kEventPosterRatio = 5 / 7;
|
||||
|
||||
/// 목록 메타 줄 — "공연 · 수성구 · 1.2km". 홈 행사 칸이 대부분 공연이라 분류를 굵게 앞에 둔다(2026-11-12)
|
||||
class EventMetaText extends StatelessWidget {
|
||||
const EventMetaText({super.key, required this.item});
|
||||
|
||||
final EventItem item;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context).textTheme.bodySmall;
|
||||
final kind = item.kind?.label(context.l10n);
|
||||
final rest = eventMeta(item);
|
||||
return Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
if (kind != null)
|
||||
TextSpan(
|
||||
text: kind,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (kind != null && rest.isNotEmpty) const TextSpan(text: ' · '),
|
||||
if (rest.isNotEmpty) TextSpan(text: rest),
|
||||
],
|
||||
),
|
||||
style: style,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 행사 사진 — 사진이 없으면 행사 종류별 자리 표시를 보인다([PublicImage]).
|
||||
class EventImage extends StatelessWidget {
|
||||
const EventImage({
|
||||
|
||||
@@ -21,10 +21,7 @@ import 'package:wandergo/shared/widgets/language_button.dart';
|
||||
import 'package:wandergo/shared/widgets/load_error.dart';
|
||||
import 'package:wandergo/shared/widgets/section_header.dart';
|
||||
|
||||
/// 홈 가로 목록 높이 — 사진 110 + 여백·글자 세 줄
|
||||
const double _kEventStripHeight = 186;
|
||||
|
||||
/// 3 홈 — 기준 위치 · 제목 · 검색창 · 카테고리 · 이번 주말 축제·행사 · 가까운 반려동물 동반 장소 · 출처.
|
||||
/// 3 홈 — 기준 위치 · 제목 · 검색창 · 카테고리 · 이번 주말 공연·행사 · 가까운 반려동물 동반 장소 · 출처.
|
||||
/// 배너·AI·추천 코스·지역 바로가기·로그인 버튼은 해당 기능을 만들 때 붙인다.
|
||||
class HomePage extends ConsumerWidget {
|
||||
const HomePage({super.key});
|
||||
@@ -195,7 +192,7 @@ class _BaseLocationButton extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 이번 주말 축제·행사 — 비면 다가오는 축제·행사
|
||||
/// 이번 주말 공연·행사 — 비면 다가오는 공연·행사
|
||||
class _EventsSection extends ConsumerWidget {
|
||||
const _EventsSection();
|
||||
|
||||
@@ -239,7 +236,7 @@ class _EventsSection extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
AsyncData(:final value) => SizedBox(
|
||||
height: _kEventStripHeight,
|
||||
height: kEventCardHeight,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: kPagePadding),
|
||||
@@ -275,7 +272,7 @@ class _EventStripSkeleton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: _kEventStripHeight,
|
||||
height: kEventCardHeight,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
|
||||
@@ -7,7 +7,7 @@ const int kPlaceSearchRadiusM = 20000;
|
||||
/// 검색 화면의 분류 탭 — 백엔드 분류 여러 개를 사람이 고르는 단위로 묶는다.
|
||||
///
|
||||
/// 디자인의 '축제' 탭은 행사 API 에 검색어 조건이 없어 아직 넣지 않았다
|
||||
/// (홈 카테고리의 '축제'는 행사 목록으로 간다).
|
||||
/// (홈 카테고리의 '공연·행사'는 행사 목록으로 간다).
|
||||
enum PlaceTab {
|
||||
all([]),
|
||||
sights([PlaceCategory.attraction, PlaceCategory.leisure]),
|
||||
|
||||
@@ -14,7 +14,7 @@ import 'package:wandergo/features/place/domain/place_search.dart';
|
||||
/// 홈 '가까운 반려동물 동반 장소' 에 보이는 수
|
||||
const int kHomePetPlaceLimit = 3;
|
||||
|
||||
/// 장소 상세 '근처 축제·행사' 반경(m) — 없으면 칸을 숨긴다(2026-11-03 합의)
|
||||
/// 장소 상세 '근처 공연·행사' 반경(m) — 없으면 칸을 숨긴다(2026-11-03 합의)
|
||||
const int kNearbyEventRadiusM = 5000;
|
||||
const int kNearbyEventLimit = 3;
|
||||
|
||||
@@ -41,7 +41,7 @@ final homePetPlacesProvider = FutureProvider<List<PlaceItem>>((ref) async {
|
||||
return page.items;
|
||||
});
|
||||
|
||||
/// 장소 상세 '근처 축제·행사' — 그 장소 반경 5km, 오늘부터 30일
|
||||
/// 장소 상세 '근처 공연·행사' — 그 장소 반경 5km, 오늘부터 30일
|
||||
final nearbyEventsProvider = FutureProvider.family<List<EventItem>, GeoPoint>((
|
||||
ref,
|
||||
point,
|
||||
|
||||
@@ -631,7 +631,7 @@ class _PlaceAboutSectionState extends State<PlaceAboutSection> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 근처 축제·행사 — 반경 5km·30일 안에 없으면(또는 못 불러오면) 칸을 숨긴다
|
||||
/// 근처 공연·행사 — 반경 5km·30일 안에 없으면(또는 못 불러오면) 칸을 숨긴다
|
||||
class NearbyEventsSection extends ConsumerWidget {
|
||||
const NearbyEventsSection({super.key, required this.point});
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
"locationOutsideServiceArea": "WanderGo only covers Daegu for now, so we're showing downtown Daegu.",
|
||||
"baseLocationButtonLabel": "Change base location, current {place}",
|
||||
"homeTitle": "Where to this weekend?",
|
||||
"homeWeekendEvents": "Festivals & events this weekend",
|
||||
"homeUpcomingEvents": "Upcoming festivals & events",
|
||||
"homeEventsEmpty": "No festivals or events nearby yet.",
|
||||
"homeWeekendEvents": "Shows & events this weekend",
|
||||
"homeUpcomingEvents": "Upcoming shows & events",
|
||||
"homeEventsEmpty": "No shows or events nearby yet.",
|
||||
"seeAll": "See all",
|
||||
"retry": "Try again",
|
||||
"loadFailed": "Couldn't load this.",
|
||||
@@ -57,7 +57,7 @@
|
||||
"searchClear": "Clear search",
|
||||
"categoryNavLabel": "Categories",
|
||||
"categorySights": "Sights",
|
||||
"categoryFestival": "Festivals",
|
||||
"categoryFestival": "Shows & events",
|
||||
"categoryPet": "With pets",
|
||||
"categoryFood": "Food & cafés",
|
||||
"categoryStay": "Stays",
|
||||
@@ -136,7 +136,7 @@
|
||||
"hotlineBody": "24/7 travel help in English, 日本語, 中文",
|
||||
"showMore": "Show more",
|
||||
"showLess": "Show less",
|
||||
"nearbyEvents": "Festivals & events nearby",
|
||||
"nearbyEvents": "Shows & events nearby",
|
||||
"nearbyPlaces": "Good to go with",
|
||||
"photoCredit": "Photo: {provider} · {license}",
|
||||
"koglType1": "KOGL Type 1",
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
}
|
||||
},
|
||||
"homeTitle": "이번 주말, 어디 갈까요?",
|
||||
"homeWeekendEvents": "이번 주말 축제·행사",
|
||||
"homeUpcomingEvents": "다가오는 축제·행사",
|
||||
"homeEventsEmpty": "근처에 예정된 축제·행사가 아직 없어요.",
|
||||
"homeWeekendEvents": "이번 주말 공연·행사",
|
||||
"homeUpcomingEvents": "다가오는 공연·행사",
|
||||
"homeEventsEmpty": "근처에 예정된 공연·행사가 아직 없어요.",
|
||||
"seeAll": "전체 보기",
|
||||
"retry": "다시 시도",
|
||||
"loadFailed": "불러오지 못했어요.",
|
||||
@@ -92,7 +92,7 @@
|
||||
"searchClear": "검색어 지우기",
|
||||
"categoryNavLabel": "카테고리",
|
||||
"categorySights": "관광지",
|
||||
"categoryFestival": "축제",
|
||||
"categoryFestival": "공연·행사",
|
||||
"categoryPet": "반려동물",
|
||||
"categoryFood": "맛집·카페",
|
||||
"categoryStay": "숙소",
|
||||
@@ -226,7 +226,7 @@
|
||||
"hotlineBody": "영어·일본어·중국어 24시간 안내",
|
||||
"showMore": "더 보기",
|
||||
"showLess": "접기",
|
||||
"nearbyEvents": "근처 축제·행사",
|
||||
"nearbyEvents": "근처 공연·행사",
|
||||
"nearbyPlaces": "함께 가기 좋은 곳",
|
||||
"photoCredit": "사진 {provider} · {license}",
|
||||
"@photoCredit": {
|
||||
|
||||
Reference in New Issue
Block a user