Skip to content

Commit

Permalink
feat : 검색 기능 추가
Browse files Browse the repository at this point in the history
issues : #27
  • Loading branch information
huchujj committed Aug 14, 2024
1 parent 7276fbc commit 4fb10f7
Show file tree
Hide file tree
Showing 12 changed files with 315 additions and 82 deletions.
2 changes: 1 addition & 1 deletion frontend/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
55 changes: 55 additions & 0 deletions frontend/lib/models/search_content_dto.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class SearchContentResponse {
final bool hasNextPage;
final List<SearchContentDto> searchContentDtos;

SearchContentResponse({
required this.hasNextPage,
required this.searchContentDtos,
});

factory SearchContentResponse.fromJson(Map<String, dynamic> json) {
return SearchContentResponse(
hasNextPage: json['hasNextPage'],
searchContentDtos: List<SearchContentDto>.from(
json['searchContentDtos']
.map((item) => SearchContentDto.fromJson(item)),
),
);
}

Map<String, dynamic> toJson() {
return {
'hasNextPage': hasNextPage,
'searchContentDtos':
searchContentDtos.map((item) => item.toJson()).toList(),
};
}
}

class SearchContentDto {
final int contentId;
final String contentTitle;
final String contentImage;

SearchContentDto({
required this.contentId,
required this.contentTitle,
required this.contentImage,
});

factory SearchContentDto.fromJson(Map<String, dynamic> json) {
return SearchContentDto(
contentId: json['contentId'],
contentTitle: json['contentTitle'],
contentImage: json['contentImage'],
);
}

Map<String, dynamic> toJson() {
return {
'contentId': contentId,
'contentTitle': contentTitle,
'contentImage': contentImage,
};
}
}
4 changes: 2 additions & 2 deletions frontend/lib/pages/camera_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'dart:math' as math;
import 'package:path/path.dart' show join;

class CameraPage extends StatefulWidget {
final String imagePath;
final String? imagePath;

const CameraPage({
super.key,
Expand Down Expand Up @@ -80,7 +80,7 @@ class _CameraPageState extends State<CameraPage> {
child: Transform.rotate(
angle: math.pi / 2,
child: Image.asset(
widget.imagePath,
widget.imagePath ?? '',
fit: BoxFit.cover,
),
),
Expand Down
7 changes: 3 additions & 4 deletions frontend/lib/pages/detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ class _DetailPageState extends State<DetailPage> {
itemBuilder: (context, index) {
final place = contentDetail!.placeDtos[index];
return _buildListItem(
index + 1,
place.placeImage,
place.placeName,
place.placeOverview,
Expand All @@ -160,8 +159,8 @@ class _DetailPageState extends State<DetailPage> {
);
}

Widget _buildListItem(int index, String placeImage, String title,
String description, String placeAddress) {
Widget _buildListItem(String placeImage, String title, String description,
String placeAddress) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expand Down Expand Up @@ -192,7 +191,7 @@ class _DetailPageState extends State<DetailPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$index화 / $title',
title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
Expand Down
18 changes: 9 additions & 9 deletions frontend/lib/pages/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class _HomePageState extends State<HomePage> {
Container(
height: 600,
width: double.infinity,
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: AppColors.mainSkyColor,
),
child: Stack(
Expand All @@ -72,27 +72,27 @@ class _HomePageState extends State<HomePage> {
Positioned(
top: 60,
left: 20,
child: Image.asset('assets/icons/logo.png'),
width: 100,
child: Image.asset('assets/icons/logo.png'),
),
Positioned(
const Positioned(
top: 150,
left: 20,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'안녕하세요, 오리님',
style: TextStyle(
color: AppColors.boldFontsColor,
fontSize: 23,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
SizedBox(
height: 20,
),
const Text(
Text(
'오늘의 추천 촬영지는\n바로 여기임',
style: TextStyle(
color: AppColors.thinFontsColor,
Expand All @@ -117,7 +117,7 @@ class _HomePageState extends State<HomePage> {
),
),
Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: AppColors.mainWhiteColor,
),
child: Column(
Expand All @@ -132,8 +132,8 @@ class _HomePageState extends State<HomePage> {
const SizedBox(
height: 30,
),
Row(
children: const [
const Row(
children: [
Padding(
padding: EdgeInsets.only(left: 20),
child: Text(
Expand Down
98 changes: 91 additions & 7 deletions frontend/lib/pages/search_page.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,87 @@
import 'package:flutter/material.dart';
import 'package:frontend/colors/app_colors.dart';
import 'package:frontend/models/search_content_dto.dart';
import 'package:frontend/services/api_service.dart';
import 'package:frontend/widgets/custom_bottom_navigation_bar.dart';
import 'package:frontend/widgets/search_box.dart';
import 'package:frontend/widgets/recent_search_word.dart';
import 'package:frontend/widgets/select_movie_drama_tab.dart';
import 'package:frontend/widgets/selectable_circle.dart';
import 'package:frontend/pages/detail_page.dart'; // DetailPage 임포트

class SearchPage extends StatefulWidget {
const SearchPage({
super.key,
required this.isDrama,
this.searchContentResponse,
this.hint,
});

final bool isDrama;
final String? hint;
final SearchContentResponse? searchContentResponse;

@override
_SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
late bool isDrama;
String? hint;
late List<dynamic> keywords = [];
late List<SelectableCircle> selectableCircles = [];

@override
void initState() {
super.initState();
isDrama = widget.isDrama;
hint = widget.hint;
_fetchRecentSearches();
_generateSelectableCircles();
}

Future<void> _fetchRecentSearches() async {
final recentKeywords = await APIService.getRecentSearch(
hint ?? '', isDrama ? 'Drama' : 'Movie');
if (recentKeywords != null) {
setState(() {
keywords = recentKeywords;
});
}
}

void _generateSelectableCircles() {
if (widget.searchContentResponse != null) {
selectableCircles = widget.searchContentResponse!.searchContentDtos
.map((dto) => SelectableCircle(
contentTitle: dto.contentTitle,
contentImage: dto.contentImage,
onTap: () {
// DetailPage로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DetailPage(contentId: dto.contentId),
),
);
},
))
.toList();
}
}

void _onTabSelected(int index) {
setState(
() {
isDrama = index == 1; // '드라마' 탭이 선택되면 true, '영화' 탭이 선택되면 false
},
);

// 선택된 탭에 따라 필요한 작업을 수행
_fetchRecentSearches();
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand Down Expand Up @@ -47,7 +110,11 @@ class _SearchPageState extends State<SearchPage> {
),
child: Column(
children: [
const SearchBox(isComeFromHome: false, isDrama: true),
SearchBox(
isComeFromHome: false,
isDrama: isDrama,
hint: hint, // hint를 SearchBox에 전달
),
const SizedBox(
height: 5.0,
),
Expand All @@ -65,16 +132,33 @@ class _SearchPageState extends State<SearchPage> {
const SizedBox(
height: 10.0,
),
const Row(
children: [
RecentSearchWord(),
],
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: keywords
.map((keyword) => RecentSearchWord(text: keyword))
.toList(),
),
),
const SizedBox(
height: 30.0,
),
const SelectMovieDramaTab(),
const SelectableCircle(),
SelectMovieDramaTab(onTabSelected: _onTabSelected),
GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 가로로 3개씩 배치
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemCount: selectableCircles.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(), // 전체 페이지 스크롤 사용
itemBuilder: (context, index) {
return selectableCircles[index];
},
),
],
),
),
Expand Down
57 changes: 57 additions & 0 deletions frontend/lib/services/api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:http/http.dart' as http;
import 'dart:async';
import 'package:http_parser/http_parser.dart';
import 'package:frontend/models/content_dto.dart';
import 'package:frontend/models/search_content_dto.dart';

const String domainUrl =
'https://port-0-hackathon-be-lyqylohp8957ca6e.sel5.cloudtype.app';
Expand Down Expand Up @@ -93,4 +94,60 @@ class APIService {
return null;
}
}

static Future<SearchContentResponse?> search(
String keyword, String type) async {
final url =
'$domainUrl/api/content/search?userId=1&keyword=$keyword&searchType=$type&pagesize=9&pageindex=0';
try {
final response = await http.get(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
},
);

if (response.statusCode == 200) {
print("불러오기 성공");
final responseData = json.decode(utf8.decode(response.bodyBytes));
print(responseData);

if (responseData != null && responseData is Map<String, dynamic>) {
return SearchContentResponse.fromJson(responseData);
}
}
return null;
} catch (error) {
print("에러 발생: $error");
return null;
}
}

static Future<List<dynamic>?> getRecentSearch(
String keyword, String type) async {
const url = '$domainUrl/api/content/search/recent/keyword?userId=1';
try {
final response = await http.get(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
},
);

if (response.statusCode == 200) {
print("불러오기 성공");
final responseData = json.decode(utf8.decode(response.bodyBytes));
print(responseData);

if (responseData != null && responseData is Map<String, dynamic>) {
print(responseData['keywords']);
return responseData['keywords'];
}
}
return null;
} catch (error) {
print("에러 발생: $error");
return null;
}
}
}
2 changes: 2 additions & 0 deletions frontend/lib/widgets/custom_bottom_navigation_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class CustomBottomNavigationBar extends StatefulWidget {
class _CustomBottomNavigationBarState extends State<CustomBottomNavigationBar> {
int _selectedIndex = 0;

final List<Widget> _pages = [];

// 탭 클릭시 호출될 함수
void _onItemTapped(int index) {
setState(() {
Expand Down
Loading

0 comments on commit 4fb10f7

Please sign in to comment.