diff --git a/PLUB.xcodeproj/project.pbxproj b/PLUB.xcodeproj/project.pbxproj index e065092e7..4fd4beb8c 100644 --- a/PLUB.xcodeproj/project.pbxproj +++ b/PLUB.xcodeproj/project.pbxproj @@ -308,6 +308,8 @@ C346B4AA29B1079900884E4F /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = C346B4A929B1079900884E4F /* FirebaseCrashlytics */; }; C346B4AC29B1079900884E4F /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = C346B4AB29B1079900884E4F /* FirebaseMessaging */; }; C34C097029773860001AFF16 /* DateBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34C096F29773860001AFF16 /* DateBottomSheetViewController.swift */; }; + C34D41F22A07395500F9E7AF /* MyFeedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D41F12A07395500F9E7AF /* MyFeedTableViewCell.swift */; }; + C34D41F42A07666D00F9E7AF /* NoActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D41F32A07666D00F9E7AF /* NoActivityTableViewCell.swift */; }; C34F047E29C7049400E5B67E /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34F047D29C7049400E5B67E /* SettingViewController.swift */; }; C34F048129C70FB900E5B67E /* SettingDetailSubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34F048029C70FB900E5B67E /* SettingDetailSubView.swift */; }; C34F048429C715A800E5B67E /* SettingSubview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34F048329C715A800E5B67E /* SettingSubview.swift */; }; @@ -321,6 +323,7 @@ C359FEAA29A08B7E00B2F561 /* ScheduleTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C359FEA929A08B7E00B2F561 /* ScheduleTitleView.swift */; }; C359FEAC29A09C5200B2F561 /* ScheduleDateSubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C359FEAB29A09C5200B2F561 /* ScheduleDateSubView.swift */; }; C35AFD012998B0A500AB5CD3 /* EditMeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35AFD002998B0A500AB5CD3 /* EditMeetingViewModel.swift */; }; + C35C062F2A065A3400DCB8EB /* InquireMyFeedUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35C062E2A065A3400DCB8EB /* InquireMyFeedUseCase.swift */; }; C361743129AF95E2006AEAB1 /* MyMeetingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C361743029AF95E2006AEAB1 /* MyMeetingResponse.swift */; }; C361743329AF9B2D006AEAB1 /* MeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C361743229AF9B2D006AEAB1 /* MeetingViewModel.swift */; }; C361743629AFB875006AEAB1 /* MeetingCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C361743529AFB875006AEAB1 /* MeetingCollectionViewCell.swift */; }; @@ -411,6 +414,7 @@ C3FA748C2974984800A8BE4C /* MeetingIntroduceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FA748B2974984800A8BE4C /* MeetingIntroduceViewModel.swift */; }; C3FC1815298509C50083039A /* QuestionDeleteBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FC1814298509C50083039A /* QuestionDeleteBottomSheetViewController.swift */; }; C3FC181729852D370083039A /* QuestionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FC181629852D370083039A /* QuestionHeaderView.swift */; }; + C3FF87AC2A06572000DA6017 /* MyFeedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FF87AB2A06572000DA6017 /* MyFeedResponse.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -702,6 +706,8 @@ C344E53E2986B938009F73A9 /* MeetingCategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingCategoryViewController.swift; sourceTree = ""; }; C344E5402986B9D7009F73A9 /* MeetingCategoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingCategoryViewModel.swift; sourceTree = ""; }; C34C096F29773860001AFF16 /* DateBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateBottomSheetViewController.swift; sourceTree = ""; }; + C34D41F12A07395500F9E7AF /* MyFeedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyFeedTableViewCell.swift; sourceTree = ""; }; + C34D41F32A07666D00F9E7AF /* NoActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoActivityTableViewCell.swift; sourceTree = ""; }; C34F047D29C7049400E5B67E /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; C34F048029C70FB900E5B67E /* SettingDetailSubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingDetailSubView.swift; sourceTree = ""; }; C34F048329C715A800E5B67E /* SettingSubview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingSubview.swift; sourceTree = ""; }; @@ -715,6 +721,7 @@ C359FEA929A08B7E00B2F561 /* ScheduleTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleTitleView.swift; sourceTree = ""; }; C359FEAB29A09C5200B2F561 /* ScheduleDateSubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleDateSubView.swift; sourceTree = ""; }; C35AFD002998B0A500AB5CD3 /* EditMeetingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMeetingViewModel.swift; sourceTree = ""; }; + C35C062E2A065A3400DCB8EB /* InquireMyFeedUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InquireMyFeedUseCase.swift; sourceTree = ""; }; C361743029AF95E2006AEAB1 /* MyMeetingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyMeetingResponse.swift; sourceTree = ""; }; C361743229AF9B2D006AEAB1 /* MeetingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingViewModel.swift; sourceTree = ""; }; C361743529AFB875006AEAB1 /* MeetingCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingCollectionViewCell.swift; sourceTree = ""; }; @@ -805,6 +812,7 @@ C3FA748B2974984800A8BE4C /* MeetingIntroduceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingIntroduceViewModel.swift; sourceTree = ""; }; C3FC1814298509C50083039A /* QuestionDeleteBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionDeleteBottomSheetViewController.swift; sourceTree = ""; }; C3FC181629852D370083039A /* QuestionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionHeaderView.swift; sourceTree = ""; }; + C3FF87AB2A06572000DA6017 /* MyFeedResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyFeedResponse.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1916,6 +1924,7 @@ isa = PBXGroup; children = ( C37237F22A02B0FF00328763 /* InquireMyTodoUseCase.swift */, + C35C062E2A065A3400DCB8EB /* InquireMyFeedUseCase.swift */, ); path = MyPage; sourceTree = ""; @@ -1925,6 +1934,8 @@ children = ( C374E79229FC1E52004738C2 /* MyTodoTableViewCell.swift */, C374E79429FC1E7E004738C2 /* MyTodoSectionHeaderView.swift */, + C34D41F12A07395500F9E7AF /* MyFeedTableViewCell.swift */, + C34D41F32A07666D00F9E7AF /* NoActivityTableViewCell.swift */, ); path = Cell; sourceTree = ""; @@ -2036,6 +2047,7 @@ children = ( C3B3435A29BE3E5100935B73 /* MyPlubbingResponse.swift */, C374E79629FC2F4D004738C2 /* MyTodoResponse.swift */, + C3FF87AB2A06572000DA6017 /* MyFeedResponse.swift */, ); path = Response; sourceTree = ""; @@ -2495,6 +2507,7 @@ BA5D9ED529E3EE7400F06AB5 /* ArchiveRequest.swift in Sources */, 7085678828EEACFB008047DC /* InterestSelectCollectionViewCell.swift in Sources */, C3B3435429BE3A1400935B73 /* MyPageRouter.swift in Sources */, + C34D41F22A07395500F9E7AF /* MyFeedTableViewCell.swift in Sources */, BA5F050229F7D26500A3FA14 /* GetFeedDetailUseCase.swift in Sources */, BA780E0F297138F10032C178 /* HeaderType.swift in Sources */, C385D10F29894BF200EF88EC /* CreateMeetingViewModel.swift in Sources */, @@ -2508,6 +2521,7 @@ C3330AD529F9A1830023CB04 /* ActiveMeetingViewModel.swift in Sources */, 70CF333929992FA60077FF47 /* RecruitmentFilterSlider.swift in Sources */, BA42D1F328EDE10F00C20061 /* LoginViewController.swift in Sources */, + C3FF87AC2A06572000DA6017 /* MyFeedResponse.swift in Sources */, C3413E122995450B004B8DBD /* UnderlineSegmentedControl.swift in Sources */, BAE5D3372975B20000268D44 /* TokenResponse.swift in Sources */, C38A5B9F297B022A00485355 /* SearchView.swift in Sources */, @@ -2608,6 +2622,7 @@ 70727A4529D5E570003DE956 /* TodoAlertView.swift in Sources */, C3AB7441296B21E6003DD5E2 /* MeetingQuestionViewController.swift in Sources */, C3100CB729BD8A5B005FCCAD /* MyPageViewController.swift in Sources */, + C35C062F2A065A3400DCB8EB /* InquireMyFeedUseCase.swift in Sources */, BAB320EA29F3B6440056698C /* ArchiveUploadCell.swift in Sources */, BAC8930929755B87000D44E2 /* SignUpRequest.swift in Sources */, C371CDB229784F4E008950DD /* WeekDateCollectionViewCell.swift in Sources */, @@ -2781,6 +2796,7 @@ C31605172997934100D27488 /* RecruitPostViewModel.swift in Sources */, 7095A58C292FB0B6002A52E6 /* InterestTypeCollectionViewCell.swift in Sources */, C37237F32A02B0FF00328763 /* InquireMyTodoUseCase.swift in Sources */, + C34D41F42A07666D00F9E7AF /* NoActivityTableViewCell.swift in Sources */, BA85BAA629A72D2400B4C59A /* BoardsRequest.swift in Sources */, BA780E1129713BF30032C178 /* ParameterType.swift in Sources */, BABB011C297ED74C004178EC /* InterestViewModel.swift in Sources */, diff --git a/PLUB/Sources/Models/MyPage/Response/MyFeedResponse.swift b/PLUB/Sources/Models/MyPage/Response/MyFeedResponse.swift new file mode 100644 index 000000000..a67dd7fac --- /dev/null +++ b/PLUB/Sources/Models/MyPage/Response/MyFeedResponse.swift @@ -0,0 +1,30 @@ +// +// MyFeedResponse.swift +// PLUB +// +// Created by 김수빈 on 2023/05/06. +// + +import Foundation + +struct MyFeedResponse: Codable { + let plubbingInfo: PlubbingInfo + let feedInfo: FeedInfo + + enum CodingKeys: String, CodingKey { + case plubbingInfo + case feedInfo = "myFeedList" + } +} + +struct FeedInfo: Codable { + let totalElements: Int + let isLast: Bool + let feedList: [FeedsContent] + + enum CodingKeys: String, CodingKey { + case totalElements + case isLast = "last" + case feedList = "content" + } +} diff --git a/PLUB/Sources/Models/MyPage/Response/MyTodoResponse.swift b/PLUB/Sources/Models/MyPage/Response/MyTodoResponse.swift index 6ff46d4ee..eb4d4f064 100644 --- a/PLUB/Sources/Models/MyPage/Response/MyTodoResponse.swift +++ b/PLUB/Sources/Models/MyPage/Response/MyTodoResponse.swift @@ -38,6 +38,6 @@ struct TodoContent: Codable { enum CodingKeys: String, CodingKey { case date, totalLikes, isAuthor, todoList - case todoID = "todoTimelineID" + case todoID = "todoTimelineId" } } diff --git a/PLUB/Sources/Network/Routers/MyPageRouter.swift b/PLUB/Sources/Network/Routers/MyPageRouter.swift index 5ecc00a53..66aa9b4db 100644 --- a/PLUB/Sources/Network/Routers/MyPageRouter.swift +++ b/PLUB/Sources/Network/Routers/MyPageRouter.swift @@ -10,12 +10,13 @@ import Alamofire enum MyPageRouter { case inquireMyMeeting(MyPlubbingParameter) case inquireMyTodo(Int, Int) + case inquireMyFeed(Int, Int) } extension MyPageRouter: Router { var method: HTTPMethod { switch self { - case .inquireMyMeeting, .inquireMyTodo: + case .inquireMyMeeting, .inquireMyTodo, .inquireMyFeed: return .get } } @@ -26,6 +27,8 @@ extension MyPageRouter: Router { return "/plubbings/all/my" case .inquireMyTodo(let plubbingID, _): return "/plubbings/\(plubbingID)/timeline/my" + case .inquireMyFeed(let plubbingID, _): + return "/plubbings/\(plubbingID)/feeds/my" } } @@ -35,12 +38,14 @@ extension MyPageRouter: Router { return .query(parameter) case .inquireMyTodo(_, let cursorID): return .query(["cursorId": cursorID]) + case .inquireMyFeed(_, let cursorID): + return .query(["cursorId": cursorID]) } } var headers: HeaderType { switch self { - case .inquireMyMeeting, .inquireMyTodo: + case .inquireMyMeeting, .inquireMyTodo, .inquireMyFeed: return .withAccessToken } } diff --git a/PLUB/Sources/Network/Services/MyPageService.swift b/PLUB/Sources/Network/Services/MyPageService.swift index f6491b53a..e2006274c 100644 --- a/PLUB/Sources/Network/Services/MyPageService.swift +++ b/PLUB/Sources/Network/Services/MyPageService.swift @@ -33,4 +33,11 @@ extension MyPageService { ) -> Observable { sendObservableRequest(MyPageRouter.inquireMyTodo(plubbingID, cursorID)) } + + func inquireMyFeed( + plubbingID: Int, + cursorID: Int + ) -> Observable { + sendObservableRequest(MyPageRouter.inquireMyFeed(plubbingID, cursorID)) + } } diff --git a/PLUB/Sources/UseCases/MyPage/InquireMyFeedUseCase.swift b/PLUB/Sources/UseCases/MyPage/InquireMyFeedUseCase.swift new file mode 100644 index 000000000..b2f858c6a --- /dev/null +++ b/PLUB/Sources/UseCases/MyPage/InquireMyFeedUseCase.swift @@ -0,0 +1,22 @@ +// +// InquireMyFeedUseCase.swift +// PLUB +// +// Created by 김수빈 on 2023/05/06. +// + +import UIKit + +import RxSwift + +protocol InquireMyFeedUseCase { + func execute(plubbingID: Int, cursorID: Int) -> Observable +} + +final class DefaultInquireMyFeedUseCase: InquireMyFeedUseCase { + + func execute(plubbingID: Int, cursorID: Int) -> Observable { + MyPageService.shared + .inquireMyFeed(plubbingID: plubbingID, cursorID: cursorID) + } +} diff --git a/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewController.swift b/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewController.swift index 81701b1d8..2b4fb9b9a 100644 --- a/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewController.swift +++ b/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewController.swift @@ -8,7 +8,7 @@ import UIKit final class ActiveMeetingViewController: BaseViewController { - private let viewModel: ActiveMeetingViewModel + private let viewModel: ActiveMeetingViewModelType private let recruitingHeaderView = RecruitingHeaderView() private lazy var tableView = UITableView(frame: .zero, style: .grouped).then { @@ -20,6 +20,8 @@ final class ActiveMeetingViewController: BaseViewController { $0.tableHeaderView = recruitingHeaderView $0.tableHeaderView?.frame.size.height = 114 $0.register(MyTodoTableViewCell.self, forCellReuseIdentifier: MyTodoTableViewCell.identifier) + $0.register(MyFeedTableViewCell.self, forCellReuseIdentifier: MyFeedTableViewCell.identifier) + $0.register(NoActivityTableViewCell.self, forCellReuseIdentifier: NoActivityTableViewCell.identifier) $0.register(MyTodoSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MyTodoSectionHeaderView.identifier) } @@ -29,7 +31,7 @@ final class ActiveMeetingViewController: BaseViewController { $0.layer.masksToBounds = true } - init(viewModel: ActiveMeetingViewModel) { + init(viewModel: ActiveMeetingViewModelType) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -65,12 +67,18 @@ final class ActiveMeetingViewController: BaseViewController { override func bind() { super.bind() - viewModel.meetingInfo + viewModel.meetingInfoDriver .drive(with: self) { owner, myInfo in owner.recruitingHeaderView.setupData(with: myInfo, type: .active) } .disposed(by: disposeBag) + viewModel.reloadTaleViewDriver + .drive(with: self) { owner, myInfo in + owner.tableView.reloadData() + } + .disposed(by: disposeBag) + recruitButton .rx.tap .asDriver() @@ -137,21 +145,65 @@ extension ActiveMeetingViewController: UITableViewDelegate { extension ActiveMeetingViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - return 2 + return MyActivityType.allCases.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell( - withIdentifier: MyTodoTableViewCell.identifier, - for: indexPath - ) as? MyTodoTableViewCell else { return UITableViewCell() } - - cell.setupData() - - return cell + switch MyActivityType.allCases[indexPath.section] { + case .todo: + if viewModel.todoList.isEmpty { + guard let cell = tableView.dequeueReusableCell( + withIdentifier: NoActivityTableViewCell.identifier, + for: indexPath + ) as? NoActivityTableViewCell else { return UITableViewCell() } + + cell.setupData(type: .todo) + + return cell + } else { + let todo = viewModel.todoList[indexPath.row] + guard let cell = tableView.dequeueReusableCell( + withIdentifier: MyTodoTableViewCell.identifier, + for: indexPath + ) as? MyTodoTableViewCell else { return UITableViewCell() } + + cell.setupData(with: todo) + + return cell + } + + case .post: + if viewModel.feedList.isEmpty { + guard let cell = tableView.dequeueReusableCell( + withIdentifier: NoActivityTableViewCell.identifier, + for: indexPath + ) as? NoActivityTableViewCell else { return UITableViewCell() } + + cell.setupData(type: .post) + + return cell + } else { + let feed = viewModel.feedList[indexPath.row] + guard let cell = tableView.dequeueReusableCell( + withIdentifier: MyFeedTableViewCell.identifier, + for: indexPath + ) as? MyFeedTableViewCell else { return UITableViewCell() } + + cell.configure(with: feed.toBoardModel) + + return cell + } + } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 2 + switch MyActivityType.allCases[section] { + case .todo: + let todoCount = viewModel.todoList.count + return todoCount == 0 ? 1 : todoCount + case .post: + let feedCount = viewModel.feedList.count + return feedCount == 0 ? 1 : feedCount + } } } diff --git a/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewModel.swift b/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewModel.swift index 70ac8bead..d04a04366 100644 --- a/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewModel.swift +++ b/PLUB/Sources/Views/MyPage/Active/ActiveMeetingViewModel.swift @@ -10,33 +10,61 @@ import Foundation import RxSwift import RxCocoa +protocol ActiveMeetingViewModelType { + // MARK: Property + var plubbingID: Int { get } + var todoList: [TodoContent] { get } + var feedList: [FeedsContent] { get } + + // MARK: Input + + // MARK: Output + var meetingInfoDriver: Driver { get } // 내 정보 데이터 + var reloadTaleViewDriver: Driver { get } // 테이블 뷰 리로드 +} + final class ActiveMeetingViewModel { private let disposeBag = DisposeBag() + + // MARK: Property private(set) var plubbingID: Int + private(set) var todoList = [TodoContent]() + private(set) var feedList = [FeedsContent]() + // MARK: UseCase private let inquireMyTodoUseCase: InquireMyTodoUseCase + private let inquireMyFeedUseCase: InquireMyFeedUseCase - // Output - let meetingInfo: Driver // 내 정보 데이터 - + // MARK: Subjects private let meetingInfoSubject = PublishSubject() + private let reloadTaleViewSubject = PublishSubject() - init(plubbingID: Int, inquireMyTodoUseCase: InquireMyTodoUseCase) { + init( + plubbingID: Int, + inquireMyTodoUseCase: InquireMyTodoUseCase, + inquireMyFeedUseCase: InquireMyFeedUseCase + ) { self.plubbingID = plubbingID self.inquireMyTodoUseCase = inquireMyTodoUseCase + self.inquireMyFeedUseCase = inquireMyFeedUseCase - meetingInfo = meetingInfoSubject.asDriver(onErrorDriveWith: .empty()) - - fetchMyTodo() + fetchActiveMeetingData() } - private func fetchMyTodo() { - inquireMyTodoUseCase + private func fetchActiveMeetingData(){ + let myTodo = inquireMyTodoUseCase + .execute(plubbingID: plubbingID, cursorID: 0) + let myFeed = inquireMyFeedUseCase .execute(plubbingID: plubbingID, cursorID: 0) - .withUnretained(self) - .subscribe(onNext: { owner, model in - owner.handleMeetingInfo(plubbing: model.plubbingInfo) - }) + + Observable.zip(myTodo, myFeed) + .subscribe(with: self) { owner, result in + let (myTodoResult, myFeedResult) = result + owner.handleMeetingInfo(plubbing: myTodoResult.plubbingInfo) + owner.handleMyTodoInfo(todoInfo: myTodoResult.todoInfo) + owner.handleMyFeedInfo(feedInfo: myFeedResult.feedInfo) + owner.reloadTaleViewSubject.onNext(()) + } .disposed(by: disposeBag) } @@ -52,6 +80,19 @@ final class ActiveMeetingViewModel { ) } + private func handleMyTodoInfo(todoInfo: TodoInfo) { + todoList = todoInfo.todoContent + .prefix(2) + .map { $0 } + } + + private func handleMyFeedInfo(feedInfo: FeedInfo) { + feedList = feedInfo.feedList + .filter { $0.viewType != .system } + .prefix(2) + .map { $0 } + } + private func createScheduleString(days: [Day], time: String) -> String { let dateStr = days .map { $0.kor } @@ -66,3 +107,15 @@ final class ActiveMeetingViewModel { return (dateStr.isEmpty ? "온라인" : dateStr) + " | " + timeStr } } + + +extension ActiveMeetingViewModel: ActiveMeetingViewModelType { + // MARK: Input + + // MARK: Output + var meetingInfoDriver: Driver { meetingInfoSubject.asDriver(onErrorDriveWith: .empty()) + } + + var reloadTaleViewDriver: Driver { reloadTaleViewSubject.asDriver(onErrorDriveWith: .empty()) + } +} diff --git a/PLUB/Sources/Views/MyPage/Active/Cell/MyFeedTableViewCell.swift b/PLUB/Sources/Views/MyPage/Active/Cell/MyFeedTableViewCell.swift new file mode 100644 index 000000000..06cfae3b0 --- /dev/null +++ b/PLUB/Sources/Views/MyPage/Active/Cell/MyFeedTableViewCell.swift @@ -0,0 +1,192 @@ +// +// MyFeedTableViewCell.swift +// PLUB +// +// Created by 김수빈 on 2023/05/07. +// + +import UIKit + +import Kingfisher +import SnapKit +import Then + +final class MyFeedTableViewCell: UITableViewCell { + + static let identifier = "MyFeedTableViewCell" + + // MARK: - Properties + + var feedID: Int? + + // MARK: - UI Components + + private let backView = UIView().then { + $0.backgroundColor = .white + $0.layer.cornerRadius = 10 + } + + private let wholeStackView = UIStackView().then { + $0.alignment = .top + $0.spacing = 8 + } + + private let containerStackView = UIStackView().then { + $0.axis = .vertical + $0.spacing = 8 + } + + private let headerStackView = UIStackView().then { + $0.distribution = .equalCentering + $0.alignment = .center + } + + private let contentImageView = UIImageView().then { + $0.layer.cornerRadius = 8 + $0.clipsToBounds = true + $0.contentMode = .scaleAspectFill + } + + // MARK: Boards Info + + private let boardsInfoStackView = UIStackView().then { + $0.alignment = .center + $0.spacing = 4 + } + + private let profileImageView = UIImageView(image: .init(named: "userDefaultImage")).then { + $0.contentMode = .scaleAspectFit + $0.layer.cornerRadius = 12 + $0.clipsToBounds = true + } + + private let authorLabel = UILabel().then { + $0.font = .caption2 + $0.textColor = .deepGray + } + + private let dateLabel = UILabel().then { + $0.font = .overLine + $0.textColor = .deepGray + } + + // MARK: Detail Info + + private let likeCommentStackView = UIStackView().then { + $0.alignment = .center + } + + private let heartImageView = UIImageView(image: .init(named: "heartFilled")) + private let heartCountLabel = UILabel().then { + $0.font = .overLine + $0.textColor = .deepGray + } + private let commentImageView = UIImageView(image: .init(named: "commentDots")) + private let commentCountLabel = UILabel().then { + $0.font = .overLine + $0.textColor = .deepGray + } + + // MARK: Content Info + + private let titleLabel = UILabel().then { + $0.font = .subtitle + $0.textColor = .black + } + + private let contentLabel = UILabel().then { + $0.numberOfLines = 2 + $0.font = .caption + $0.textColor = .black + } + + // MARK: - Initializations + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupLayouts() + setupConstraints() + setupStyles() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Configuration + + private func setupLayouts() { + contentView.addSubview(backView) + backView.addSubview(wholeStackView) + wholeStackView.addArrangedSubview(containerStackView) + wholeStackView.addArrangedSubview(contentImageView) + + [headerStackView, titleLabel, contentLabel].forEach { + containerStackView.addArrangedSubview($0) + } + + headerStackView.addArrangedSubview(boardsInfoStackView) + headerStackView.addArrangedSubview(likeCommentStackView) + + [profileImageView, authorLabel, dateLabel].forEach { + boardsInfoStackView.addArrangedSubview($0) + } + + [heartImageView, heartCountLabel, commentImageView, commentCountLabel].forEach { + likeCommentStackView.addArrangedSubview($0) + } + } + + private func setupConstraints() { + backView.snp.makeConstraints { + $0.directionalVerticalEdges.equalToSuperview().inset(4) + $0.directionalHorizontalEdges.equalToSuperview().inset(16) + } + + wholeStackView.snp.makeConstraints { + $0.directionalVerticalEdges.equalToSuperview().inset(12) + $0.directionalHorizontalEdges.equalToSuperview().inset(16) + } + + profileImageView.snp.makeConstraints { + $0.size.equalTo(24) + } + } + + private func setupStyles() { + backgroundColor = .background + selectionStyle = .none + } + + func configure(with model: BoardModel) { + feedID = model.feedID + if let profileImageLink = model.authorProfileImageLink { + profileImageView.kf.setImage(with: URL(string: profileImageLink)) + } + authorLabel.text = model.author + dateLabel.text = DateFormatter().then { $0.dateFormat = "| yyyy. MM. dd" }.string(from: model.date) + heartCountLabel.text = String(model.likeCount) + commentCountLabel.text = String(model.commentCount) + + titleLabel.text = model.title + contentLabel.text = model.content + if let contentImageLink = model.imageLink { + contentImageView.kf.setImage(with: URL(string: contentImageLink)) + } + + // change stackview's axis + wholeStackView.axis = model.type == .photoAndText ? .horizontal : .vertical + wholeStackView.alignment = model.type == .photoAndText ? .top : .fill + + // change constraints according to `model.type` + if model.type != .photo { + contentImageView.snp.remakeConstraints { + $0.size.lessThanOrEqualTo(90) + } + } else { + contentImageView.snp.remakeConstraints { + $0.height.lessThanOrEqualTo(222) + } + } + } +} diff --git a/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoSectionHeaderView.swift b/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoSectionHeaderView.swift index e91babab2..3bd4fb721 100644 --- a/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoSectionHeaderView.swift +++ b/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoSectionHeaderView.swift @@ -22,6 +22,15 @@ enum MyActivityType: CaseIterable { return "MY 게시글" } } + + var noneText: String { + switch self { + case .todo: + return "To-Do 추가하러 가기" + case .post: + return "새 게시글 작성하기" + } + } } protocol MyTodoSectionHeaderViewDelegate: AnyObject { diff --git a/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoTableViewCell.swift b/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoTableViewCell.swift index 3be476413..333add0b8 100644 --- a/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoTableViewCell.swift +++ b/PLUB/Sources/Views/MyPage/Active/Cell/MyTodoTableViewCell.swift @@ -31,7 +31,6 @@ final class MyTodoTableViewCell: UITableViewCell { private let dateLabel = UILabel().then { $0.font = .subtitle $0.textColor = .main - $0.text = "오늘" } private let likeButton = UIButton().then { @@ -42,7 +41,6 @@ final class MyTodoTableViewCell: UITableViewCell { private let likeLabel = UILabel().then { $0.textColor = .deepGray $0.font = .overLine - $0.text = "3" } private let todoStackView = UIStackView().then { @@ -128,10 +126,49 @@ final class MyTodoTableViewCell: UITableViewCell { selectionStyle = .none } - func setupData() { - for _ in 0...5 { - let todoListView = TodoListView() + func setupData(with todo: TodoContent) { + let date = DateFormatterFactory + .dateWithHypen + .date(from: todo.date) ?? Date() + + let isPast = (Date().compare(date) == .orderedDescending) ? true : false + + setupLineAndPointView(isPast: isPast) + setupTodoViewStyle(isPast: isPast) + setupDateLabel(date: date, isPast: isPast) + setupLikeButton(totalLikes: todo.totalLikes) + + for todoItem in todo.todoList { + let todoListView = TodoListView(todo: todoItem) todoStackView.addArrangedSubview(todoListView) } } + + private func setupLineAndPointView(isPast: Bool) { + lineView.backgroundColor = isPast ? UIColor(hex: 0xD9D9D9): .main + let pointImage = isPast ? UIImage(named: "pointInactived") : UIImage(named: "pointActived") + pointImageView.image = pointImage + } + + private func setupTodoViewStyle(isPast: Bool) { + todoView.backgroundColor = isPast ? .white : .subBackground + todoView.layer.borderColor = isPast ? UIColor.white.cgColor : UIColor.main.cgColor + } + + private func setupDateLabel(date: Date, isPast: Bool) { + dateLabel.textColor = isPast ? .deepGray : .main + + let todoDateText = Calendar.current.isDateInToday(date) + ? "오늘" + : DateFormatterFactory + .todolistDate.string(from: date) + + dateLabel.text = todoDateText + } + + private func setupLikeButton(totalLikes: Int) { + likeLabel.text = "\(totalLikes)" + let buttonImage = totalLikes > 0 ? UIImage(named: "heartFilled") : UIImage(named: "heart") + likeButton.setImage(buttonImage, for: .normal) + } } diff --git a/PLUB/Sources/Views/MyPage/Active/Cell/NoActivityTableViewCell.swift b/PLUB/Sources/Views/MyPage/Active/Cell/NoActivityTableViewCell.swift new file mode 100644 index 000000000..115ecbab2 --- /dev/null +++ b/PLUB/Sources/Views/MyPage/Active/Cell/NoActivityTableViewCell.swift @@ -0,0 +1,55 @@ +// +// NoActivityTableViewCell.swift +// PLUB +// +// Created by 김수빈 on 2023/05/07. +// + +import UIKit + +import SnapKit +import Then + +final class NoActivityTableViewCell: UITableViewCell { + static let identifier = "NoActivityTableViewCell" + + private let button = UIButton().then { + $0.backgroundColor = .lightGray + $0.layer.cornerRadius = 10 + $0.titleLabel?.font = .button + $0.setTitleColor(.deepGray, for: .normal) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupLayouts() + setupConstraints() + setupStyles() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupLayouts() { + contentView.addSubview(button) + } + + private func setupConstraints() { + button.snp.makeConstraints { + $0.directionalHorizontalEdges.equalToSuperview().inset(17) + $0.top.equalToSuperview() + $0.bottom.equalToSuperview().inset(8) + $0.height.equalTo(48) + } + } + + private func setupStyles() { + backgroundColor = .background + selectionStyle = .none + } + + func setupData(type: MyActivityType) { + button.setTitle(type.noneText, for: .normal) + } +} diff --git a/PLUB/Sources/Views/MyPage/Active/Component/TodoListView.swift b/PLUB/Sources/Views/MyPage/Active/Component/TodoListView.swift index 5c495e551..6abbcdb2b 100644 --- a/PLUB/Sources/Views/MyPage/Active/Component/TodoListView.swift +++ b/PLUB/Sources/Views/MyPage/Active/Component/TodoListView.swift @@ -16,13 +16,13 @@ final class TodoListView: UIView { private let todoLabel = UILabel().then { $0.textColor = .black $0.font = .body2 - $0.text = "독후감 쓴 내용 팀원들이랑 공유하기" } - override init(frame: CGRect) { - super.init(frame: frame) + init(todo: Todo) { + super.init(frame: .zero) setupLayouts() setupConstraints() + setupData(todo: todo) } required init?(coder: NSCoder) { @@ -48,4 +48,19 @@ final class TodoListView: UIView { $0.trailing.equalToSuperview() } } + + private func setupData(todo: Todo) { + let attributeString: NSAttributedString + if todo.isChecked { + attributeString = NSAttributedString( + string: todo.content, + attributes: [.strikethroughStyle: NSUnderlineStyle.single.rawValue] + ) + } else { + attributeString = NSAttributedString(string: todo.content) + } + todoLabel.attributedText = attributeString + + checkButton.isChecked = todo.isChecked + } } diff --git a/PLUB/Sources/Views/MyPage/MyPageViewController.swift b/PLUB/Sources/Views/MyPage/MyPageViewController.swift index 1d1955db4..d856896ff 100644 --- a/PLUB/Sources/Views/MyPage/MyPageViewController.swift +++ b/PLUB/Sources/Views/MyPage/MyPageViewController.swift @@ -180,7 +180,8 @@ extension MyPageViewController: UITableViewDelegate { let vc = ActiveMeetingViewController( viewModel: ActiveMeetingViewModel( plubbingID: plubbingID, - inquireMyTodoUseCase: DefaultInquireMyTodoUseCase() + inquireMyTodoUseCase: DefaultInquireMyTodoUseCase(), + inquireMyFeedUseCase: DefaultInquireMyFeedUseCase() ) ) vc.hidesBottomBarWhenPushed = true