Skip to content

Commit

Permalink
Merge pull request #610 from wakmusic/606-implement-infinity-scroll-o…
Browse files Browse the repository at this point in the history
…n-search

🔀 :: (#606) 검색 결과화면에 인피니티 스크롤을 구현합니다.
  • Loading branch information
yongbeomkwak authored Jun 16, 2024
2 parents 54067f3 + 767ce59 commit 22ab94d
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct SearchPlaylistDTO: Decodable {
userName: String?,
imageUrl: String,
songCount: Int,
createdAt: Int,
createdAt: Double,
`private`: Bool
) {
self.key = key
Expand All @@ -25,7 +25,8 @@ public struct SearchPlaylistDTO: Decodable {
public let key, title, imageUrl: String
public let userName: String?
public let `private`: Bool
public let songCount, createdAt: Int
public let songCount: Int
public let createdAt: Double
}

public extension SearchPlaylistDTO {
Expand All @@ -35,7 +36,7 @@ public extension SearchPlaylistDTO {
title: title,
userName: userName ?? "임시 닉네임",
image: imageUrl,
date: createdAt.changeDateFormat(origin: "yyMMdd", result: "yyyy.MM.dd"),
date: (createdAt / 1000.0).unixTimeToDate.dateToString(format: "yyyy.MM.dd"),
count: songCount,
isPrivate: self.`private`
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ extension ListSearchResultCollectionViewLayout {
heightDimension: .fractionalHeight(1.0)
)

let headerLayout = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(30))

let header = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerLayout,
elementKind: SearchResultHeaderView.kind,
alignment: .top
)

let item: NSCollectionLayoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
Expand All @@ -39,7 +31,6 @@ extension ListSearchResultCollectionViewLayout {

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: .zero, leading: 20.0, bottom: 20.0, trailing: 20.0)
section.boundarySupplementaryItems = [header]

return section
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ extension SongSearchResultCollectionViewLayout {
heightDimension: .fractionalHeight(1.0)
)

let headerLayout = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(30))

let header = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerLayout,
elementKind: SearchResultHeaderView.kind,
alignment: .top
)

let item: NSCollectionLayoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
Expand All @@ -39,7 +31,6 @@ extension SongSearchResultCollectionViewLayout {

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: .zero, leading: 20.0, bottom: 20.0, trailing: 20.0)
section.boundarySupplementaryItems = [header]

return section
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,51 @@ import ReactorKit
import SearchDomainInterface

final class ListSearchResultReactor: Reactor {
#warning("유즈케이스는 추후 연결")
enum Action {
case viewDidLoad
case changeSortType(SortType)
#warning("무한 스크롤을 고려한 스크롤 액션")
case askLoadMore
}

enum Mutation {
case updateSortType(SortType)
case updateDataSource([SearchPlaylistEntity])
case updateDataSource(dataSource: [SearchPlaylistEntity], canLoad: Bool)
case updateLoadingState(Bool)
case updateScrollPage
}

struct State {
var isLoading: Bool
var sortType: SortType
var scrollPage: Int
var dataSource: [SearchPlaylistEntity]
var canLoad: Bool
}

var initialState: State
private let text: String
private let fetchSearchPlaylistsUseCase: any FetchSearchPlaylistsUseCase
private let limit: Int = 20

init(text: String, fetchSearchPlaylistsUseCase: any FetchSearchPlaylistsUseCase) {
self.initialState = State(
isLoading: false,
sortType: .latest,
scrollPage: 1,
dataSource: []
dataSource: [],
canLoad: true
)

self.text = text
self.fetchSearchPlaylistsUseCase = fetchSearchPlaylistsUseCase
}

func mutate(action: Action) -> Observable<Mutation> {
let state = self.currentState

switch action {
case .viewDidLoad:
return updateDataSource(order: .latest, text: self.text, scrollPage: 1)
case .viewDidLoad, .askLoadMore:
return updateDataSource(order: state.sortType, text: self.text, scrollPage: state.scrollPage)
case let .changeSortType(type):
return updateSortType(type)
}
Expand All @@ -53,10 +58,13 @@ final class ListSearchResultReactor: Reactor {
switch mutation {
case let .updateSortType(type):
newState.sortType = type
case let .updateDataSource(dataSource):
newState.dataSource = dataSource
case let .updateDataSource(dataSource, canLoad):
newState.dataSource += dataSource
newState.canLoad = canLoad
case let .updateLoadingState(isLoading):
newState.isLoading = isLoading
case .updateScrollPage:
newState.scrollPage += 1
}

return newState
Expand All @@ -76,11 +84,12 @@ extension ListSearchResultReactor {
return .concat([
.just(Mutation.updateLoadingState(true)),
fetchSearchPlaylistsUseCase
.execute(order: order, text: text, page: scrollPage, limit: 20)
.execute(order: order, text: text, page: scrollPage, limit: limit)
.asObservable()
.map { dataSource -> Mutation in
return Mutation.updateDataSource(dataSource)
.map { [limit] dataSource -> Mutation in
return Mutation.updateDataSource(dataSource: dataSource, canLoad: dataSource.count == limit)
},
.just(Mutation.updateScrollPage),
.just(Mutation.updateLoadingState(false))
])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import SearchDomainInterface
import SongsDomainInterface

final class SongSearchResultReactor: Reactor {
#warning("유즈케이스는 추후 연결")
enum Action {
case viewDidLoad
case changeSortType(SortType)
case changeFilterType(FilterType)
#warning("무한 스크롤을 고려한 스크롤 액션")
case askLoadMore
}

enum Mutation {
case updateSortType(SortType)
case updateFilterType(FilterType)
case updateDataSource([SongEntity])
case updateDataSource(dataSource: [SongEntity], canLoad: Bool)
case updateSelectedCount(Int)
case updateLoadingState(Bool)
case updateScrollPage
}

struct State {
Expand All @@ -27,23 +27,24 @@ final class SongSearchResultReactor: Reactor {
var selectedCount: Int
var scrollPage: Int
var dataSource: [SongEntity]
var canLoad: Bool
}

var initialState: State

private let fetchSearchSongsUseCase: any FetchSearchSongsUseCase
private let text: String
private let limit: Int = 20

init(text: String, fetchSearchSongsUseCase: any FetchSearchSongsUseCase) {
LogManager.printDebug("\(Self.self) init with Text :\(text)")

self.initialState = State(
isLoading: false,
isLoading: true,
sortType: .latest,
filterType: .all,
selectedCount: 0,
scrollPage: 1,
dataSource: []
dataSource: [],
canLoad: true
)

self.text = text
Expand All @@ -54,8 +55,13 @@ final class SongSearchResultReactor: Reactor {
let state = self.currentState

switch action {
case .viewDidLoad:
return updateDataSource(order: state.sortType, filter: state.filterType, text: self.text, scrollPage: 1)
case .viewDidLoad, .askLoadMore:
return updateDataSource(
order: state.sortType,
filter: state.filterType,
text: self.text,
scrollPage: state.scrollPage
)
case let .changeSortType(type):
return updateSortType(type)
case let .changeFilterType(type):
Expand All @@ -71,14 +77,18 @@ final class SongSearchResultReactor: Reactor {
newState.sortType = type
case let .updateFilterType(type):
newState.filterType = type
case let .updateDataSource(dataSource):
newState.dataSource = dataSource
case let .updateDataSource(dataSource, canLoad):
newState.dataSource += dataSource
newState.canLoad = canLoad

case let .updateSelectedCount(count):
break

case let .updateLoadingState(isLoading):
newState.isLoading = isLoading

case .updateScrollPage:
newState.scrollPage += 1
}

return newState
Expand All @@ -101,14 +111,15 @@ extension SongSearchResultReactor {
scrollPage: Int
) -> Observable<Mutation> {
return .concat([
.just(Mutation.updateLoadingState(true)),
.just(Mutation.updateLoadingState(true)), // 로딩
fetchSearchSongsUseCase
.execute(order: order, filter: filter, text: text, page: scrollPage, limit: 20)
.execute(order: order, filter: filter, text: text, page: scrollPage, limit: limit)
.asObservable()
.map { dataSource -> Mutation in
return Mutation.updateDataSource(dataSource)
.map { [limit] dataSource -> Mutation in
return Mutation.updateDataSource(dataSource: dataSource, canLoad: dataSource.count == limit)
},
.just(Mutation.updateLoadingState(false))
.just(Mutation.updateScrollPage), // 스크롤 페이지 증가
.just(Mutation.updateLoadingState(false)) // 로딩 종료
])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ protocol SearchResultHeaderViewDelegate: AnyObject {
}

final class SearchResultHeaderView:
UICollectionReusableView {
UIView {
static let kind = "search-result-section-header"

weak var delegate: SearchResultHeaderViewDelegate?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ extension SongResultCell {
}

titleLabel.snp.makeConstraints {
$0.top.equalTo(thumbnailView.snp.top).offset(-1)
$0.top.equalTo(thumbnailView.snp.top)
$0.leading.equalTo(thumbnailView.snp.trailing).offset(8)
}

Expand Down
Loading

0 comments on commit 22ab94d

Please sign in to comment.