Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions WSSiOS/Network/Search/SearchService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,59 @@ protocol SearchService {
keywordIds: [Int],
page: Int,
size: Int) -> Single<DetailSearchNovels>
func getRecentSearches() -> Single<[RecentSearch]>
func deleteRecentSearch(id: Int) -> Single<Void>
func deleteAllRecentSearches() -> Single<Void>
}

final class DefaultSearchService: NSObject, Networking { }

extension DefaultSearchService: SearchService {
func getRecentSearches() -> Single<[RecentSearch]> {
do {
let request = try makeHTTPRequest(method: .get,
path: URLs.Search.recentSearch,
headers: APIConstants.accessTokenHeader,
body: nil)
NetworkLogger.log(request: request)
return tokenCheckURLSession.rx.data(request: request)
.map { try self.decode(data: $0, to: RecentSearches.self).recentSearches }
.asSingle()
} catch {
return Single.error(error)
}
}

func deleteRecentSearch(id: Int) -> Single<Void> {
do {
let request = try makeHTTPRequest(method: .delete,
path: URLs.Search.deleteRecentSearchKeyword(id: id),
headers: APIConstants.accessTokenHeader,
body: nil)
NetworkLogger.log(request: request)
return tokenCheckURLSession.rx.data(request: request)
.map { _ in }
.asSingle()
} catch {
return Single.error(error)
}
}

func deleteAllRecentSearches() -> Single<Void> {
do {
let request = try makeHTTPRequest(method: .delete,
path: URLs.Search.deleteAllRecentSearchKeywords,
headers: APIConstants.accessTokenHeader,
body: nil)
NetworkLogger.log(request: request)
return tokenCheckURLSession.rx.data(request: request)
.map { _ in }
.asSingle()
} catch {
return Single.error(error)
}
}

func getSosopicks() -> Single<SosoPickNovels> {
do {
let request = try makeHTTPRequest(method: .get,
Expand Down
3 changes: 3 additions & 0 deletions WSSiOS/Resource/Constants/Strings/StringLiterals+Search.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ extension StringLiterals {
enum Search {
static let title = "탐색하기"
static let searchbar = "작품 제목, 작가를 검색하세요"

static let recentSearchTitle = "최근 검색어"
static let deleteAll = "전체삭제"

static let induceTitle = "뭐 읽을지 고민될 땐?"
static let induceDescription = "장르, 연재상태, 별점, 키워드로 작품 찾기"
Expand Down
5 changes: 5 additions & 0 deletions WSSiOS/Resource/Constants/URLs/URLs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ enum URLs {
static let sosoPick = "/soso-picks"
static let normalSearch = "/novels"
static let detailSearch = "/novels/filtered"
static let recentSearch = "/novels/recent-searches"
static func deleteRecentSearchKeyword(id: Int) -> String {
return "/novels/recent-searches/\(id)"
}
static let deleteAllRecentSearchKeywords = "/novels/recent-searches"
}

enum Keyword {
Expand Down
12 changes: 11 additions & 1 deletion WSSiOS/Source/Data/DTO/SearchResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,20 @@ struct SearchNovel: Codable {
var interestCount: Int
var novelRating: Float
var novelRatingCount: Int

enum CodingKeys: String, CodingKey {
case novelId, novelImage, interestCount, novelRating, novelRatingCount
case novelTitle = "title"
case novelAuthor = "author"
}
}

/// 최근 검색어 조회 API
struct RecentSearches: Codable {
let recentSearches: [RecentSearch]
}

struct RecentSearch: Codable {
let id: Int
let keyword: String
}
15 changes: 15 additions & 0 deletions WSSiOS/Source/Data/Repository/SearchRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ protocol SearchRepository {
upperNovelRating: Float,
keywordIds: [Int],
page: Int) -> Observable<DetailSearchNovels>
func getRecentSearches() -> Observable<[RecentSearch]>
func deleteRecentSearch(id: Int) -> Observable<Void>
func deleteAllRecentSearches() -> Observable<Void>
}

struct DefaultSearchRepository: SearchRepository {
Expand Down Expand Up @@ -52,4 +55,16 @@ struct DefaultSearchRepository: SearchRepository {
page: page,
size: searchSize).asObservable()
}

func getRecentSearches() -> Observable<[RecentSearch]> {
return searchService.getRecentSearches().asObservable()
}

func deleteRecentSearch(id: Int) -> Observable<Void> {
return searchService.deleteRecentSearch(id: id).asObservable()
}

func deleteAllRecentSearches() -> Observable<Void> {
return searchService.deleteAllRecentSearches().asObservable()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// NormalSearchRecentView.swift
// WSSiOS
//
// Created by onesunny2 on 5/6/25.
//

import UIKit

import SnapKit
import Then

final class NormalSearchRecentView: UIView {

//MARK: - Components

private let titleLabel = UILabel()
let deleteAllButton = UIButton()
let recentTagCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout())

//MARK: - Life Cycle

override init(frame: CGRect) {
super.init(frame: frame)

setUI()
setHierarchy()
setLayout()
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

//MARK: - UI

private func setUI() {
titleLabel.do {
$0.applyWSSFont(.title2, with: StringLiterals.Search.recentSearchTitle)
$0.textColor = .wssBlack
}

deleteAllButton.do {
$0.setTitle(StringLiterals.Search.deleteAll, for: .normal)
$0.setTitleColor(.wssGray200, for: .normal)
$0.titleLabel?.font = .Body4
}

recentTagCollectionView.do {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 8
layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)

$0.collectionViewLayout = layout
$0.isScrollEnabled = true
$0.showsHorizontalScrollIndicator = false
$0.backgroundColor = .clear
}
}

private func setHierarchy() {
self.addSubviews(titleLabel, deleteAllButton, recentTagCollectionView)
}

private func setLayout() {
titleLabel.snp.makeConstraints {
$0.top.equalToSuperview().inset(8)
$0.leading.equalToSuperview().inset(20)
}

deleteAllButton.snp.makeConstraints {
$0.centerY.equalTo(titleLabel)
$0.trailing.equalToSuperview().inset(20)
}

recentTagCollectionView.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(12)
$0.leading.trailing.equalToSuperview()
$0.height.equalTo(35)
$0.bottom.equalToSuperview().inset(12)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// NormalSearchRecentTagCell.swift
// WSSiOS
//
// Created by onesunny2 on 5/6/25.
//

import UIKit

import RxSwift
import SnapKit
import Then

final class NormalSearchRecentTagCell: UICollectionViewCell {

//MARK: - Properties

var deleteAction: (() -> Void)?
private var disposeBag = DisposeBag()

//MARK: - Components

private let keywordLabel = UILabel()
private let deleteButton = UIButton()
private let contentStackView = UIStackView()

//MARK: - Life Cycle

override init(frame: CGRect) {
super.init(frame: frame)

setUI()
setHierarchy()
setLayout()
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func prepareForReuse() {
super.prepareForReuse()
deleteAction = nil
disposeBag = DisposeBag()
}

//MARK: - UI

private func setUI() {
self.contentView.do {
$0.layer.cornerRadius = 17.5
$0.layer.borderWidth = 1
$0.layer.borderColor = UIColor.wssPrimary100.cgColor
$0.backgroundColor = .wssWhite
}

contentStackView.do {
$0.axis = .horizontal
$0.spacing = 6
$0.alignment = .center
}

keywordLabel.do {
$0.textColor = .wssPrimary100
}

deleteButton.do {
$0.setImage(.icKeywordCancel, for: .normal)
}
}

private func setHierarchy() {
contentView.addSubview(contentStackView)
contentStackView.addArrangedSubviews(keywordLabel, deleteButton)
}

private func setLayout() {
contentStackView.snp.makeConstraints {
$0.horizontalEdges.equalToSuperview().inset(13)
$0.centerY.equalToSuperview()
}

deleteButton.snp.makeConstraints {
$0.size.equalTo(16)
}
}

//MARK: - Data

func bindData(keyword: String) {
keywordLabel.applyWSSFont(.body2, with: keyword)

deleteButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.deleteAction?()
})
.disposed(by: disposeBag)
}
}
Loading