From 9496fcc0a67ab5b501832b37d27a045cae78ceb2 Mon Sep 17 00:00:00 2001 From: SeungHyeon Hong Date: Sat, 13 May 2023 18:49:32 +0900 Subject: [PATCH 1/3] [REFACTOR] Abstract BoardBottomSheetVC invocation in BoardDetailVC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공지 UI에서도 게시글 UI와 같은 디자인과 기능을 공유하기 때문에, 새롭게 기능을 구현하는 것 보다 기존의 코드를 재사용 하는 것이 유지보수 측면에서 더 낫다고 판단했습니다. 그래서 게시글 코드 내에 BoardBottomSheetVC를 직접 호출하는 곳이 있었는데, 공지에서도 바텀시트를 따로 사용해야하기 때문에 이를 추상화할 필요가 있었습니다. 그래서 BoardBottomSheetViewControllerType이라는 프로토콜을 ViewModel을 만들 때 파라미터로 받도록 구현하여, ViewModel과 ViewController는 어떤 바텀시트가 들어오든 상관없이 직접 생성하여 호출하는 식으로 처리하였습니다. --- .../Home/Clipboard/BoardDetailViewController.swift | 4 ++-- .../Clipboard/ViewModel/BoardDetailViewModel.swift | 13 +++++++------ .../ViewModel/BoardDetailViewModelFactory.swift | 1 + .../Component/BoardBottomSheetViewController.swift | 12 +++++++++++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift b/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift index 88fa331d2..b9f613e3c 100644 --- a/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift +++ b/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift @@ -160,8 +160,8 @@ final class BoardDetailViewController: BaseViewController { viewModel.showBoardBottomSheetObservable .subscribe(with: self) { owner, tuple in - let (accessType, isPinned) = tuple - let viewController = BoardBottomSheetViewController(accessType: accessType, isPinned: isPinned) + let (bottomSheetType, accessType, isPinned) = tuple + let viewController = bottomSheetType.init(accessType: accessType, isPinned: isPinned) viewController.delegate = owner owner.present(viewController, animated: true) } diff --git a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift index aaacb6cb3..e87bd279b 100644 --- a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift +++ b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift @@ -42,7 +42,7 @@ protocol BoardDetailViewModelType: BoardDetailViewModel { var showCommentBottomSheetObservable: Observable<(commentID: Int, userType: CommentOptionBottomSheetViewController.UserAccessType)> { get } - var showBoardBottomSheetObservable: Observable<(BoardBottomSheetViewController.AccessType, Bool)> { get } + var showBoardBottomSheetObservable: Observable<(BoardBottomSheetViewControllerType.Type, BoardBottomSheetViewController.AccessType, Bool)> { get } } protocol FeedLikeDelegate: AnyObject { @@ -92,7 +92,7 @@ final class BoardDetailViewModel { private let decoratorNameSubject = PublishSubject<(labelText: String, buttonText: String)>() private let bottomCellSubject = PublishSubject<(collectionViewHeight: CGFloat, offset: CGFloat)>() private let showCommentBottomSheetSubject = PublishSubject<(commentID: Int, userType: CommentOptionBottomSheetViewController.UserAccessType)>() - private let boardBottomSheetParameterSubject = ReplaySubject<(BoardBottomSheetViewController.AccessType, Bool)>.create(bufferSize: 1) + private let boardBottomSheetParameterSubject = PublishSubject<(BoardBottomSheetViewControllerType.Type, BoardBottomSheetViewController.AccessType, Bool)>() private let boardOptionTappedSubject = PublishSubject() private let targetIDSubject = BehaviorSubject(value: nil) private let deleteIDSubject = PublishSubject() @@ -101,6 +101,7 @@ final class BoardDetailViewModel { // MARK: - Initializations init( + boardBottomSheetController: BoardBottomSheetViewControllerType.Type, getFeedDetailUseCase: GetFeedDetailUseCase, getCommentsUseCase: GetCommentsUseCase, postCommentUseCase: PostCommentUseCase, @@ -115,7 +116,7 @@ final class BoardDetailViewModel { self.editCommentUseCase = editCommentUseCase self.likeFeedUseCase = likeFeedUseCase - fetchInitialStateUI() + fetchInitialStateUI(boardBottomSheetController: boardBottomSheetController) createComments() pagingSetup() deleteComments() @@ -130,7 +131,7 @@ final class BoardDetailViewModel { extension BoardDetailViewModel { /// 댓글 정보를 가져와 초기 상태의 UI를 업데이트합니다. - private func fetchInitialStateUI() { + private func fetchInitialStateUI(boardBottomSheetController: BoardBottomSheetViewControllerType.Type) { let feedObservable = getFeedDetailUseCase.execute() @@ -157,7 +158,7 @@ extension BoardDetailViewModel { } else { accessType = .normal } - owner.boardBottomSheetParameterSubject.onNext((accessType, tuple.feed.isPinned)) + owner.boardBottomSheetParameterSubject.onNext((boardBottomSheetController, accessType, tuple.feed.isPinned)) owner.comments.formUnion(tuple.comments) // 댓글 삽입 owner.setCollectionView(tuple.collectionView, content: tuple.feed.toBoardModel) @@ -343,7 +344,7 @@ extension BoardDetailViewModel: BoardDetailViewModelType { showCommentBottomSheetSubject.asObservable() } - var showBoardBottomSheetObservable: Observable<(BoardBottomSheetViewController.AccessType, Bool)> { + var showBoardBottomSheetObservable: Observable<(BoardBottomSheetViewControllerType.Type, BoardBottomSheetViewController.AccessType, Bool)> { boardOptionTappedSubject .withLatestFrom(boardBottomSheetParameterSubject) } diff --git a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift index 2a6957099..2883c87de 100644 --- a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift +++ b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift @@ -18,6 +18,7 @@ final class BoardDetailViewModelWithFeedsFactory: BoardDetailViewModelFactory { static func make(plubbingID: Int, feedID: Int) -> BoardDetailViewModelType { return BoardDetailViewModel( + boardBottomSheetController: BoardBottomSheetViewController.self, getFeedDetailUseCase: DefaultGetFeedDetailUseCase(plubbingID: plubbingID, feedID: feedID), getCommentsUseCase: DefaultGetCommentsUseCase(plubbingID: plubbingID, feedID: feedID), postCommentUseCase: DefaultPostCommentUseCase(plubbingID: plubbingID, feedID: feedID), diff --git a/PLUB/Sources/Views/Home/MainPage/Component/BoardBottomSheetViewController.swift b/PLUB/Sources/Views/Home/MainPage/Component/BoardBottomSheetViewController.swift index 3ff876690..542de1dd5 100644 --- a/PLUB/Sources/Views/Home/MainPage/Component/BoardBottomSheetViewController.swift +++ b/PLUB/Sources/Views/Home/MainPage/Component/BoardBottomSheetViewController.swift @@ -15,6 +15,16 @@ protocol BoardBottomSheetDelegate: AnyObject { func selectedBoardSheetType(type: BoardBottomSheetType) } + +// MARK: 바텀시트 추상화 + +protocol BoardBottomSheetViewControllerType: BottomSheetViewController { + + init(accessType: BoardBottomSheetViewController.AccessType, isPinned: Bool) + + var delegate: BoardBottomSheetDelegate? { get set } +} + enum BoardBottomSheetType { case fix // 클립보드 고정 case modify // 게시글 수정 @@ -22,7 +32,7 @@ enum BoardBottomSheetType { case delete // 게시글 삭제 } -final class BoardBottomSheetViewController: BottomSheetViewController { +final class BoardBottomSheetViewController: BottomSheetViewController, BoardBottomSheetViewControllerType { // MARK: - Properties From b0eb5be50bb53a59f8e2f1ed4dbed35f3eace6ce Mon Sep 17 00:00:00 2001 From: SeungHyeon Hong Date: Sat, 13 May 2023 19:33:26 +0900 Subject: [PATCH 2/3] [ADD] Add DeleteFeedUseCase and inject o BoardDetailViewModel --- PLUB.xcodeproj/project.pbxproj | 4 +++ .../UseCases/Feeds/DeleteFeedUseCase.swift | 29 +++++++++++++++++++ .../ViewModel/BoardDetailViewModel.swift | 11 ++++--- .../BoardDetailViewModelFactory.swift | 1 + 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 PLUB/Sources/UseCases/Feeds/DeleteFeedUseCase.swift diff --git a/PLUB.xcodeproj/project.pbxproj b/PLUB.xcodeproj/project.pbxproj index 92c6d93ba..d35cfd1e5 100644 --- a/PLUB.xcodeproj/project.pbxproj +++ b/PLUB.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ BA2173B929F57A97000D72B1 /* DeleteImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2173B829F57A97000D72B1 /* DeleteImageUseCase.swift */; }; BA2173BB29F61955000D72B1 /* UploadArchiveUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2173BA29F61955000D72B1 /* UploadArchiveUseCase.swift */; }; BA2173BD29F61A36000D72B1 /* EditArchiveUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2173BC29F61A36000D72B1 /* EditArchiveUseCase.swift */; }; + BA2553282A0F961600194B5A /* DeleteFeedUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2553272A0F961600194B5A /* DeleteFeedUseCase.swift */; }; BA3001E429FF98E300C3FB89 /* DeleteArchiveUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3001E329FF98E300C3FB89 /* DeleteArchiveUseCase.swift */; }; BA340E1629782207002BAF2C /* IntroduceTagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA340E1529782207002BAF2C /* IntroduceTagCollectionViewCell.swift */; }; BA340E1C297822CC002BAF2C /* IntroduceCategoryInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA340E1B297822CC002BAF2C /* IntroduceCategoryInfoView.swift */; }; @@ -558,6 +559,7 @@ BA2173B829F57A97000D72B1 /* DeleteImageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteImageUseCase.swift; sourceTree = ""; }; BA2173BA29F61955000D72B1 /* UploadArchiveUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadArchiveUseCase.swift; sourceTree = ""; }; BA2173BC29F61A36000D72B1 /* EditArchiveUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditArchiveUseCase.swift; sourceTree = ""; }; + BA2553272A0F961600194B5A /* DeleteFeedUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFeedUseCase.swift; sourceTree = ""; }; BA3001E329FF98E300C3FB89 /* DeleteArchiveUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteArchiveUseCase.swift; sourceTree = ""; }; BA340E1529782207002BAF2C /* IntroduceTagCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroduceTagCollectionViewCell.swift; sourceTree = ""; }; BA340E1B297822CC002BAF2C /* IntroduceCategoryInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroduceCategoryInfoView.swift; sourceTree = ""; }; @@ -1377,6 +1379,7 @@ isa = PBXGroup; children = ( BA57F3F529ED30D200A9F790 /* DeleteCommentUseCase.swift */, + BA2553272A0F961600194B5A /* DeleteFeedUseCase.swift */, BA5EC6C529EFD4E5000A68B7 /* EditCommentUseCase.swift */, BAB2E56D29E30F96006B7BDC /* GetCommentsUseCase.swift */, BA5F050129F7D26500A3FA14 /* GetFeedDetailUseCase.swift */, @@ -2798,6 +2801,7 @@ 705F81C329AFAA3B00830C4F /* BoardViewController.swift in Sources */, BAE5D3352975B10F00268D44 /* ReissuanceRequest.swift in Sources */, BA57F3FA29ED4EEC00A9F790 /* ArchiveUploadViewModel.swift in Sources */, + BA2553282A0F961600194B5A /* DeleteFeedUseCase.swift in Sources */, 70197B6E29535BAA000503F6 /* HomeViewModel.swift in Sources */, BA5D9ED929E3F16900F06AB5 /* ArchiveContent.swift in Sources */, 70F1DFE0297A90AE00F9BC83 /* MeetingService.swift in Sources */, diff --git a/PLUB/Sources/UseCases/Feeds/DeleteFeedUseCase.swift b/PLUB/Sources/UseCases/Feeds/DeleteFeedUseCase.swift new file mode 100644 index 000000000..ae7f8550f --- /dev/null +++ b/PLUB/Sources/UseCases/Feeds/DeleteFeedUseCase.swift @@ -0,0 +1,29 @@ +// +// DeleteFeedUseCase.swift +// PLUB +// +// Created by 홍승현 on 2023/05/13. +// + +import RxSwift + +protocol DeleteFeedUseCase { + func execute() -> Observable +} + +final class DefaultDeleteFeedUseCase: DeleteFeedUseCase { + + private let plubbingID: Int + private let feedID: Int + + init(plubbingID: Int, feedID: Int) { + self.plubbingID = plubbingID + self.feedID = feedID + } + + + func execute() -> Observable { + FeedsService.shared.deleteFeed(plubbingID: plubbingID, feedID: feedID) + .map { _ in Void() } + } +} diff --git a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift index e87bd279b..d6927f2fc 100644 --- a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift +++ b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift @@ -77,12 +77,13 @@ final class BoardDetailViewModel { // MARK: Use Cases + private let deleteFeedUseCase: DeleteFeedUseCase private let getFeedDetailUseCase: GetFeedDetailUseCase - private let getCommentsUseCase: GetCommentsUseCase - private let postCommentUseCase: PostCommentUseCase + private let getCommentsUseCase: GetCommentsUseCase + private let postCommentUseCase: PostCommentUseCase private let deleteCommentUseCase: DeleteCommentUseCase - private let editCommentUseCase: EditCommentUseCase - private let likeFeedUseCase: LikeFeedUseCase + private let editCommentUseCase: EditCommentUseCase + private let likeFeedUseCase: LikeFeedUseCase // MARK: Subjects @@ -102,6 +103,7 @@ final class BoardDetailViewModel { init( boardBottomSheetController: BoardBottomSheetViewControllerType.Type, + deleteFeedUseCase: DeleteFeedUseCase, getFeedDetailUseCase: GetFeedDetailUseCase, getCommentsUseCase: GetCommentsUseCase, postCommentUseCase: PostCommentUseCase, @@ -109,6 +111,7 @@ final class BoardDetailViewModel { editCommentUseCase: EditCommentUseCase, likeFeedUseCase: LikeFeedUseCase ) { + self.deleteFeedUseCase = deleteFeedUseCase self.getFeedDetailUseCase = getFeedDetailUseCase self.getCommentsUseCase = getCommentsUseCase self.postCommentUseCase = postCommentUseCase diff --git a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift index 2883c87de..341e4c987 100644 --- a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift +++ b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModelFactory.swift @@ -19,6 +19,7 @@ final class BoardDetailViewModelWithFeedsFactory: BoardDetailViewModelFactory { static func make(plubbingID: Int, feedID: Int) -> BoardDetailViewModelType { return BoardDetailViewModel( boardBottomSheetController: BoardBottomSheetViewController.self, + deleteFeedUseCase: DefaultDeleteFeedUseCase(plubbingID: plubbingID, feedID: feedID), getFeedDetailUseCase: DefaultGetFeedDetailUseCase(plubbingID: plubbingID, feedID: feedID), getCommentsUseCase: DefaultGetCommentsUseCase(plubbingID: plubbingID, feedID: feedID), postCommentUseCase: DefaultPostCommentUseCase(plubbingID: plubbingID, feedID: feedID), From 2bbf6f169b74aaa542414d45f74bcb176a82f952 Mon Sep 17 00:00:00 2001 From: SeungHyeon Hong Date: Sat, 13 May 2023 19:35:39 +0900 Subject: [PATCH 3/3] [FEAT] Implement board deletion feature --- .../Clipboard/BoardDetailViewController.swift | 9 +++++- .../ViewModel/BoardDetailViewModel.swift | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift b/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift index b9f613e3c..91a1c42a0 100644 --- a/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift +++ b/PLUB/Sources/Views/Home/Clipboard/BoardDetailViewController.swift @@ -166,6 +166,12 @@ final class BoardDetailViewController: BaseViewController { owner.present(viewController, animated: true) } .disposed(by: disposeBag) + + viewModel.popViewControllerByMySelfObservable + .subscribe(with: self) { owner, _ in + owner.navigationController?.popViewController(animated: true) + } + .disposed(by: disposeBag) } } @@ -211,7 +217,8 @@ extension BoardDetailViewController: CommentOptionBottomSheetDelegate { extension BoardDetailViewController: BoardBottomSheetDelegate { func selectedBoardSheetType(type: BoardBottomSheetType) { - PLUBToast.makeToast(text: "\(type)") + viewModel.boardOptionObserver.onNext(type) + dismiss(animated: true) } } diff --git a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift index d6927f2fc..ee08ef1d3 100644 --- a/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift +++ b/PLUB/Sources/Views/Home/Clipboard/ViewModel/BoardDetailViewModel.swift @@ -32,6 +32,8 @@ protocol BoardDetailViewModelType: BoardDetailViewModel { /// 댓(답)글, 댓글 수정, 댓글 삭제의 옵션을 처리할 경우 해당 옵저버를 이용합니다. var commentOptionObserver: AnyObserver { get } + var boardOptionObserver: AnyObserver { get } + //Output /// 수정할 댓글의 정보를 전달합니다. @@ -43,6 +45,8 @@ protocol BoardDetailViewModelType: BoardDetailViewModel { var showCommentBottomSheetObservable: Observable<(commentID: Int, userType: CommentOptionBottomSheetViewController.UserAccessType)> { get } var showBoardBottomSheetObservable: Observable<(BoardBottomSheetViewControllerType.Type, BoardBottomSheetViewController.AccessType, Bool)> { get } + + var popViewControllerByMySelfObservable: Observable { get } } protocol FeedLikeDelegate: AnyObject { @@ -95,6 +99,8 @@ final class BoardDetailViewModel { private let showCommentBottomSheetSubject = PublishSubject<(commentID: Int, userType: CommentOptionBottomSheetViewController.UserAccessType)>() private let boardBottomSheetParameterSubject = PublishSubject<(BoardBottomSheetViewControllerType.Type, BoardBottomSheetViewController.AccessType, Bool)>() private let boardOptionTappedSubject = PublishSubject() + private let popViewControllerByMySelfSubject = PublishSubject() + private let boardOptionInputSubject = PublishSubject() private let targetIDSubject = BehaviorSubject(value: nil) private let deleteIDSubject = PublishSubject() private let commentOptionSubject = BehaviorSubject(value: .commentOrReply) @@ -124,6 +130,7 @@ final class BoardDetailViewModel { pagingSetup() deleteComments() editComments() + boardOptionProcess() } private let disposeBag = DisposeBag() @@ -301,6 +308,20 @@ extension BoardDetailViewModel { } .disposed(by: disposeBag) } + + /// 게시글 설정 파이프라인입니다. + /// 게시글 삭제, 게시글 수정, 게시글 신고 로직이 들어가 있습니다. + private func boardOptionProcess() { + let sharedBoardOptionSubject = boardOptionInputSubject.share() + + sharedBoardOptionSubject + .filter { $0 == .delete } + .flatMap { [deleteFeedUseCase] _ in deleteFeedUseCase.execute() } + .do { _ in PLUBToast.makeToast(text: "게시글이 삭제되었습니다.") } + .bind(to: popViewControllerByMySelfSubject) + .disposed(by: disposeBag) + + } } // MARK: - BoardDetailViewModelType @@ -333,6 +354,10 @@ extension BoardDetailViewModel: BoardDetailViewModelType { commentOptionSubject.asObserver() } + var boardOptionObserver: AnyObserver { + boardOptionInputSubject.asObserver() + } + // Output var editCommentTextObservable: Observable { @@ -351,6 +376,10 @@ extension BoardDetailViewModel: BoardDetailViewModelType { boardOptionTappedSubject .withLatestFrom(boardBottomSheetParameterSubject) } + + var popViewControllerByMySelfObservable: Observable { + popViewControllerByMySelfSubject.asObservable() + } } // MARK: - Diffable DataSource