From c7a6bd441d70c4ff6fb691dcbe4e52061a1084bf Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:28:49 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20#453=20=EA=B3=B5=ED=86=B5=ED=80=98?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=9D=B8=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B3=B5=EA=B0=90=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestViewController.swift | 30 +++++++++++++++-- .../ViewModel/CommonQuestViewModel.swift | 32 +++++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift index 48dbb117..e67eadb2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -86,6 +86,31 @@ extension CommonQuestViewController { } } .store(in: &cancellable) + + viewModel.output.commonQuestLikeCountPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let result): + self?.updateLikeCount(answerID: result.answerID, likeCount: result.likeCount) + case .failure(let error): + ByeBooLogger.error(error) + } + } + .store(in: &cancellable) + } + + private func updateLikeCount(answerID: Int, likeCount: Int) { + guard let answerIndex = viewModel.indexOfAnswer(answerID: answerID) else { + return + } + + let indexPath = IndexPath(row: answerIndex + 1, section: 0) + guard let cell = rootView.commonQuestTableView.cellForRow(at: indexPath) as? CommonQuestAnswerCell else { + return + } + + cell.questContentView.updateUI(likeCount: likeCount) } } @@ -266,7 +291,8 @@ extension CommonQuestViewController: UITableViewDataSource { } extension CommonQuestViewController: CommonQuestLikeCommentProtocol { - func likeButtonDidTap() { - // TODO: like button + func likeButtonDidTap(answerID: Int) { + ByeBooLogger.debug("answerID: \(answerID)") + viewModel.action(.likeButtonDidTap(answerID: answerID)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift index 1c84b4e6..3a2b7391 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -12,7 +12,10 @@ final class CommonQuestViewModel { private let cancellables = Set() private let commonQuestSubject = PassthroughSubject, Never>.init() + private let likeCountSubject = PassthroughSubject, Never>.init() + private let fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase + private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase private let minute: Double = 60 private let hour: Double = 3600 private let day: Double = 86400 @@ -24,10 +27,16 @@ final class CommonQuestViewModel { private var nextCursor: Int? = nil private var currentDate: String = DateFormatter.toAPIDateString(from: .now) - init(fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase) { + init( + fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase, + postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase + ) { self.fetchCommonQuestByDateUseCase = fetchCommonQuestByDateUseCase + self.postCommonQuestLikeUseCase = postCommonQuestLikeUseCase + self.output = Output( - commonQuestPublisher: commonQuestSubject.eraseToAnyPublisher() + commonQuestPublisher: commonQuestSubject.eraseToAnyPublisher(), + commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } @@ -57,6 +66,17 @@ final class CommonQuestViewModel { } } } + + private func postCommonQuestLike(answerID: Int) { + Task { + do { + let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) + likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) + } catch (let error as ByeBooError) { + likeCountSubject.send(.failure(error)) + } + } + } } extension CommonQuestViewModel: ViewModelType { @@ -65,10 +85,12 @@ extension CommonQuestViewModel: ViewModelType { case viewWillAppear case moveDateButtonDidTap(selectedDate: String) case scrollAnswer + case likeButtonDidTap(answerID: Int) } struct Output { let commonQuestPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -85,6 +107,8 @@ extension CommonQuestViewModel: ViewModelType { return } fetchCommonQuestByDate(date: currentDate, cursor: nextCursor) + case .likeButtonDidTap(let answerID): + postCommonQuestLike(answerID: answerID) } } } @@ -127,6 +151,10 @@ extension CommonQuestViewModel { } return answers[index].answerID } + + func indexOfAnswer(answerID: Int) -> Int? { + answers.firstIndex { $0.answerID == answerID } + } func getProfileIcon(at index: Int) -> UIImage? { guard index >= 0 && index < answers.count else { return nil } From 4bc9844917a4d6a668323167ec7c14b6d0a68ed8 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:29:39 +0900 Subject: [PATCH 2/9] =?UTF-8?q?chore:=20#453=20answerID=20=EB=93=A4?= =?UTF-8?q?=EA=B3=A0=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Cells/CommonQuestAnswerCell.swift | 1 + .../Cells/CommonQuestMyAnswerCell.swift | 2 ++ .../View/CommonQuest/Common/QuestContentView.swift | 14 ++++++++++---- .../View/CommonQuest/CommonQuestHistoryView.swift | 2 ++ .../CommonQuestHistoryViewController.swift | 1 + .../CommonQuestMyAnswersViewController.swift | 1 + 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift index ea9ea4a4..4ca91827 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift @@ -94,6 +94,7 @@ extension CommonQuestAnswerCell { userNicknameLabel.text = answer.writer answerID = answer.answerID questContentView.configure( + answerID: answer.answerID, content: answer.content, writtenAt: writtenAt, isLiked: answer.isLiked, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift index 650ce1ec..9f6164c4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift @@ -95,6 +95,7 @@ final class CommonQuestMyAnswerCell: UITableViewCell { extension CommonQuestMyAnswerCell { func bind( + answerID: Int, question: String, content: String, writtenAt: String, @@ -104,6 +105,7 @@ extension CommonQuestMyAnswerCell { ) { questionContentLabel.text = question questContentView.configure( + answerID: answerID, content: content, writtenAt: writtenAt, isLiked: isLiked, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index c405fcc8..8903d482 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -8,7 +8,7 @@ import UIKit protocol CommonQuestLikeCommentProtocol: AnyObject { - func likeButtonDidTap() + func likeButtonDidTap(answerID: Int) } final class QuestContentView: BaseView { @@ -26,6 +26,7 @@ final class QuestContentView: BaseView { weak var delegate: CommonQuestLikeCommentProtocol? + private var answerID: Int = 0 private var likeCounts: Int = 0 override func setUI() { @@ -102,6 +103,7 @@ final class QuestContentView: BaseView { } func configure( + answerID: Int, content: String, writtenAt: String? = nil, isLiked: Bool, @@ -109,6 +111,7 @@ final class QuestContentView: BaseView { commentCount: Int, showAllText: Bool ) { + self.answerID = answerID self.likeCounts = likeCount answerContentTextView.do { $0.textContainer.maximumNumberOfLines = showAllText ? 0 : 2 @@ -123,11 +126,14 @@ final class QuestContentView: BaseView { commentCountLabel.text = String(commentCount) } + func updateUI(likeCount: Int) { + likeCountLabel.text = String(likeCount) + } + @objc private func likeButtonDidTap() { likeButton.isSelected.toggle() - likeCounts += likeButton.isSelected ? 1 : -1 - likeCountLabel.text = String(likeCounts) - delegate?.likeButtonDidTap() +// likeCountLabel.text = String(likeCounts) + delegate?.likeButtonDidTap(answerID: self.answerID) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift index d807133c..3720d36a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift @@ -165,6 +165,7 @@ extension CommonQuestHistoryView { extension CommonQuestHistoryView { func configure( + answerID: Int, question: String, writtenAt: String, profileIcon: UIImage, @@ -177,6 +178,7 @@ extension CommonQuestHistoryView { questionContentLabel.text = question dateLabel.text = writtenAt questContentView.configure( + answerID: answerID, content: content, isLiked: isLiked, likeCount: likeCount, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index 8ff99038..5dfecc24 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -238,6 +238,7 @@ extension CommonQuestHistoryViewController { self.writerID = answer.writerID rootView.configure( + answerID: answerID, question: entity.question, writtenAt: ServerDateFormatter.shared.relativeTimeString(from: answer.writtenAt) ?? "", //TODO: ViewModel로 수정 profileIcon: ProfileIcon.image(for: answer.profileIcon) ?? .relievedBadge, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift index acfa8a41..c3aa9cae 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -191,6 +191,7 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { let cell: CommonQuestMyAnswerCell = tableView.dequeueReusableCell(for: indexPath) cell.questContentView.delegate = self cell.bind( + answerID: answer.answerID, question: answer.question, content: answer.content, writtenAt: answer.writtenAt, From 0ac791549390f5b238f407a337c103d316d61770 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:29:59 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20#453=20=EA=B3=B5=EA=B0=90=20api=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Model/PostCommonQuestLikeResponseDTO.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift new file mode 100644 index 00000000..0949503c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift @@ -0,0 +1,13 @@ +// +// postCommonQuestLikeResponseDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/10/26. +// + +import Foundation + +struct PostCommonQuestLikeResponseDTO: Decodable { + let likeCount: Int + let isLiked: Bool +} From 0f1189dbd103032c4112bbe4bcf8d91320fdb477 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:30:28 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20#453=20=EA=B3=B5=EA=B0=90=20API=20U?= =?UTF-8?q?seCase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/EndPoint/CommonQuestAPI.swift | 16 ++++++++----- .../Repository/CommonQuestRepository.swift | 8 +++++++ .../Interface/CommonQuestInterface.swift | 1 + .../UseCase/PostCommonQuestLikeUseCase.swift | 24 +++++++++++++++++++ 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift index 1a83bec4..5468fc36 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift @@ -15,6 +15,7 @@ enum CommonQuestAPI { case fetchCommonQuestDetail(asnwerID: Int) case updateCommonQuest(answerID: Int, dto: UpdateCommonQuestRequestDTO) case deleteCommonQuest(answerID: Int) + case postCommonQuestLike(answerID: Int) } extension CommonQuestAPI: EndPoint { @@ -23,7 +24,7 @@ extension CommonQuestAPI: EndPoint { switch self { case .fetchCommonQuest, .fetchCommonQuestDetail: return "/api/v2/common-quests" - case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest: + case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest, .postCommonQuestLike: return "/api/v1/common-quests" } } @@ -36,12 +37,14 @@ extension CommonQuestAPI: EndPoint { return "" case .updateCommonQuest(let answerID, _), .deleteCommonQuest(let answerID), .fetchCommonQuestDetail(let answerID): return "/\(answerID)" + case .postCommonQuestLike(let answerID): + return "/\(answerID)/likes" } } var method: HTTPMethod { switch self { - case .postCommonQuest: + case .postCommonQuest, .postCommonQuestLike: return .post case .fetchCommonQuest, .fetchCommonQuestDetail: return .get @@ -54,14 +57,15 @@ extension CommonQuestAPI: EndPoint { var headers: HeaderType { switch self { - case .postCommonQuest, .fetchCommonQuest, .updateCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: + case .postCommonQuest, .fetchCommonQuest, .updateCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail, + .postCommonQuestLike: return .withAuth } } var parameterEncoding: any ParameterEncoding { switch self { - case .postCommonQuest, .updateCommonQuest: + case .postCommonQuest, .updateCommonQuest, .postCommonQuestLike: return JSONEncoding.default case .fetchCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: return URLEncoding.default @@ -70,7 +74,7 @@ extension CommonQuestAPI: EndPoint { var queryParameters: [String : String]? { switch self { - case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: + case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail, .postCommonQuestLike: return nil case .fetchCommonQuest(let date, let cursor): if let cursor { @@ -87,7 +91,7 @@ extension CommonQuestAPI: EndPoint { switch self { case .postCommonQuest(_, let dto): return try? dto.toDictionary() - case .fetchCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: + case .fetchCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail, .postCommonQuestLike: return nil case .updateCommonQuest(_, let dto): return try? dto.toDictionary() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index 9057aacd..c6710114 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -74,4 +74,12 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { return commonQuestDetail.toEntity(userID: userID) } + + func postCommonQuestLikes(answerID: Int) async throws -> Int { + let response = try await network.request( + CommonQuestAPI.postCommonQuestLike(answerID: answerID), + decodingType: PostCommonQuestLikeResponseDTO.self + ) + return response.likeCount + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift index d2647f63..cc3bc3ec 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift @@ -13,4 +13,5 @@ protocol CommonQuestInterface { func updateCommonQuest(answerID: Int, answer: String) async throws func deleteCommonQuest(answerID: Int) async throws func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity + func postCommonQuestLikes(answerID: Int) async throws -> Int } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift new file mode 100644 index 00000000..10629b53 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift @@ -0,0 +1,24 @@ +// +// PostCommonQuestLikeUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/10/26. +// + +import Foundation + +protocol PostCommonQuestLikeUseCase { + func execute(answerID: Int) async throws -> Int +} + +struct DefaultPostCommonQuestLikeUseCase: PostCommonQuestLikeUseCase { + private let repository: CommonQuestInterface + + init(repository: CommonQuestInterface) { + self.repository = repository + } + + func execute(answerID: Int) async throws -> Int { + return try await repository.postCommonQuestLikes(answerID: answerID) + } +} From fa06565a236d2f64d95c6f3c7a24127bc4db9df8 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:30:40 +0900 Subject: [PATCH 5/9] =?UTF-8?q?chore:=20#453=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 5 +++++ .../Presentation/PresentationDependencyAssembler.swift | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 462e36cc..8cca03fa 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -207,8 +207,13 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: DeleteCommonQuestUseCase.self) { _ in return DefaultDeleteCommonQuestUseCase(repository: commonQuestRepository) } + DIContainer.shared.register(type: FetchCommonQuestDetailUseCase.self) { _ in return DefaultFetchCommonQuestDetailUseCase(repository: commonQuestRepository) } + + DIContainer.shared.register(type: PostCommonQuestLikeUseCase.self) { _ in + return DefaultPostCommonQuestLikeUseCase(repository: commonQuestRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 8b693e49..82aa50f5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -286,13 +286,15 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: CommonQuestViewModel.self) { container in - guard let fetchCommonQuestByDateUseCase = container.resolve(type: FetchCommonQuestByDateUseCase.self) else { + guard let fetchCommonQuestByDateUseCase = container.resolve(type: FetchCommonQuestByDateUseCase.self), + let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } return CommonQuestViewModel( - fetchCommonQuestByDateUseCase: fetchCommonQuestByDateUseCase + fetchCommonQuestByDateUseCase: fetchCommonQuestByDateUseCase, + postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } From e2c402a5fcffdc49fbc66cb98b7bcc5a667c0eec Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:44:32 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20#453=20=EB=82=B4=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=80=98=EC=8A=A4=ED=8A=B8=20=EB=AA=A8=EC=95=84=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EA=B3=B5=EA=B0=90=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestMyAnswersViewController.swift | 32 +++++++++++++++++-- .../CommonQuestMyAnswerViewModel.swift | 31 ++++++++++++++++-- .../PresentationDependencyAssembler.swift | 6 ++-- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift index c3aa9cae..ed21044f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -69,6 +69,7 @@ extension CommonQuestMyAnswersViewController { private func bind() { bindName() bindCommonQuestAnswers() + bindLikeCount() } private func bindName() { @@ -98,6 +99,33 @@ extension CommonQuestMyAnswersViewController { } .store(in: &cancellable) } + + private func bindLikeCount() { + viewModel.output.commonQuestLikeCountPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let result): + self?.updateLikeCount(answerID: result.answerID, likeCount: result.likeCount) + case .failure(let error): + ByeBooLogger.error(error) + } + } + .store(in: &cancellable) + } + + private func updateLikeCount(answerID: Int, likeCount: Int) { + guard let answerIndex = viewModel.indexOfAnswer(answerID: answerID) else { + return + } + + let indexPath = IndexPath(row: 0, section: answerIndex) + guard let cell = rootView.answersTableView.cellForRow(at: indexPath) as? CommonQuestMyAnswerCell else { + return + } + + cell.questContentView.updateUI(likeCount: likeCount) + } } extension CommonQuestMyAnswersViewController: UITableViewDelegate { @@ -204,7 +232,7 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { } extension CommonQuestMyAnswersViewController: CommonQuestLikeCommentProtocol { - func likeButtonDidTap() { - // TODO: like button + func likeButtonDidTap(answerID: Int) { + viewModel.action(.likeButtonDidTap(answerID: answerID)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift index a41dafa8..e296559c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -13,8 +13,12 @@ final class CommonQuestMyAnswerViewModel { private let cancellables = Set() private let nameSubject = PassthroughSubject, Never>.init() private let answersSubject = PassthroughSubject, Never>.init() + private let likeCountSubject = PassthroughSubject, Never>.init() + private let getUserNameUseCase: GetUserNameUseCase private let fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase + private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase + private(set) var output: Output private var commonQuestAnswers: CommonQuestMyAnswersEntity? @@ -24,13 +28,17 @@ final class CommonQuestMyAnswerViewModel { init( getUserNameUseCase: GetUserNameUseCase, - fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase + fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase, + postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase ) { self.getUserNameUseCase = getUserNameUseCase self.fetchCommonQuestMyAnswersUseCase = fetchCommonQuestMyAnswersUseCase + self.postCommonQuestLikeUseCase = postCommonQuestLikeUseCase + self.output = Output( namePublisher: nameSubject.eraseToAnyPublisher(), - answersPublisher: answersSubject.eraseToAnyPublisher() + answersPublisher: answersSubject.eraseToAnyPublisher(), + commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } @@ -58,6 +66,17 @@ final class CommonQuestMyAnswerViewModel { } } } + + private func postCommonQuestLike(answerID: Int) { + Task { + do { + let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) + likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) + } catch (let error as ByeBooError) { + likeCountSubject.send(.failure(error)) + } + } + } } extension CommonQuestMyAnswerViewModel: ViewModelType { @@ -65,11 +84,13 @@ extension CommonQuestMyAnswerViewModel: ViewModelType { enum Input { case viewWillAppear case scrollAnswer + case likeButtonDidTap(answerID: Int) } struct Output { let namePublisher: AnyPublisher, Never> let answersPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -79,6 +100,8 @@ extension CommonQuestMyAnswerViewModel: ViewModelType { fetchUserCommonQuestAnswers() case .scrollAnswer: fetchUserCommonQuestAnswers(cursor: nextCursor) + case .likeButtonDidTap(let answerID): + postCommonQuestLike(answerID: answerID) } } } @@ -89,6 +112,10 @@ extension CommonQuestMyAnswerViewModel { answers.count } + func indexOfAnswer(answerID: Int) -> Int? { + answers.firstIndex { $0.answerID == answerID } + } + func getAnswer(at index: Int) -> CommonQuestMyAnswerEntity? { guard index >= 0 && index < answersCount else { return nil diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 82aa50f5..6b7273fa 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -300,14 +300,16 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: CommonQuestMyAnswerViewModel.self) { container in guard let getUserNameUseCase = container.resolve(type: GetUserNameUseCase.self), - let fetchCommonQuestMyAnswersUseCase = container.resolve(type: FetchCommonQuestMyAnswersUseCase.self) else { + let fetchCommonQuestMyAnswersUseCase = container.resolve(type: FetchCommonQuestMyAnswersUseCase.self), + let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } return CommonQuestMyAnswerViewModel( getUserNameUseCase: getUserNameUseCase, - fetchCommonQuestMyAnswersUseCase: fetchCommonQuestMyAnswersUseCase + fetchCommonQuestMyAnswersUseCase: fetchCommonQuestMyAnswersUseCase, + postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } From 3bc3258133da7480327db7a86f23b2691e0e1257 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 18:01:10 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20#453=20histroyVC=20=EA=B3=B5?= =?UTF-8?q?=EA=B0=90=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Common/QuestContentView.swift | 1 - .../CommonQuest/CommonQuestHistoryView.swift | 2 +- .../CommonQuestHistoryViewController.swift | 19 +++++++++++++++ .../CommonQuestHistoryViewModel.swift | 24 +++++++++++++++++-- .../PresentationDependencyAssembler.swift | 6 +++-- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index 8903d482..7ade4def 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -133,7 +133,6 @@ final class QuestContentView: BaseView { @objc private func likeButtonDidTap() { likeButton.isSelected.toggle() -// likeCountLabel.text = String(likeCounts) delegate?.likeButtonDidTap(answerID: self.answerID) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift index 3720d36a..0554be48 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift @@ -23,7 +23,7 @@ final class CommonQuestHistoryView: BaseView { private let answerView = UIView() private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() - private let questContentView = QuestContentView() + private(set) var questContentView = QuestContentView() private(set) var commentListView = SelfSizingTableView() private let commentTextView = CommentTextView() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index 5dfecc24..0f0760f3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -76,6 +76,7 @@ final class CommonQuestHistoryViewController: BaseViewController { $0.separatorStyle = .none $0.register(CommentTableViewCell.self) } + rootView.questContentView.delegate = self } } @@ -231,6 +232,18 @@ extension CommonQuestHistoryViewController { } } .store(in: &cancellable) + + viewModel.output.commonQuestLikeCountPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let result): + self?.rootView.questContentView.updateUI(likeCount: result.likeCount) + case .failure(let error): + ByeBooLogger.error(error) + } + } + .store(in: &cancellable) } private func bindData(entity: CommonQuestDetailEntity) { @@ -289,3 +302,9 @@ extension CommonQuestHistoryViewController: KeyboardHandleProtocol { } } } + +extension CommonQuestHistoryViewController: CommonQuestLikeCommentProtocol { + func likeButtonDidTap(answerID: Int) { + viewModel.action(.likeButtonDidTap(answerID: answerID)) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift index 9c0d1fe6..fee6a1c1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -10,20 +10,25 @@ import Foundation final class CommonQuestHistoryViewModel { private let fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase + private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase private let fetchCommentListSubject: PassthroughSubject, Never> = .init() + private let likeCountSubject = PassthroughSubject, Never>.init() private var entity: CommonQuestDetailEntity? = nil private var cancellables = Set() private(set) var output: Output init( - fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase + fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase, + postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase ) { self.fetchCommonQuestCommentsUseCase = fetchCommonQuestCommentsUseCase + self.postCommonQuestLikeUseCase = postCommonQuestLikeUseCase output = Output( - fetchCommonQuestDetailPublisher: fetchCommentListSubject.eraseToAnyPublisher() + fetchCommonQuestDetailPublisher: fetchCommentListSubject.eraseToAnyPublisher(), + commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } } @@ -31,16 +36,20 @@ final class CommonQuestHistoryViewModel { extension CommonQuestHistoryViewModel: ViewModelType { enum Input { case viewWillAppear(answerID: Int) + case likeButtonDidTap(answerID: Int) } struct Output { let fetchCommonQuestDetailPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { switch trigger { case .viewWillAppear(let answerID): fetchCommonQuestComments(answerID: answerID) + case .likeButtonDidTap(let answerID): + postCommonQuestLike(answerID: answerID) } } } @@ -72,4 +81,15 @@ extension CommonQuestHistoryViewModel { } } } + + private func postCommonQuestLike(answerID: Int) { + Task { + do { + let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) + likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) + } catch (let error as ByeBooError) { + likeCountSubject.send(.failure(error)) + } + } + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 6b7273fa..5d7c1297 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -355,13 +355,15 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: CommonQuestHistoryViewModel.self) { container in - guard let fetchCommonQuestDetailUseCase = container.resolve(type: FetchCommonQuestDetailUseCase.self) else { + guard let fetchCommonQuestDetailUseCase = container.resolve(type: FetchCommonQuestDetailUseCase.self), + let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } return CommonQuestHistoryViewModel( - fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase + fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase, + postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } } From 4e431cd5a2fb88d1c3691732bd40279843fd180a Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 14 Jun 2026 15:42:50 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20#453=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostCommonQuestLikeResponseDTO.swift | 6 +++++ .../Repository/CommonQuestRepository.swift | 4 ++-- .../Domain/Entity/CommonQuestLikeEntity.swift | 13 +++++++++++ .../Interface/CommonQuestInterface.swift | 2 +- .../UseCase/PostCommonQuestLikeUseCase.swift | 4 ++-- .../CommonQuest/Common/QuestContentView.swift | 4 ++-- .../CommonQuestHistoryViewController.swift | 3 ++- .../CommonQuestMyAnswersViewController.swift | 11 +++++++--- .../CommonQuestViewController.swift | 7 +++--- .../CommonQuestHistoryViewModel.swift | 21 +++++++++++++----- .../CommonQuestMyAnswerViewModel.swift | 22 ++++++++++++++----- .../ViewModel/CommonQuestViewModel.swift | 21 +++++++++++++----- 12 files changed, 86 insertions(+), 32 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift index 0949503c..4370f012 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/PostCommonQuestLikeResponseDTO.swift @@ -11,3 +11,9 @@ struct PostCommonQuestLikeResponseDTO: Decodable { let likeCount: Int let isLiked: Bool } + +extension PostCommonQuestLikeResponseDTO { + func toEntity() -> CommonQuestLikeEntity { + .init(isLiked: isLiked, likeCount: likeCount) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index c6710114..8a766109 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -75,11 +75,11 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { return commonQuestDetail.toEntity(userID: userID) } - func postCommonQuestLikes(answerID: Int) async throws -> Int { + func postCommonQuestLikes(answerID: Int) async throws -> CommonQuestLikeEntity { let response = try await network.request( CommonQuestAPI.postCommonQuestLike(answerID: answerID), decodingType: PostCommonQuestLikeResponseDTO.self ) - return response.likeCount + return response.toEntity() } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swift new file mode 100644 index 00000000..9f30a2cf --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestLikeEntity.swift @@ -0,0 +1,13 @@ +// +// CommonQuestLikeEntity.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/13/26. +// + +import Foundation + +struct CommonQuestLikeEntity { + let isLiked: Bool + let likeCount: Int +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift index cc3bc3ec..478df520 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift @@ -13,5 +13,5 @@ protocol CommonQuestInterface { func updateCommonQuest(answerID: Int, answer: String) async throws func deleteCommonQuest(answerID: Int) async throws func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity - func postCommonQuestLikes(answerID: Int) async throws -> Int + func postCommonQuestLikes(answerID: Int) async throws -> CommonQuestLikeEntity } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift index 10629b53..50a0e7b4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestLikeUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol PostCommonQuestLikeUseCase { - func execute(answerID: Int) async throws -> Int + func execute(answerID: Int) async throws -> CommonQuestLikeEntity } struct DefaultPostCommonQuestLikeUseCase: PostCommonQuestLikeUseCase { @@ -18,7 +18,7 @@ struct DefaultPostCommonQuestLikeUseCase: PostCommonQuestLikeUseCase { self.repository = repository } - func execute(answerID: Int) async throws -> Int { + func execute(answerID: Int) async throws -> CommonQuestLikeEntity { return try await repository.postCommonQuestLikes(answerID: answerID) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index 7ade4def..8306dc88 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -126,13 +126,13 @@ final class QuestContentView: BaseView { commentCountLabel.text = String(commentCount) } - func updateUI(likeCount: Int) { + func updateUI(likeCount: Int, isLiked: Bool) { likeCountLabel.text = String(likeCount) + likeButton.isSelected = isLiked } @objc private func likeButtonDidTap() { - likeButton.isSelected.toggle() delegate?.likeButtonDidTap(answerID: self.answerID) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index 0f0760f3..8079f645 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -238,7 +238,8 @@ extension CommonQuestHistoryViewController { .sink { [weak self] result in switch result { case .success(let result): - self?.rootView.questContentView.updateUI(likeCount: result.likeCount) + let entity = result.entity + self?.rootView.questContentView.updateUI(likeCount: entity.likeCount, isLiked: entity.isLiked) case .failure(let error): ByeBooLogger.error(error) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift index ed21044f..c4abda6d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -106,7 +106,12 @@ extension CommonQuestMyAnswersViewController { .sink { [weak self] result in switch result { case .success(let result): - self?.updateLikeCount(answerID: result.answerID, likeCount: result.likeCount) + let entity = result.entity + self?.updateLikeCount( + answerID: result.answerID, + likeCount: entity.likeCount, + isLiked: entity.isLiked + ) case .failure(let error): ByeBooLogger.error(error) } @@ -114,7 +119,7 @@ extension CommonQuestMyAnswersViewController { .store(in: &cancellable) } - private func updateLikeCount(answerID: Int, likeCount: Int) { + private func updateLikeCount(answerID: Int, likeCount: Int, isLiked: Bool) { guard let answerIndex = viewModel.indexOfAnswer(answerID: answerID) else { return } @@ -124,7 +129,7 @@ extension CommonQuestMyAnswersViewController { return } - cell.questContentView.updateUI(likeCount: likeCount) + cell.questContentView.updateUI(likeCount: likeCount, isLiked: isLiked) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift index e67eadb2..0f4efc65 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -92,7 +92,8 @@ extension CommonQuestViewController { .sink { [weak self] result in switch result { case .success(let result): - self?.updateLikeCount(answerID: result.answerID, likeCount: result.likeCount) + let entity = result.entity + self?.updateLikeCount(answerID: result.answerID, likeCount: entity.likeCount, isLiked: entity.isLiked) case .failure(let error): ByeBooLogger.error(error) } @@ -100,7 +101,7 @@ extension CommonQuestViewController { .store(in: &cancellable) } - private func updateLikeCount(answerID: Int, likeCount: Int) { + private func updateLikeCount(answerID: Int, likeCount: Int, isLiked: Bool) { guard let answerIndex = viewModel.indexOfAnswer(answerID: answerID) else { return } @@ -110,7 +111,7 @@ extension CommonQuestViewController { return } - cell.questContentView.updateUI(likeCount: likeCount) + cell.questContentView.updateUI(likeCount: likeCount, isLiked: isLiked) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift index fee6a1c1..b86558bf 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -13,10 +13,11 @@ final class CommonQuestHistoryViewModel { private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase private let fetchCommentListSubject: PassthroughSubject, Never> = .init() - private let likeCountSubject = PassthroughSubject, Never>.init() + private let likeCountSubject = PassthroughSubject, Never>.init() private var entity: CommonQuestDetailEntity? = nil private var cancellables = Set() + private var likeTasks: [Int: Task] = [:] private(set) var output: Output init( @@ -41,7 +42,7 @@ extension CommonQuestHistoryViewModel: ViewModelType { struct Output { let fetchCommonQuestDetailPublisher: AnyPublisher, Never> - let commonQuestLikeCountPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -83,13 +84,21 @@ extension CommonQuestHistoryViewModel { } private func postCommonQuestLike(answerID: Int) { - Task { + likeTasks[answerID]?.cancel() + + likeTasks[answerID] = Task { do { - let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) - likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) - } catch (let error as ByeBooError) { + let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) + guard !Task.isCancelled else { return } + likeCountSubject.send(.success((answerID, entity))) + } catch { + guard let error = error as? ByeBooError else { + return + } + guard !Task.isCancelled else { return } likeCountSubject.send(.failure(error)) } + likeTasks[answerID] = nil } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift index e296559c..6a2ed058 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -13,7 +13,7 @@ final class CommonQuestMyAnswerViewModel { private let cancellables = Set() private let nameSubject = PassthroughSubject, Never>.init() private let answersSubject = PassthroughSubject, Never>.init() - private let likeCountSubject = PassthroughSubject, Never>.init() + private let likeCountSubject = PassthroughSubject, Never>.init() private let getUserNameUseCase: GetUserNameUseCase private let fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase @@ -26,6 +26,8 @@ final class CommonQuestMyAnswerViewModel { private(set) var hasMorePages = true private var nextCursor: Int? = nil + private var likeTasks: [Int: Task] = [:] + init( getUserNameUseCase: GetUserNameUseCase, fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase, @@ -68,13 +70,21 @@ final class CommonQuestMyAnswerViewModel { } private func postCommonQuestLike(answerID: Int) { - Task { + likeTasks[answerID]?.cancel() + + likeTasks[answerID] = Task { do { - let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) - likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) - } catch (let error as ByeBooError) { + let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) + guard !Task.isCancelled else { return } + likeCountSubject.send(.success((answerID: answerID, entity))) + } catch { + guard let error = error as? ByeBooError else { + return + } + guard !Task.isCancelled else { return } likeCountSubject.send(.failure(error)) } + likeTasks[answerID] = nil } } } @@ -90,7 +100,7 @@ extension CommonQuestMyAnswerViewModel: ViewModelType { struct Output { let namePublisher: AnyPublisher, Never> let answersPublisher: AnyPublisher, Never> - let commonQuestLikeCountPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift index 3a2b7391..693d0782 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -12,7 +12,7 @@ final class CommonQuestViewModel { private let cancellables = Set() private let commonQuestSubject = PassthroughSubject, Never>.init() - private let likeCountSubject = PassthroughSubject, Never>.init() + private let likeCountSubject = PassthroughSubject, Never>.init() private let fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase @@ -26,6 +26,7 @@ final class CommonQuestViewModel { private(set) var hasMorePages = true private var nextCursor: Int? = nil private var currentDate: String = DateFormatter.toAPIDateString(from: .now) + private var likeTasks: [Int: Task] = [:] init( fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase, @@ -68,13 +69,21 @@ final class CommonQuestViewModel { } private func postCommonQuestLike(answerID: Int) { - Task { + likeTasks[answerID]?.cancel() + + likeTasks[answerID] = Task { do { - let likeCount = try await postCommonQuestLikeUseCase.execute(answerID: answerID) - likeCountSubject.send(.success((answerID: answerID, likeCount: likeCount))) - } catch (let error as ByeBooError) { + let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) + guard !Task.isCancelled else { return } + likeCountSubject.send(.success((answerID: answerID, entity))) + } catch { + guard let error = error as? ByeBooError else { + return + } + guard !Task.isCancelled else { return } likeCountSubject.send(.failure(error)) } + likeTasks[answerID] = nil } } } @@ -90,7 +99,7 @@ extension CommonQuestViewModel: ViewModelType { struct Output { let commonQuestPublisher: AnyPublisher, Never> - let commonQuestLikeCountPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { From 84a82cb3c6cdbcb89e8fbb004ceee16fe0844424 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 20 Jun 2026 00:37:41 +0900 Subject: [PATCH 9/9] =?UTF-8?q?chore:=20#453=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift | 4 +++- .../Feature/Quest/ViewModel/CommonQuestViewModel.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift index b86558bf..2edfd06d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -89,8 +89,10 @@ extension CommonQuestHistoryViewModel { likeTasks[answerID] = Task { do { let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) - guard !Task.isCancelled else { return } + try Task.checkCancellation() likeCountSubject.send(.success((answerID, entity))) + } catch is CancellationError { + ByeBooLogger.debug("Task 취소됨") } catch { guard let error = error as? ByeBooError else { return diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift index 693d0782..86454f62 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -10,7 +10,7 @@ import UIKit final class CommonQuestViewModel { - private let cancellables = Set() + private var cancellables = Set() private let commonQuestSubject = PassthroughSubject, Never>.init() private let likeCountSubject = PassthroughSubject, Never>.init()