diff --git a/SOPT-iOS/Projects/Data/Sources/Transform/PokeMyFriendsListTransform.swift b/SOPT-iOS/Projects/Data/Sources/Transform/PokeMyFriendsListTransform.swift index eaedf592..3cd982b7 100644 --- a/SOPT-iOS/Projects/Data/Sources/Transform/PokeMyFriendsListTransform.swift +++ b/SOPT-iOS/Projects/Data/Sources/Transform/PokeMyFriendsListTransform.swift @@ -13,6 +13,6 @@ import Networks extension PokeMyFriendsListEntity { public func toDomain() -> PokeMyFriendsListModel { - return PokeMyFriendsListModel(friendList: friendList.map { $0.toDomain() }, pageSize: pageSize, pageNum: pageNum) + return PokeMyFriendsListModel(friendList: friendList.map { $0.toDomain() }, totalSize: totalSize, pageSize: pageSize, pageNum: pageNum) } } diff --git a/SOPT-iOS/Projects/Domain/Sources/Model/PokeMyFriendsListModel.swift b/SOPT-iOS/Projects/Domain/Sources/Model/PokeMyFriendsListModel.swift index 14546b9f..b6b79d5a 100644 --- a/SOPT-iOS/Projects/Domain/Sources/Model/PokeMyFriendsListModel.swift +++ b/SOPT-iOS/Projects/Domain/Sources/Model/PokeMyFriendsListModel.swift @@ -10,11 +10,13 @@ import Foundation public struct PokeMyFriendsListModel { public let friendList: [PokeUserModel] + public let totalSize: Int public let pageSize: Int public let pageNum: Int - public init(friendList: [PokeUserModel], pageSize: Int, pageNum: Int) { + public init(friendList: [PokeUserModel], totalSize: Int, pageSize: Int, pageNum: Int) { self.friendList = friendList + self.totalSize = totalSize self.pageSize = pageSize self.pageNum = pageNum } diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/PokeMainPresentable.swift b/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/PokeMainPresentable.swift index cded50b2..3e2d6337 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/PokeMainPresentable.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/PokeMainPresentable.swift @@ -14,6 +14,7 @@ public protocol PokeMainViewControllable: ViewControllable { } public protocol PokeMainCoordinatable { var onNaviBackTap: (() -> Void)? { get set } + var onPokeNotificationsTap: (() -> Void)? { get set } var onMyFriendsTap: (() -> Void)? { get set } } diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeCoordinator.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeCoordinator.swift index adceac56..3fddfd8c 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeCoordinator.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeCoordinator.swift @@ -38,6 +38,10 @@ final class PokeCoordinator: DefaultCoordinator { self?.finishFlow?() } + pokeMain.vm.onPokeNotificationsTap = { [weak self] in + self?.runPokeNotificationListFlow() + } + pokeMain.vm.onMyFriendsTap = { [weak self] in self?.runPokeMyFriendsFlow() } @@ -46,6 +50,23 @@ final class PokeCoordinator: DefaultCoordinator { router.present(rootController, animated: true, modalPresentationSytle: .overFullScreen) } + private func runPokeNotificationListFlow() { + let pokeNotificationListCoordinator = PokeNotificationListCoordinator( + router: Router( + rootController: rootController! + ), + factory: factory + ) + + pokeNotificationListCoordinator.finishFlow = { [weak self, weak pokeNotificationListCoordinator] in + pokeNotificationListCoordinator?.childCoordinators = [] + self?.removeDependency(pokeNotificationListCoordinator) + } + + addDependency(pokeNotificationListCoordinator) + pokeNotificationListCoordinator.start() + } + private func runPokeMyFriendsFlow() { let pokeMyFriendsCoordinator = PokeMyFriendsCoordinator(factory: factory, router: Router(rootController: rootController!)) diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeNotificationListCoordinator.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeNotificationListCoordinator.swift index ea01971a..adece894 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeNotificationListCoordinator.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Coordinator/PokeNotificationListCoordinator.swift @@ -35,6 +35,6 @@ extension PokeNotificationListCoordinator { let viewController = self.factory.makePokeNotificationList() self.rootController = viewController.vc.asNavigationController - self.router.present(self.rootController, animated: true, modalPresentationSytle: .overFullScreen) + self.router.push(viewController.vc) } } diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Models/PokeRelation+.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Models/PokeRelation+.swift index 838a9f45..daa51dc8 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Models/PokeRelation+.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Models/PokeRelation+.swift @@ -49,6 +49,19 @@ public extension PokeRelation { return "" } } + + var queryValueName: String { + switch self { + case .newFriend: + return "new" + case .bestFriend: + return "bestfriend" + case .soulmate: + return "soulmate" + default: + return "" + } + } } public extension PokeUserModel { diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift index 8af5261b..7ea06bf0 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift @@ -195,6 +195,13 @@ extension PokeMainVC { } } +extension PokeMainVC: UIGestureRecognizerDelegate { + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + guard let viewControllers = self.navigationController?.viewControllers else { return false } + return viewControllers.count > 1 + } +} + // MARK: - Methods extension PokeMainVC { diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift index dce6aaa0..bcf7d4d1 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift @@ -20,6 +20,7 @@ public class PokeMainViewModel: typealias UserId = Int public var onNaviBackTap: (() -> Void)? + public var onPokeNotificationsTap: (() -> Void)? public var onMyFriendsTap: (() -> Void)? // MARK: - Properties @@ -76,8 +77,8 @@ extension PokeMainViewModel { }.store(in: cancelBag) input.pokedSectionHeaderButtonTap - .sink { _ in - print("찌르기 알림 뷰로 이동") + .sink { [weak self] _ in + self?.onPokeNotificationsTap?() }.store(in: cancelBag) input.friendSectionHeaderButtonTap diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/VC/PokeMyFriendsListVC.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/VC/PokeMyFriendsListVC.swift index 02d5ad92..63c042c8 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/VC/PokeMyFriendsListVC.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/VC/PokeMyFriendsListVC.swift @@ -25,6 +25,8 @@ public final class PokeMyFriendsListVC: UIViewController, PokeMyFriendsListViewC public var viewModel: PokeMyFriendsListViewModel private var cancelBag = CancelBag() + private let reachToBottomSubject = PassthroughSubject() + // MARK: - UI Components private let headerView = PokeFriendsSectionHeaderView() @@ -59,6 +61,7 @@ public final class PokeMyFriendsListVC: UIViewController, PokeMyFriendsListViewC extension PokeMyFriendsListVC { private func setUI() { view.backgroundColor = DSKitAsset.Colors.gray800.color + tableView.backgroundColor = .clear headerView.setTitle(viewModel.relation.title) headerView.setDescription(viewModel.relation.friendBaselineDescription) } @@ -72,7 +75,7 @@ extension PokeMyFriendsListVC { } tableView.snp.makeConstraints { make in - make.top.equalTo(headerView.snp.bottom) + make.top.equalTo(headerView.snp.bottom).offset(16) make.leading.bottom.trailing.equalToSuperview() } } @@ -90,9 +93,20 @@ extension PokeMyFriendsListVC { extension PokeMyFriendsListVC { private func bindViewModel() { let input = PokeMyFriendsListViewModel.Input(viewDidLoad: Just(()).asDriver(), - closeButtonTap: self.headerView.rightButtonTap) + closeButtonTap: self.headerView.rightButtonTap, + reachToBottom: self.reachToBottomSubject.asDriver()) let output = viewModel.transform(from: input, cancelBag: cancelBag) + + output.friendCount + .sink { [weak self] count in + self?.headerView.setFriendsCount(count) + }.store(in: cancelBag) + + output.needToReloadFriendList + .sink { [weak self] _ in + self?.tableView.reloadData() + }.store(in: cancelBag) } } @@ -100,7 +114,7 @@ extension PokeMyFriendsListVC { extension PokeMyFriendsListVC: UITableViewDelegate, UITableViewDataSource { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 4 + return viewModel.friends.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -110,10 +124,25 @@ extension PokeMyFriendsListVC: UITableViewDelegate, UITableViewDataSource { return UITableViewCell() } + cell.selectionStyle = .none + cell.setData(model: viewModel.friends[indexPath.row]) + return cell } public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 70 } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offsetY = scrollView.contentOffset.y + let contentHeight = scrollView.contentSize.height + let height = scrollView.frame.height + + guard offsetY > 0 , contentHeight > 0 else { return } + + if height > contentHeight - offsetY { + self.reachToBottomSubject.send() + } + } } diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift index d73e1192..179804aa 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift @@ -25,17 +25,25 @@ public class PokeMyFriendsListViewModel: private var cancelBag = CancelBag() public let relation: PokeRelation + var friends = [PokeUserModel]() + + private var isPaging = false + private var page = 0 + // MARK: - Inputs public struct Input { let viewDidLoad: Driver let closeButtonTap: Driver + let reachToBottom: Driver } // MARK: - Outputs public struct Output { + let friendCount = PassthroughSubject() + let needToReloadFriendList = PassthroughSubject() } // MARK: - initialization @@ -52,8 +60,15 @@ extension PokeMyFriendsListViewModel { self.bindOutput(output: output, cancelBag: cancelBag) input.viewDidLoad - .sink { _ in - print("데이터 요청") + .merge(with: input.reachToBottom) + .withUnretained(self) + .filter { owner, _ in + owner.isPaging == false + } + .sink { owner, _ in + owner.isPaging = true + owner.useCase.getFriends(relation: owner.relation.queryValueName, page: owner.page) + owner.page += 1 }.store(in: cancelBag) input.closeButtonTap @@ -65,6 +80,13 @@ extension PokeMyFriendsListViewModel { } private func bindOutput(output: Output, cancelBag: CancelBag) { - + useCase.myFriendsList + .withUnretained(self) + .sink { owner, model in + owner.friends.append(contentsOf: model.friendList) + output.friendCount.send(model.totalSize) + output.needToReloadFriendList.send() + owner.isPaging = false + }.store(in: cancelBag) } } diff --git a/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeMyFriendsListEntity.swift b/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeMyFriendsListEntity.swift index affcfea4..5b49d8f4 100644 --- a/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeMyFriendsListEntity.swift +++ b/SOPT-iOS/Projects/Modules/Networks/Sources/Entity/PokeMyFriendsListEntity.swift @@ -10,6 +10,7 @@ import Foundation public struct PokeMyFriendsListEntity: Codable { public let friendList: [PokeUserEntity] + public let totalSize: Int public let pageSize: Int public let pageNum: Int }