Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 필터 뷰 구현 #97

Merged
merged 3 commits into from
Mar 19, 2023
Merged
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
4 changes: 4 additions & 0 deletions Favor/Favor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
2E7C30B7295D8E1000BAC43D /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2E7C30B6295D8E1000BAC43D /* SnapKit */; };
2E8C663F29BB95C400006CBF /* SignUpVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EE0D58029751EBD0064E932 /* SignUpVC.swift */; };
2E8C664029BB95DB00006CBF /* FindPasswordViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ED326BC29B2F78B001963D0 /* FindPasswordViewReactor.swift */; };
2E9F988F29C4B2A200789297 /* FilterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9F988E29C4B2A200789297 /* FilterVC.swift */; };
2EA38492298D7C5F0081499C /* UpcomingCellReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA3848F298D7C5F0081499C /* UpcomingCellReactor.swift */; };
2EA38493298D7C5F0081499C /* TimelineCellReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA38490298D7C5F0081499C /* TimelineCellReactor.swift */; };
2EA38494298D7C5F0081499C /* HeaderViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA38491298D7C5F0081499C /* HeaderViewReactor.swift */; };
Expand Down Expand Up @@ -152,6 +153,7 @@
2E7C30A4295D7C4500BAC43D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2E7C30A7295D7C4500BAC43D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
2E7C30A9295D7C4500BAC43D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2E9F988E29C4B2A200789297 /* FilterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterVC.swift; sourceTree = "<group>"; };
2EA3848F298D7C5F0081499C /* UpcomingCellReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpcomingCellReactor.swift; sourceTree = "<group>"; };
2EA38490298D7C5F0081499C /* TimelineCellReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineCellReactor.swift; sourceTree = "<group>"; };
2EA38491298D7C5F0081499C /* HeaderViewReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderViewReactor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -497,6 +499,7 @@
isa = PBXGroup;
children = (
2EA3849F298D7C960081499C /* HomeVC.swift */,
2E9F988E29C4B2A200789297 /* FilterVC.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -950,6 +953,7 @@
2EA384B6299234770081499C /* SearchVC.swift in Sources */,
E5CFF72B298404F700E11D9A /* AppStep.swift in Sources */,
2EC371FA2995262D00D1007B /* SearchResultVC.swift in Sources */,
2E9F988F29C4B2A200789297 /* FilterVC.swift in Sources */,
2EC6E11D29BC65BF006FBC11 /* HomeFlow.swift in Sources */,
2ED22DAF296FF4A900ED6221 /* SignInVC.swift in Sources */,
2EA3849C298D7C680081499C /* UpcomingCell.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion Favor/Favor/Sources/Flows/AppStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ enum AppStep: Step {

// MARK: - Home
case homeIsRequired
case filterIsRequired
case filterIsRequired(SortType)
case filterIsComplete(SortType)

// MARK: - Search
case searchIsRequired
Expand Down
29 changes: 25 additions & 4 deletions Favor/Favor/Sources/Flows/HomeFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import UIKit

import FavorKit
import RxCocoa
import RxFlow
import RxSwift

final class HomeFlow: Flow {

Expand All @@ -23,8 +25,11 @@ final class HomeFlow: Flow {
case .homeIsRequired:
return self.navigateToHome()

case .filterIsRequired:
return self.navigateToFilter()
case .filterIsRequired(let sortType):
return self.navigateToFilter(sortedBy: sortType)

case .filterIsComplete(let sortType):
return self.dismissFilter(sortedBy: sortType)

default: return .none
}
Expand All @@ -45,10 +50,26 @@ private extension HomeFlow {
))
}

func navigateToFilter() -> FlowContributors {
let filterVC = BaseBottomSheetViewController()
func navigateToFilter(sortedBy sortType: SortType) -> FlowContributors {
let filterVC = FilterViewController()
filterVC.currentSortType = sortType
self.rootViewController.present(filterVC, animated: true)

return .one(
flowContributor: .contribute(
withNextPresentable: filterVC,
withNextStepper: filterVC
))
}

func dismissFilter(sortedBy sortType: SortType) -> FlowContributors {
self.rootViewController.topViewController?.dismiss(animated: true) {
guard let homeVC = self.rootViewController.topViewController as? HomeViewController else {
return
}
// TODO: Realm DB 구현하며 Sort, Filter 방식 변경
homeVC.reactor?.currentSortType.accept(sortType)
}
return .none
}
}
37 changes: 25 additions & 12 deletions Favor/Favor/Sources/Scenes/Home/Reactors/HomeViewReactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import OSLog
import UIKit

import FavorKit
import ReactorKit
import RxCocoa
import RxFlow
Expand All @@ -18,6 +19,9 @@ final class HomeViewReactor: Reactor, Stepper {

var initialState: State
var steps = PublishRelay<Step>()

// Global State
let currentSortType = BehaviorRelay<SortType>(value: .latest)

enum Action {
case viewDidLoad
Expand All @@ -28,11 +32,13 @@ final class HomeViewReactor: Reactor, Stepper {
}

enum Mutation {
case setUpcoming(HomeSection.HomeSectionModel)
case setTimeline(HomeSection.HomeSectionModel)
case popNewToast(String)
case updateUpcoming(HomeSection.HomeSectionModel)
case updateTimeline(HomeSection.HomeSectionModel)
}

struct State {
@Pulse var toastMessage: String?
var upcomingSection = HomeSection.HomeSectionModel(
model: .upcoming,
items: []
Expand All @@ -41,12 +47,15 @@ final class HomeViewReactor: Reactor, Stepper {
model: .timeline,
items: []
)
var currentSortType: SortType
}

// MARK: - Initializer

init() {
self.initialState = State()
self.initialState = State(
currentSortType: self.currentSortType.value
)
}

// MARK: - Functions
Expand All @@ -55,8 +64,8 @@ final class HomeViewReactor: Reactor, Stepper {
switch action {
case .viewDidLoad:
return .concat([
.just(.setUpcoming(self.fetchUpcoming(self.getUpcomingMock()))),
.just(.setTimeline(self.fetchTimeline(self.getTimelineMock())))
.just(.updateUpcoming(self.fetchUpcoming())),
.just(.updateTimeline(self.fetchTimeline()))
])

case .searchButtonDidTap:
Expand All @@ -74,7 +83,7 @@ final class HomeViewReactor: Reactor, Stepper {
switch sectionType {
case .upcoming: break
case .timeline:
self.steps.accept(AppStep.filterIsRequired)
self.steps.accept(AppStep.filterIsRequired(self.currentSortType.value))
}
return .empty()
}
Expand All @@ -88,9 +97,13 @@ final class HomeViewReactor: Reactor, Stepper {
var newState = state

switch mutation {
case .setUpcoming(let model):
case .popNewToast(let message):
newState.toastMessage = message

case .updateUpcoming(let model):
newState.upcomingSection = model
case .setTimeline(let model):

case .updateTimeline(let model):
newState.timelineSection = model
}

Expand All @@ -116,8 +129,8 @@ final class HomeViewReactor: Reactor, Stepper {
}

private extension HomeViewReactor {
func fetchUpcoming(_ data: [CardCellData]) -> HomeSection.HomeSectionModel {
let upcomingItems = data.map {
func fetchUpcoming() -> HomeSection.HomeSectionModel {
let upcomingItems = self.getUpcomingMock().map {
let reactor = UpcomingCellReactor(cellData: $0)
return HomeSection.HomeSectionItem.upcoming(reactor)
}
Expand All @@ -127,8 +140,8 @@ private extension HomeViewReactor {
)
}

func fetchTimeline(_ data: [TimelineCellData]) -> HomeSection.HomeSectionModel {
let timelineItems = data.map {
func fetchTimeline() -> HomeSection.HomeSectionModel {
let timelineItems = getTimelineMock().map {
let reactor = TimelineCellReactor(cellData: $0)
return HomeSection.HomeSectionItem.timeline(reactor)
}
Expand Down
139 changes: 139 additions & 0 deletions Favor/Favor/Sources/Scenes/Home/ViewControllers/FilterVC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// FilterVC.swift
// Favor
//
// Created by 이창준 on 2023/03/17.
//

import UIKit

import FavorKit
import RxCocoa
import RxFlow
import SnapKit

final class FilterViewController: BaseBottomSheetViewController, Stepper {

// MARK: - Constants

// MARK: - Properties

public var currentSortType: SortType = .latest {
didSet { self.updateButton() }
}

// MARK: - UI Components

var steps = PublishRelay<Step>()

private lazy var sortLabel: UILabel = {
let label = UILabel()
label.font = .favorFont(.bold, size: 18)
label.text = "정렬 기준"
return label
}()

private lazy var latestButton = self.makeSelectionButton(title: "최신순")
private lazy var oldestButton = self.makeSelectionButton(title: "과거순")

private lazy var buttons: [UIButton] = []

private lazy var buttonStack: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 24
stackView.alignment = .leading
return stackView
}()

// MARK: - Life Cycle

override func viewDidLoad() {
super.viewDidLoad()

self.updateTitle("필터")
self.buttons = [latestButton, oldestButton]
self.updateButton()
}

// MARK: - UI Setup

override func setupLayouts() {
super.setupLayouts()

[
self.latestButton,
self.oldestButton
].forEach {
self.buttonStack.addArrangedSubview($0)
}

[
self.sortLabel,
self.buttonStack
].forEach {
self.view.addSubview($0)
}
}

override func setupConstraints() {
super.setupConstraints()

self.sortLabel.snp.makeConstraints { make in
make.top.equalTo(self.topMenuContainerView.snp.bottom).offset(40)
make.directionalHorizontalEdges.equalTo(self.view.layoutMarginsGuide)
}

self.buttonStack.snp.makeConstraints { make in
make.top.equalTo(self.sortLabel.snp.bottom).offset(32)
make.directionalHorizontalEdges.equalTo(self.view.layoutMarginsGuide)
}
}

override func bind() {
self.latestButton.rx.tap
.asDriver(onErrorRecover: { _ in return .never()})
.drive(with: self, onNext: { owner, _ in
owner.currentSortType = .latest
owner.steps.accept(AppStep.filterIsComplete(.latest))
})
.disposed(by: self.disposeBag)

self.oldestButton.rx.tap
.asDriver(onErrorRecover: { _ in return .never()})
.drive(with: self, onNext: { owner, _ in
owner.currentSortType = .oldest
owner.steps.accept(AppStep.filterIsComplete(.oldest))
})
.disposed(by: self.disposeBag)
}
}

private extension FilterViewController {
func makeSelectionButton(title: String) -> UIButton {
var config = UIButton.Configuration.plain()
var container = AttributeContainer()
container.font = .favorFont(.regular, size: 16)
container.foregroundColor = .favorColor(.titleAndLine)
config.attributedTitle = AttributedString(title, attributes: container)
config.image = .favorIcon(.select)?.withTintColor(.favorColor(.icon))
config.imagePlacement = .leading
config.imagePadding = 20
config.baseBackgroundColor = .clear

let button = UIButton(configuration: config)
button.configurationUpdateHandler = { button in
var config = button.configuration
let image: UIImage? = button.isSelected ? .favorIcon(.select) : .favorIcon(.deselect)
config?.image = image?.withTintColor(.favorColor(.icon))
button.configuration = config
}
return button
}

func updateButton() {
self.buttons.enumerated().forEach { index, button in
button.isSelected = self.currentSortType.rawValue == index ? true : false
}
}
}
14 changes: 8 additions & 6 deletions Favor/Favor/Sources/Scenes/Home/ViewControllers/HomeVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ final class HomeViewController: BaseViewController, View {
.disposed(by: self.disposeBag)

self.searchButton.rx.tap
.do(onNext: { // TODO: 토스트 메시지 테스트용 코드 삭제
self.presentToast("🍞 토스트 메시지 🍞", duration: .short)
})
.map { Reactor.Action.searchButtonDidTap }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
Expand All @@ -142,6 +139,14 @@ final class HomeViewController: BaseViewController, View {
.disposed(by: self.disposeBag)

// State
reactor.state.map { $0.toastMessage }
.compactMap { $0 }
.asDriver(onErrorRecover: { _ in return .never()})
.drive(with: self, onNext: { owner, message in
owner.presentToast(message, duration: .short)
})
.disposed(by: self.disposeBag)

reactor.state.map { [$0.upcomingSection, $0.timelineSection] }
.do(onNext: {
print("⬆️ Section: \($0)")
Expand Down Expand Up @@ -181,9 +186,6 @@ private extension HomeViewController {
sectionType: HomeSectionType,
isEmpty: Bool
) -> NSCollectionLayoutSection {
// 이대로는 그냥 무조건 첫 아이템이 Empty 자리에 박힌다.
// 비어있느냐 아니냐에 따라 구분을 두어야할듯
// item 타입이 .empty인지 구분해서 emptyItem을 넣을지 말지 선택?
let emptyItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
Expand Down
4 changes: 2 additions & 2 deletions Favor/Favor/Sources/Scenes/Home/Views/HeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ private extension HeaderView {
let handler: UIButton.ConfigurationUpdateHandler = { button in
switch button.state {
case .selected:
button.configuration?.baseBackgroundColor = .clear
button.configuration?.background.backgroundColor = .clear
button.configuration?.baseForegroundColor = .favorColor(.titleAndLine)
case .normal:
button.configuration?.baseBackgroundColor = .clear
button.configuration?.background.backgroundColor = .clear
button.configuration?.baseForegroundColor = .favorColor(.explain)
default:
break
Expand Down
Loading