Skip to content

Commit

Permalink
feat: 검색 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
lina0322 committed Oct 4, 2024
1 parent b746404 commit f6feeed
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 78 deletions.
18 changes: 9 additions & 9 deletions Projects/Core/PPACData/Sources/DTO/MemeResponseDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,31 @@ extension MemeWithPaginationResponseDTO {
struct MemeResponseDTO: Decodable {
let _id: String
let title: String
let keywords: [KeywordResponseDTO]
let keywords: [KeywordResponseDTO]?
let image: String
let reaction: Int
let source: String
let isTodayMeme: Bool
let isDeleted: Bool?
let createdAt: String?
let updatedAt: String
let isSaved: Bool
let isReaction: Bool
let isSaved: Bool?
let isReaction: Bool?
let watch: Int?

public init(
_id: String,
title: String,
keywords: [KeywordResponseDTO],
keywords: [KeywordResponseDTO]?,
image: String,
reaction: Int,
source: String,
isTodayMeme: Bool,
isDeleted: Bool?,
createdAt: String?,
updatedAt: String,
isSaved: Bool,
isReaction: Bool,
isSaved: Bool?,
isReaction: Bool?,
watch: Int?
)
{
Expand Down Expand Up @@ -108,13 +108,13 @@ extension MemeResponseDTO {
return MemeDetail(
id: self._id,
title: self.title,
keywords: self.keywords.map { $0.name },
keywords: self.keywords?.compactMap { $0.name } ?? [],
imageUrlString: self.image,
source: self.source,
isTodayMeme: self.isTodayMeme,
reaction: self.reaction,
isFarmemed: self.isSaved,
isReaction: self.isReaction
isFarmemed: self.isSaved ?? false,
isReaction: self.isReaction ?? false
)
}
}
12 changes: 12 additions & 0 deletions Projects/Core/PPACData/Sources/Endpoint/MemeEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import PPACUtil
public enum MemeEndpoint: Requestable {
case recommendMeme(size: Int)
case getSearchKeywordMemeList(page: Int, size: Int, keyword: String)
case getSearchByTextMemeList(page: Int, size: Int, text: String)
case meme(memeId: String)
case bookmark(memeId: String)
case deleteBookmark(memeId: String)
Expand All @@ -26,6 +27,8 @@ public enum MemeEndpoint: Requestable {
return .get
case .getSearchKeywordMemeList:
return .get
case .getSearchByTextMemeList:
return .get
case .meme:
return .get
case .bookmark:
Expand All @@ -51,6 +54,8 @@ public enum MemeEndpoint: Requestable {
return "/meme/recommend-memes"
case .getSearchKeywordMemeList(_,_,let keyword):
return "/meme/search/\(keyword)"
case .getSearchByTextMemeList(_,_,let text):
return "/meme/search"
case .meme(let memeId):
return "/meme/\(memeId)"
case .bookmark(let memeId):
Expand All @@ -77,6 +82,13 @@ public enum MemeEndpoint: Requestable {
"keyword" : "\(keyword)"
]
return .query(parameters)
case .getSearchByTextMemeList(let page, let size, let text):
let parameters: [String: String] = [
"q": "\(text)",
"page" : "\(page)",
"size" : "\(size)",
]
return .query(parameters)
case .bookmark:
return nil
case .deleteBookmark:
Expand Down
17 changes: 17 additions & 0 deletions Projects/Core/PPACData/Sources/Repository/MemeRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ public class MemeRepositoryImpl: MemeRepository {
}
}

public func getSearchByTextMemeList(
page: Int,
size: Int,
text: String
) async throws -> MemeListWithPagination {
let endpoint = MemeEndpoint.getSearchByTextMemeList(page: page, size: size, text: text)
let result = await networkservice.request(endpoint, dataType: BaseDTO<MemeWithPaginationResponseDTO>.self)

switch result {
case .success(let data):
guard let memeWithPaginationResponseDTO = data.data else { throw NetworkError.dataDecodingError }
return memeWithPaginationResponseDTO.toModel()
case .failure(let error):
throw error
}
}

public func getMemeDetail(memeId: String) async throws -> MemeDetail {
let endpoint = MemeEndpoint.meme(memeId: memeId)
let result = await networkservice.request(endpoint, dataType: BaseDTO<MemeResponseDTO>.self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public protocol MemeRepository {

func getRecommendMemes(size: Int) async throws -> [MemeDetail]
func getSearchKeywordMemeList(page: Int, size: Int, keyword: String) async throws -> MemeListWithPagination
func getSearchByTextMemeList(page: Int, size: Int, text: String) async throws -> MemeListWithPagination
func getMemeDetail(memeId: String) async throws -> MemeDetail
func bookmarkMeme(memeId: String) async throws
func deleteBookmarkMeme(memeId: String) async throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,19 @@ public class SearchKeywordUseCaseImpl: SearchKeywordUseCase {
try await repository.getSearchKeywordMemeList(page: page, size: size, keyword: keyword)
}
}

public protocol SearchByTextUseCase {
func execute(page: Int, size: Int, text: String) async throws -> MemeListWithPagination
}

public class SearchByTextUseCaseImpl: SearchByTextUseCase {
private let repository: MemeRepository

public init(repository: MemeRepository) {
self.repository = repository
}

public func execute(page: Int, size: Int, text: String) async throws -> MemeListWithPagination {
try await repository.getSearchByTextMemeList(page: page, size: size, text: text)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct MemeListWithPagination {
/// page 당 밈 개수
public let perPageOfMemes: Int
/// 현재 page
public let currentPage: Int
public var currentPage: Int

public init(
totalPages: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ public final class SearchResultRouter: Router, SearchResultRouting {
public var childRouters: [any Router] = []

let keyword: String

let text: String

// MARK: - Initializers

public init(_ navigationController: UINavigationController, keyword: String) {
public init(_ navigationController: UINavigationController, keyword: String, text: String) {
navigationController.isNavigationBarHidden = true
self.navigationController = navigationController
self.keyword = keyword
self.text = text
}

// MARK: - Methods
Expand All @@ -41,8 +43,10 @@ public final class SearchResultRouter: Router, SearchResultRouting {
self.pushView(
SearchResultView(viewModel: SearchResultViewModel(
keyword: keyword,
router: self,
text: text,
router: self,
searchKeywordUseCase: SearchKeywordUseCaseImpl(repository: repository),
searchByTextUseCase: SearchByTextUseCaseImpl(repository: repository),
copyImageUseCase: CopyImageUseCaseImpl(),
watchMemeUseCase: WatchMemeUseCaseImpl(repository: repository)
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,34 @@ import PopupView

public struct SearchResultView: View {
@ObservedObject var viewModel: SearchResultViewModel
@State var text: String

public init(viewModel: SearchResultViewModel) {
self.viewModel = viewModel
self.text = viewModel.state.text
}

public var body: some View {
VStack(spacing: 0) {
HStack(spacing: 12) {
Button(action: {
viewModel.dispatch(type: .naviBackButtonTapped)
}) {
ResourceKitAsset.Icon.back.swiftUIImage
}

SearchBar(text: $text)
.onSubmit {
viewModel.dispatch(type: .search(text: text))
}
}
.padding(.horizontal, 20)
.padding(.vertical, 6)

Rectangle()
.fill(Color.Background.assistive)
.frame(maxWidth: .infinity)
.frame(height: 1)
.padding(.top, 51)

ScrollView {
VStack(alignment: .leading, spacing: 0) {
Expand All @@ -38,12 +54,6 @@ public struct SearchResultView: View {
}
}
}
.plainNavigationBar(
backHandler: { viewModel.dispatch(type: .naviBackButtonTapped) },
rightActionHandler: nil,
hasConfigureButton: false,
title: viewModel.state.keyword
)
.onAppear {
viewModel.dispatch(type: .viewWillAppear)
}
Expand All @@ -56,7 +66,7 @@ public struct SearchResultView: View {

private var memeListView: some View {
VStack(alignment: .leading, spacing: 0) {
Text("\(viewModel.state.memeList.count)개의 밈을 찾았어요")
Text("\(viewModel.state.memePagination.totalMemes)개의 밈을 찾았어요")
.font(Font.Body.Medium.medium)
.foregroundColor(Color.Text.primary)
.padding(.all, 20)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public final class SearchResultViewModel: ViewModelType, ObservableObject {

public enum Action {
case viewWillAppear
case search(text: String)
case memeDetailTapped(meme: MemeDetail)
case memeCopyTapped(meme: MemeDetail)
case naviBackButtonTapped
Expand All @@ -35,6 +36,7 @@ public final class SearchResultViewModel: ViewModelType, ObservableObject {

public struct State {
var keyword: String
var text: String
var memeList: [MemeDetail]
var memePagination: MemeListWithPagination.Pagination
var isActiveCopyPopup: Bool = false
Expand All @@ -47,21 +49,30 @@ public final class SearchResultViewModel: ViewModelType, ObservableObject {
@Published public var state: State

private let searchKeywordUseCase: SearchKeywordUseCase
private let searchByTextUseCase: SearchByTextUseCase
private let copyImageUseCase: CopyImageUseCase
private let watchMemeUseCase: WatchMemeUseCase

// MARK: - Initializers

public init(
keyword: String,
text: String,
router: SearchResultRouting?,
searchKeywordUseCase: SearchKeywordUseCase,
searchByTextUseCase: SearchByTextUseCase,
copyImageUseCase: CopyImageUseCase,
watchMemeUseCase: WatchMemeUseCase
) {
self.router = router
self.state = State(keyword: keyword, memeList: [], memePagination: .default)
self.state = State(
keyword: keyword,
text: text,
memeList: [],
memePagination: .default
)
self.searchKeywordUseCase = searchKeywordUseCase
self.searchByTextUseCase = searchByTextUseCase
self.copyImageUseCase = copyImageUseCase
self.watchMemeUseCase = watchMemeUseCase
}
Expand All @@ -74,6 +85,8 @@ public final class SearchResultViewModel: ViewModelType, ObservableObject {
switch type {
case .viewWillAppear:
await fetchData()
case .search(text: let text):
await fetchData(with: text)
case .memeDetailTapped(let meme):
router?.showMemeDetail(memeDetail: meme)
logSearch(event: .meme, keyword: state.keyword)
Expand All @@ -90,21 +103,45 @@ public final class SearchResultViewModel: ViewModelType, ObservableObject {
}

@MainActor
private func fetchData() async {
private func fetchData(with newText: String = "") async {
do {
guard state.memePagination.currentPage < state.memePagination.totalPages else { return }
if newText.isEmpty == false {
state.text = newText
state.keyword = ""
state.memeList = []
state.memePagination.currentPage = -1
}

guard state.memePagination.currentPage < state.memePagination.totalPages
else {
return
}
state.isLoading = true

let result = try await searchKeywordUseCase
.execute(
page: state.memePagination.currentPage + 1,
size: state.memePagination.perPageOfMemes,
keyword: state.keyword
)
if state.text.isEmpty == false {
let result = try await searchByTextUseCase
.execute(
page: state.memePagination.currentPage + 1,
size: state.memePagination.perPageOfMemes,
text: state.text
)

state.memeList += result.memeList
state.memePagination = result.pagination
state.isLoading = false

state.memeList += result.memeList
state.memePagination = result.pagination
state.isLoading = false
} else if state.keyword.isEmpty == false {
let result = try await searchKeywordUseCase
.execute(
page: state.memePagination.currentPage + 1,
size: state.memePagination.perPageOfMemes,
keyword: state.keyword
)

state.memeList += result.memeList
state.memePagination = result.pagination
state.isLoading = false
}

self.logSearch(
interaction: .scroll,
Expand Down
Loading

0 comments on commit f6feeed

Please sign in to comment.