Skip to content

Commit

Permalink
[Feat] sopt-makers#360 - 파트별 랭킹 타입 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
lsj8706 committed Mar 31, 2024
1 parent 0ecc79a commit 91d85a9
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import BaseFeatureDependency
public enum RankingViewType {
case all
case currentGeneration(info: UsersActiveGenerationStatusViewResponse)
case partRanking
}

public protocol StampFeatureViewBuildable {
Expand All @@ -29,5 +30,6 @@ public protocol StampFeatureViewBuildable {
completionHandler: (() -> Void)?
) -> MissionCompletedViewControllable
func makeRankingVC(rankingViewType: RankingViewType) -> RankingViewControllable
func makePartRankingVC(rankingViewType: RankingViewType) -> PartRankingViewControllable
func makeStampGuideVC() -> StampGuideViewControllable
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ public protocol RankingCoordinatable {
var onSwiped: (() -> Void)? { get set }
var onNaviBackTap: (() -> Void)? { get set }
}
public protocol PartRankingViewControllable: ViewControllable & RankingCoordinatable { }
public protocol StampGuideViewControllable: ViewControllable { }
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ final class RankingCoordinator: DefaultCoordinator {
}

public override func start() {
switch rankingViewType {
case .all, .currentGeneration:
showRanking()
case .partRanking:
showPartRanking()
}
}

private func showRanking() {
Expand All @@ -48,7 +53,22 @@ final class RankingCoordinator: DefaultCoordinator {
}
router.push(ranking)
}


private func showPartRanking() {
var ranking = factory.makePartRankingVC(rankingViewType: self.rankingViewType)
ranking.onSwiped = { [weak self] in
self?.router.popModule()
}
ranking.onCellTap = { [weak self] (username, sentence) in
self?.showOtherMissionList(username, sentence)
}
ranking.onNaviBackTap = { [weak self] in
self?.router.popModule()
self?.finishFlow?()
}
router.push(ranking)
}

private func showOtherMissionList(_ username: String, _ sentence: String) {
var otherMissionList = factory.makeMissionListVC(
sceneType: .ranking(userName: username, sentence: sentence)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,10 @@ extension StampBuilder: StampFeatureViewBuildable {
let stampGuideVC = StampGuideVC()
return stampGuideVC
}

public func makePartRankingVC(rankingViewType: RankingViewType) -> PartRankingViewControllable {
let vc = PartRankingVC(rankingViewType: rankingViewType)
vc.viewModel = PartRankingViewModel(rankingViewType: rankingViewType)
return vc
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ final class StampCoordinator: DefaultCoordinator {
addDependency(rankingCoordinator)
rankingCoordinator.start()
}

private func runMissionDetailFlow(_ model: MissionListModel, _ username: String?) {
let missionDetailCoordinator = MissionDetailCoordinator(
router: Router(rootController: rootController!),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ extension MissionListVC {
partRankingFloatingButton.publisher(for: .touchUpInside)
.withUnretained(self)
.sink { owner, _ in
owner.onPartRankingButtonTap?(.all)
owner.onPartRankingButtonTap?(.partRanking)
}.store(in: self.cancelBag)

currentGenerationRankFloatingButton.publisher(for: .touchUpInside)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// PartRankingCompositionalLayout.swift
// StampFeature
//
// Created by Aiden.lee on 2024/03/31.
// Copyright © 2024 SOPT-iOS. All rights reserved.
//

import UIKit

extension PartRankingVC {
static let standardWidth = UIScreen.main.bounds.width - 40.adjusted

func createLayout() -> UICollectionViewLayout {
return UICollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
switch RankingSection.type(sectionIndex) {
case .chart: return self.createChartSection()
case .list: return self.createListSection()
}
}
}

private func createChartSection() -> NSCollectionLayoutSection {
let size = NSCollectionLayoutSize(widthDimension: .absolute(RankingVC.standardWidth - 28.adjusted), heightDimension: .estimated(300))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 0, leading: 34.adjusted, bottom: 0, trailing: 34.adjusted)
section.orthogonalScrollingBehavior = .none
return section
}

private func createListSection() -> NSCollectionLayoutSection {
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(79.adjustedH))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 28.adjustedH, leading: 20.adjusted, bottom: 60.adjustedH, trailing: 20.adjusted)
section.interGroupSpacing = .init(10.adjustedH)
section.orthogonalScrollingBehavior = .none

return section
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
//
// PartRankingVC.swift
// StampFeature
//
// Created by Aiden.lee on 2024/03/31.
// Copyright © 2024 SOPT-iOS. All rights reserved.
//

import UIKit

import Core
import Domain
import DSKit

import Combine
import SnapKit
import Then

import StampFeatureInterface
import BaseFeatureDependency

public class PartRankingVC: UIViewController, PartRankingViewControllable {

// MARK: - Properties

public var viewModel: PartRankingViewModel!
private var cancelBag = CancelBag()

lazy var dataSource: UICollectionViewDiffableDataSource<RankingSection, AnyHashable>! = nil

// MARK: - RankingCoordinatable

public var onCellTap: ((String, String) -> Void)?
public var onSwiped: (() -> Void)?
public var onNaviBackTap: (() -> Void)?

// MARK: - UI Components

lazy var naviBar = STNavigationBar(self, type: .titleWithLeftButton, ignorePopAction: true)
.setTitleTypoStyle(.SoptampFont.h2)
.setTitle("파트별 랭킹")
.setRightButton(.none)

private lazy var rankingCollectionView: UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: self.createLayout())
cv.showsVerticalScrollIndicator = true
cv.backgroundColor = .white
cv.refreshControl = refresher
return cv
}()

private let refresher: UIRefreshControl = {
let rf = UIRefreshControl()
return rf
}()

// MARK: - View Life Cycle
private let rankingViewType: RankingViewType

init(rankingViewType: RankingViewType) {
self.rankingViewType = rankingViewType

super.init(nibName: nil, bundle: nil)

guard case .currentGeneration(let info) = rankingViewType else { return }

let navigationTitle = String(describing: info.currentGeneration) + "기 랭킹"
self.naviBar.setTitle(navigationTitle)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

public override func viewDidLoad() {
super.viewDidLoad()
self.setUI()
self.setLayout()
self.setDelegate()
self.setGesture()
self.registerCells()
self.bindViews()
self.bindViewModels()
self.setDataSource()
}
}

// MARK: - UI & Layouts

extension PartRankingVC {

private func setUI() {
self.view.backgroundColor = .white
self.navigationController?.isNavigationBarHidden = true
}

private func setLayout() {
self.view.addSubviews(naviBar, rankingCollectionView)

naviBar.snp.makeConstraints { make in
make.leading.top.trailing.equalTo(view.safeAreaLayoutGuide)
}

rankingCollectionView.snp.makeConstraints { make in
make.top.equalTo(naviBar.snp.bottom).offset(4)
make.leading.trailing.equalToSuperview()
make.bottom.equalToSuperview()
}
}
}

// MARK: - Methods

extension PartRankingVC {

private func bindViews() {
naviBar.leftButtonTapped
.withUnretained(self)
.sink { owner, _ in
owner.onNaviBackTap?()
}.store(in: cancelBag)
}

private func bindViewModels() {
let refreshStarted = refresher.publisher(for: .valueChanged)
.mapVoid()
.asDriver()

let input = PartRankingViewModel.Input(
viewDidLoad: Driver.just(()),
refreshStarted: refreshStarted
)

let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag)
}

private func setDelegate() {
rankingCollectionView.delegate = self
}

private func setGesture() {
let swipeGesture = UIPanGestureRecognizer(target: self, action: #selector(swipeBack(_:)))
swipeGesture.delegate = self
self.rankingCollectionView.addGestureRecognizer(swipeGesture)
}

@objc
private func swipeBack(_ sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: rankingCollectionView)
let velocityMinimum: CGFloat = 1000
guard let navigation = self.navigationController else { return }
let isScrollY: Bool = abs(velocity.x) > abs(velocity.y) + 200
let isNotRootView = navigation.viewControllers.count >= 2
if velocity.x >= velocityMinimum
&& isNotRootView
&& isScrollY {
self.rankingCollectionView.isScrollEnabled = false
self.onSwiped?()
}
}

private func registerCells() {
RankingChartCVC.register(target: rankingCollectionView)
RankingListCVC.register(target: rankingCollectionView)
}

private func setDataSource() {
dataSource = UICollectionViewDiffableDataSource(collectionView: rankingCollectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
switch RankingSection.type(indexPath.section) {
case .chart:
guard let chartCell = collectionView.dequeueReusableCell(withReuseIdentifier: RankingChartCVC.className, for: indexPath) as? RankingChartCVC,
let chartCellModel = itemIdentifier as? RankingChartModel else { return UICollectionViewCell() }
chartCell.setData(model: chartCellModel)
chartCell.balloonTapped = { [weak self] balloonModel in
guard let self = self else { return }
let item = balloonModel.toRankingListTapItem()
self.onCellTap?(item.username, item.sentence)
}
return chartCell

case .list:
guard let rankingListCell = collectionView.dequeueReusableCell(withReuseIdentifier: RankingListCVC.className, for: indexPath) as? RankingListCVC,
let rankingListCellModel = itemIdentifier as? RankingModel else { return UICollectionViewCell() }
rankingListCell.setData(model: rankingListCellModel,
rank: indexPath.row + 1 + 3)

return rankingListCell
}
})
}

func applySnapshot(model: [RankingModel]) {
var snapshot = NSDiffableDataSourceSnapshot<RankingSection, AnyHashable>()
snapshot.appendSections([.chart, .list])
guard model.count >= 4 else { return }
guard let chartCellModels = Array(model[0...2]) as? [RankingModel],
let rankingListModel = Array(model[3...model.count-1]) as? [RankingModel] else { return }
let chartCellModel = RankingChartModel.init(ranking: chartCellModels)
snapshot.appendItems([chartCellModel], toSection: .chart)
snapshot.appendItems(rankingListModel, toSection: .list)
dataSource.apply(snapshot, animatingDifferences: false)
self.view.setNeedsLayout()
}

private func endRefresh() {
self.refresher.endRefreshing()
}
}

extension PartRankingVC: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard indexPath.section >= 1 else { return }

guard let tappedCell = collectionView.cellForItem(at: indexPath) as? RankingListTappable,
let item = tappedCell.getModelItem() else { return }
self.onCellTap?(item.username, item.sentence)
}
}

extension PartRankingVC: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
}
Loading

0 comments on commit 91d85a9

Please sign in to comment.