From b1d80dde66d971383d26d75ee2f8d979a48e562c Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Sun, 27 Oct 2024 17:21:26 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=ED=8B=B0=EC=BC=93=ED=8C=85=20?= =?UTF-8?q?=ED=94=8C=EB=9E=AB=ED=8F=BC=20=EC=8B=A4=EC=A0=9C=20API=20respon?= =?UTF-8?q?se=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event_detail/view/event_detail_ticketing_site_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/presentation/event_detail/view/event_detail_ticketing_site_view.dart b/lib/presentation/event_detail/view/event_detail_ticketing_site_view.dart index 90d88a0..45ad641 100644 --- a/lib/presentation/event_detail/view/event_detail_ticketing_site_view.dart +++ b/lib/presentation/event_detail/view/event_detail_ticketing_site_view.dart @@ -60,16 +60,18 @@ class TicketingSiteLink extends StatelessWidget { ); } - // TODO: enum으로 변환 필요. 정확히 어떤 데이터가 내려오는지 확인 후 변환 필요 String _getImagePath(String platform) => switch (platform) { '인터파크' => Assets.images.interpark.path, - 'Yes 24' => Assets.images.yes24.path, + '예스 24' => Assets.images.yes24.path, + // 멜론 추가 필요 + /* 아직 미구현 '네이버 예약' => Assets.images.naverReversation.path, '티켓링크' => Assets.images.ticketLink.path, '마이리얼트립' => Assets.images.myRealTrip.path, 'CGV' => Assets.images.cgv.path, '메가박스' => Assets.images.megabox.path, '롯데 시네마' => Assets.images.lotteCinema.path, + */ _ => '' }; From 5f2f6fab4e6258fad7e8c3d2c79e1841d93057e3 Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Sun, 3 Nov 2024 23:21:22 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=AA=A8=EB=8D=B8,=20=EC=97=94=ED=8B=B0=ED=8B=B0,?= =?UTF-8?q?=20API=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_source/remote/cultural_event_api.dart | 13 +++++++++++++ .../popular_search_keyword_model.dart | 14 ++++++++++++++ .../recent_search_keyword_model.dart | 16 ++++++++++++++++ .../popular_search_keyword_entity.dart | 15 +++++++++++++++ .../recent_search_keyword_entity.dart | 16 ++++++++++++++++ 5 files changed, 74 insertions(+) create mode 100644 lib/data/model/cultural_event/popular_search_keyword_model.dart create mode 100644 lib/data/model/cultural_event/recent_search_keyword_model.dart create mode 100644 lib/domain/entity/cultural_event/popular_search_keyword_entity.dart create mode 100644 lib/domain/entity/cultural_event/recent_search_keyword_entity.dart diff --git a/lib/data/data_source/remote/cultural_event_api.dart b/lib/data/data_source/remote/cultural_event_api.dart index a69dcdd..cd944ec 100644 --- a/lib/data/data_source/remote/cultural_event_api.dart +++ b/lib/data/data_source/remote/cultural_event_api.dart @@ -3,6 +3,8 @@ import 'package:retrofit/retrofit.dart'; import 'package:ticats_app/app/config/app_const.dart'; import 'package:ticats_app/data/model/cultural_event/cultural_event_model.dart'; import 'package:ticats_app/data/model/cultural_event/cultural_events_model.dart'; +import 'package:ticats_app/data/model/cultural_event/popular_search_keyword_model.dart'; +import 'package:ticats_app/data/model/cultural_event/recent_search_keyword_model.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_events_search_entity.dart'; part 'cultural_event_api.g.dart'; @@ -18,4 +20,15 @@ abstract class CulturalEventAPI { @GET("/{id}") Future getCulturalEventInfo(@Path("id") String id); + + @GET("/popular-keywords") + Future getPopularSearchKeywords(); + + @GET("/recent-keywords") + Future getRecentSearchKeywords(); + + @GET("/search") + Future getSearchedCulturalEvents( + @Queries(encoded: true) CulturalEventsSearchEntity queries, + ); } diff --git a/lib/data/model/cultural_event/popular_search_keyword_model.dart b/lib/data/model/cultural_event/popular_search_keyword_model.dart new file mode 100644 index 0000000..1173331 --- /dev/null +++ b/lib/data/model/cultural_event/popular_search_keyword_model.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'popular_search_keyword_model.freezed.dart'; +part 'popular_search_keyword_model.g.dart'; + +@freezed +class PopularSearchKeywordModel with _$PopularSearchKeywordModel { + const factory PopularSearchKeywordModel({ + @Default("") String title, + @Default(0) int ordering, + }) = _PopularSearchKeywordModel; + + factory PopularSearchKeywordModel.fromJson(Map json) => _$PopularSearchKeywordModelFromJson(json); +} \ No newline at end of file diff --git a/lib/data/model/cultural_event/recent_search_keyword_model.dart b/lib/data/model/cultural_event/recent_search_keyword_model.dart new file mode 100644 index 0000000..12dcb21 --- /dev/null +++ b/lib/data/model/cultural_event/recent_search_keyword_model.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'recent_search_keyword_model.freezed.dart'; + +part 'recent_search_keyword_model.g.dart'; + +@freezed +class RecentSearchKeywordModel with _$RecentSearchKeywordModel { + const factory RecentSearchKeywordModel({ + required int id, + @Default("") String title, + @Default(0) int ordering, + }) = _RecentSearchKeywordModel; + + factory RecentSearchKeywordModel.fromJson(Map json) => _$RecentSearchKeywordModelFromJson(json); +} \ No newline at end of file diff --git a/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart b/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart new file mode 100644 index 0000000..9ccf053 --- /dev/null +++ b/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'popular_search_keyword_entity.freezed.dart'; +part 'popular_search_keyword_entity.g.dart'; + +@freezed +class PopularSearchKeywordEntity with _$PopularSearchKeywordEntity { + const factory PopularSearchKeywordEntity({ + @Default("") String title, + @Default(0) int ordering, + }) = _PopularSearchKeywordEntity; + + factory PopularSearchKeywordEntity.fromJson(Map json) => _$PopularSearchKeywordEntityFromJson(json); +} + diff --git a/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart b/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart new file mode 100644 index 0000000..6107125 --- /dev/null +++ b/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'recent_search_keyword_entity.freezed.dart'; + +part 'recent_search_keyword_entity.g.dart'; + +@freezed +class RecentSearchKeywordEntity with _$RecentSearchKeywordEntity { + const factory RecentSearchKeywordEntity({ + required int id, + @Default("") String title, + @Default(0) int ordering, + }) = _RecentSearchKeywordEntity; + + factory RecentSearchKeywordEntity.fromJson(Map json) => _$RecentSearchKeywordEntityFromJson(json); +} \ No newline at end of file From 76bc2e0c62d7b2923cd9d5d477148a2b1bea0551 Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Mon, 4 Nov 2024 00:35:19 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D,=20=EC=B6=94=EC=B2=9C=20=EC=9D=B8=EA=B8=B0=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=96=B4=20=EA=B8=B0=EB=B3=B8=20UI=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/home/home_view.dart | 2 +- lib/presentation/main/main_page.dart | 3 +- lib/presentation/search/search_view.dart | 67 +++++++++++++++++++ .../view/popular_search_keyword_view.dart | 54 +++++++++++++++ .../search/view/search_history_view.dart | 32 +++++++++ 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 lib/presentation/search/search_view.dart create mode 100644 lib/presentation/search/view/popular_search_keyword_view.dart create mode 100644 lib/presentation/search/view/search_history_view.dart diff --git a/lib/presentation/home/home_view.dart b/lib/presentation/home/home_view.dart index 434f794..0bc9044 100644 --- a/lib/presentation/home/home_view.dart +++ b/lib/presentation/home/home_view.dart @@ -65,7 +65,7 @@ class HomeView extends BasePage { height: 24.w, colorFilter: const ColorFilter.mode(AppGrayscale.gray10, BlendMode.srcIn), ), - onPressed: () => ref.read(mainPageControllerProvider.notifier).setBottomNavigationBarIndex, + onPressed: () => ref.read(mainPageControllerProvider.notifier).setBottomNavigationBarIndex(1), ), ], ); diff --git a/lib/presentation/main/main_page.dart b/lib/presentation/main/main_page.dart index 20a23b8..b59ab0f 100644 --- a/lib/presentation/main/main_page.dart +++ b/lib/presentation/main/main_page.dart @@ -3,12 +3,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ticats_app/app/base/base_page.dart'; import 'package:ticats_app/presentation/home/home_view.dart'; import 'package:ticats_app/presentation/main/provider/main_page_provider.dart'; +import 'package:ticats_app/presentation/search/search_view.dart'; import 'widget/ticats_bottom_navigation_bar.dart'; final List _pages = [ const HomeView(), - const HomeView(), + const SearchView(), const HomeView(), const HomeView(), const HomeView(), diff --git a/lib/presentation/search/search_view.dart b/lib/presentation/search/search_view.dart new file mode 100644 index 0000000..e644c98 --- /dev/null +++ b/lib/presentation/search/search_view.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ticats_app/app/base/base_page.dart'; +import 'package:ticats_app/app/config/app_color.dart'; +import 'package:ticats_app/app/config/app_radius.dart'; +import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/gen/assets.gen.dart'; +import 'package:ticats_app/presentation/search/view/popular_search_keyword_view.dart'; +import 'package:ticats_app/presentation/search/view/search_history_view.dart'; + +class SearchView extends BasePage { + const SearchView({super.key}); + + @override + Widget buildPage(BuildContext context, WidgetRef ref) { + return SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 4.h), + SearchHistoryView(), + PopularSearchKeywordView(), + ], + ), + ); + } + + @override + Color? get screenBackgroundColor => AppGrayscale.gray99; + + @override + PreferredSizeWidget? buildAppBar(BuildContext context, WidgetRef ref) { + return AppBar( + title: Container( + padding: + EdgeInsets.only(left: 24.w, top: 5.h, bottom: 5.h, right: 16.w), + color: Colors.white, + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: AppGrayscale.gray95, borderRadius: AppRadius.small), + child: TextField( + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + horizontal: 16.w, vertical: 10.h), + border: OutlineInputBorder( + borderRadius: AppRadius.small, + borderSide: BorderSide.none), + hintText: '티켓을 검색해보세요!', + hintStyle: AppTypeface.label16Regular + .copyWith(color: AppGrayscale.gray55))), + )), + IconButton( + onPressed: () {}, + icon: Assets.icons.search.svg( + width: 24.w, + height: 24.w, + colorFilter: const ColorFilter.mode( + AppGrayscale.gray10, BlendMode.srcIn), + )), + ], + )), + ); + } +} diff --git a/lib/presentation/search/view/popular_search_keyword_view.dart b/lib/presentation/search/view/popular_search_keyword_view.dart new file mode 100644 index 0000000..ae49a11 --- /dev/null +++ b/lib/presentation/search/view/popular_search_keyword_view.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_riverpod/src/consumer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ticats_app/app/base/base_view.dart'; +import 'package:ticats_app/app/config/app_color.dart'; +import 'package:ticats_app/app/config/app_typeface.dart'; + +class PopularSearchKeywordView extends BaseView { + const PopularSearchKeywordView({super.key}); + + @override + Widget buildView(BuildContext context, WidgetRef ref) { + return Padding( + padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('추천 인기 검색어', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: _keywordWidget(), + ) + ], + ), + ); + } + + Widget _keywordWidget() { + return Padding( + padding: EdgeInsets.symmetric(vertical: 4.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '1', + style: AppTypeface.body20Bold.copyWith(color: AppColor.primaryDark), + ), + SizedBox( + width: 12.w, + ), + Text( + '인피니트', + style: + AppTypeface.label16Regular.copyWith(color: AppGrayscale.gray10), + ) + ], + ), + ); + } +} diff --git a/lib/presentation/search/view/search_history_view.dart b/lib/presentation/search/view/search_history_view.dart new file mode 100644 index 0000000..89fbdc3 --- /dev/null +++ b/lib/presentation/search/view/search_history_view.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_riverpod/src/consumer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ticats_app/app/base/base_view.dart'; +import 'package:ticats_app/app/config/app_color.dart'; +import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; + +class SearchHistoryView extends BaseView { + @override + Widget buildView(BuildContext context, WidgetRef ref) { + return Padding( + padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('검색 기록', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h), + Row( + children: [ + TicatsChip.close('기록', onTap: () {}), + TicatsChip.close('기록', onTap: () {}) + ], + ) + ], + ), + ); + } +} From d91eb4af58e536ef60129cad6298e10a533a1531 Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Mon, 4 Nov 2024 07:40:49 +0900 Subject: [PATCH 4/7] =?UTF-8?q?chore:=20=EC=9E=84=EC=8B=9C=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/search/search_view.dart | 1 + .../search/view/search_history_view.dart | 35 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/presentation/search/search_view.dart b/lib/presentation/search/search_view.dart index e644c98..60a7878 100644 --- a/lib/presentation/search/search_view.dart +++ b/lib/presentation/search/search_view.dart @@ -42,6 +42,7 @@ class SearchView extends BasePage { decoration: BoxDecoration( color: AppGrayscale.gray95, borderRadius: AppRadius.small), child: TextField( + onTapOutside: (event) => FocusScope.of(context).unfocus(), decoration: InputDecoration( contentPadding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 10.h), diff --git a/lib/presentation/search/view/search_history_view.dart b/lib/presentation/search/view/search_history_view.dart index 89fbdc3..65e47da 100644 --- a/lib/presentation/search/view/search_history_view.dart +++ b/lib/presentation/search/view/search_history_view.dart @@ -10,23 +10,30 @@ import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; class SearchHistoryView extends BaseView { @override Widget buildView(BuildContext context, WidgetRef ref) { - return Padding( - padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('검색 기록', - style: AppTypeface.label16Medium - .copyWith(color: AppGrayscale.gray30)), - SizedBox(height: 16.h), - Row( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('검색 기록', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h) + ], + ), + ), + Padding( + padding: EdgeInsets.only(left: 20.w), + child: Row( children: [ - TicatsChip.close('기록', onTap: () {}), TicatsChip.close('기록', onTap: () {}) ], - ) - ], - ), + ), + ) + ], ); } } From 1a632bbfd1f4d23485ef734dd3f28af116f2eb89 Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Mon, 11 Nov 2024 03:31:00 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5,=20=EC=9D=B8=EA=B8=B0=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=9E=91=EC=84=B1,=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EC=B2=98=EB=A6=AC=20=EC=A0=84=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/icons/close_circle_fill.svg | 6 + lib/app/config/app_router.dart | 2 +- .../remote/cultural_event_api.dart | 4 +- .../popular_search_keyword_model.dart | 13 +- .../recent_search_keyword_model.dart | 12 +- .../cultural_event_repository_impl.dart | 26 +++- .../repository/cultural_event_repository.dart | 4 + .../usecase/cultural_event_usecases.dart | 24 ++++ .../common/widget/ticats_chip.dart | 14 +- lib/presentation/main/main_page.dart | 4 +- .../searched_event_list_controller.dart | 77 +++++++++++ lib/presentation/search/search_page.dart | 130 ++++++++++++++++++ lib/presentation/search/search_view.dart | 68 --------- .../search/view/keyword_list_view.dart | 51 +++++++ .../view/popular_search_keyword_view.dart | 30 ++-- .../search/view/search_history_view.dart | 12 +- .../view/searched_event_list_event_view.dart | 34 +++++ 17 files changed, 416 insertions(+), 95 deletions(-) create mode 100644 assets/icons/close_circle_fill.svg create mode 100644 lib/presentation/search/provider/searched_event_list_controller.dart create mode 100644 lib/presentation/search/search_page.dart delete mode 100644 lib/presentation/search/search_view.dart create mode 100644 lib/presentation/search/view/keyword_list_view.dart create mode 100644 lib/presentation/search/view/searched_event_list_event_view.dart diff --git a/assets/icons/close_circle_fill.svg b/assets/icons/close_circle_fill.svg new file mode 100644 index 0000000..c01a13c --- /dev/null +++ b/assets/icons/close_circle_fill.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/app/config/app_router.dart b/lib/app/config/app_router.dart index b060a53..7934759 100644 --- a/lib/app/config/app_router.dart +++ b/lib/app/config/app_router.dart @@ -41,7 +41,7 @@ class Router extends _$Router { @override GoRouter build() { return GoRouter( - initialLocation: Routes.login, + initialLocation: Routes.main, navigatorKey: rootNavigatorKey, routes: [ // Auth diff --git a/lib/data/data_source/remote/cultural_event_api.dart b/lib/data/data_source/remote/cultural_event_api.dart index cd944ec..44af033 100644 --- a/lib/data/data_source/remote/cultural_event_api.dart +++ b/lib/data/data_source/remote/cultural_event_api.dart @@ -22,10 +22,10 @@ abstract class CulturalEventAPI { Future getCulturalEventInfo(@Path("id") String id); @GET("/popular-keywords") - Future getPopularSearchKeywords(); + Future> getPopularSearchKeywords(); @GET("/recent-keywords") - Future getRecentSearchKeywords(); + Future> getRecentSearchKeywords(); @GET("/search") Future getSearchedCulturalEvents( diff --git a/lib/data/model/cultural_event/popular_search_keyword_model.dart b/lib/data/model/cultural_event/popular_search_keyword_model.dart index 1173331..5f1faf6 100644 --- a/lib/data/model/cultural_event/popular_search_keyword_model.dart +++ b/lib/data/model/cultural_event/popular_search_keyword_model.dart @@ -1,6 +1,8 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; part 'popular_search_keyword_model.freezed.dart'; + part 'popular_search_keyword_model.g.dart'; @freezed @@ -10,5 +12,12 @@ class PopularSearchKeywordModel with _$PopularSearchKeywordModel { @Default(0) int ordering, }) = _PopularSearchKeywordModel; - factory PopularSearchKeywordModel.fromJson(Map json) => _$PopularSearchKeywordModelFromJson(json); -} \ No newline at end of file + factory PopularSearchKeywordModel.fromJson(Map json) => + _$PopularSearchKeywordModelFromJson(json); +} + +extension PopularSearchKeywordModelX on PopularSearchKeywordModel { + PopularSearchKeywordEntity toEntity() { + return PopularSearchKeywordEntity(title: title, ordering: ordering); + } +} diff --git a/lib/data/model/cultural_event/recent_search_keyword_model.dart b/lib/data/model/cultural_event/recent_search_keyword_model.dart index 12dcb21..5d337da 100644 --- a/lib/data/model/cultural_event/recent_search_keyword_model.dart +++ b/lib/data/model/cultural_event/recent_search_keyword_model.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_entity.dart'; part 'recent_search_keyword_model.freezed.dart'; @@ -12,5 +13,12 @@ class RecentSearchKeywordModel with _$RecentSearchKeywordModel { @Default(0) int ordering, }) = _RecentSearchKeywordModel; - factory RecentSearchKeywordModel.fromJson(Map json) => _$RecentSearchKeywordModelFromJson(json); -} \ No newline at end of file + factory RecentSearchKeywordModel.fromJson(Map json) => + _$RecentSearchKeywordModelFromJson(json); +} + +extension RecentSearchKeywordModelX on RecentSearchKeywordModel { + RecentSearchKeywordEntity toEntity() { + return RecentSearchKeywordEntity(id: id, title: title, ordering: ordering); + } +} diff --git a/lib/data/repository_impl/cultural_event_repository_impl.dart b/lib/data/repository_impl/cultural_event_repository_impl.dart index 7a9f068..9ea1617 100644 --- a/lib/data/repository_impl/cultural_event_repository_impl.dart +++ b/lib/data/repository_impl/cultural_event_repository_impl.dart @@ -4,8 +4,12 @@ import 'package:ticats_app/app/network/dio_provider.dart'; import 'package:ticats_app/data/data_source/remote/cultural_event_api.dart'; import 'package:ticats_app/data/model/cultural_event/cultural_event_model.dart'; import 'package:ticats_app/data/model/cultural_event/cultural_events_model.dart'; +import 'package:ticats_app/data/model/cultural_event/popular_search_keyword_model.dart'; +import 'package:ticats_app/data/model/cultural_event/recent_search_keyword_model.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_event_entity.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_events_search_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_entity.dart'; import 'package:ticats_app/domain/repository/cultural_event_repository.dart'; part 'cultural_event_repository_impl.g.dart'; @@ -36,20 +40,30 @@ class CulturalEventRepositoryImpl implements CulturalEventRepository { } @override - Future> getPointEvents(CulturalEventsSearchEntity quries) async { - quries = quries.copyWith(ordering: TicatsEventOrdering.point); + Future> getPointEvents(CulturalEventsSearchEntity queries) async { + queries = queries.copyWith(ordering: TicatsEventOrdering.point); - CulturalEventsModel response = await _api.getCulturalEvents(quries); + CulturalEventsModel response = await _api.getCulturalEvents(queries); return response.toEntityList(); } @override - Future> getRecommendEvents(CulturalEventsSearchEntity quries) async { - quries = quries.copyWith(ordering: TicatsEventOrdering.recommend); + Future> getRecommendEvents(CulturalEventsSearchEntity queries) async { + queries = queries.copyWith(ordering: TicatsEventOrdering.recommend); - CulturalEventsModel response = await _api.getCulturalEvents(quries); + CulturalEventsModel response = await _api.getCulturalEvents(queries); return response.toEntityList(); } + + Future> getPopularSearchKeywords() async { + List response = await _api.getPopularSearchKeywords(); + return response.map((e) => e.toEntity()).toList(); + } + + Future> getRecentSearchKeywords() async { + List response = await _api.getRecentSearchKeywords(); + return response.map((e) => e.toEntity()).toList(); + } } @riverpod diff --git a/lib/domain/repository/cultural_event_repository.dart b/lib/domain/repository/cultural_event_repository.dart index 187a2d2..ddd750e 100644 --- a/lib/domain/repository/cultural_event_repository.dart +++ b/lib/domain/repository/cultural_event_repository.dart @@ -1,5 +1,7 @@ import 'package:ticats_app/domain/entity/cultural_event/cultural_event_entity.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_events_search_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_entity.dart'; abstract class CulturalEventRepository { Future getCulturalEventInfo(String id); @@ -7,4 +9,6 @@ abstract class CulturalEventRepository { Future> getRecommendEvents(CulturalEventsSearchEntity request); Future> getPointEvents(CulturalEventsSearchEntity request); Future> getOpenDateEvents(CulturalEventsSearchEntity request); + Future> getRecentSearchKeywords(); + Future> getPopularSearchKeywords(); } diff --git a/lib/domain/usecase/cultural_event_usecases.dart b/lib/domain/usecase/cultural_event_usecases.dart index 9c5ca5b..fea7952 100644 --- a/lib/domain/usecase/cultural_event_usecases.dart +++ b/lib/domain/usecase/cultural_event_usecases.dart @@ -2,6 +2,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:ticats_app/data/repository_impl/cultural_event_repository_impl.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_event_entity.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_events_search_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_entity.dart'; import 'package:ticats_app/domain/repository/cultural_event_repository.dart'; part 'cultural_event_usecases.g.dart'; @@ -16,6 +18,8 @@ class CulturalEventUsecases { GetOpenDateEvents get getOpenDateEvents => GetOpenDateEvents(_repository); GetPointEvents get getPointEvents => GetPointEvents(_repository); GetRecommendEvents get getRecommendEvents => GetRecommendEvents(_repository); + GetRecentSearchKeywords get getRecentSearchKeywords => GetRecentSearchKeywords(_repository); + GetPopularSearchKeywords get getPopularSearchKeywords => GetPopularSearchKeywords(_repository); } class GetEventInfo { @@ -68,6 +72,26 @@ class GetRecommendEvents { } } +class GetRecentSearchKeywords { + final CulturalEventRepository _repository; + + GetRecentSearchKeywords(this._repository); + + Future> execute() async { + return await _repository.getRecentSearchKeywords(); + } +} + +class GetPopularSearchKeywords { + final CulturalEventRepository _repository; + + GetPopularSearchKeywords(this._repository); + + Future> execute() async { + return await _repository.getPopularSearchKeywords(); + } +} + @riverpod CulturalEventUsecases culturalEventUsecases(CulturalEventUsecasesRef ref) { return CulturalEventUsecases(repository: ref.read(culturalEventRepositoryProvider)); diff --git a/lib/presentation/common/widget/ticats_chip.dart b/lib/presentation/common/widget/ticats_chip.dart index b96f968..da307f7 100644 --- a/lib/presentation/common/widget/ticats_chip.dart +++ b/lib/presentation/common/widget/ticats_chip.dart @@ -11,6 +11,7 @@ class TicatsChip extends StatelessWidget { this.hasClose = true, this.icon, this.textStyle, + this.iconTap, super.key, }); @@ -19,9 +20,10 @@ class TicatsChip extends StatelessWidget { final IconData? icon; final TextStyle? textStyle; final bool hasClose; + final VoidCallback? iconTap; - factory TicatsChip.close(String text, {required onTap}) { - return TicatsChip(text, onTap: onTap, icon: Icons.close); + factory TicatsChip.close(String text, {required onTap, required iconTap}) { + return TicatsChip(text, onTap: onTap, icon: Icons.close, iconTap: iconTap); } factory TicatsChip.arrowDown(String text, {required onTap}) { @@ -47,7 +49,13 @@ class TicatsChip extends StatelessWidget { Text(text, style: textStyle ?? AppTypeface.label14Medium.copyWith(color: AppGrayscale.gray20)), if (icon != null) ...[ SizedBox(width: 6.w), - Icon(icon, size: 20.w, color: AppGrayscale.gray20), + GestureDetector( + onTap: () { + iconTap!(); // 아이콘을 클릭하면 iconTap 호출 + return; // onTap 전파를 막기 위해 return 추가 + }, + child: Icon(icon, size: 20.w, color: AppGrayscale.gray20), + ), ], ], ), diff --git a/lib/presentation/main/main_page.dart b/lib/presentation/main/main_page.dart index b59ab0f..712ea20 100644 --- a/lib/presentation/main/main_page.dart +++ b/lib/presentation/main/main_page.dart @@ -3,13 +3,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ticats_app/app/base/base_page.dart'; import 'package:ticats_app/presentation/home/home_view.dart'; import 'package:ticats_app/presentation/main/provider/main_page_provider.dart'; -import 'package:ticats_app/presentation/search/search_view.dart'; +import 'package:ticats_app/presentation/search/search_page.dart'; import 'widget/ticats_bottom_navigation_bar.dart'; final List _pages = [ const HomeView(), - const SearchView(), + SearchPage(), const HomeView(), const HomeView(), const HomeView(), diff --git a/lib/presentation/search/provider/searched_event_list_controller.dart b/lib/presentation/search/provider/searched_event_list_controller.dart new file mode 100644 index 0000000..87c1549 --- /dev/null +++ b/lib/presentation/search/provider/searched_event_list_controller.dart @@ -0,0 +1,77 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:ticats_app/domain/entity/cultural_event/cultural_event_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/cultural_events_search_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; +import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_entity.dart'; +import 'package:ticats_app/domain/usecase/cultural_event_usecases.dart'; + +part 'searched_event_list_controller.freezed.dart'; +part 'searched_event_list_controller.g.dart'; + +@freezed +sealed class SearchedEventListState with _$SearchedEventListState { + const factory SearchedEventListState({ + @Default([]) List searchedEvents, + @Default(CulturalEventsSearchEntity()) CulturalEventsSearchEntity filter, + @Default(false) bool isSubmitted, + @Default([]) List recentSearchKeywords, + @Default([]) List popularSearchKeywords, + }) = _SearchedEventListState; +} + +@riverpod +class SearchedEventListController extends _$SearchedEventListController { + late final CulturalEventUsecases _culturalEventUseCase; + + @override + Future build() async { + _culturalEventUseCase = ref.read(culturalEventUsecasesProvider); + return SearchedEventListState( + searchedEvents: await fetchSearchedEvents(), + recentSearchKeywords: await fetchRecentSearchKeywords(), + popularSearchKeywords: await fetchPopularSearchKeywords() + ); + } + + Future> fetchSearchedEvents( + {String? keyword}) async { + final List response = await _culturalEventUseCase + .getEvents + .execute(CulturalEventsSearchEntity(keyword: keyword)); + + return response; + } + + Future> fetchRecentSearchKeywords() async { + final List response = await _culturalEventUseCase + .getRecentSearchKeywords + .execute(); + + return response; + } + + Future> fetchPopularSearchKeywords() async { + final List response = await _culturalEventUseCase + .getPopularSearchKeywords + .execute(); + + return response; + } + + void onSearchTextChanged(String value) async { + state = AsyncData(state.value!.copyWith( + searchedEvents: await fetchSearchedEvents(keyword: value), + isSubmitted: false)); + } + + void submit(String value) async { + state = AsyncData(state.value!.copyWith( + searchedEvents: await fetchSearchedEvents(keyword: value), + isSubmitted: true)); + } + + void clickRemoveIcon() async { + state = AsyncData(state.value!.copyWith(isSubmitted: false)); + } +} diff --git a/lib/presentation/search/search_page.dart b/lib/presentation/search/search_page.dart new file mode 100644 index 0000000..7d395bf --- /dev/null +++ b/lib/presentation/search/search_page.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:ticats_app/app/base/base_page.dart'; +import 'package:ticats_app/app/config/app_color.dart'; +import 'package:ticats_app/app/config/app_radius.dart'; +import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/gen/assets.gen.dart'; +import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; +import 'package:ticats_app/presentation/search/view/keyword_list_view.dart'; +import 'package:ticats_app/presentation/search/view/popular_search_keyword_view.dart'; +import 'package:ticats_app/presentation/search/view/search_history_view.dart'; +import 'package:ticats_app/presentation/search/view/searched_event_list_event_view.dart'; + +final searchTextControllerProvider = + StateProvider((ref) { + final controller = TextEditingController(); + ref.onDispose(() { + controller.dispose(); + }); + return controller; +}); + +class SearchPage extends BasePage { + const SearchPage({super.key}); + + @override + Widget buildPage(BuildContext context, WidgetRef ref) { + return SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 4.h), + AsyncValueWidget( + value: ref.watch(searchedEventListControllerProvider), + data: (state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state.isSubmitted == false) ...[ + if (ref.watch(searchTextControllerProvider).text == + '') ...[ + SearchHistoryView(), + PopularSearchKeywordView(), + ] else ...[ + KeywordListView() + ] + ] else ...[ + SearchedEventListEventView() + ] + ], + ); + }) + ], + ), + ); + } + + @override + Color? get screenBackgroundColor => AppGrayscale.gray99; + + @override + PreferredSizeWidget? buildAppBar(BuildContext context, WidgetRef ref) { + return AppBar( + title: Container( + padding: + EdgeInsets.only(left: 24.w, top: 5.h, bottom: 5.h, right: 16.w), + color: Colors.white, + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: AppGrayscale.gray95, borderRadius: AppRadius.small), + child: TextField( + controller: ref.watch(searchTextControllerProvider), + onTapOutside: (event) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + contentPadding: + EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), + border: OutlineInputBorder( + borderRadius: AppRadius.small, + borderSide: BorderSide.none), + hintText: '티켓을 검색해보세요!', + hintStyle: AppTypeface.label16Regular + .copyWith(color: AppGrayscale.gray55), + suffixIcon: Container( + margin: EdgeInsets.only(right: 16.w), + child: GestureDetector( + onTap: () { + ref.watch(searchTextControllerProvider).text = ''; + ref.read(searchedEventListControllerProvider.notifier).clickRemoveIcon(); + }, + child: SvgPicture.asset( + Assets.icons.closeCircleFill.path, + width: 20.w, + height: 20.w, + ), + )), + suffixIconConstraints: BoxConstraints( + minWidth: 20.w, + minHeight: 20.w, + ), + ), + onChanged: (value) { + ref + .read(searchedEventListControllerProvider.notifier) + .onSearchTextChanged(value); + }, + onSubmitted: (value) { + ref + .read(searchedEventListControllerProvider.notifier) + .submit(value); + }, + ), + )), + IconButton( + onPressed: () {}, + icon: Assets.icons.search.svg( + width: 24.w, + height: 24.w, + colorFilter: const ColorFilter.mode( + AppGrayscale.gray10, BlendMode.srcIn), + )), + ], + )), + ); + } +} diff --git a/lib/presentation/search/search_view.dart b/lib/presentation/search/search_view.dart deleted file mode 100644 index 60a7878..0000000 --- a/lib/presentation/search/search_view.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:ticats_app/app/base/base_page.dart'; -import 'package:ticats_app/app/config/app_color.dart'; -import 'package:ticats_app/app/config/app_radius.dart'; -import 'package:ticats_app/app/config/app_typeface.dart'; -import 'package:ticats_app/gen/assets.gen.dart'; -import 'package:ticats_app/presentation/search/view/popular_search_keyword_view.dart'; -import 'package:ticats_app/presentation/search/view/search_history_view.dart'; - -class SearchView extends BasePage { - const SearchView({super.key}); - - @override - Widget buildPage(BuildContext context, WidgetRef ref) { - return SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: 4.h), - SearchHistoryView(), - PopularSearchKeywordView(), - ], - ), - ); - } - - @override - Color? get screenBackgroundColor => AppGrayscale.gray99; - - @override - PreferredSizeWidget? buildAppBar(BuildContext context, WidgetRef ref) { - return AppBar( - title: Container( - padding: - EdgeInsets.only(left: 24.w, top: 5.h, bottom: 5.h, right: 16.w), - color: Colors.white, - child: Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - color: AppGrayscale.gray95, borderRadius: AppRadius.small), - child: TextField( - onTapOutside: (event) => FocusScope.of(context).unfocus(), - decoration: InputDecoration( - contentPadding: EdgeInsets.symmetric( - horizontal: 16.w, vertical: 10.h), - border: OutlineInputBorder( - borderRadius: AppRadius.small, - borderSide: BorderSide.none), - hintText: '티켓을 검색해보세요!', - hintStyle: AppTypeface.label16Regular - .copyWith(color: AppGrayscale.gray55))), - )), - IconButton( - onPressed: () {}, - icon: Assets.icons.search.svg( - width: 24.w, - height: 24.w, - colorFilter: const ColorFilter.mode( - AppGrayscale.gray10, BlendMode.srcIn), - )), - ], - )), - ); - } -} diff --git a/lib/presentation/search/view/keyword_list_view.dart b/lib/presentation/search/view/keyword_list_view.dart new file mode 100644 index 0000000..0a21208 --- /dev/null +++ b/lib/presentation/search/view/keyword_list_view.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_riverpod/src/consumer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ticats_app/app/base/base_view.dart'; +import 'package:ticats_app/app/config/app_color.dart'; +import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; +import 'package:ticats_app/presentation/search/search_page.dart'; + +class KeywordListView extends BaseView { + const KeywordListView({super.key}); + + @override + Widget buildView(BuildContext context, WidgetRef ref) { + return AsyncValueWidget( + value: ref.watch(searchedEventListControllerProvider), + data: (state) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 40.h, horizontal: 20.w), + child: ListView.separated( + shrinkWrap: true, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + ref.watch(searchTextControllerProvider).text = + state.searchedEvents[index].title; + ref + .read(searchedEventListControllerProvider.notifier) + .submit(state.searchedEvents[index].title); + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 10.h), + child: Text( + state.searchedEvents[index].title, + style: AppTypeface.label16Medium, + ), + ), + ); + }, + itemCount: state.searchedEvents.length > 5 + ? 5 + : state.searchedEvents.length, + separatorBuilder: (context, index) { + return Divider(color: AppGrayscale.gray85); + }, + )); + }); + } +} diff --git a/lib/presentation/search/view/popular_search_keyword_view.dart b/lib/presentation/search/view/popular_search_keyword_view.dart index ae49a11..abe15b4 100644 --- a/lib/presentation/search/view/popular_search_keyword_view.dart +++ b/lib/presentation/search/view/popular_search_keyword_view.dart @@ -5,6 +5,9 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:ticats_app/app/base/base_view.dart'; import 'package:ticats_app/app/config/app_color.dart'; import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; +import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; class PopularSearchKeywordView extends BaseView { const PopularSearchKeywordView({super.key}); @@ -16,20 +19,31 @@ class PopularSearchKeywordView extends BaseView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('추천 인기 검색어', - style: AppTypeface.label16Medium - .copyWith(color: AppGrayscale.gray30)), - SizedBox(height: 16.h), Padding( - padding: EdgeInsets.symmetric(horizontal: 8.h), - child: _keywordWidget(), - ) + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: AsyncValueWidget( + value: ref.watch(searchedEventListControllerProvider), + data: (state) { + return Visibility( + visible: state.popularSearchKeywords.isNotEmpty, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('추천 인기 검색어', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h), + ...state.popularSearchKeywords + .map((e) => _keywordWidget(e)) + ]), + ); + })) ], ), ); } - Widget _keywordWidget() { + Widget _keywordWidget(PopularSearchKeywordEntity keywordEntity) { return Padding( padding: EdgeInsets.symmetric(vertical: 4.h), child: Row( diff --git a/lib/presentation/search/view/search_history_view.dart b/lib/presentation/search/view/search_history_view.dart index 65e47da..0303bf5 100644 --- a/lib/presentation/search/view/search_history_view.dart +++ b/lib/presentation/search/view/search_history_view.dart @@ -8,6 +8,8 @@ import 'package:ticats_app/app/config/app_typeface.dart'; import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; class SearchHistoryView extends BaseView { + const SearchHistoryView({super.key}); + @override Widget buildView(BuildContext context, WidgetRef ref) { return Column( @@ -29,7 +31,15 @@ class SearchHistoryView extends BaseView { padding: EdgeInsets.only(left: 20.w), child: Row( children: [ - TicatsChip.close('기록', onTap: () {}) + TicatsChip.close( + '기록', + onTap: () { + print('asdf'); + }, + iconTap: () { + print('아이콘'); + }, + ) ], ), ) diff --git a/lib/presentation/search/view/searched_event_list_event_view.dart b/lib/presentation/search/view/searched_event_list_event_view.dart new file mode 100644 index 0000000..f9c06b2 --- /dev/null +++ b/lib/presentation/search/view/searched_event_list_event_view.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:ticats_app/app/base/base_view.dart'; +import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/presentation/common/widget/ticats_event_widget.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; + +class SearchedEventListEventView extends BaseView { + const SearchedEventListEventView({super.key, this.categoryName}); + final String? categoryName; + + @override + Widget buildView(BuildContext context, WidgetRef ref) { + return AsyncValueWidget( + value: ref.watch(searchedEventListControllerProvider), + data: (value) { + final eventList = value.searchedEvents; + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: GridView.builder( + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 16.w, childAspectRatio: 167.w / 323.w), + physics: const NeverScrollableScrollPhysics(), + itemCount: eventList.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return SizedBox(child: TicatsEventWidget.big(event: eventList[index])); + }), + ); + }, + ); + } +} From db5567c555a0251097bc156c8e265f56ed96b0c8 Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Mon, 11 Nov 2024 15:47:49 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=203=EC=B0=A8=20=EC=8A=A4=ED=94=84?= =?UTF-8?q?=EB=A6=B0=ED=8A=B8=20=EA=B2=80=EC=83=89=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/icons/ticats_search_cat.svg | 24 +++++ lib/app/config/app_router.dart | 11 ++- lib/app/config/app_typeface.dart | 1 + .../remote/cultural_event_api.dart | 3 + .../cultural_event_repository_impl.dart | 31 ++++-- .../repository/cultural_event_repository.dart | 13 ++- .../usecase/cultural_event_usecases.dart | 36 +++++-- .../common/ticats_checkbox_bottom_sheet.dart | 32 +++--- .../common/widget/ticats_chip.dart | 12 ++- .../event_list/event_list_page.dart | 6 +- .../provider/event_list_controller.dart | 23 +++-- .../view/event_list_filter_view.dart | 34 +++++-- .../searched_event_list_controller.dart | 64 +++++++++--- lib/presentation/search/search_page.dart | 77 +++++++++++---- .../search/view/search_history_view.dart | 86 +++++++++------- .../view/search_result_not_found_view.dart | 81 +++++++++++++++ .../view/searched_event_list_event_view.dart | 9 +- .../view/searched_event_list_filter_view.dart | 98 +++++++++++++++++++ 18 files changed, 515 insertions(+), 126 deletions(-) create mode 100644 assets/icons/ticats_search_cat.svg create mode 100644 lib/presentation/search/view/search_result_not_found_view.dart create mode 100644 lib/presentation/search/view/searched_event_list_filter_view.dart diff --git a/assets/icons/ticats_search_cat.svg b/assets/icons/ticats_search_cat.svg new file mode 100644 index 0000000..627aa5b --- /dev/null +++ b/assets/icons/ticats_search_cat.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/app/config/app_router.dart b/lib/app/config/app_router.dart index 7934759..8872f46 100644 --- a/lib/app/config/app_router.dart +++ b/lib/app/config/app_router.dart @@ -41,7 +41,7 @@ class Router extends _$Router { @override GoRouter build() { return GoRouter( - initialLocation: Routes.main, + initialLocation: Routes.login, navigatorKey: rootNavigatorKey, routes: [ // Auth @@ -60,13 +60,15 @@ class Router extends _$Router { GoRoute( path: Routes.eventListOfCategory, name: Routes.eventListOfCategory, - builder: (context, state) => EventListOfCategoryPage(categoryName: state.uri.queryParameters['category'] as String), + builder: (context, state) => EventListOfCategoryPage( + categoryName: state.uri.queryParameters['category'] as String), ), GoRoute( path: Routes.eventList, name: Routes.eventList, - builder: (context, state) => EventListPage(title: state.uri.queryParameters['title'] as String), + builder: (context, state) => EventListPage( + title: state.uri.queryParameters['title'] as String), ), // Event Detail @@ -102,7 +104,8 @@ class Router extends _$Router { GoRoute( path: 'select-entertainment', name: Routes.registerSelectEntertianment, - builder: (context, state) => const RegisterSelectEntertainmentPage(), + builder: (context, state) => + const RegisterSelectEntertainmentPage(), ), ], ), diff --git a/lib/app/config/app_typeface.dart b/lib/app/config/app_typeface.dart index e4c02a8..a0d6401 100644 --- a/lib/app/config/app_typeface.dart +++ b/lib/app/config/app_typeface.dart @@ -26,6 +26,7 @@ class AppTypeface { static TextStyle get label14Bold => TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w700, height: 1.57, color: AppColor.black); static TextStyle get label14Medium => TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500, height: 1.57, color: AppColor.black); static TextStyle get label14SemiBold => TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600, height: 1.57, color: AppColor.black); + static TextStyle get label14Regular => TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w400, height: 1.57, color: AppColor.black); static TextStyle get label12Bold => TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w700, height: 1.5, color: AppColor.black); static TextStyle get label12SemiBold => TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600, height: 1.5, color: AppColor.black); static TextStyle get label12Medium => TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w500, height: 1.5, color: AppColor.black); diff --git a/lib/data/data_source/remote/cultural_event_api.dart b/lib/data/data_source/remote/cultural_event_api.dart index 44af033..559a899 100644 --- a/lib/data/data_source/remote/cultural_event_api.dart +++ b/lib/data/data_source/remote/cultural_event_api.dart @@ -27,6 +27,9 @@ abstract class CulturalEventAPI { @GET("/recent-keywords") Future> getRecentSearchKeywords(); + @DELETE("/recent-keywords") + Future deleteRecentSearchKeyword(int id); + @GET("/search") Future getSearchedCulturalEvents( @Queries(encoded: true) CulturalEventsSearchEntity queries, diff --git a/lib/data/repository_impl/cultural_event_repository_impl.dart b/lib/data/repository_impl/cultural_event_repository_impl.dart index 9ea1617..a4b04e2 100644 --- a/lib/data/repository_impl/cultural_event_repository_impl.dart +++ b/lib/data/repository_impl/cultural_event_repository_impl.dart @@ -26,21 +26,25 @@ class CulturalEventRepositoryImpl implements CulturalEventRepository { } @override - Future> getCulturalEvents(CulturalEventsSearchEntity queries) async { + Future> getCulturalEvents( + CulturalEventsSearchEntity queries) async { CulturalEventsModel response = await _api.getCulturalEvents(queries); return response.toEntityList(); } @override - Future> getOpenDateEvents(CulturalEventsSearchEntity queries) async { - queries = queries.copyWith(isOpened: false, ordering: TicatsEventOrdering.ticketOpenDate); + Future> getOpenDateEvents( + CulturalEventsSearchEntity queries) async { + queries = queries.copyWith( + isOpened: false, ordering: TicatsEventOrdering.ticketOpenDate); CulturalEventsModel response = await _api.getCulturalEvents(queries); return response.toEntityList(); } @override - Future> getPointEvents(CulturalEventsSearchEntity queries) async { + Future> getPointEvents( + CulturalEventsSearchEntity queries) async { queries = queries.copyWith(ordering: TicatsEventOrdering.point); CulturalEventsModel response = await _api.getCulturalEvents(queries); @@ -48,26 +52,37 @@ class CulturalEventRepositoryImpl implements CulturalEventRepository { } @override - Future> getRecommendEvents(CulturalEventsSearchEntity queries) async { + Future> getRecommendEvents( + CulturalEventsSearchEntity queries) async { queries = queries.copyWith(ordering: TicatsEventOrdering.recommend); CulturalEventsModel response = await _api.getCulturalEvents(queries); return response.toEntityList(); } + @override Future> getPopularSearchKeywords() async { - List response = await _api.getPopularSearchKeywords(); + List response = + await _api.getPopularSearchKeywords(); return response.map((e) => e.toEntity()).toList(); } + @override Future> getRecentSearchKeywords() async { - List response = await _api.getRecentSearchKeywords(); + List response = + await _api.getRecentSearchKeywords(); return response.map((e) => e.toEntity()).toList(); } + + @override + Future deleteRecentSearchKeyword(int id) async { + await _api.deleteRecentSearchKeyword(id); + } } @riverpod -CulturalEventRepository culturalEventRepository(CulturalEventRepositoryRef ref) { +CulturalEventRepository culturalEventRepository( + CulturalEventRepositoryRef ref) { final api = CulturalEventAPI(ref.read(dioProvider)); return CulturalEventRepositoryImpl(api: api); diff --git a/lib/domain/repository/cultural_event_repository.dart b/lib/domain/repository/cultural_event_repository.dart index ddd750e..12c66bd 100644 --- a/lib/domain/repository/cultural_event_repository.dart +++ b/lib/domain/repository/cultural_event_repository.dart @@ -5,10 +5,15 @@ import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_en abstract class CulturalEventRepository { Future getCulturalEventInfo(String id); - Future> getCulturalEvents(CulturalEventsSearchEntity request); - Future> getRecommendEvents(CulturalEventsSearchEntity request); - Future> getPointEvents(CulturalEventsSearchEntity request); - Future> getOpenDateEvents(CulturalEventsSearchEntity request); + Future> getCulturalEvents( + CulturalEventsSearchEntity request); + Future> getRecommendEvents( + CulturalEventsSearchEntity request); + Future> getPointEvents( + CulturalEventsSearchEntity request); + Future> getOpenDateEvents( + CulturalEventsSearchEntity request); Future> getRecentSearchKeywords(); Future> getPopularSearchKeywords(); + Future deleteRecentSearchKeyword(int id); } diff --git a/lib/domain/usecase/cultural_event_usecases.dart b/lib/domain/usecase/cultural_event_usecases.dart index fea7952..edb0f2d 100644 --- a/lib/domain/usecase/cultural_event_usecases.dart +++ b/lib/domain/usecase/cultural_event_usecases.dart @@ -9,7 +9,8 @@ import 'package:ticats_app/domain/repository/cultural_event_repository.dart'; part 'cultural_event_usecases.g.dart'; class CulturalEventUsecases { - CulturalEventUsecases({required CulturalEventRepository repository}) : _repository = repository; + CulturalEventUsecases({required CulturalEventRepository repository}) + : _repository = repository; final CulturalEventRepository _repository; @@ -18,8 +19,12 @@ class CulturalEventUsecases { GetOpenDateEvents get getOpenDateEvents => GetOpenDateEvents(_repository); GetPointEvents get getPointEvents => GetPointEvents(_repository); GetRecommendEvents get getRecommendEvents => GetRecommendEvents(_repository); - GetRecentSearchKeywords get getRecentSearchKeywords => GetRecentSearchKeywords(_repository); - GetPopularSearchKeywords get getPopularSearchKeywords => GetPopularSearchKeywords(_repository); + GetRecentSearchKeywords get getRecentSearchKeywords => + GetRecentSearchKeywords(_repository); + DeleteRecentSearchKeyword get deleteRecentSearchKeyword => + DeleteRecentSearchKeyword(_repository); + GetPopularSearchKeywords get getPopularSearchKeywords => + GetPopularSearchKeywords(_repository); } class GetEventInfo { @@ -37,7 +42,8 @@ class GetEvents { GetEvents(this._repository); - Future> execute(CulturalEventsSearchEntity request) async { + Future> execute( + CulturalEventsSearchEntity request) async { return await _repository.getCulturalEvents(request); } } @@ -47,7 +53,8 @@ class GetOpenDateEvents { GetOpenDateEvents(this._repository); - Future> execute(CulturalEventsSearchEntity request) async { + Future> execute( + CulturalEventsSearchEntity request) async { return await _repository.getOpenDateEvents(request); } } @@ -57,7 +64,8 @@ class GetPointEvents { GetPointEvents(this._repository); - Future> execute(CulturalEventsSearchEntity request) async { + Future> execute( + CulturalEventsSearchEntity request) async { return await _repository.getPointEvents(request); } } @@ -67,7 +75,8 @@ class GetRecommendEvents { GetRecommendEvents(this._repository); - Future> execute(CulturalEventsSearchEntity request) async { + Future> execute( + CulturalEventsSearchEntity request) async { return await _repository.getRecommendEvents(request); } } @@ -82,6 +91,16 @@ class GetRecentSearchKeywords { } } +class DeleteRecentSearchKeyword { + final CulturalEventRepository _repository; + + DeleteRecentSearchKeyword(this._repository); + + Future execute(int id) async { + await _repository.deleteRecentSearchKeyword(id); + } +} + class GetPopularSearchKeywords { final CulturalEventRepository _repository; @@ -94,5 +113,6 @@ class GetPopularSearchKeywords { @riverpod CulturalEventUsecases culturalEventUsecases(CulturalEventUsecasesRef ref) { - return CulturalEventUsecases(repository: ref.read(culturalEventRepositoryProvider)); + return CulturalEventUsecases( + repository: ref.read(culturalEventRepositoryProvider)); } diff --git a/lib/presentation/common/ticats_checkbox_bottom_sheet.dart b/lib/presentation/common/ticats_checkbox_bottom_sheet.dart index d5db953..74cd098 100644 --- a/lib/presentation/common/ticats_checkbox_bottom_sheet.dart +++ b/lib/presentation/common/ticats_checkbox_bottom_sheet.dart @@ -43,10 +43,12 @@ class TicatsCheckboxBottomSheet extends StatefulWidget { }); @override - State> createState() => _TicatsCheckboxBottomSheetState(); + State> createState() => + _TicatsCheckboxBottomSheetState(); } -class _TicatsCheckboxBottomSheetState extends State> { +class _TicatsCheckboxBottomSheetState + extends State> { late List selectedValues; @override @@ -60,11 +62,13 @@ class _TicatsCheckboxBottomSheetState extends State extends State((states) { - return AppGrayscale.gray50; - }), - ) - ), + fillColor: + WidgetStateProperty.resolveWith((states) { + return AppGrayscale.gray50; + }), + )), child: CheckboxListTile( contentPadding: EdgeInsets.zero, - visualDensity: VisualDensity(vertical: -4.h), activeColor: AppColor.primaryNormal, - title: Text(_getEnumLabel(option), style: AppTypeface.body18Semibold.copyWith(color: isSelected ? AppColor.primaryDark : AppGrayscale.gray50)), + title: Text(_getEnumLabel(option), + style: AppTypeface.body18Semibold.copyWith( + color: isSelected + ? AppColor.primaryDark + : AppGrayscale.gray50)), value: isSelected, onChanged: (bool? checked) { setState(() { @@ -94,7 +101,8 @@ class _TicatsCheckboxBottomSheetState extends State ref.read(eventListControllerProvider().notifier).scrollData(), + loadFunction: () => + ref.read(eventListControllerProvider().notifier).scrollData(), ); return SingleChildScrollView( @@ -39,5 +40,6 @@ class EventListPage extends BasePage { } @override - PreferredSizeWidget? buildAppBar(BuildContext context, WidgetRef ref) => TicatsAppBar.back(title); + PreferredSizeWidget? buildAppBar(BuildContext context, WidgetRef ref) => + TicatsAppBar.back(title); } diff --git a/lib/presentation/event_list/provider/event_list_controller.dart b/lib/presentation/event_list/provider/event_list_controller.dart index 88a7540..ce97a7e 100644 --- a/lib/presentation/event_list/provider/event_list_controller.dart +++ b/lib/presentation/event_list/provider/event_list_controller.dart @@ -47,26 +47,33 @@ class EventListController extends _$EventListController { Future selectOrdering(TicatsEventOrdering ordering) async { final newFilter = state.value!.filter.copyWith(ordering: ordering); - state = AsyncValue.data(state.value!.copyWith(events: await _fetchEvents(filter: newFilter), filter: newFilter)); + state = AsyncValue.data(state.value!.copyWith( + events: await _fetchEvents(filter: newFilter), filter: newFilter)); } Future selectCategories(List categories) async { - final newFilter = state.value!.filter.copyWith( - categories: categories.map((e) => e.name).toList()); + final newFilter = state.value!.filter + .copyWith(categories: categories.map((e) => e.name).toList()); - state = AsyncValue.data(state.value!.copyWith(events: await _fetchEvents(filter: newFilter), filter: newFilter)); + state = AsyncValue.data(state.value!.copyWith( + events: await _fetchEvents(filter: newFilter), filter: newFilter)); } Future scrollData() async { - final newFilter = state.value!.filter.copyWith(page: state.value!.filter.page + 1); + final newFilter = + state.value!.filter.copyWith(page: state.value!.filter.page + 1); final newEvents = await _fetchEvents(filter: newFilter); - state = AsyncValue.data(state.value!.copyWith(events: [...state.value!.events, ...newEvents], filter: newFilter)); + state = AsyncValue.data(state.value!.copyWith( + events: [...state.value!.events, ...newEvents], filter: newFilter)); } - Future> _fetchEvents({CulturalEventsSearchEntity? filter}) async { + Future> _fetchEvents( + {CulturalEventsSearchEntity? filter}) async { final List response = - await _culturalEventUseCase.getEvents.execute(filter ?? state.value?.filter ?? const CulturalEventsSearchEntity()); + await _culturalEventUseCase.getEvents.execute(filter ?? + state.value?.filter ?? + const CulturalEventsSearchEntity()); return response; } diff --git a/lib/presentation/event_list/view/event_list_filter_view.dart b/lib/presentation/event_list/view/event_list_filter_view.dart index 245d8de..6690788 100644 --- a/lib/presentation/event_list/view/event_list_filter_view.dart +++ b/lib/presentation/event_list/view/event_list_filter_view.dart @@ -9,8 +9,10 @@ import 'package:ticats_app/presentation/common/ticats_radio_bottom_sheet.dart'; import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; import 'package:ticats_app/presentation/event_list/provider/event_list_controller.dart'; -final orderingProvider = StateProvider((ref) => TicatsEventOrdering.ticketOpenDate); -final categoryProvider = StateProvider>((ref) => [TicatsEventCategory.ALL]); +final orderingProvider = StateProvider( + (ref) => TicatsEventOrdering.ticketOpenDate); +final categoryProvider = StateProvider>( + (ref) => [TicatsEventCategory.ALL]); class EventListFilterView extends ConsumerStatefulWidget { const EventListFilterView({super.key, this.categoryName}); @@ -28,7 +30,8 @@ class _EventListFilterViewState extends ConsumerState { void initState() { super.initState(); Future.microtask(() { - ref.read(orderingProvider.notifier).state = TicatsEventOrdering.ticketOpenDate; + ref.read(orderingProvider.notifier).state = + TicatsEventOrdering.ticketOpenDate; ref.read(categoryProvider.notifier).state = [TicatsEventCategory.ALL]; }); } @@ -51,24 +54,35 @@ class _EventListFilterViewState extends ConsumerState { options: TicatsEventCategory.values, selectedValues: categoryValue, onChanged: (value) async { - await ref.read(eventListControllerProvider(categoryName: widget.categoryName).notifier) + await ref + .read(eventListControllerProvider( + categoryName: widget.categoryName) + .notifier) .selectCategories(value); setState(() { - categoryTitle = value.first.label; + if (value.isEmpty) { + categoryTitle = '카테고리'; + } else { + categoryTitle = value.first.label; + } }); - } - ); + }); }), SizedBox(width: 8.w), ], TicatsChip.arrowDown( - TicatsEventOrdering.values.firstWhere((element) => element == orderingValue).label, + TicatsEventOrdering.values + .firstWhere((element) => element == orderingValue) + .label, onTap: () { showTicatsRadioBottomSheet( options: TicatsEventOrdering.values, groupValue: orderingValue, onChanged: (value) async { - await ref.read(eventListControllerProvider(categoryName: widget.categoryName).notifier) + await ref + .read(eventListControllerProvider( + categoryName: widget.categoryName) + .notifier) .selectOrdering(value); ref.read(orderingProvider.notifier).state = value; context.pop(); @@ -84,4 +98,4 @@ class _EventListFilterViewState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/search/provider/searched_event_list_controller.dart b/lib/presentation/search/provider/searched_event_list_controller.dart index 87c1549..1518003 100644 --- a/lib/presentation/search/provider/searched_event_list_controller.dart +++ b/lib/presentation/search/provider/searched_event_list_controller.dart @@ -1,5 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:ticats_app/app/enum/ticats_event_category.enum.dart'; +import 'package:ticats_app/app/enum/ticats_event_ordering.enum.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_event_entity.dart'; import 'package:ticats_app/domain/entity/cultural_event/cultural_events_search_entity.dart'; import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; @@ -29,45 +31,81 @@ class SearchedEventListController extends _$SearchedEventListController { _culturalEventUseCase = ref.read(culturalEventUsecasesProvider); return SearchedEventListState( searchedEvents: await fetchSearchedEvents(), - recentSearchKeywords: await fetchRecentSearchKeywords(), - popularSearchKeywords: await fetchPopularSearchKeywords() - ); + recentSearchKeywords: await fetchRecentSearchKeywords(), + popularSearchKeywords: await fetchPopularSearchKeywords()); } Future> fetchSearchedEvents( - {String? keyword}) async { + {CulturalEventsSearchEntity? filter}) async { final List response = await _culturalEventUseCase .getEvents - .execute(CulturalEventsSearchEntity(keyword: keyword)); + .execute(filter ?? const CulturalEventsSearchEntity()); return response; } Future> fetchRecentSearchKeywords() async { - final List response = await _culturalEventUseCase - .getRecentSearchKeywords - .execute(); + final List response = + await _culturalEventUseCase.getRecentSearchKeywords.execute(); return response; } Future> fetchPopularSearchKeywords() async { - final List response = await _culturalEventUseCase - .getPopularSearchKeywords - .execute(); + final List response = + await _culturalEventUseCase.getPopularSearchKeywords.execute(); return response; } + Future deleteRecentSearchKeyword(int id) async { + final updatedList = + List.from(state.value!.recentSearchKeywords) + ..removeWhere((e) => e.id == id); + state = AsyncData(state.value!.copyWith(recentSearchKeywords: updatedList)); + await _culturalEventUseCase.deleteRecentSearchKeyword.execute(id); + } + + Future selectOrdering(TicatsEventOrdering ordering) async { + final newFilter = state.value!.filter.copyWith(ordering: ordering, page: 0); + + state = AsyncValue.data(state.value!.copyWith( + searchedEvents: await fetchSearchedEvents(filter: newFilter), + filter: newFilter)); + } + + Future selectCategories(List categories) async { + final newFilter = state.value!.filter + .copyWith(categories: categories.map((e) => e.name).toList(), page: 0); + + state = AsyncValue.data(state.value!.copyWith( + searchedEvents: await fetchSearchedEvents(filter: newFilter), + filter: newFilter)); + } + + Future scrollData() async { + final newFilter = + state.value!.filter.copyWith(page: state.value!.filter.page + 1); + final newEvents = await fetchSearchedEvents(filter: newFilter); + + state = AsyncValue.data(state.value!.copyWith( + searchedEvents: [...state.value!.searchedEvents, ...newEvents], + filter: newFilter)); + } + void onSearchTextChanged(String value) async { + final newFilter = state.value!.filter.copyWith(keyword: value, page: 0); state = AsyncData(state.value!.copyWith( - searchedEvents: await fetchSearchedEvents(keyword: value), + searchedEvents: await fetchSearchedEvents(filter: newFilter), + filter: newFilter, isSubmitted: false)); } void submit(String value) async { + final newFilter = state.value!.filter.copyWith(keyword: value, page: 0); state = AsyncData(state.value!.copyWith( - searchedEvents: await fetchSearchedEvents(keyword: value), + searchedEvents: await fetchSearchedEvents(filter: newFilter), + filter: newFilter, isSubmitted: true)); } diff --git a/lib/presentation/search/search_page.dart b/lib/presentation/search/search_page.dart index 7d395bf..ee8bf45 100644 --- a/lib/presentation/search/search_page.dart +++ b/lib/presentation/search/search_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/svg.dart'; @@ -8,11 +9,14 @@ import 'package:ticats_app/app/config/app_radius.dart'; import 'package:ticats_app/app/config/app_typeface.dart'; import 'package:ticats_app/gen/assets.gen.dart'; import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/presentation/event_list/hook/use_infinite_scroll_hook.dart'; import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; import 'package:ticats_app/presentation/search/view/keyword_list_view.dart'; import 'package:ticats_app/presentation/search/view/popular_search_keyword_view.dart'; import 'package:ticats_app/presentation/search/view/search_history_view.dart'; +import 'package:ticats_app/presentation/search/view/search_result_not_found_view.dart'; import 'package:ticats_app/presentation/search/view/searched_event_list_event_view.dart'; +import 'package:ticats_app/presentation/search/view/searched_event_list_filter_view.dart'; final searchTextControllerProvider = StateProvider((ref) { @@ -28,7 +32,19 @@ class SearchPage extends BasePage { @override Widget buildPage(BuildContext context, WidgetRef ref) { + final scrollController = useScrollController(); + + useInfiniteScrollHook( + ref: ref, + scrollController: scrollController, + loadFunction: () { + return ref + .read(searchedEventListControllerProvider.notifier) + .scrollData(); + }); + return SingleChildScrollView( + controller: scrollController, child: Column( children: [ SizedBox(height: 4.h), @@ -38,16 +54,28 @@ class SearchPage extends BasePage { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // 검색 버튼을 누르지 않음 and 키보드로 submit이 되지 않음, if (state.isSubmitted == false) ...[ + // 텍스트를 입력 중이지 않을 때 if (ref.watch(searchTextControllerProvider).text == '') ...[ - SearchHistoryView(), - PopularSearchKeywordView(), + const SearchHistoryView(), + const PopularSearchKeywordView(), + // 텍스트를 입력 중일 때 ] else ...[ - KeywordListView() + const KeywordListView() ] + // 검색 버튼을 눌렀거나 키보드로 submit이 되었고, ] else ...[ - SearchedEventListEventView() + // 검색 결과가 없을 때 + const SearchedEventListFilterView(), + SizedBox(height: 28.h), + if (state.searchedEvents.isEmpty) ...[ + const SearchResultNotFoundView() + // 검색 결과가 있을 때 + ] else ...[ + const SearchedEventListEventView() + ] ] ], ); @@ -85,19 +113,28 @@ class SearchPage extends BasePage { hintText: '티켓을 검색해보세요!', hintStyle: AppTypeface.label16Regular .copyWith(color: AppGrayscale.gray55), - suffixIcon: Container( - margin: EdgeInsets.only(right: 16.w), - child: GestureDetector( - onTap: () { - ref.watch(searchTextControllerProvider).text = ''; - ref.read(searchedEventListControllerProvider.notifier).clickRemoveIcon(); - }, - child: SvgPicture.asset( - Assets.icons.closeCircleFill.path, - width: 20.w, - height: 20.w, - ), - )), + suffixIcon: Visibility( + visible: ref + .watch(searchTextControllerProvider) + .text + .isNotEmpty, + child: Container( + margin: EdgeInsets.only(right: 16.w), + child: GestureDetector( + onTap: () { + ref.watch(searchTextControllerProvider).text = ''; + ref + .read(searchedEventListControllerProvider + .notifier) + .clickRemoveIcon(); + }, + child: SvgPicture.asset( + Assets.icons.closeCircleFill.path, + width: 20.w, + height: 20.w, + ), + )), + ), suffixIconConstraints: BoxConstraints( minWidth: 20.w, minHeight: 20.w, @@ -116,7 +153,11 @@ class SearchPage extends BasePage { ), )), IconButton( - onPressed: () {}, + onPressed: () { + ref + .read(searchedEventListControllerProvider.notifier) + .submit(ref.watch(searchTextControllerProvider).text); + }, icon: Assets.icons.search.svg( width: 24.w, height: 24.w, diff --git a/lib/presentation/search/view/search_history_view.dart b/lib/presentation/search/view/search_history_view.dart index 0303bf5..21dd5db 100644 --- a/lib/presentation/search/view/search_history_view.dart +++ b/lib/presentation/search/view/search_history_view.dart @@ -5,45 +5,65 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:ticats_app/app/base/base_view.dart'; import 'package:ticats_app/app/config/app_color.dart'; import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; +import 'package:ticats_app/presentation/search/search_page.dart'; class SearchHistoryView extends BaseView { const SearchHistoryView({super.key}); @override Widget buildView(BuildContext context, WidgetRef ref) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('검색 기록', - style: AppTypeface.label16Medium - .copyWith(color: AppGrayscale.gray30)), - SizedBox(height: 16.h) - ], - ), - ), - Padding( - padding: EdgeInsets.only(left: 20.w), - child: Row( - children: [ - TicatsChip.close( - '기록', - onTap: () { - print('asdf'); - }, - iconTap: () { - print('아이콘'); - }, - ) - ], - ), - ) - ], - ); + return AsyncValueWidget( + value: ref.watch(searchedEventListControllerProvider), + data: (state) { + return Visibility( + visible: state.recentSearchKeywords.isNotEmpty, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('검색 기록', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h) + ], + ), + ), + SingleChildScrollView( + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Row( + children: [ + SizedBox(width: 16.w), + ...state.recentSearchKeywords.map((e) => Container( + margin: EdgeInsets.only(right: 8.w), + child: TicatsChip.close(e.title, onTap: () { + ref.watch(searchTextControllerProvider).text = + e.title; + ref + .read(searchedEventListControllerProvider + .notifier) + .submit(e.title); + }, iconTap: () { + ref + .read(searchedEventListControllerProvider + .notifier) + .deleteRecentSearchKeyword(e.id); + }), + )), + SizedBox(width: 8.w) + ], + ), + ), + ], + ), + ); + }); } } diff --git a/lib/presentation/search/view/search_result_not_found_view.dart b/lib/presentation/search/view/search_result_not_found_view.dart new file mode 100644 index 0000000..7af358e --- /dev/null +++ b/lib/presentation/search/view/search_result_not_found_view.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_riverpod/src/consumer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:ticats_app/app/base/base_view.dart'; +import 'package:ticats_app/app/config/app_typeface.dart'; +import 'package:ticats_app/gen/assets.gen.dart'; +import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; +import 'package:ticats_app/presentation/search/search_page.dart'; + +class SearchResultNotFoundView extends BaseView { + const SearchResultNotFoundView({super.key}); + + @override + Widget buildView(BuildContext context, WidgetRef ref) { + return SingleChildScrollView( + child: AsyncValueWidget( + value: ref.watch(searchedEventListControllerProvider), + data: (state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 68.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.icons.ticatsSearchCat.path), + SizedBox(height: 24.h), + Text( + '아쉽게도 일치하는 내용이 없어요.\n다른 검색어를 입력해보세요.', + style: AppTypeface.label16Semibold, + textAlign: TextAlign.center, + ), + SizedBox(height: 24.h), + Text('* 공연이 종료되는 경우에는 검색되지 않아요', + style: AppTypeface.label14Regular), + ], + ), + ), + ), + Padding( + padding: + EdgeInsets.symmetric(vertical: 8.h, horizontal: 24.w), + child: Text( + '이런 검색어는 어떨까요?', + style: AppTypeface.body18Bold, + textAlign: TextAlign.left, + )), + Align( + alignment: Alignment.center, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 12.h, horizontal: 24.w), + child: Wrap( + runSpacing: 12.h, + spacing: 12.w, + alignment: WrapAlignment.center, + children: state.popularSearchKeywords + .map((e) => TicatsChip(e.title, onTap: () { + ref + .watch(searchTextControllerProvider) + .text = e.title; + ref + .read( + searchedEventListControllerProvider + .notifier) + .submit(e.title); + })) + .toList())), + ), + ], + ); + }), + ); + } +} diff --git a/lib/presentation/search/view/searched_event_list_event_view.dart b/lib/presentation/search/view/searched_event_list_event_view.dart index f9c06b2..15daf73 100644 --- a/lib/presentation/search/view/searched_event_list_event_view.dart +++ b/lib/presentation/search/view/searched_event_list_event_view.dart @@ -19,13 +19,16 @@ class SearchedEventListEventView extends BaseView { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: GridView.builder( - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 16.w, childAspectRatio: 167.w / 323.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16.w, + childAspectRatio: 167.w / 323.w), physics: const NeverScrollableScrollPhysics(), itemCount: eventList.length, shrinkWrap: true, itemBuilder: (context, index) { - return SizedBox(child: TicatsEventWidget.big(event: eventList[index])); + return SizedBox( + child: TicatsEventWidget.big(event: eventList[index])); }), ); }, diff --git a/lib/presentation/search/view/searched_event_list_filter_view.dart b/lib/presentation/search/view/searched_event_list_filter_view.dart new file mode 100644 index 0000000..c450fc5 --- /dev/null +++ b/lib/presentation/search/view/searched_event_list_filter_view.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ticats_app/app/enum/ticats_event_category.enum.dart'; +import 'package:ticats_app/app/enum/ticats_event_ordering.enum.dart'; +import 'package:ticats_app/presentation/common/ticats_checkbox_bottom_sheet.dart'; +import 'package:ticats_app/presentation/common/ticats_radio_bottom_sheet.dart'; +import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; +import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; + +final orderingProvider = StateProvider( + (ref) => TicatsEventOrdering.ticketOpenDate); +final categoryProvider = StateProvider>( + (ref) => [TicatsEventCategory.ALL]); + +class SearchedEventListFilterView extends ConsumerStatefulWidget { + const SearchedEventListFilterView({super.key, this.categoryName}); + + final String? categoryName; + + @override + _EventListFilterViewState createState() => _EventListFilterViewState(); +} + +class _EventListFilterViewState + extends ConsumerState { + String categoryTitle = '카테고리'; + + @override + void initState() { + super.initState(); + Future.microtask(() { + ref.read(orderingProvider.notifier).state = + TicatsEventOrdering.ticketOpenDate; + ref.read(categoryProvider.notifier).state = [TicatsEventCategory.ALL]; + }); + } + + @override + Widget build(BuildContext context) { + final orderingValue = ref.watch(orderingProvider); + final categoryValue = ref.watch(categoryProvider); + + return SingleChildScrollView( + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 20.w), + if (widget.categoryName == null) ...[ + TicatsChip.arrowDown(categoryTitle, onTap: () { + showTicatsCheckboxBottomSheet( + options: TicatsEventCategory.values, + selectedValues: categoryValue, + onChanged: (value) async { + await ref + .read(searchedEventListControllerProvider.notifier) + .selectCategories(value); + setState(() { + if (value.isEmpty) { + categoryTitle = '카테고리'; + } else { + categoryTitle = value.first.label; + } + }); + }); + }), + SizedBox(width: 8.w), + ], + TicatsChip.arrowDown( + TicatsEventOrdering.values + .firstWhere((element) => element == orderingValue) + .label, + onTap: () { + showTicatsRadioBottomSheet( + options: TicatsEventOrdering.values, + groupValue: orderingValue, + onChanged: (value) async { + await ref + .read(searchedEventListControllerProvider.notifier) + .selectOrdering(value); + ref.read(orderingProvider.notifier).state = value; + context.pop(); + }, + ); + }, + ), + SizedBox(width: 8.w), + TicatsChip.arrowDown('공연 현황', onTap: () { + // TODO 공연중, 공연예정에 관한 API 필요 + }), + ], + ), + ); + } +} From 8fd77bdd9ea254c849ac22e805a8f98e7228dca2 Mon Sep 17 00:00:00 2001 From: chojja7188 Date: Thu, 21 Nov 2024 01:12:29 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EB=8D=9C=20=EB=90=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=EB=93=A4=20=EB=B3=B4=EC=B6=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/cultural_event_api.dart | 4 +- .../popular_search_keyword_model.dart | 4 +- .../recent_search_keyword_model.dart | 4 +- .../popular_search_keyword_entity.dart | 2 +- .../recent_search_keyword_entity.dart | 2 +- .../repository/cultural_event_repository.dart | 2 +- .../searched_event_list_controller.dart | 8 +- lib/presentation/search/search_page.dart | 5 +- .../view/popular_search_keyword_view.dart | 39 +++---- .../search/view/search_history_view.dart | 100 +++++++++--------- .../view/search_result_not_found_view.dart | 6 +- 11 files changed, 84 insertions(+), 92 deletions(-) diff --git a/lib/data/data_source/remote/cultural_event_api.dart b/lib/data/data_source/remote/cultural_event_api.dart index 559a899..456ca10 100644 --- a/lib/data/data_source/remote/cultural_event_api.dart +++ b/lib/data/data_source/remote/cultural_event_api.dart @@ -27,8 +27,8 @@ abstract class CulturalEventAPI { @GET("/recent-keywords") Future> getRecentSearchKeywords(); - @DELETE("/recent-keywords") - Future deleteRecentSearchKeyword(int id); + @DELETE("/recent-keywords/{id}") + Future deleteRecentSearchKeyword(@Path("id") int id); @GET("/search") Future getSearchedCulturalEvents( diff --git a/lib/data/model/cultural_event/popular_search_keyword_model.dart b/lib/data/model/cultural_event/popular_search_keyword_model.dart index 5f1faf6..27bb9b3 100644 --- a/lib/data/model/cultural_event/popular_search_keyword_model.dart +++ b/lib/data/model/cultural_event/popular_search_keyword_model.dart @@ -8,7 +8,7 @@ part 'popular_search_keyword_model.g.dart'; @freezed class PopularSearchKeywordModel with _$PopularSearchKeywordModel { const factory PopularSearchKeywordModel({ - @Default("") String title, + @Default("") String keyword, @Default(0) int ordering, }) = _PopularSearchKeywordModel; @@ -18,6 +18,6 @@ class PopularSearchKeywordModel with _$PopularSearchKeywordModel { extension PopularSearchKeywordModelX on PopularSearchKeywordModel { PopularSearchKeywordEntity toEntity() { - return PopularSearchKeywordEntity(title: title, ordering: ordering); + return PopularSearchKeywordEntity(keyword: keyword, ordering: ordering); } } diff --git a/lib/data/model/cultural_event/recent_search_keyword_model.dart b/lib/data/model/cultural_event/recent_search_keyword_model.dart index 5d337da..f50a008 100644 --- a/lib/data/model/cultural_event/recent_search_keyword_model.dart +++ b/lib/data/model/cultural_event/recent_search_keyword_model.dart @@ -9,7 +9,7 @@ part 'recent_search_keyword_model.g.dart'; class RecentSearchKeywordModel with _$RecentSearchKeywordModel { const factory RecentSearchKeywordModel({ required int id, - @Default("") String title, + @Default("") String keyword, @Default(0) int ordering, }) = _RecentSearchKeywordModel; @@ -19,6 +19,6 @@ class RecentSearchKeywordModel with _$RecentSearchKeywordModel { extension RecentSearchKeywordModelX on RecentSearchKeywordModel { RecentSearchKeywordEntity toEntity() { - return RecentSearchKeywordEntity(id: id, title: title, ordering: ordering); + return RecentSearchKeywordEntity(id: id, keyword: keyword, ordering: ordering); } } diff --git a/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart b/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart index 9ccf053..b30cc45 100644 --- a/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart +++ b/lib/domain/entity/cultural_event/popular_search_keyword_entity.dart @@ -6,7 +6,7 @@ part 'popular_search_keyword_entity.g.dart'; @freezed class PopularSearchKeywordEntity with _$PopularSearchKeywordEntity { const factory PopularSearchKeywordEntity({ - @Default("") String title, + @Default("") String keyword, @Default(0) int ordering, }) = _PopularSearchKeywordEntity; diff --git a/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart b/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart index 6107125..5affad0 100644 --- a/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart +++ b/lib/domain/entity/cultural_event/recent_search_keyword_entity.dart @@ -8,7 +8,7 @@ part 'recent_search_keyword_entity.g.dart'; class RecentSearchKeywordEntity with _$RecentSearchKeywordEntity { const factory RecentSearchKeywordEntity({ required int id, - @Default("") String title, + @Default("") String keyword, @Default(0) int ordering, }) = _RecentSearchKeywordEntity; diff --git a/lib/domain/repository/cultural_event_repository.dart b/lib/domain/repository/cultural_event_repository.dart index 12c66bd..328bd62 100644 --- a/lib/domain/repository/cultural_event_repository.dart +++ b/lib/domain/repository/cultural_event_repository.dart @@ -14,6 +14,6 @@ abstract class CulturalEventRepository { Future> getOpenDateEvents( CulturalEventsSearchEntity request); Future> getRecentSearchKeywords(); - Future> getPopularSearchKeywords(); Future deleteRecentSearchKeyword(int id); + Future> getPopularSearchKeywords(); } diff --git a/lib/presentation/search/provider/searched_event_list_controller.dart b/lib/presentation/search/provider/searched_event_list_controller.dart index 1518003..f224b5c 100644 --- a/lib/presentation/search/provider/searched_event_list_controller.dart +++ b/lib/presentation/search/provider/searched_event_list_controller.dart @@ -59,11 +59,10 @@ class SearchedEventListController extends _$SearchedEventListController { } Future deleteRecentSearchKeyword(int id) async { - final updatedList = - List.from(state.value!.recentSearchKeywords) - ..removeWhere((e) => e.id == id); - state = AsyncData(state.value!.copyWith(recentSearchKeywords: updatedList)); await _culturalEventUseCase.deleteRecentSearchKeyword.execute(id); + state = AsyncData(state.value!.copyWith( + recentSearchKeywords: await fetchRecentSearchKeywords(), + )); } Future selectOrdering(TicatsEventOrdering ordering) async { @@ -106,6 +105,7 @@ class SearchedEventListController extends _$SearchedEventListController { state = AsyncData(state.value!.copyWith( searchedEvents: await fetchSearchedEvents(filter: newFilter), filter: newFilter, + recentSearchKeywords: await fetchRecentSearchKeywords(), isSubmitted: true)); } diff --git a/lib/presentation/search/search_page.dart b/lib/presentation/search/search_page.dart index ee8bf45..829b3f7 100644 --- a/lib/presentation/search/search_page.dart +++ b/lib/presentation/search/search_page.dart @@ -59,9 +59,8 @@ class SearchPage extends BasePage { // 텍스트를 입력 중이지 않을 때 if (ref.watch(searchTextControllerProvider).text == '') ...[ - const SearchHistoryView(), - const PopularSearchKeywordView(), - // 텍스트를 입력 중일 때 + SearchHistoryView(recentSearchKeywords: state.recentSearchKeywords), + PopularSearchKeywordView(popularSearchKeywords: state.popularSearchKeywords), ] else ...[ const KeywordListView() ] diff --git a/lib/presentation/search/view/popular_search_keyword_view.dart b/lib/presentation/search/view/popular_search_keyword_view.dart index abe15b4..578747e 100644 --- a/lib/presentation/search/view/popular_search_keyword_view.dart +++ b/lib/presentation/search/view/popular_search_keyword_view.dart @@ -6,11 +6,10 @@ import 'package:ticats_app/app/base/base_view.dart'; import 'package:ticats_app/app/config/app_color.dart'; import 'package:ticats_app/app/config/app_typeface.dart'; import 'package:ticats_app/domain/entity/cultural_event/popular_search_keyword_entity.dart'; -import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; -import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; class PopularSearchKeywordView extends BaseView { - const PopularSearchKeywordView({super.key}); + final List popularSearchKeywords; + const PopularSearchKeywordView({super.key, required this.popularSearchKeywords}); @override Widget buildView(BuildContext context, WidgetRef ref) { @@ -21,43 +20,37 @@ class PopularSearchKeywordView extends BaseView { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.h), - child: AsyncValueWidget( - value: ref.watch(searchedEventListControllerProvider), - data: (state) { - return Visibility( - visible: state.popularSearchKeywords.isNotEmpty, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('추천 인기 검색어', - style: AppTypeface.label16Medium - .copyWith(color: AppGrayscale.gray30)), - SizedBox(height: 16.h), - ...state.popularSearchKeywords - .map((e) => _keywordWidget(e)) - ]), - ); - })) + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('추천 인기 검색어', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h), + ...popularSearchKeywords + .map((e) => _keywordWidget(e)) + ]) + ) ], ), ); } - Widget _keywordWidget(PopularSearchKeywordEntity keywordEntity) { + Widget _keywordWidget(PopularSearchKeywordEntity keyword) { return Padding( padding: EdgeInsets.symmetric(vertical: 4.h), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - '1', + '${keyword.ordering}', style: AppTypeface.body20Bold.copyWith(color: AppColor.primaryDark), ), SizedBox( width: 12.w, ), Text( - '인피니트', + keyword.keyword, style: AppTypeface.label16Regular.copyWith(color: AppGrayscale.gray10), ) diff --git a/lib/presentation/search/view/search_history_view.dart b/lib/presentation/search/view/search_history_view.dart index 21dd5db..ee747f7 100644 --- a/lib/presentation/search/view/search_history_view.dart +++ b/lib/presentation/search/view/search_history_view.dart @@ -6,64 +6,64 @@ import 'package:ticats_app/app/base/base_view.dart'; import 'package:ticats_app/app/config/app_color.dart'; import 'package:ticats_app/app/config/app_typeface.dart'; import 'package:ticats_app/presentation/common/widget/async_value_widget.dart'; +import 'package:ticats_app/domain/entity/cultural_event/recent_search_keyword_entity.dart'; import 'package:ticats_app/presentation/common/widget/ticats_chip.dart'; import 'package:ticats_app/presentation/search/provider/searched_event_list_controller.dart'; import 'package:ticats_app/presentation/search/search_page.dart'; class SearchHistoryView extends BaseView { - const SearchHistoryView({super.key}); + final List recentSearchKeywords; + + const SearchHistoryView({super.key, required this.recentSearchKeywords}); @override Widget buildView(BuildContext context, WidgetRef ref) { - return AsyncValueWidget( - value: ref.watch(searchedEventListControllerProvider), - data: (state) { - return Visibility( - visible: state.recentSearchKeywords.isNotEmpty, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('검색 기록', - style: AppTypeface.label16Medium - .copyWith(color: AppGrayscale.gray30)), - SizedBox(height: 16.h) - ], - ), - ), - SingleChildScrollView( - physics: const ClampingScrollPhysics(), - scrollDirection: Axis.horizontal, - child: Row( - children: [ - SizedBox(width: 16.w), - ...state.recentSearchKeywords.map((e) => Container( - margin: EdgeInsets.only(right: 8.w), - child: TicatsChip.close(e.title, onTap: () { - ref.watch(searchTextControllerProvider).text = - e.title; - ref - .read(searchedEventListControllerProvider - .notifier) - .submit(e.title); - }, iconTap: () { - ref - .read(searchedEventListControllerProvider - .notifier) - .deleteRecentSearchKeyword(e.id); - }), - )), - SizedBox(width: 8.w) - ], - ), - ), - ], + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 36.h, left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('검색 기록', + style: AppTypeface.label16Medium + .copyWith(color: AppGrayscale.gray30)), + SizedBox(height: 16.h) + ], + ), + ), + Padding( + padding: EdgeInsets.only(left: 20.w), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: recentSearchKeywords + .map((e) => Padding( + padding: EdgeInsets.only(right: 8.w), + child: TicatsChip.close( + e.keyword, + onTap: () { + ref.watch(searchTextControllerProvider).text = + e.keyword; + ref + .read(searchedEventListControllerProvider + .notifier) + .submit(e.keyword); + }, + iconTap: () { + ref + .read(searchedEventListControllerProvider + .notifier) + .deleteRecentSearchKeyword(e.id); + }, + ), + )) + .toList(), ), - ); - }); + ), + ) + ], + ); } } diff --git a/lib/presentation/search/view/search_result_not_found_view.dart b/lib/presentation/search/view/search_result_not_found_view.dart index 7af358e..40eddea 100644 --- a/lib/presentation/search/view/search_result_not_found_view.dart +++ b/lib/presentation/search/view/search_result_not_found_view.dart @@ -61,15 +61,15 @@ class SearchResultNotFoundView extends BaseView { spacing: 12.w, alignment: WrapAlignment.center, children: state.popularSearchKeywords - .map((e) => TicatsChip(e.title, onTap: () { + .map((e) => TicatsChip(e.keyword, onTap: () { ref .watch(searchTextControllerProvider) - .text = e.title; + .text = e.keyword; ref .read( searchedEventListControllerProvider .notifier) - .submit(e.title); + .submit(e.keyword); })) .toList())), ),