diff --git a/flutter/lib/core/icons/app_icons.dart b/flutter/lib/core/icons/app_icons.dart index df1e30a..240c329 100644 --- a/flutter/lib/core/icons/app_icons.dart +++ b/flutter/lib/core/icons/app_icons.dart @@ -25,6 +25,7 @@ enum AppIconData { '', ), chevronDown(''), + chevronUp(''), chevronRight(''), chevronLeft(''), arrowRight(''), diff --git a/flutter/lib/core/theme/app_colors.dart b/flutter/lib/core/theme/app_colors.dart index 4ffbd8e..c02e5d4 100644 --- a/flutter/lib/core/theme/app_colors.dart +++ b/flutter/lib/core/theme/app_colors.dart @@ -54,6 +54,9 @@ abstract final class AppColors { /// 흰 바탕 대비가 3.2:1 이라 읽어야 하는 글자에는 [textHint] 를 쓴다 static const Color iconMuted = Color(0xFF8A90A2); + /// 검색어 지우기 원 + static const Color clearFill = Color(0xFFA3A8B6); + /// 진행 막대 바탕 static const Color progressTrack = Color(0xFFF1F2F6); diff --git a/flutter/lib/core/theme/app_theme.dart b/flutter/lib/core/theme/app_theme.dart index 7bd278d..e44ef82 100644 --- a/flutter/lib/core/theme/app_theme.dart +++ b/flutter/lib/core/theme/app_theme.dart @@ -70,9 +70,11 @@ abstract final class AppTheme { elevation: 0, scrolledUnderElevation: 0, centerTitle: false, + // 화면 제목 17/24 w700(시안 머리) titleTextStyle: text.titleSmall?.copyWith( fontSize: 17, height: 24 / 17, + fontWeight: FontWeight.w700, ), ), filledButtonTheme: FilledButtonThemeData( diff --git a/flutter/lib/features/event/presentation/pages/event_detail_page.dart b/flutter/lib/features/event/presentation/pages/event_detail_page.dart index 0266936..5e0368d 100644 --- a/flutter/lib/features/event/presentation/pages/event_detail_page.dart +++ b/flutter/lib/features/event/presentation/pages/event_detail_page.dart @@ -28,12 +28,13 @@ class EventDetailPage extends ConsumerWidget { appBar: AppBar(), body: switch (detail) { AsyncData(:final value) => _DetailBody(detail: value), - AsyncError(:final error) => Padding( - padding: const EdgeInsets.all(kPagePadding), - child: LoadError( - error: error, - onRetry: () => ref.invalidate(eventDetailProvider(id)), - ), + AsyncError(:final error) => ListView( + children: [ + LoadErrorBlock( + error: error, + onRetry: () => ref.invalidate(eventDetailProvider(id)), + ), + ], ), _ => const Center(child: CircularProgressIndicator()), }, diff --git a/flutter/lib/features/event/presentation/pages/event_list_page.dart b/flutter/lib/features/event/presentation/pages/event_list_page.dart index c058c2e..d72651d 100644 --- a/flutter/lib/features/event/presentation/pages/event_list_page.dart +++ b/flutter/lib/features/event/presentation/pages/event_list_page.dart @@ -92,7 +92,14 @@ class _EventListPageState extends ConsumerState { } Widget _body(BuildContext context) { - if (_items.isEmpty && _error == null && !_hasNext) { + final error = _error; + if (_items.isEmpty && error != null) { + // 목록 전체를 못 불러왔다 — 공통 오류 화면(시안 상태 2) + return ListView( + children: [LoadErrorBlock(error: error, onRetry: _loadMore)], + ); + } + if (_items.isEmpty && error == null && !_hasNext) { return Center( child: Text( context.l10n.homeEventsEmpty, diff --git a/flutter/lib/features/event/presentation/widgets/event_card.dart b/flutter/lib/features/event/presentation/widgets/event_card.dart index 75f5936..3ef455d 100644 --- a/flutter/lib/features/event/presentation/widgets/event_card.dart +++ b/flutter/lib/features/event/presentation/widgets/event_card.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:wandergo/core/icons/app_icons.dart'; import 'package:wandergo/core/l10n/l10n.dart'; import 'package:wandergo/core/theme/app_colors.dart'; import 'package:wandergo/core/utils/format.dart'; +import 'package:wandergo/core/utils/keep_all.dart'; import 'package:wandergo/features/event/data/models/event_models.dart'; import 'package:wandergo/features/event/presentation/widgets/event_visuals.dart'; @@ -117,8 +119,8 @@ class EventRow extends StatelessWidget { ), const SizedBox(height: 4), Text( - item.name, - style: textTheme.titleSmall, + keepAll(item.name), + style: textTheme.titleSmall?.copyWith(height: 20 / 15), maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -127,6 +129,13 @@ class EventRow extends StatelessWidget { ], ), ), + const SizedBox(width: 8), + const AppIcon( + AppIconData.chevronRight, + size: 18, + strokeWidth: 2, + color: AppColors.iconMuted, + ), ], ), ), diff --git a/flutter/lib/features/home/presentation/pages/home_page.dart b/flutter/lib/features/home/presentation/pages/home_page.dart index 9d2588c..7ec1e92 100644 --- a/flutter/lib/features/home/presentation/pages/home_page.dart +++ b/flutter/lib/features/home/presentation/pages/home_page.dart @@ -315,8 +315,8 @@ class _Footer extends StatelessWidget { children: [ const Divider(), const SizedBox(height: 12), + // 출처와 방문 전 확인 안내는 한 문단(시안
) Text(keepAll(l10n.footerSources), style: style), - const SizedBox(height: 10), Text(keepAll(l10n.footerNotice), style: style), const SizedBox(height: 10), Text('© ${l10n.appTitle}', style: style), diff --git a/flutter/lib/features/home/presentation/widgets/category_grid.dart b/flutter/lib/features/home/presentation/widgets/category_grid.dart index 9e18c46..61937f1 100644 --- a/flutter/lib/features/home/presentation/widgets/category_grid.dart +++ b/flutter/lib/features/home/presentation/widgets/category_grid.dart @@ -86,7 +86,9 @@ class CategoryGrid extends ConsumerWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (var c = 0; c < _kColumns; c++) + for (var c = 0; c < _kColumns; c++) ...[ + // 열 사이 8(시안 gap 20px 8px) + if (c > 0) const SizedBox(width: 8), Expanded( child: c < rows[r].length ? _CategoryCell( @@ -97,6 +99,7 @@ class CategoryGrid extends ConsumerWidget { ) : const SizedBox.shrink(), ), + ], ], ), ], diff --git a/flutter/lib/features/onboarding/presentation/pages/onboarding_page.dart b/flutter/lib/features/onboarding/presentation/pages/onboarding_page.dart index e7304be..b8ebb65 100644 --- a/flutter/lib/features/onboarding/presentation/pages/onboarding_page.dart +++ b/flutter/lib/features/onboarding/presentation/pages/onboarding_page.dart @@ -13,6 +13,7 @@ import 'package:wandergo/features/location/domain/base_location.dart'; import 'package:wandergo/features/location/presentation/providers/base_location_provider.dart'; import 'package:wandergo/features/location/presentation/widgets/region_sheet.dart'; import 'package:wandergo/features/onboarding/presentation/providers/onboarding_provider.dart'; +import 'package:wandergo/shared/widgets/character_image.dart'; import 'package:wandergo/shared/widgets/language_button.dart'; /// 2 첫 실행 · 위치 권한 — 현재 위치를 쓸지, 동네를 직접 고를지 정한다 @@ -71,31 +72,36 @@ class _OnboardingPageState extends ConsumerState { child: LanguageButton(compact: false), ), ), + // 그림 · 제목 · 설명은 위 줄과 아래 묶음 사이의 세로 가운데(짧은 화면에서는 넘긴다) Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Column( - children: [ - const SizedBox(height: 40), - Image.asset( - 'assets/images/character.png', - width: 176, - height: 128, - excludeFromSemantics: true, + child: LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, ), - const SizedBox(height: 24), - Text( - keepAll(l10n.onboardingTitle), - style: textTheme.headlineSmall, - textAlign: TextAlign.center, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CharacterImage(width: 176, height: 128), + const SizedBox(height: 24), + Text( + keepAll(l10n.onboardingTitle), + style: textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + keepAll(l10n.onboardingBody), + style: textTheme.bodyLarge, + textAlign: TextAlign.center, + ), + ], + ), ), - const SizedBox(height: 12), - Text( - keepAll(l10n.onboardingBody), - style: textTheme.bodyLarge, - textAlign: TextAlign.center, - ), - ], + ), ), ), ), @@ -117,6 +123,10 @@ class _OnboardingPageState extends ConsumerState { const SizedBox(height: 4), TextButton( onPressed: _busy ? null : _pickRegion, + // 누르는 칸은 44(Material 기본 48 탭 영역은 쓰지 않는다) + style: TextButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), child: Text(l10n.onboardingPickRegion), ), ], diff --git a/flutter/lib/features/place/presentation/pages/place_detail_page.dart b/flutter/lib/features/place/presentation/pages/place_detail_page.dart index 11010bf..85edd5b 100644 --- a/flutter/lib/features/place/presentation/pages/place_detail_page.dart +++ b/flutter/lib/features/place/presentation/pages/place_detail_page.dart @@ -48,12 +48,9 @@ class PlaceDetailPage extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding(padding: EdgeInsets.all(12), child: _BackButton()), - Padding( - padding: const EdgeInsets.all(kPagePadding), - child: LoadError( - error: error, - onRetry: () => ref.invalidate(placeDetailProvider(id)), - ), + LoadErrorBlock( + error: error, + onRetry: () => ref.invalidate(placeDetailProvider(id)), ), ], ), @@ -251,22 +248,23 @@ class _TopBar extends StatelessWidget { } } -/// 흰 원 안의 뒤로 버튼 — 사진 위에서도 보이게 옅은 그림자 +/// 흰 원 안의 뒤로 버튼 — 지름 44, 그림자 없음(시안) class _BackButton extends StatelessWidget { const _BackButton(); @override Widget build(BuildContext context) { - return Material( - color: AppColors.background, - shape: const CircleBorder(), - elevation: 1, - shadowColor: AppColors.text.withValues(alpha: 0.4), - child: IconButton( - tooltip: context.l10n.back, - onPressed: () => context.pop(), - icon: const AppIcon(AppIconData.chevronLeft, color: AppColors.text), + return IconButton( + tooltip: context.l10n.back, + onPressed: () => context.pop(), + style: IconButton.styleFrom( + backgroundColor: AppColors.background, + fixedSize: const Size.square(44), + minimumSize: const Size.square(44), + padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), + icon: const AppIcon(AppIconData.chevronLeft, color: AppColors.text), ); } } @@ -332,7 +330,7 @@ class _Hero extends StatelessWidget { Positioned( left: kPagePadding, right: kPagePadding, - bottom: _kSheetOverlap + 12, + bottom: _kSheetOverlap + 14, child: Text( l10n.photoCredit( detail.attribution.provider, @@ -371,7 +369,8 @@ class _TitleBlock extends ConsumerWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (meta.isNotEmpty) Text(meta, style: textTheme.bodyMedium), + if (meta.isNotEmpty) + Text(meta, style: textTheme.bodyMedium?.copyWith(height: 18 / 13)), const SizedBox(height: 6), Text( keepAll(item.displayName), @@ -383,7 +382,10 @@ class _TitleBlock extends ConsumerWidget { ], if (distance != null) ...[ const SizedBox(height: 6), - Text(distance, style: textTheme.bodyMedium), + Text( + distance, + style: textTheme.bodyMedium?.copyWith(height: 18 / 13), + ), ], ], ); diff --git a/flutter/lib/features/place/presentation/pages/place_search_page.dart b/flutter/lib/features/place/presentation/pages/place_search_page.dart index 14dedda..63850c7 100644 --- a/flutter/lib/features/place/presentation/pages/place_search_page.dart +++ b/flutter/lib/features/place/presentation/pages/place_search_page.dart @@ -16,6 +16,7 @@ import 'package:wandergo/features/place/data/repositories/place_repository.dart' import 'package:wandergo/features/place/domain/place_search.dart'; import 'package:wandergo/features/place/presentation/widgets/place_tiles.dart'; import 'package:wandergo/features/place/presentation/widgets/place_visuals.dart'; +import 'package:wandergo/shared/widgets/app_back_button.dart'; import 'package:wandergo/shared/widgets/load_error.dart'; /// 한 번에 받는 수 @@ -179,12 +180,9 @@ class _PlaceSearchPageState extends ConsumerState { final error = _error; if (_items.isEmpty) { if (error != null) { - return Padding( - padding: const EdgeInsets.all(kPagePadding), - child: Align( - alignment: Alignment.topCenter, - child: LoadError(error: error, onRetry: _loadMore), - ), + // 결과 전체를 못 불러왔다 — 공통 오류 화면(시안 상태 2) + return ListView( + children: [LoadErrorBlock(error: error, onRetry: _loadMore)], ); } if (!_hasNext && !_loading) { @@ -261,11 +259,7 @@ class _SearchHeader extends StatelessWidget { padding: const EdgeInsets.fromLTRB(8, 8, 16, 8), child: Row( children: [ - IconButton( - tooltip: l10n.back, - onPressed: () => context.pop(), - icon: const AppIcon(AppIconData.chevronLeft, color: AppColors.text), - ), + AppBackButton(onPressed: () => context.pop()), const SizedBox(width: 4), Expanded( child: SizedBox( @@ -293,24 +287,35 @@ class _SearchHeader extends StatelessWidget { ), ), prefixIconConstraints: const BoxConstraints(), + // 지우기 — 누르는 칸 36, 오른쪽 여백 4(시안 입력창 padding 0 4 0 12) + suffixIconConstraints: const BoxConstraints(), suffixIcon: controller.text.isEmpty ? null - : IconButton( - tooltip: l10n.searchClear, - onPressed: onClear, - icon: Container( - width: 18, - height: 18, - decoration: const BoxDecoration( - color: AppColors.textHint, - shape: BoxShape.circle, + : Padding( + padding: const EdgeInsets.only(right: 4), + child: IconButton( + tooltip: l10n.searchClear, + onPressed: onClear, + style: IconButton.styleFrom( + fixedSize: const Size.square(36), + minimumSize: const Size.square(36), + padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - alignment: Alignment.center, - child: const AppIcon( - AppIconData.close, - size: 10, - color: Colors.white, - strokeWidth: 3.5, + icon: Container( + width: 18, + height: 18, + decoration: const BoxDecoration( + color: AppColors.clearFill, + shape: BoxShape.circle, + ), + alignment: Alignment.center, + child: const AppIcon( + AppIconData.close, + size: 10, + color: Colors.white, + strokeWidth: 3.5, + ), ), ), ), @@ -523,7 +528,7 @@ class _EmptyResult extends StatelessWidget { child: const AppIcon( AppIconData.search, size: 30, - color: AppColors.textHint, + color: AppColors.iconMuted, ), ), ), diff --git a/flutter/lib/features/place/presentation/widgets/place_detail_sections.dart b/flutter/lib/features/place/presentation/widgets/place_detail_sections.dart index 651beec..10a46dd 100644 --- a/flutter/lib/features/place/presentation/widgets/place_detail_sections.dart +++ b/flutter/lib/features/place/presentation/widgets/place_detail_sections.dart @@ -20,8 +20,9 @@ import 'package:wandergo/shared/utils/open_link.dart'; /// 관광통역안내 전화(영어·일본어·중국어 24시간) — 영어 화면에만 보인다(2026-10-29 합의) const String kTravelHotline = '1330'; -/// 이용 안내 왼쪽 이름 칸 너비 -const double _kInfoLabelWidth = 72; +/// 이용 안내 왼쪽 이름 칸 너비 — 한국어 56, 영어 72(시안) +double _infoLabelWidth(BuildContext context) => + context.l10n.localeName == 'ko' ? 56 : 72; /// 이용 안내 오른쪽 동작 칸 너비 — 글자(복사·전화) · 아이콘(외부 링크) const double _kTextActionWidth = 56; @@ -113,7 +114,7 @@ class PlaceGlance extends StatelessWidget { ), const SizedBox(height: 8), Text( - fact.label, + keepAll(fact.label), textAlign: TextAlign.center, style: TextStyle( fontSize: 12, @@ -411,8 +412,11 @@ class _InfoRow extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - width: _kInfoLabelWidth, - child: Text(label, style: textTheme.bodyMedium), + width: _infoLabelWidth(context), + child: Text( + keepAll(label), + style: textTheme.bodyMedium?.copyWith(height: 20 / 13), + ), ), const SizedBox(width: 12), Expanded( @@ -431,7 +435,10 @@ class _InfoRow extends StatelessWidget { ), if (secondary != null) ...[ const SizedBox(height: 2), - Text(keepAll(secondary!), style: textTheme.bodySmall), + Text( + keepAll(secondary!), + style: textTheme.bodySmall?.copyWith(height: 17 / 12), + ), ], ], ), @@ -483,6 +490,8 @@ class _TextAction extends StatelessWidget { foregroundColor: AppColors.brand, padding: const EdgeInsets.only(left: 8), minimumSize: const Size(44, 44), + // 글자를 오른쪽 끝에 붙인다(시안 padding-left 8) + alignment: Alignment.centerRight, textStyle: Theme.of(context).textTheme.labelLarge, ), child: Text(label), @@ -505,6 +514,12 @@ class _LinkAction extends StatelessWidget { child: IconButton( tooltip: tooltip, onPressed: onPressed, + // 아이콘을 오른쪽 끝에 붙인다 + style: IconButton.styleFrom( + fixedSize: const Size.square(44), + padding: EdgeInsets.zero, + alignment: Alignment.centerRight, + ), icon: const AppIcon( AppIconData.externalLink, size: 16, @@ -576,7 +591,7 @@ class _PlaceAboutSectionState extends State { Widget build(BuildContext context) { final l10n = context.l10n; final textTheme = Theme.of(context).textTheme; - final style = textTheme.bodyLarge?.copyWith(fontSize: 14, height: 22 / 14); + final style = textTheme.bodyLarge?.copyWith(fontSize: 14, height: 23 / 14); final text = keepAll(widget.text); return _Section( @@ -607,7 +622,7 @@ class _PlaceAboutSectionState extends State { Expanded( child: Text( l10n.sourceLabel(widget.provider), - style: textTheme.bodySmall, + style: textTheme.bodySmall?.copyWith(height: 17 / 12), ), ), if (overflows || _expanded) @@ -616,9 +631,23 @@ class _PlaceAboutSectionState extends State { style: TextButton.styleFrom( foregroundColor: AppColors.textSecondary, padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, textStyle: textTheme.labelLarge, ), - child: Text(_expanded ? l10n.showLess : l10n.showMore), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_expanded ? l10n.showLess : l10n.showMore), + const SizedBox(width: 2), + AppIcon( + _expanded + ? AppIconData.chevronUp + : AppIconData.chevronDown, + size: 14, + color: AppColors.textSecondary, + ), + ], + ), ), ], ), diff --git a/flutter/lib/features/place/presentation/widgets/place_tiles.dart b/flutter/lib/features/place/presentation/widgets/place_tiles.dart index 176f3d0..37b32b5 100644 --- a/flutter/lib/features/place/presentation/widgets/place_tiles.dart +++ b/flutter/lib/features/place/presentation/widgets/place_tiles.dart @@ -73,6 +73,7 @@ class PlaceRow extends StatelessWidget { ], ), style: textTheme.bodyMedium?.copyWith( + height: 18 / 13, color: AppColors.textSecondary, ), maxLines: 1, @@ -128,7 +129,7 @@ class PetPlaceRow extends StatelessWidget { children: [ Text( item.displayName, - style: textTheme.titleSmall, + style: textTheme.titleSmall?.copyWith(height: 20 / 15), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -137,6 +138,7 @@ class PetPlaceRow extends StatelessWidget { Text( meta, style: textTheme.bodyMedium?.copyWith( + height: 18 / 13, color: AppColors.textSecondary, ), ), @@ -146,7 +148,7 @@ class PetPlaceRow extends StatelessWidget { Text.rich( TextSpan( children: [ - petSpan(pet, fontSize: 12), + petSpan(pet, fontSize: 12, iconSize: 13), if (verifiedAt != null) TextSpan( text: ' · ${l10n.placeVerifiedOn(verifiedAt)}', @@ -202,7 +204,7 @@ class PlaceCard extends StatelessWidget { const SizedBox(height: 10), Text( item.displayName, - style: textTheme.titleSmall, + style: textTheme.titleSmall?.copyWith(height: 20 / 15), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -215,7 +217,7 @@ class PlaceCard extends StatelessWidget { ), if (pet != null) ...[ const SizedBox(height: 4), - PetLabel(text: pet, fontSize: 12), + PetLabel(text: pet, fontSize: 12, iconSize: 13), ], ], ), diff --git a/flutter/lib/features/place/presentation/widgets/place_visuals.dart b/flutter/lib/features/place/presentation/widgets/place_visuals.dart index 6a55eba..d754946 100644 --- a/flutter/lib/features/place/presentation/widgets/place_visuals.dart +++ b/flutter/lib/features/place/presentation/widgets/place_visuals.dart @@ -169,42 +169,51 @@ class PlaceImage extends StatelessWidget { /// 반려동물 동반 글자 — 꽉 찬 발바닥 + 초록 굵은 글자(배지 없이 글자에 녹인다) class PetLabel extends StatelessWidget { - const PetLabel({super.key, required this.text, this.fontSize = 13}); + const PetLabel({ + super.key, + required this.text, + this.fontSize = 13, + this.iconSize, + }); final String text; final double fontSize; + /// 없으면 글자 크기 + final double? iconSize; + @override Widget build(BuildContext context) { return Text.rich( - petSpan(text, fontSize: fontSize), + petSpan(text, fontSize: fontSize, iconSize: iconSize), maxLines: 1, overflow: TextOverflow.ellipsis, ); } } -/// 다른 글자 사이에 넣는 반려동물 동반 글자 -InlineSpan petSpan(String text, {double fontSize = 13}) => TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Padding( - padding: const EdgeInsets.only(right: 4), - child: AppIcon( - AppIconData.pawSolid, - size: fontSize, - color: AppColors.pet, - ), - ), - ), +/// 다른 글자 사이에 넣는 반려동물 동반 글자 — 발바닥은 [iconSize](없으면 글자 크기) +InlineSpan petSpan(String text, {double fontSize = 13, double? iconSize}) => TextSpan( - text: text, - style: TextStyle( - fontSize: fontSize, - fontWeight: FontWeight.w600, - color: AppColors.pet, - ), - ), - ], -); + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: const EdgeInsets.only(right: 4), + child: AppIcon( + AppIconData.pawSolid, + size: iconSize ?? fontSize, + color: AppColors.pet, + ), + ), + ), + TextSpan( + text: text, + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.w600, + color: AppColors.pet, + ), + ), + ], + ); diff --git a/flutter/lib/shared/widgets/language_button.dart b/flutter/lib/shared/widgets/language_button.dart index 18f0a69..4227df0 100644 --- a/flutter/lib/shared/widgets/language_button.dart +++ b/flutter/lib/shared/widgets/language_button.dart @@ -28,6 +28,8 @@ class LanguageButton extends ConsumerWidget { style: OutlinedButton.styleFrom( minimumSize: Size(44, compact ? 44 : 40), padding: EdgeInsets.only(left: 12, right: compact ? 12 : 10), + // 보이는 크기 그대로 자리를 잡는다(Material 기본 48 탭 영역 없이, 시안 머리 높이) + tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Row( mainAxisSize: MainAxisSize.min, diff --git a/flutter/lib/shared/widgets/section_header.dart b/flutter/lib/shared/widgets/section_header.dart index 3f7ce77..5b294a5 100644 --- a/flutter/lib/shared/widgets/section_header.dart +++ b/flutter/lib/shared/widgets/section_header.dart @@ -25,6 +25,8 @@ class SectionHeader extends StatelessWidget { onPressed: onSeeAll, style: TextButton.styleFrom( padding: EdgeInsets.zero, + // 누르는 칸은 44 를 지키되 Material 기본 48 탭 영역은 쓰지 않는다(시안 머리 높이에 가깝게) + tapTargetSize: MaterialTapTargetSize.shrinkWrap, textStyle: textTheme.bodyMedium, ), child: Row( diff --git a/flutter/test/widget_test.dart b/flutter/test/widget_test.dart index 01a6dcc..791d787 100644 --- a/flutter/test/widget_test.dart +++ b/flutter/test/widget_test.dart @@ -362,8 +362,8 @@ void main() { expect(_text('목줄 착용'), findsOneWidget); expect(_text('실내 시설은 동반 불가'), findsOneWidget); // 상시 개방은 한눈에 보기에 오르고, 주차 원문이 불가능이면 오르지 않는다 - expect(find.text('상시 개방'), findsWidgets); - expect(find.text('주차 가능'), findsNothing); + expect(_text('상시 개방'), findsWidgets); + expect(_text('주차 가능'), findsNothing); expect(find.text('출처: 한국관광공사 · 2026-06-26 기준'), findsOneWidget); });