From 1ae504e80608091f1b18b09c193bca22fe75b595 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 18:45:18 +0900 Subject: [PATCH 001/231] =?UTF-8?q?add:=20#428=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IconSystem/comment.imageset/Contents.json | 21 +++++++++++++++++++ .../IconSystem/comment.imageset/comment.svg | 3 +++ .../IconSystem/heart.imageset/Contents.json | 12 ----------- .../heart.imageset/Property 1=heart.svg | 3 --- .../heart_off.imageset/Contents.json | 21 +++++++++++++++++++ .../heart_off.imageset/heart_off.svg | 3 +++ .../heart_on.imageset/Contents.json | 21 +++++++++++++++++++ .../IconSystem/heart_on.imageset/heart_on.svg | 3 +++ 8 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json new file mode 100644 index 00000000..d4e54c51 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "comment.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg new file mode 100644 index 00000000..44e8b841 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json deleted file mode 100644 index de556b1e..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Property 1=heart.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg deleted file mode 100644 index b35689a1..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json new file mode 100644 index 00000000..d022b3c5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "heart_off.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg new file mode 100644 index 00000000..6a2d39c7 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg @@ -0,0 +1,3 @@ + + + diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json new file mode 100644 index 00000000..b5e8cbaf --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "heart_on.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg new file mode 100644 index 00000000..70bf32ba --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg @@ -0,0 +1,3 @@ + + + From 25d3085df52827b656d4ef9b7da14f820f8e611c Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 18:45:38 +0900 Subject: [PATCH 002/231] =?UTF-8?q?style:=20#428=20=ED=80=98=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20content,=20=EB=8C=93=EA=B8=80=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Cells/QuestContentView.swift | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift new file mode 100644 index 00000000..df9a737c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift @@ -0,0 +1,133 @@ +// +// QuestContentView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 4/29/26. +// + +import UIKit + +protocol CommonQuestLikeCommentProtocol: AnyObject { + func likeButtonDidTap() +} + +final class QuestContentView: BaseView { + + private let answerContentLabel = UILabel() + private let writtenDateLabel = UILabel() + + private let likeCommentStackView = UIStackView() + private let likeContainerView = UIStackView() + private(set) var likeButton = UIButton() + private let likeCountLabel = UILabel() + private let commentContainerView = UIStackView() + private let commentIcon = UIImageView() + private let commentCountLabel = UILabel() + + weak var delegate: CommonQuestLikeCommentProtocol? + + private var likeCounts: Int = 0 + + override init(frame: CGRect) { + super.init(frame: frame) + setStyle() + setUI() + setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setUI() { + self.addSubviews( + answerContentLabel, + writtenDateLabel, + likeCommentStackView + ) + likeCommentStackView.addArrangedSubviews(likeContainerView, commentContainerView) + likeContainerView.addArrangedSubviews(likeButton, likeCountLabel) + commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) + } + + override func setStyle() { + answerContentLabel.applyByeBooFont( + style: .body3R16, + color: .grayscale100, + numberOfLines: 2 + ) + writtenDateLabel.applyByeBooFont( + style: .cap2R12, + color: .grayscale400 + ) + likeCommentStackView.do { + $0.axis = .horizontal + $0.spacing = 16.adjustedW + } + [likeContainerView, commentContainerView].forEach { + $0.do { + $0.axis = .horizontal + $0.spacing = 4.adjustedW + } + } + likeButton.do { + $0.setImage(.heartOff, for: .normal) + $0.setImage(.heartOn, for: .selected) + $0.addTarget(self, action: #selector(likeButtonDidTap), for: .touchUpInside) + } + commentIcon.do { + $0.image = .comment + } + [likeCountLabel, commentCountLabel].forEach { + $0.do { + $0.text = "0" + $0.applyByeBooFont(style: .cap2R12, color: .grayscale100) + } + } + } + + override func setLayout() { + answerContentLabel.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(47.adjustedH) + } + writtenDateLabel.snp.makeConstraints { + $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) + $0.leading.equalToSuperview() + $0.height.equalTo(16.adjustedH) + } + likeCommentStackView.snp.makeConstraints { + $0.centerY.equalTo(writtenDateLabel) + $0.trailing.equalToSuperview() + } + [likeButton, commentIcon].forEach { + $0.snp.makeConstraints { + $0.size.equalTo(20) + } + } + } + + func configure( + content: String, + writtenAt: String, + isLiked: Bool, + likeCount: Int, + commentCount: Int + ) { + self.likeCounts = likeCount + answerContentLabel.text = content + writtenDateLabel.text = writtenAt + likeButton.isSelected = isLiked + likeCountLabel.text = String(likeCount) + commentCountLabel.text = String(commentCount) + } + + @objc + private func likeButtonDidTap() { + likeButton.isSelected.toggle() + likeCounts += likeButton.isSelected ? 1 : -1 + likeCountLabel.text = String(likeCounts) + delegate?.likeButtonDidTap() + } +} From 33d1a932e4df4a87531061759f5550adce6ea625 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 18:45:50 +0900 Subject: [PATCH 003/231] =?UTF-8?q?feat:=20#428=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EB=88=84=EB=A5=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommonQuestAnswerCell.swift | 34 +++++---------- .../Cells/CommonQuestMyAnswerCell.swift | 43 ++++++++----------- .../CommonQuestMyAnswersViewController.swift | 13 +++++- .../CommonQuestViewController.swift | 7 +++ 4 files changed, 46 insertions(+), 51 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 7419c854..034016d9 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 @@ -16,8 +16,7 @@ final class CommonQuestAnswerCell: UITableViewCell { private let containerView = UIView() private let userIconView = UIImageView() private let userNicknameLabel = UILabel() - private let answerContentLabel = UILabel() - private let writtenDateLabel = UILabel() + private(set) var questContentView = QuestContentView() override init( style: UITableViewCell.CellStyle, @@ -47,24 +46,14 @@ final class CommonQuestAnswerCell: UITableViewCell { style: .body6R14, color: .grayscale200 ) - answerContentLabel.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 0 - ) - writtenDateLabel.applyByeBooFont( - style: .body6R14, - color: .grayscale400 - ) } private func setUI() { - addSubview(containerView) + contentView.addSubview(containerView) containerView.addSubviews( userIconView, userNicknameLabel, - answerContentLabel, - writtenDateLabel + questContentView ) } @@ -84,14 +73,9 @@ final class CommonQuestAnswerCell: UITableViewCell { $0.leading.equalTo(userIconView.snp.trailing).offset(4.adjustedW) $0.centerY.equalTo(userIconView.snp.centerY) } - answerContentLabel.snp.makeConstraints { - $0.top.equalTo(userIconView.snp.bottom).offset(12.adjustedH) + questContentView.snp.makeConstraints { + $0.top.equalTo(userNicknameLabel.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.height.equalTo(47.adjustedH) - } - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) } } @@ -108,9 +92,13 @@ extension CommonQuestAnswerCell { userIconView.image = profileIcon } userNicknameLabel.text = answer.writer - answerContentLabel.text = answer.content - writtenDateLabel.text = writtenAt answerID = answer.answerID + questContentView.configure( + content: answer.content, + writtenAt: writtenAt, + isLiked: false, + likeCount: 4, + commentCount: 3) } func getAnswewrID() -> Int? { 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 1df86b35..9b33af32 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 @@ -13,8 +13,7 @@ final class CommonQuestMyAnswerCell: UITableViewCell { private let questionView = UIView() private let questionMarkLabel = UILabel() private let questionContentLabel = UILabel() - private let answerContentLabel = UILabel() - private let writtenDateLabel = UILabel() + private(set) var questContentView = QuestContentView() override init( style: UITableViewCell.CellStyle, @@ -53,23 +52,13 @@ final class CommonQuestMyAnswerCell: UITableViewCell { ) $0.lineBreakStrategy = [] } - answerContentLabel.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 2 - ) - writtenDateLabel.applyByeBooFont( - style: .cap2R12, - color: .grayscale400 - ) } private func setUI() { contentView.addSubview(containerView) containerView.addSubviews( questionView, - answerContentLabel, - writtenDateLabel + questContentView ) questionView.addSubviews( questionMarkLabel, @@ -86,25 +75,19 @@ final class CommonQuestMyAnswerCell: UITableViewCell { $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) } questionMarkLabel.snp.makeConstraints { - $0.top.equalTo(questionContentLabel.snp.top) + $0.top.equalToSuperview() $0.leading.equalToSuperview() $0.width.equalTo(17.adjustedW) } questionContentLabel.snp.makeConstraints { - $0.verticalEdges.equalToSuperview() + $0.top.equalToSuperview() $0.leading.equalTo(questionMarkLabel.snp.trailing).offset(4.adjustedW) $0.trailing.equalToSuperview() } - answerContentLabel.snp.makeConstraints { - $0.top.equalTo(questionView.snp.bottom).offset(12.adjustedH) + questContentView.snp.makeConstraints { + $0.top.equalTo(questionContentLabel.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.height.equalTo(47.adjustedH) - } - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) - $0.height.equalTo(16.adjustedH) } } } @@ -114,10 +97,18 @@ extension CommonQuestMyAnswerCell { func bind( question: String, content: String, - writtenAt: String + writtenAt: String, + isLiked: Bool, + likeCount: Int, + commentCount: Int ) { questionContentLabel.text = question - answerContentLabel.text = content - writtenDateLabel.text = writtenAt + questContentView.configure( + content: content, + writtenAt: writtenAt, + isLiked: isLiked, + likeCount: likeCount, + commentCount: commentCount + ) } } 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 97473a87..d6a3b842 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -194,12 +194,21 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { } let cell: CommonQuestMyAnswerCell = tableView.dequeueReusableCell(for: indexPath) - + cell.questContentView.delegate = self cell.bind( question: answer.question, content: answer.content, - writtenAt: answer.writtenAt + writtenAt: answer.writtenAt, + isLiked: true, + likeCount: 3, + commentCount: 3 ) return cell } } + +extension CommonQuestMyAnswersViewController: CommonQuestLikeCommentProtocol { + func likeButtonDidTap() { + // TODO: like button + } +} 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 924c64bd..e54c7b10 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -260,6 +260,7 @@ extension CommonQuestViewController: UITableViewDataSource { let answer = viewModel.getAnswer(at: indexPath.row - 1) let profileIcon = viewModel.getProfileIcon(at: indexPath.row - 1) let writtenAt = viewModel.getWrittenAt(at: indexPath.row - 1) + cell.questContentView.delegate = self if let answer, let writtenAt { @@ -281,3 +282,9 @@ extension CommonQuestViewController: UITableViewDataSource { return cell } } + +extension CommonQuestViewController: CommonQuestLikeCommentProtocol { + func likeButtonDidTap() { + // TODO: like button + } +} From 23b5086435167af19d8274ceae41953114c84a21 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 23:26:25 +0900 Subject: [PATCH 004/231] =?UTF-8?q?style:=20#428=20=EB=82=98=EC=9D=98?= =?UTF-8?q?=EC=97=AC=EC=A0=95=EC=97=90=EC=84=9C=EB=8F=84=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=EC=95=84=EC=9D=B4=EC=BD=98=EC=9D=B4=20?= =?UTF-8?q?=EB=9C=A8=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/QuestContentView.swift | 36 +++++---- .../CommonQuest/CommonQuestHistoryView.swift | 74 +++++-------------- .../CommonQuestHistoryViewController.swift | 9 ++- .../CommonQuestMyAnswersViewController.swift | 4 +- .../CommonQuestViewController.swift | 7 +- ...WriteQuestionTypeQuestViewController.swift | 2 + 6 files changed, 56 insertions(+), 76 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift index df9a737c..66e11cd3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift @@ -14,7 +14,7 @@ protocol CommonQuestLikeCommentProtocol: AnyObject { final class QuestContentView: BaseView { private let answerContentLabel = UILabel() - private let writtenDateLabel = UILabel() + private let writtenDateLabel: UILabel? = nil private let likeCommentStackView = UIStackView() private let likeContainerView = UIStackView() @@ -42,12 +42,15 @@ final class QuestContentView: BaseView { override func setUI() { self.addSubviews( answerContentLabel, - writtenDateLabel, likeCommentStackView ) likeCommentStackView.addArrangedSubviews(likeContainerView, commentContainerView) likeContainerView.addArrangedSubviews(likeButton, likeCountLabel) commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) + + if let writtenDateLabel { + self.addSubview(writtenDateLabel) + } } override func setStyle() { @@ -56,10 +59,12 @@ final class QuestContentView: BaseView { color: .grayscale100, numberOfLines: 2 ) - writtenDateLabel.applyByeBooFont( - style: .cap2R12, - color: .grayscale400 - ) + if let writtenDateLabel { + writtenDateLabel.applyByeBooFont( + style: .cap2R12, + color: .grayscale400 + ) + } likeCommentStackView.do { $0.axis = .horizontal $0.spacing = 16.adjustedW @@ -92,14 +97,17 @@ final class QuestContentView: BaseView { $0.horizontalEdges.equalToSuperview() $0.height.equalTo(47.adjustedH) } - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview() - $0.height.equalTo(16.adjustedH) + if let writtenDateLabel { + writtenDateLabel.snp.makeConstraints { + $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) + $0.leading.equalToSuperview() + $0.height.equalTo(16.adjustedH) + } } likeCommentStackView.snp.makeConstraints { - $0.centerY.equalTo(writtenDateLabel) + $0.top.equalTo(answerContentLabel.snp.bottom).offset(20) $0.trailing.equalToSuperview() + $0.bottom.equalToSuperview() } [likeButton, commentIcon].forEach { $0.snp.makeConstraints { @@ -110,14 +118,16 @@ final class QuestContentView: BaseView { func configure( content: String, - writtenAt: String, + writtenAt: String? = nil, isLiked: Bool, likeCount: Int, commentCount: Int ) { self.likeCounts = likeCount answerContentLabel.text = content - writtenDateLabel.text = writtenAt + if let writtenDateLabel { + writtenDateLabel.text = writtenAt + } likeButton.isSelected = isLiked likeCountLabel.text = String(likeCount) commentCountLabel.text = String(commentCount) 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 724b42c6..f5a54b1d 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 @@ -9,9 +9,6 @@ import UIKit final class CommonQuestHistoryView: BaseView { - private var profileIcon: UIImage? - private var nickname: String? - private let scrollView = UIScrollView() private let contentView = UIView() private let commonQuestLabel = UILabel() @@ -22,7 +19,8 @@ final class CommonQuestHistoryView: BaseView { private let answerView = UIView() private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() - private let answerContentLabel = UILabel() +// private let answerContentLabel = UILabel() + private let questContentView = QuestContentView() override func setStyle() { commonQuestLabel.applyByeBooFont( @@ -55,13 +53,6 @@ final class CommonQuestHistoryView: BaseView { style: .body6R14, color: .grayscale200 ) - answerContentLabel.do { - $0.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 0 - ) - } } override func setUI() { @@ -77,18 +68,10 @@ final class CommonQuestHistoryView: BaseView { questionMarkLabel, questionContentLabel ) - - guard let _ = profileIcon, - let _ = nickname - else { - answerView.addSubview(answerContentLabel) - return - } - answerView.addSubviews( profileIconImageView, userNicknameLabel, - answerContentLabel + questContentView ) } @@ -131,18 +114,6 @@ final class CommonQuestHistoryView: BaseView { $0.horizontalEdges.equalToSuperview() $0.bottom.equalToSuperview().inset(24.adjustedH) } - - guard let _ = profileIcon, - let _ = nickname - else { - answerContentLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(16.adjustedH) - $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview().inset(16.adjustedH) - } - return - } - profileIconImageView.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) $0.leading.equalToSuperview().inset(24.adjustedW) @@ -153,7 +124,7 @@ final class CommonQuestHistoryView: BaseView { $0.centerY.equalTo(profileIconImageView) $0.trailing.equalToSuperview().inset(24.adjustedW) } - answerContentLabel.snp.makeConstraints { + questContentView.snp.makeConstraints { $0.top.equalTo(profileIconImageView.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) @@ -166,31 +137,24 @@ extension CommonQuestHistoryView { func configure( question: String, writtenAt: String, - profileIcon: UIImage?, - nickname: String?, - content: String + profileIcon: UIImage, + nickname: String, + content: String, + isLiked: Bool, + likeCount: Int, + commentCount: Int ) { questionContentLabel.text = question dateLabel.text = writtenAt - answerContentLabel.text = content - - answerContentLabel.do { - $0.applyByeBooFont( - style: .body3R16, - text: content, - color: .grayscale100, - numberOfLines: 0 - ) - } - - if let profileIcon { - self.profileIcon = profileIcon - profileIconImageView.image = profileIcon - } + questContentView.configure( + content: content, + isLiked: isLiked, + likeCount: likeCount, + commentCount: commentCount + ) + + profileIconImageView.image = profileIcon + userNicknameLabel.text = nickname - if let nickname { - self.nickname = nickname - userNicknameLabel.text = nickname - } } } 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 7c80aab0..c58dd0c9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -109,8 +109,8 @@ extension CommonQuestHistoryViewController { func configure( question: String, writtenAt: String, - profileIcon: UIImage? = nil, - nickname: String? = nil, + profileIcon: UIImage, + nickname: String, content: String, answerID: Int? = nil, writerID: Int? = nil, @@ -134,7 +134,10 @@ extension CommonQuestHistoryViewController { writtenAt: writtenAt, profileIcon: profileIcon, nickname: nickname, - content: content + content: content, + isLiked: false, + likeCount: 4, + commentCount: 5 ) } } 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 d6a3b842..0e592468 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -115,6 +115,8 @@ extension CommonQuestMyAnswersViewController: UITableViewDelegate { historyViewController.configure( question: answer.question, writtenAt: answer.writtenAt, + profileIcon: .relievedBadge, + nickname: "냐냐냐", content: answer.content, answerID: answer.answerID ) @@ -209,6 +211,6 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { extension CommonQuestMyAnswersViewController: CommonQuestLikeCommentProtocol { func likeButtonDidTap() { - // TODO: like button + // TODO: like button } } 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 e54c7b10..e6cfeb14 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -137,15 +137,14 @@ extension CommonQuestViewController: UITableViewDelegate { DateFormatter.toDisplayDateString(from: $0) } - guard let formattedWrittenAt else { - return - } + guard let formattedWrittenAt else { return } + guard let profileIcon = viewModel.getProfileIcon(at: answerIndex) else { return } let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() historyViewController.configure( question: viewModel.question, writtenAt: formattedWrittenAt, - profileIcon: viewModel.getProfileIcon(at: answerIndex), + profileIcon: profileIcon, nickname: answer.writer, content: answer.content, answerID: answer.answerID, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift index 0dbd56b0..7f70b05f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift @@ -217,6 +217,8 @@ extension WriteQuestionTypeQuestViewController: ToastPresentable, ToastErrorHand historyVC.configure( question: questTitle, writtenAt: writtenAt, + profileIcon: .relievedBadge, // TODO: 서버 수정 후 고치기 + nickname: "냐냐냐", content: self.rootView.questTextField.textView.text ) } From 96e34782870c72f34dc56ecd6a5140fd94135e25 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Thu, 30 Apr 2026 18:05:34 +0900 Subject: [PATCH 005/231] =?UTF-8?q?style:=20#429=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=20=EB=B0=B0=EC=B9=98=ED=95=A0=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../empty_notice.imageset/Contents.json | 21 +++++++++++++++++++ .../empty_notice.imageset/emptyNotice.svg | 3 +++ .../exist_notice.imageset/Contents.json | 21 +++++++++++++++++++ .../exist_notice.imageset/existNotice.svg | 4 ++++ 4 files changed, 49 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/emptyNotice.svg create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/existNotice.svg diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/Contents.json new file mode 100644 index 00000000..6ec51c5e --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "emptyNotice.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/emptyNotice.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/emptyNotice.svg new file mode 100644 index 00000000..c97a889e --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/empty_notice.imageset/emptyNotice.svg @@ -0,0 +1,3 @@ + + + diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/Contents.json new file mode 100644 index 00000000..1872a3eb --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "existNotice.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/existNotice.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/existNotice.svg new file mode 100644 index 00000000..4b973eda --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/exist_notice.imageset/existNotice.svg @@ -0,0 +1,4 @@ + + + + From 8f98f30d61318851556290b777be31dc153f78a3 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Thu, 30 Apr 2026 20:22:59 +0900 Subject: [PATCH 006/231] =?UTF-8?q?style:=20#429=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=95=8C=EB=A6=BC=20=EB=B2=84=ED=8A=BC=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Home/HomeHeaderView.swift | 19 +++++++++++++++---- .../ViewController/HomeViewController.swift | 4 ++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift index e7f678d0..6e283967 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift @@ -8,11 +8,11 @@ import UIKit final class HomeHeaderView: BaseView { - + + private(set) var noticeButton = UIButton() private(set) var homeStateView: HomeStateView = HomeStateView(state: .beforeJourneyStart) private let stackView = UIStackView() - private var journeyProgressView: JourneyProgressView? = nil private let helperButtonImage = UIImageView() let helperButton = UIButton() private let helperImageView = UIImageView() @@ -41,19 +41,25 @@ final class HomeHeaderView: BaseView { override func setUI() { addSubviews( + noticeButton, stackView, helperButton, helperImageView ) - + stackView.addArrangedSubviews( homeStateView ) } override func setLayout() { - stackView.snp.makeConstraints { + noticeButton.snp.makeConstraints { $0.top.equalToSuperview().inset(72.adjustedH) + $0.trailing.equalToSuperview().inset(24.adjustedH) + $0.size.equalTo(24.adjustedW) + } + stackView.snp.makeConstraints { + $0.top.equalTo(noticeButton.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) } helperButton.snp.makeConstraints { @@ -155,4 +161,9 @@ extension HomeHeaderView { func helperDidTap() { helperImageView.alpha = 0 } + + func updateNotice(isExist: Bool) { + let image: UIImage = isExist ? .existNotice : .emptyNotice + noticeButton.setImage(image, for: .normal) + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 0606557a..2b50d67c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -58,6 +58,9 @@ final class HomeViewController: BaseViewController { ) if isFirstVisit { isFirstVisit.toggle() } + + // 추후 API 연동 시 뷰모델 액션을 호출하여 알림 유무를 판단할 예정 + rootView.headerView.updateNotice(isExist: true) } override func setAddTarget() { @@ -79,6 +82,7 @@ final class HomeViewController: BaseViewController { } extension HomeViewController { + @objc private func headerDidTap() { switch state { From eecf04cda9027c200d680e9c2a9ff3b19e116adc Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Thu, 30 Apr 2026 20:23:29 +0900 Subject: [PATCH 007/231] =?UTF-8?q?refactor:=20#429=20=ED=99=88=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=ED=94=84=EB=A1=9C=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=94=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Home/HomeHeaderView.swift | 59 ++++--------------- .../Feature/Home/View/Home/HomeView.swift | 10 ---- .../ViewController/HomeViewController.swift | 9 +-- 3 files changed, 14 insertions(+), 64 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift index 6e283967..0421934d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeHeaderView.swift @@ -79,18 +79,6 @@ final class HomeHeaderView: BaseView { } extension HomeHeaderView { - func updateProgress(_ progress: Int) { - journeyProgressView?.updateProgress(progress) - } - - func updateName(_ name: String) { - journeyProgressView?.updateName(name) - } - - func updateJourney(_ title: String) { - journeyProgressView?.updateJourney(title) - } - func updateState(_ state: HomeState, _ journeyTitle: String? = nil) { helperImageView.alpha = 0 homeStateView.updateState(state, journeyTitle) @@ -99,41 +87,18 @@ extension HomeHeaderView { helperButton.alpha = 1 } - if state.hasProgress { - if journeyProgressView == nil { - journeyProgressView = JourneyProgressView() - } - - stackView.subviews.forEach { stackView.removeArrangedSubview($0) } - - guard let journeyProgressView else { return } - - stackView.addArrangedSubviews( - homeStateView, - journeyProgressView - ) - - stackView.snp.remakeConstraints { - $0.top.equalToSuperview().inset(72.adjustedH) - $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview() - } - helperButton.alpha = 0 - } else { - stackView.subviews.dropFirst().forEach { - stackView.removeArrangedSubview($0) - $0.removeFromSuperview() - } - journeyProgressView = nil - stackView.snp.remakeConstraints { - $0.top.equalToSuperview().inset(72.adjustedH) - $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - } - self.snp.makeConstraints { - $0.top.equalToSuperview() - $0.horizontalEdges.equalToSuperview() - $0.bottom.equalTo(helperButton.snp.bottom) - } + stackView.subviews.dropFirst().forEach { + stackView.removeArrangedSubview($0) + $0.removeFromSuperview() + } + stackView.snp.remakeConstraints { + $0.top.equalTo(noticeButton.snp.bottom).offset(12.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + } + self.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(helperButton.snp.bottom) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeView.swift index 685935bc..ec9b0d03 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Home/HomeView.swift @@ -63,16 +63,6 @@ extension HomeView { speechBoxView.updateText(text, withAnimation: withAnimation) } - func updateProgressView( - name: String, - progress: Int, - journey: String - ) { - headerView.updateProgress(progress) - headerView.updateName(name) - headerView.updateJourney(journey) - } - func updateState(_ state: HomeState, _ journeyTitle: String? = nil) { headerView.updateState(state, journeyTitle) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 2b50d67c..1de048f9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -140,7 +140,7 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } } .store(in: &cancellables) - + Publishers.CombineLatest3( viewModel.output.userResult, viewModel.output.journeyResult, @@ -152,14 +152,9 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { journey, state in switch (name, journey, state) { - case let (name, .success(journey), .success(state)): + case let (_, .success(journey), .success(state)): self?.rootView.updateState(state.currentStatus) self?.state = state.currentStatus - self?.rootView.updateProgressView( - name: name, - progress: state.questCount, - journey: journey.title - ) self?.journeyType = JourneyType.titleToEnum(journey.title) ?? .recording case let (_, .success(journey), .failure(.notFound)): self?.rootView.updateState(.beforeJourneyStart, journey.title) From 64b913317dc4e2a2a0fbf0b5058e7c825f09a394 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:28:07 +0900 Subject: [PATCH 008/231] =?UTF-8?q?feat:=20#429=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=86=A0=EC=BD=9C=20=EA=B8=B0=EB=B0=98=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Type/CommentCommonQuestNotice.swift | 23 +++++++++++++++++++ .../Notice/Type/LikeCommonQuestNotice.swift | 23 +++++++++++++++++++ .../View/Notice/Type/NoticeDisplayable.swift | 22 ++++++++++++++++++ .../Notice/Type/TodayQuestOpenNotice.swift | 23 +++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift new file mode 100644 index 00000000..606eec2e --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift @@ -0,0 +1,23 @@ +// +// CommentCommonQuestNotice.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +struct CommentCommonQuestNotice: NoticeDisplayable { + let nickname: String + + var isRead: Bool = false + var iconImage: UIImage { + .commonJourney + } + var title: String { + "공통여정에 댓글이 달렸어요 💬" + } + var subtitle: String { + "내가 작성한 글에 \(nickname)님이 댓글을 남겼어요" + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift new file mode 100644 index 00000000..6f249c3d --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift @@ -0,0 +1,23 @@ +// +// LikeCommonQuestNotice.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +struct LikeCommonQuestNotice: NoticeDisplayable { + let nickname: String + + var isRead: Bool = false + var iconImage: UIImage { + .commonJourney + } + var title: String { + "공통여정에 답변에 공감이 달렸어요 ❤️" + } + var subtitle: String { + "내가 작성한 글에 \(nickname)님이 공감을 남겼어요" + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift new file mode 100644 index 00000000..79c2872a --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift @@ -0,0 +1,22 @@ +// +// NoticeDisplayable.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +protocol NoticeDisplayable { + var isRead: Bool { get set } + var backgroundColor: UIColor { get } + var iconImage: UIImage { get } + var title: String { get } + var subtitle: String { get } +} + +extension NoticeDisplayable { + var backgroundColor: UIColor { + isRead ? .white5 : .primary30020 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift new file mode 100644 index 00000000..3d9aa6d4 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift @@ -0,0 +1,23 @@ +// +// TodayQuestOpenNotice.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +struct TodayQuestOpenNotice: NoticeDisplayable { + let questNumber: Int + + var isRead: Bool = false + var iconImage: UIImage { + .myOn + } + var title: String { + "오늘의 퀘스트 오픈 🌱" + } + var subtitle: String { + "\(questNumber)번째 퀘스트가 오픈됐어요, 시작해볼까요?" + } +} From 1bde6858e3d52f09f26f901785443dac02b6f7a6 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:28:58 +0900 Subject: [PATCH 009/231] =?UTF-8?q?style:=20#429=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=ED=99=94=EB=A9=B4=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Notice/NoticesView.swift | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticesView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticesView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticesView.swift new file mode 100644 index 00000000..b3bc8f42 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticesView.swift @@ -0,0 +1,62 @@ +// +// NoticesView.swift +// ByeBoo-iOS +// +// Created by APPLE on 4/30/26. +// + +import UIKit + +final class NoticesView: BaseView { + + private let noticesHeaderView = UIView() + private let disappearDeadlineLabel = UILabel() + private(set) var readAllButton = UIButton() + private(set) var contentView = NoticeContentView() + + override func setStyle() { + disappearDeadlineLabel.applyByeBooFont( + style: .cap1M12, + text: "최근 30일까지의 알림만 보여요", + color: .grayscale300 + ) + readAllButton.applyByeBooFont( + style: .cap1M12, + text: "모두 읽기", + color: .grayscale300 + ) + } + + override func setUI() { + addSubviews( + noticesHeaderView, + contentView + ) + + noticesHeaderView.addSubviews( + disappearDeadlineLabel, + readAllButton + ) + } + + override func setLayout() { + noticesHeaderView.snp.makeConstraints { + $0.top.equalTo(safeAreaLayoutGuide.snp.top).offset(16.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + $0.height.equalTo(16.adjustedH) + } + disappearDeadlineLabel.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.verticalEdges.equalToSuperview() + } + readAllButton.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.verticalEdges.equalToSuperview() + } + contentView.snp.makeConstraints { + $0.top.equalTo(noticesHeaderView.snp.bottom).offset(20.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(24.adjustedH) + } + } +} From ccb1417ee642b15c159e50e5b822f28161919d7c Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:30:22 +0900 Subject: [PATCH 010/231] =?UTF-8?q?style:=20#429=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=BB=A8=ED=85=90=EC=B8=A0=20=ED=99=94=EB=A9=B4=20UI=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Notice/NoticeContentView.swift | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeContentView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeContentView.swift new file mode 100644 index 00000000..ebe0d9db --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeContentView.swift @@ -0,0 +1,43 @@ +// +// NoticeContentView.swift +// ByeBoo-iOS +// +// Created by APPLE on 4/30/26. +// + +import UIKit + +final class NoticeContentView: BaseView { + + private let noticeStackView = UIStackView() + private let emptyNoticeView = EmptyNoticeView() + private(set) var noticeCardsView = NoticeCardsView() + + override func setStyle() { + noticeStackView.do { + $0.axis = .vertical + } + } + + override func setUI() { + addSubview(noticeStackView) + noticeStackView.addArrangedSubviews( + emptyNoticeView, + noticeCardsView + ) + } + + override func setLayout() { + noticeStackView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } +} + +extension NoticeContentView { + + func decideNoticeContent(isExistNotice: Bool) { + noticeCardsView.isHidden = !isExistNotice + emptyNoticeView.isHidden = isExistNotice + } +} From a48075a2af5fdf3fdcda4d38ef4ef59153e1fac8 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:30:41 +0900 Subject: [PATCH 011/231] =?UTF-8?q?style:=20#429=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=EC=9D=B4=201=EA=B0=9C=20=EC=9D=B4=EC=83=81=EC=9D=B8=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=BB=A8=ED=85=90=EC=B8=A0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Notice/NoticeCardCell.swift | 103 ++++++++++++++++++ .../Home/View/Notice/NoticeCardsView.swift | 38 +++++++ 2 files changed, 141 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift new file mode 100644 index 00000000..b507b78e --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -0,0 +1,103 @@ +// +// NoticeCardCell.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +final class NoticeCardCell: UITableViewCell { + + private let noticeImageView = UIImageView() + private let titleLabel = UILabel() + private let subtitleLabel = UILabel() + private let writtenTimeLabel = UILabel() + private var writtenTime: String = "" + private var noticeType: NoticeDisplayable? { + didSet { + setStyle() + } + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + setStyle() + setUI() + setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setStyle() { + guard let noticeType else { return } + + self.do { + $0.layer.cornerRadius = 12 + $0.backgroundColor = noticeType.backgroundColor + } + noticeImageView.do { + $0.image = noticeType.iconImage + } + titleLabel.applyByeBooFont( + style: .body1Sb16, + text: noticeType.title, + color: .grayscale200, + textAlignment: .left + ) + subtitleLabel.applyByeBooFont( + style: .body6R14, + text: noticeType.subtitle, + color: .grayscale100, + textAlignment: .left + ) + writtenTimeLabel.applyByeBooFont( + style: .cap2R12, + text: writtenTime, + color: .grayscale400, + textAlignment: .left + ) + } + + private func setUI() { + addSubviews( + noticeImageView, + titleLabel, + subtitleLabel, + writtenTimeLabel + ) + } + + private func setLayout() { + noticeImageView.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedW) + $0.size.equalTo(24.adjustedW) + } + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalTo(noticeImageView.snp.trailing).offset(4.adjustedW) + $0.trailing.equalToSuperview().inset(24.adjustedW) + } + subtitleLabel.snp.makeConstraints { + $0.top.equalTo(noticeImageView.snp.bottom).offset(12.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + } + writtenTimeLabel.snp.makeConstraints { + $0.top.equalTo(subtitleLabel.snp.bottom).offset(20.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(16.adjustedH) + } + } +} + +extension NoticeCardCell { + + func bind(writtenTime: String, noticeType: NoticeDisplayable) { + self.writtenTime = writtenTime + self.noticeType = noticeType + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift new file mode 100644 index 00000000..c25be376 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift @@ -0,0 +1,38 @@ +// +// NoticeCardsView.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +final class NoticeCardsView: BaseView { + + private(set) var cardTableView = UITableView() + + override func setStyle() { + cardTableView.do { + $0.backgroundColor = .grayscale900 + $0.separatorStyle = .none + $0.contentInset = UIEdgeInsets( + top: 0, + left: 0, + bottom: 24, + right: 0 + ) + } + } + + override func setUI() { + addSubview(cardTableView) + } + + override func setLayout() { + cardTableView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalToSuperview().inset(80.adjustedH) + } + } +} From edb3d51f3555dcfb9e21ad6e5a0e334f852b8d1d Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:30:54 +0900 Subject: [PATCH 012/231] =?UTF-8?q?style:=20#429=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=BB=A8?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=ED=99=94=EB=A9=B4=20UI=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Notice/EmptyNoticeView.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/EmptyNoticeView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/EmptyNoticeView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/EmptyNoticeView.swift new file mode 100644 index 00000000..175cc42e --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/EmptyNoticeView.swift @@ -0,0 +1,33 @@ +// +// EmptyNoticeView.swift +// ByeBoo-iOS +// +// Created by APPLE on 5/1/26. +// + +import UIKit + +final class EmptyNoticeView: BaseView { + + private let noNoticeLabel = UILabel() + + override func setStyle() { + noNoticeLabel.applyByeBooFont( + style: .body3R16, + text: "아직 도착한 알림이 없어요!", + color: .grayscale300 + ) + } + + override func setUI() { + addSubview(noNoticeLabel) + } + + override func setLayout() { + noNoticeLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(208.5.adjustedH) + $0.centerX.equalToSuperview() + $0.bottom.equalToSuperview().inset(397.adjustedH) + } + } +} From 1a16065ace7f41f637ed966dc6236bf5632cb049 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:31:53 +0900 Subject: [PATCH 013/231] =?UTF-8?q?feat:=20#429=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B7=B0=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NoticesViewController.swift | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift new file mode 100644 index 00000000..f68b2aec --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift @@ -0,0 +1,118 @@ +// +// NoticesViewController.swift +// ByeBoo-iOS +// +// Created by APPLE on 4/30/26. +// + +import UIKit + +final class NoticesViewController: BaseViewController { + + private let isExistNotice: Bool + private let rootView = NoticesView() + + init(isExistNotice: Bool) { + self.isExistNotice = isExistNotice + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + ByeBooNavigationBar.makeNavigationBar( + navigationItem: self.navigationItem, + navigationController: self.navigationController, + type: .titleAndBack("알림"), + action: #selector(back) + ) + + rootView.contentView.decideNoticeContent(isExistNotice: isExistNotice) + } + + override func setAddTarget() { + rootView.readAllButton.addTarget( + self, + action: #selector(readAllButtonDidTap), + for: .touchUpInside + ) + } + + override func setDelegate() { + rootView.contentView.noticeCardsView.cardTableView.do { + $0.dataSource = self + $0.delegate = self + $0.register(NoticeCardCell.self) + } + } +} + +extension NoticesViewController: BackNavigable { + + func back() { + self.navigationController?.popViewController(animated: false) + } +} + +extension NoticesViewController { + + @objc + private func readAllButtonDidTap() {} +} + +extension NoticesViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + // 실제로는 알림 개수만큼 설정 + 2 + } + + func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int + ) -> Int { + 1 + } + + func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + + guard let cell = tableView.dequeueReusableCell(withIdentifier: NoticeCardCell.identifier, for: indexPath) as? NoticeCardCell + else { + return UITableViewCell() + } + + cell.bind( + writtenTime: "방금 전", + noticeType: LikeCommonQuestNotice(nickname: "") + ) + return cell + } +} + +extension NoticesViewController: UITableViewDelegate { + + func tableView( + _ tableView: UITableView, + viewForHeaderInSection section: Int + ) -> UIView? { + UIView() + } + + func tableView( + _ tableView: UITableView, + heightForHeaderInSection section: Int + ) -> CGFloat { + 0 + } +} From e16d71bd458ac7ce3169b7fa252728b36ac094b0 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 4 May 2026 13:32:11 +0900 Subject: [PATCH 014/231] =?UTF-8?q?refactor:=20#429=20=ED=99=88=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=95=8C=EB=A6=BC=20=ED=99=94=EB=A9=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 25 ++++++++++++++++++- .../Presentation/ViewControllerFactory.swift | 4 +++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 1de048f9..89abd74c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -22,6 +22,7 @@ final class HomeViewController: BaseViewController { private var isFirstVisit: Bool = true private var journeyType: JourneyType = .recording private var isAnimating: Bool = false + private var isExistNotice: Bool? init(viewModel: HomeViewModel) { self.viewModel = viewModel @@ -59,13 +60,24 @@ final class HomeViewController: BaseViewController { if isFirstVisit { isFirstVisit.toggle() } + self.navigationController?.setNavigationBarHidden(true, animated: false) // 추후 API 연동 시 뷰모델 액션을 호출하여 알림 유무를 판단할 예정 rootView.headerView.updateNotice(isExist: true) + isExistNotice = true } override func setAddTarget() { setGesture() - rootView.headerView.helperButton.addTarget(self, action: #selector(helperDidTap), for: .touchUpInside) + rootView.headerView.helperButton.addTarget( + self, + action: #selector(helperDidTap), + for: .touchUpInside + ) + rootView.headerView.noticeButton.addTarget( + self, + action: #selector(noticeButtonDidTap), + for: .touchUpInside + ) } private func setGesture() { @@ -108,6 +120,17 @@ extension HomeViewController { self.navigationController?.pushViewController(tutorialViewController, animated: false) } + @objc + private func noticeButtonDidTap() { + guard let isExistNotice else { return } + + let viewController = ViewControllerFactory.shared.makeNoticesViewController(isExistNotice: isExistNotice) + viewController.hidesBottomBarWhenPushed = true + + self.navigationController?.setNavigationBarHidden(false, animated: false) + self.navigationController?.pushViewController(viewController, animated: false) + } + @objc private func boriDidTap() { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index 18083d63..dd356892 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -224,6 +224,10 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { return .init(viewModel: viewModel) } + + func makeNoticesViewController(isExistNotice: Bool) -> NoticesViewController { + .init(isExistNotice: isExistNotice) + } } extension ViewControllerFactory { From 8eaeb443a56183e06080331550f865595df8a73b Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:54:54 +0900 Subject: [PATCH 015/231] =?UTF-8?q?refactor:=20#428=20extension=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Extension/UITextView+.swift | 8 ++++++++ .../{Cells => Common}/QuestContentView.swift | 3 ++- .../Feature/Quest/View/Write/QuestTextField.swift | 14 +++----------- .../WriteQuestionTypeQuestViewController.swift | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/{Cells => Common}/QuestContentView.swift (99%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift index b98111bf..7cf1cc4d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift @@ -37,4 +37,12 @@ extension UITextView { attributedText = NSAttributedString(string: targetText, attributes: attributes) typingAttributes = attributes } + + func applyTextViewStyle(style: FontManager, text: String, color: UIColor) { + self.applyByeBooFont( + style: style, + text: text, + color: color + ) + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift similarity index 99% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index 66e11cd3..c5cedb0c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -30,8 +30,9 @@ final class QuestContentView: BaseView { override init(frame: CGRect) { super.init(frame: frame) - setStyle() + setUI() + setStyle() setLayout() } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift index 7b5e00dc..591dfd99 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift @@ -77,17 +77,17 @@ extension QuestTextField: UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { if isPlaceholderActive == true { isPlaceholderActive = false - applyTextViewStyle(text: "", color: .grayscale100) + textView.applyTextViewStyle(style: .body3R16 ,text: "", color: .grayscale100) } textView.textColor = .grayscale100 } func textViewDidEndEditing(_ textView: UITextView) { if textView.text.isEmpty { - applyTextViewStyle(text: placeholder, color: .grayscale300) + textView.applyTextViewStyle(style: .body3R16 ,text: placeholder, color: .grayscale300) isPlaceholderActive = true } else { - applyTextViewStyle(text: textView.text, color: .grayscale100) + textView.applyTextViewStyle(style: .body3R16, text: textView.text, color: .grayscale100) } questTextViewDelegate?.textViewDidEndEditing() } @@ -102,14 +102,6 @@ extension QuestTextField: UITextViewDelegate { } extension QuestTextField { - func applyTextViewStyle(text: String, color: UIColor) { - textView.applyByeBooFont( - style: .body3R16, - text: text, - color: color - ) - } - func updateTextViewHeight() -> CGFloat { let width = self.frame.width let fittingSize = CGSize(width: width, height: .greatestFiniteMagnitude) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift index 7f70b05f..e027c8dd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift @@ -309,7 +309,7 @@ extension WriteQuestionTypeQuestViewController { private func setQuestTextField(answer: String) { rootView.questTextField.do { - $0.applyTextViewStyle(text: answer, color: .grayscale100) + $0.textView.applyTextViewStyle(style: .body3R16 ,text: answer, color: .grayscale100) $0.isPlaceholderActive = false } rootView.layoutIfNeeded() From 1a38631cdb57acab554aa1acc0e36c5ef6f1249d Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:55:12 +0900 Subject: [PATCH 016/231] =?UTF-8?q?setting:=20#428=20=EB=A6=AC=ED=80=B4?= =?UTF-8?q?=EB=93=9C=EA=B8=80=EB=9E=98=EC=8A=A4=20=EB=AF=B8=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist index 9687993c..616e9eb9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist @@ -58,5 +58,7 @@ remote-notification + UIDesignRequiresCompatibility + From e695a05225f554a1bade377dbea54f08b546dfcc Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:55:29 +0900 Subject: [PATCH 017/231] =?UTF-8?q?feat:=20#428=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/CommentTextFieldView.swift | 221 ++++++++++++++++++ .../CommonQuest/CommonQuestHistoryView.swift | 25 +- .../CommonQuestHistoryViewController.swift | 62 ++++- 3 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift new file mode 100644 index 00000000..acaf8b3f --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift @@ -0,0 +1,221 @@ +// +// CommentTextFieldView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/4/26. +// + +import UIKit + +final class CommentTextView: BaseView { + private let topBorderLine = UIView() + private let textFieldContainer = UIView() + private let textView = UITextView() + private let countConfirmContainer = UIView() + private let textCountLabel = UILabel() + private let confirmButton = UIButton() + + private let placeholder: String = "댓글로 위로를 남겨보세요." + private var isPlaceholderActive: Bool = true + private let textCountLimit: Int = 500 + + override init(frame: CGRect) { + super.init(frame: frame) + textView.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setUI() { + addSubviews(topBorderLine,textFieldContainer, confirmButton) + textFieldContainer.addSubviews(textView) + } + + override func setStyle() { + backgroundColor = .grayscale900 + + topBorderLine.do { + $0.backgroundColor = .grayscale800 + } + + textFieldContainer.do { + $0.backgroundColor = .white5 + $0.layer.cornerRadius = 8 + } + + textView.do { + let offset = (FontManager.body6R14.lineHeight - FontManager.body6R14.font.lineHeight) / 2 + $0.backgroundColor = .clear + $0.text = placeholder + $0.font = FontManager.body6R14.font + $0.textColor = .grayscale600 + $0.textContainerInset = .init( + top: offset, + left: 0, + bottom: offset, + right: 0 + ) + $0.isScrollEnabled = false + $0.textContainer.maximumNumberOfLines = 1 + $0.textContainer.lineBreakMode = .byTruncatingTail + } + + textCountLabel.do { + $0.text = "0/\(textCountLimit)" + $0.applyByeBooFont(style: .cap2R12, color: .grayscale400) + } + + confirmButton.do { + $0.applyByeBooFont(style: .body2M16, text: "완료", color: .grayscale600) + } + } + + override func setLayout() { + textFieldContainer.snp.makeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedW) + $0.trailing.equalTo(confirmButton.snp.leading).offset(-20) + $0.height.equalTo(40.adjustedH) + $0.bottom.equalToSuperview().inset(6.adjustedH) + } + + topBorderLine.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(1.adjustedH) + } + + textView.snp.makeConstraints { + $0.verticalEdges.equalToSuperview().inset(9.5.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(12.adjustedW) + } + + confirmButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(17.5.adjustedH) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.equalTo(21.adjustedH) + } + } + +} + +extension CommentTextView: UITextViewDelegate { + func textViewDidBeginEditing(_ textView: UITextView) { + if isPlaceholderActive { + isPlaceholderActive = false + textView.text = "" + } + // 편집 시작 시 항상 paragraph style을 typingAttributes에 적용 → 줄바꿈 line height 보장 + textView.applyTextViewStyle(style: .body6R14, text: textView.text, color: .grayscale100) + updateTextViewLayoutWhenEditing() + } + + func textViewDidChange(_ textView: UITextView) { + textCountLabel.text = "\(textView.text.count)/500" + let hasText = !textView.text.isEmpty + confirmButton.isEnabled = hasText + confirmButton.applyByeBooFont(style: .body2M16, text: "완료", color: hasText ? .primary300 : .grayscale600) + + if textView.text.count > textCountLimit { + textView.deleteBackward() + return + } + + let maxHeight = 105.adjustedH + let fittingHeight = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: .infinity)).height + if fittingHeight > maxHeight { + if !textView.isScrollEnabled { + textView.isScrollEnabled = true + } + } else { + if textView.isScrollEnabled { + textView.isScrollEnabled = false + textView.contentOffset = .zero + } + } + } + + func textViewDidEndEditing(_ textView: UITextView) { + isPlaceholderActive = textView.text.isEmpty + updateTextViewLayoutWhenEndEditing() + } +} + +extension CommentTextView { + private func updateTextViewLayoutWhenEditing() { + textView.textContainer.maximumNumberOfLines = 0 + textView.textContainer.lineBreakMode = .byWordWrapping + textFieldContainer.backgroundColor = .clear + confirmButton.removeFromSuperview() + addSubviews(countConfirmContainer) + countConfirmContainer.addSubviews(textCountLabel, confirmButton) + + textFieldContainer.snp.remakeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(countConfirmContainer.snp.top) + } + + textView.snp.remakeConstraints { + $0.top.equalToSuperview().inset(9.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + $0.height.greaterThanOrEqualTo(42.adjustedH) + $0.height.lessThanOrEqualTo(105.adjustedH) + $0.bottom.equalToSuperview().inset(9.adjustedH) + } + + countConfirmContainer.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(8.adjustedH) + $0.height.equalTo(40.adjustedH) + } + + textCountLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalTo(confirmButton.snp.leading).offset(-12.adjustedW) + } + + confirmButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(40.adjustedH) + } + } + + private func updateTextViewLayoutWhenEndEditing() { + textFieldContainer.backgroundColor = .white5 + countConfirmContainer.removeFromSuperview() + addSubviews(confirmButton) + + textFieldContainer.snp.remakeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedW) + $0.trailing.equalTo(confirmButton.snp.leading).offset(-20) + $0.height.equalTo(40.adjustedH) + $0.bottom.equalToSuperview().inset(6.adjustedH) + } + + textView.snp.remakeConstraints { + $0.verticalEdges.equalToSuperview().inset(9.5.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(12.adjustedW) + } + + confirmButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(17.5.adjustedH) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.equalTo(21.adjustedH) + } + + textView.isScrollEnabled = false + textView.textContainer.maximumNumberOfLines = 1 + textView.textContainer.lineBreakMode = .byTruncatingTail + + let text = isPlaceholderActive ? placeholder : (textView.text ?? "") + let color: UIColor = isPlaceholderActive ? .grayscale600 : .grayscale100 + textView.typingAttributes = [.font: FontManager.body6R14.font, .foregroundColor: color] + textView.text = text + textView.textColor = color + } +} 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 f5a54b1d..e35145a4 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 @@ -7,9 +7,13 @@ import UIKit +import SnapKit + final class CommonQuestHistoryView: BaseView { + + private var commentTextViewBottomConstraint: Constraint? - private let scrollView = UIScrollView() + private(set) var scrollView = UIScrollView() private let contentView = UIView() private let commonQuestLabel = UILabel() private let dateLabel = UILabel() @@ -19,8 +23,9 @@ final class CommonQuestHistoryView: BaseView { private let answerView = UIView() private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() -// private let answerContentLabel = UILabel() private let questContentView = QuestContentView() + private let commentListView = UITableView() + private let commentTextView = CommentTextView() override func setStyle() { commonQuestLabel.applyByeBooFont( @@ -56,7 +61,7 @@ final class CommonQuestHistoryView: BaseView { } override func setUI() { - addSubview(scrollView) + addSubviews(scrollView, commentTextView) scrollView.addSubview(contentView) contentView.addSubviews( commonQuestLabel, @@ -129,11 +134,23 @@ final class CommonQuestHistoryView: BaseView { $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) } + commentTextView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + commentTextViewBottomConstraint = $0.bottom.equalToSuperview().inset(34.adjustedH).constraint + } } } extension CommonQuestHistoryView { - + + func updateLayout(keyboardHeight: CGFloat) { + let inset = keyboardHeight > 0 ? keyboardHeight : 34.adjustedH + commentTextViewBottomConstraint?.update(inset: inset) + } +} + +extension CommonQuestHistoryView { + func configure( question: String, writtenAt: String, 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 c58dd0c9..00dd03d9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -26,9 +26,15 @@ final class CommonQuestHistoryViewController: BaseViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = true - + addKeyboardObservers() + Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardObservers() + } override func viewDidLoad() { super.viewDidLoad() @@ -41,6 +47,12 @@ final class CommonQuestHistoryViewController: BaseViewController { secondAction: #selector(bottomUp) ) } + + override func setAddTarget() { + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + tapGestureRecognizer.cancelsTouchesInView = false + rootView.scrollView.addGestureRecognizer(tapGestureRecognizer) + } } extension CommonQuestHistoryViewController: BackNavigable { @@ -153,3 +165,51 @@ extension CommonQuestHistoryViewController: BlockReportProtocol { } } } + +extension CommonQuestHistoryViewController { + @objc + private func dismissKeyboard() { + view.endEditing(true) + } + + private func addKeyboardObservers() { + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillShow), + name: UIResponder.keyboardWillShowNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillHide), + name: UIResponder.keyboardWillHideNotification, + object: nil + ) + } + + private func removeKeyboardObservers() { + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc private func keyboardWillShow(_ notification: Notification) { + guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, + let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: keyboardFrame.height) + self.rootView.layoutIfNeeded() + } + } + + @objc private func keyboardWillHide(_ notification: Notification) { + guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: 0) + self.rootView.layoutIfNeeded() + } + } +} From a94efc5e1739365479dc4851d42a048d9e602fc3 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:59:56 +0900 Subject: [PATCH 018/231] =?UTF-8?q?chore:=20#428=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/View/CommonQuest/Common/CommentTextFieldView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift index acaf8b3f..db3fef89 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift @@ -107,7 +107,6 @@ extension CommentTextView: UITextViewDelegate { isPlaceholderActive = false textView.text = "" } - // 편집 시작 시 항상 paragraph style을 typingAttributes에 적용 → 줄바꿈 line height 보장 textView.applyTextViewStyle(style: .body6R14, text: textView.text, color: .grayscale100) updateTextViewLayoutWhenEditing() } From dc914b647ed8f2327083fc36adb7ce293195b4bd Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 17:06:16 +0900 Subject: [PATCH 019/231] =?UTF-8?q?feat:=20#431=20=EA=B8=B0=EC=A1=B4=20UI?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Offboarding/View/FinishJourneyView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/View/FinishJourneyView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/View/FinishJourneyView.swift index d992fd43..4dac8aa3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/View/FinishJourneyView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/View/FinishJourneyView.swift @@ -19,7 +19,7 @@ final class FinishJourneyView: BaseView { private let thirdTextLabel = UILabel() private let characterLottie = LottieAnimationView(name: "bori_cake") - let startButton = ByeBooButton(titleText: "새로운 이별 극복 여정 시작하기", type: .enabled) + let startButton = ByeBooButton(titleText: "새로운 여정 시작하기", type: .enabled) let lookBackButton = ByeBooButton(titleText: "완료한 여정 다시보기", type: .sub) let backHomeLabel = UILabel() From 686f29f2807090a436bee90409a656fcaf0e2f4c Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 17:39:20 +0900 Subject: [PATCH 020/231] =?UTF-8?q?add:=20#431=20=EC=97=90=EC=85=8B=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Bori/talk.imageset/Contents.json | 15 +++++++++++++++ .../Bori/talk.imageset/image 1452.svg | 9 +++++++++ 2 files changed, 24 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/image 1452.svg diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/Contents.json new file mode 100644 index 00000000..e56dd212 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "image 1452.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/image 1452.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/image 1452.svg new file mode 100644 index 00000000..07cf37f5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/Bori/talk.imageset/image 1452.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 21caa9410e9cc8f1daddca59f4b884ac18f6f919 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 17:40:01 +0900 Subject: [PATCH 021/231] =?UTF-8?q?feat:=20#431=20=EC=98=A4=ED=94=84?= =?UTF-8?q?=EB=B3=B4=EB=94=A9=20quest=20empty=20view=20UI=20+=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/View/QuestMain/QuestEmptyView.swift | 51 +++++++++++++++++++ .../QuestCheckViewController.swift | 14 ++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift new file mode 100644 index 00000000..6016a921 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift @@ -0,0 +1,51 @@ +// +// QuestEmptyView.swift +// ByeBoo-iOS +// +// Created by 최주리 on 5/5/26. +// + +import UIKit + +final class QuestEmptyView: BaseView { + + private let stackView = UIStackView() + private let imageView = UIImageView() + private let textLabel = UILabel() + let button = ByeBooButton(titleText: "보리 만나러 가기", type: .enabled) + + override func setStyle() { + stackView.do { + $0.axis = .vertical + $0.spacing = 20 + $0.alignment = .center + } + + imageView.do { + $0.image = .talk + } + + textLabel.do { + $0.text = "하츠핑님, 수고하셨어요.\n보리가 하고 싶은 말이 있다고 해요!" + $0.applyByeBooFont(style: .sub3M18, color: .grayscale50) + $0.numberOfLines = 2 + } + } + + override func setUI() { + addSubview(stackView) + stackView.addArrangedSubviews(imageView, textLabel, button) + } + + override func setLayout() { + stackView.snp.makeConstraints { + $0.center.equalToSuperview() + } + } +} + +extension QuestEmptyView { + func configure(name: String) { + textLabel.text = "\(name)님, 수고하셨어요.\n보리가 하고 싶은 말이 있다고 해요!" + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift index cfc8fb5d..3e56d9e3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift @@ -16,6 +16,7 @@ final class QuestCheckViewController: BaseViewController { private static let lastStep = 5 private let questsCheckView = ViewAllQuestBaseView(headerView: QuestCheckHeaderView()) + private let finishEmptyView = QuestEmptyView() private let viewModel: ProgressingQuestsViewModel var coordinator: QuestCheckCoordinating? private var cancellable = Set() @@ -78,6 +79,10 @@ final class QuestCheckViewController: BaseViewController { $0.backgroundColor = .grayscale900 } } + + override func setAddTarget() { + finishEmptyView.button.addTarget(self, action: #selector(goBoriButtonDidTap), for: .touchUpInside) + } } extension QuestCheckViewController: ToastPresentable, ToastErrorHandler { @@ -105,7 +110,7 @@ extension QuestCheckViewController: ToastPresentable, ToastErrorHandler { case (.success(_), .success(_), .failure(_)): self?.coordinator?.moveQuestStart() case (.success(_), .failure(.notFound), .failure(_)): - self?.coordinator?.moveFinishQuest() + self?.view = self?.finishEmptyView case (_, .failure(let error), _), (_, _, .failure(let error)): self?.handleError(error) default: @@ -208,6 +213,13 @@ extension QuestCheckViewController { } } +extension QuestCheckViewController { + @objc + private func goBoriButtonDidTap() { + coordinator?.moveFinishQuest() + } +} + extension QuestCheckViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { From 767e09fcb57a02c34f02d4ca5e3fea6f87f45886 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 18:00:27 +0900 Subject: [PATCH 022/231] =?UTF-8?q?refactor:=20#413=20task=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/AIAnswerViewController.swift | 6 ++++++ .../Quest/ViewModel/AIAnswerViewModel.swift | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/AIAnswerViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/AIAnswerViewController.swift index 1e7843ec..50fda3ed 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/AIAnswerViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/AIAnswerViewController.swift @@ -50,6 +50,12 @@ final class AIAnswerViewController: BaseViewController { ) ) } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + viewModel.action(.viewDidDisappear) + } } extension AIAnswerViewController { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift index 91456604..98ccb2cc 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift @@ -20,6 +20,8 @@ final class AIAnswerViewModel { private var questID: Int = 1 private var isAnswerExists: Bool = false + private var fetchAIAnswerTask: Task? + init(fetchAIAnswerUseCase: FetchAIAnswerUseCase) { self.fetchAIAnswerUseCase = fetchAIAnswerUseCase @@ -34,6 +36,7 @@ final class AIAnswerViewModel { extension AIAnswerViewModel: ViewModelType { enum Input { case viewDidLoad(questID: Int, isAIAnswerExists: Bool) + case viewDidDisappear } struct Output { @@ -48,13 +51,17 @@ extension AIAnswerViewModel: ViewModelType { self.isAnswerExists = isExists fetchAIAnswer() + case .viewDidDisappear: + cancelTask() } } } extension AIAnswerViewModel { private func fetchAIAnswer() { - Task { + fetchAIAnswerTask?.cancel() + + fetchAIAnswerTask = Task { do { AILoadingSubject.send(true) @@ -74,4 +81,9 @@ extension AIAnswerViewModel { } } } + + private func cancelTask() { + fetchAIAnswerTask?.cancel() + fetchAIAnswerTask = nil + } } From d969ebe9747f3b8871ea2602990bc1882232f2b0 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 18:00:41 +0900 Subject: [PATCH 023/231] =?UTF-8?q?fix:=20#431=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/PresentationDependencyAssembler.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index b4a49e37..8ecf838a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -91,9 +91,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: ArchiveQuestViewModel.self) { container in - guard let questAnswerUseCase = container.resolve(type: QuestAnswerUseCase.self), - let fetchAIAnswerUseCase = container.resolve(type: FetchAIAnswerUseCase.self) - else { + guard let questAnswerUseCase = container.resolve(type: QuestAnswerUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } @@ -103,7 +101,6 @@ struct PresentationDependencyAssembler: DependencyAssembler { ) } - DIContainer.shared.register(type: HomeViewModel.self) { container in guard let characterUseCase = container.resolve(type: FetchCharacterDialogueUseCase.self), let questStatusUseCase = container.resolve(type: FetchQuestStatusUseCase.self), From 51b818ae0b94ba9b49348861928ffc1359131a84 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 18:01:14 +0900 Subject: [PATCH 024/231] =?UTF-8?q?refactor:=20#413=20AI=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=8C=90=EB=8B=A8=20=ED=9B=84=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20repository=20->=20usecase=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repository/QuestsRepository.swift | 45 +++++++++---------- .../Domain/Interface/QuestsInterface.swift | 3 +- .../Domain/UseCase/FetchAIAnswerUseCase.swift | 6 ++- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift index 8aebd63b..a6762f3c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift @@ -122,24 +122,23 @@ struct DefaultQuestRepository: QuestsInterface { .emptyAnswerStub() } - func fetchAIAnswer(questID: Int, isAnswerExists: Bool) async throws -> AIAnswerEntity { - let result: AIAnswerResponseDTO - - if isAnswerExists { - result = try await network.request( - QuestAPI.fetchAIAnswer(questID: questID), - decodingType: AIAnswerResponseDTO.self - ) - } else { - result = try await network.request( - QuestAPI.createAIAnswer(questID: questID), - decodingType: AIAnswerResponseDTO.self - ) - } + func fetchAIAnswer(questID: Int) async throws -> AIAnswerEntity { + let result: AIAnswerResponseDTO = try await network.request( + QuestAPI.createAIAnswer(questID: questID), + decodingType: AIAnswerResponseDTO.self + ) return result.toEntity() } + func createAIAnswer(questID: Int) async throws -> AIAnswerEntity { + let result = try await network.request( + QuestAPI.createAIAnswer(questID: questID), + decodingType: AIAnswerResponseDTO.self + ) + return result.toEntity() + } + // MARK: private function private func makeSignedURL(imageKey: String) async throws -> String { @@ -188,14 +187,6 @@ struct DefaultQuestRepository: QuestsInterface { QuestAPI.editActive(questID: questID, request: editQuestActiveDTO) ) } - - private func createAIAnswer(questID: Int) async throws -> AIAnswerEntity { - let result = try await network.request( - QuestAPI.createAIAnswer(questID: questID), - decodingType: AIAnswerResponseDTO.self - ) - return result.toEntity() - } } final class MockQuestsRepository: QuestsInterface { @@ -258,9 +249,13 @@ final class MockQuestsRepository: QuestsInterface { .emptyAnswerStub() } - func fetchAIAnswer(questID: Int, isAnswerExists: Bool) async throws -> AIAnswerEntity { + func fetchAIAnswer(questID: Int) async throws -> AIAnswerEntity { +// throw ByeBooError.unknownError + .stub() + } + + func createAIAnswer(questID: Int) async throws -> AIAnswerEntity { try await Task.sleep(for: .seconds(2)) - throw ByeBooError.unknownError -// .stub() + return .stub() } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/QuestsInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/QuestsInterface.swift index beb9e86e..2c0a3d71 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/QuestsInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/QuestsInterface.swift @@ -31,5 +31,6 @@ protocol QuestsInterface { imageKey: String, isImageChanged: Bool) async throws func fetchCommoncQuest(date: String) async throws -> CommonQuestAnswersEntity - func fetchAIAnswer(questID: Int, isAnswerExists: Bool) async throws -> AIAnswerEntity + func fetchAIAnswer(questID: Int) async throws -> AIAnswerEntity + func createAIAnswer(questID: Int) async throws -> AIAnswerEntity } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchAIAnswerUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchAIAnswerUseCase.swift index 03745916..fcea3f1b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchAIAnswerUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchAIAnswerUseCase.swift @@ -17,6 +17,10 @@ struct DefaultFetchAIAnswerUseCase: FetchAIAnswerUseCase { } func execute(questID: Int, isAnswerExists: Bool) async throws -> AIAnswerEntity { - try await repository.fetchAIAnswer(questID: questID, isAnswerExists: isAnswerExists) + if isAnswerExists { + return try await repository.fetchAIAnswer(questID: questID) + } else { + return try await repository.createAIAnswer(questID: questID) + } } } From f8d039a97f92ae1f3d40c24de52688b10e98f36f Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Tue, 5 May 2026 23:04:16 +0900 Subject: [PATCH 025/231] =?UTF-8?q?feat:=20#431=20=EB=B7=B0=20=EC=84=B8?= =?UTF-8?q?=EB=B6=80=20=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/View/QuestMain/QuestEmptyView.swift | 8 +++++++- .../Quest/ViewController/QuestCheckViewController.swift | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift index 6016a921..cc59026f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/QuestMain/QuestEmptyView.swift @@ -29,6 +29,7 @@ final class QuestEmptyView: BaseView { $0.text = "하츠핑님, 수고하셨어요.\n보리가 하고 싶은 말이 있다고 해요!" $0.applyByeBooFont(style: .sub3M18, color: .grayscale50) $0.numberOfLines = 2 + $0.textAlignment = .center } } @@ -39,7 +40,12 @@ final class QuestEmptyView: BaseView { override func setLayout() { stackView.snp.makeConstraints { - $0.center.equalToSuperview() + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().inset(176.adjustedH) + } + + button.snp.makeConstraints { + $0.width.equalToSuperview() } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift index 3e56d9e3..52f2b5b6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift @@ -109,7 +109,8 @@ extension QuestCheckViewController: ToastPresentable, ToastErrorHandler { self?.updateQuestMainUI(name: name, journey: journey, quests: quests) case (.success(_), .success(_), .failure(_)): self?.coordinator?.moveQuestStart() - case (.success(_), .failure(.notFound), .failure(_)): + case let (.success(name), .failure(.notFound), .failure(_)): + self?.finishEmptyView.configure(name: name) self?.view = self?.finishEmptyView case (_, .failure(let error), _), (_, _, .failure(let error)): self?.handleError(error) From e36e93f27c6ed0133e550179b2f436f4dea41208 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 6 May 2026 11:38:54 +0900 Subject: [PATCH 026/231] =?UTF-8?q?refactor:=20#429=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=83=80=EC=9E=85=20=EA=B5=AC=ED=98=84=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/View/Notice/NoticeCardCell.swift | 30 ++++++++----------- .../Type/CommentCommonQuestNotice.swift | 23 -------------- .../Notice/Type/LikeCommonQuestNotice.swift | 23 -------------- .../View/Notice/Type/NoticeDisplayable.swift | 22 -------------- .../Notice/Type/TodayQuestOpenNotice.swift | 23 -------------- .../NoticesViewController.swift | 6 +--- 6 files changed, 13 insertions(+), 114 deletions(-) delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index b507b78e..dc597d0a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -13,12 +13,6 @@ final class NoticeCardCell: UITableViewCell { private let titleLabel = UILabel() private let subtitleLabel = UILabel() private let writtenTimeLabel = UILabel() - private var writtenTime: String = "" - private var noticeType: NoticeDisplayable? { - didSet { - setStyle() - } - } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -33,30 +27,21 @@ final class NoticeCardCell: UITableViewCell { } private func setStyle() { - guard let noticeType else { return } - self.do { $0.layer.cornerRadius = 12 - $0.backgroundColor = noticeType.backgroundColor - } - noticeImageView.do { - $0.image = noticeType.iconImage } titleLabel.applyByeBooFont( style: .body1Sb16, - text: noticeType.title, color: .grayscale200, textAlignment: .left ) subtitleLabel.applyByeBooFont( style: .body6R14, - text: noticeType.subtitle, color: .grayscale100, textAlignment: .left ) writtenTimeLabel.applyByeBooFont( style: .cap2R12, - text: writtenTime, color: .grayscale400, textAlignment: .left ) @@ -96,8 +81,17 @@ final class NoticeCardCell: UITableViewCell { extension NoticeCardCell { - func bind(writtenTime: String, noticeType: NoticeDisplayable) { - self.writtenTime = writtenTime - self.noticeType = noticeType + func bind( + backgroundColor: UIColor, + iconImage: UIImage, + title: String, + subtitle: String, + writtenTiem: String + ) { + self.backgroundColor = backgroundColor + noticeImageView.image = iconImage + titleLabel.text = title + subtitleLabel.text = subtitle + writtenTimeLabel.text = writtenTiem } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift deleted file mode 100644 index 606eec2e..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/CommentCommonQuestNotice.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// CommentCommonQuestNotice.swift -// ByeBoo-iOS -// -// Created by APPLE on 5/1/26. -// - -import UIKit - -struct CommentCommonQuestNotice: NoticeDisplayable { - let nickname: String - - var isRead: Bool = false - var iconImage: UIImage { - .commonJourney - } - var title: String { - "공통여정에 댓글이 달렸어요 💬" - } - var subtitle: String { - "내가 작성한 글에 \(nickname)님이 댓글을 남겼어요" - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift deleted file mode 100644 index 6f249c3d..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/LikeCommonQuestNotice.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// LikeCommonQuestNotice.swift -// ByeBoo-iOS -// -// Created by APPLE on 5/1/26. -// - -import UIKit - -struct LikeCommonQuestNotice: NoticeDisplayable { - let nickname: String - - var isRead: Bool = false - var iconImage: UIImage { - .commonJourney - } - var title: String { - "공통여정에 답변에 공감이 달렸어요 ❤️" - } - var subtitle: String { - "내가 작성한 글에 \(nickname)님이 공감을 남겼어요" - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift deleted file mode 100644 index 79c2872a..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/NoticeDisplayable.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// NoticeDisplayable.swift -// ByeBoo-iOS -// -// Created by APPLE on 5/1/26. -// - -import UIKit - -protocol NoticeDisplayable { - var isRead: Bool { get set } - var backgroundColor: UIColor { get } - var iconImage: UIImage { get } - var title: String { get } - var subtitle: String { get } -} - -extension NoticeDisplayable { - var backgroundColor: UIColor { - isRead ? .white5 : .primary30020 - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift deleted file mode 100644 index 3d9aa6d4..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/Type/TodayQuestOpenNotice.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// TodayQuestOpenNotice.swift -// ByeBoo-iOS -// -// Created by APPLE on 5/1/26. -// - -import UIKit - -struct TodayQuestOpenNotice: NoticeDisplayable { - let questNumber: Int - - var isRead: Bool = false - var iconImage: UIImage { - .myOn - } - var title: String { - "오늘의 퀘스트 오픈 🌱" - } - var subtitle: String { - "\(questNumber)번째 퀘스트가 오픈됐어요, 시작해볼까요?" - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift index f68b2aec..78046246 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift @@ -91,11 +91,7 @@ extension NoticesViewController: UITableViewDataSource { else { return UITableViewCell() } - - cell.bind( - writtenTime: "방금 전", - noticeType: LikeCommonQuestNotice(nickname: "") - ) + return cell } } From 3daba6ce9b23fc44fe8f6de47e5e33e707a82278 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Wed, 6 May 2026 22:20:12 +0900 Subject: [PATCH 027/231] =?UTF-8?q?fix:=20#435=20=EB=A6=AC=ED=80=B4?= =?UTF-8?q?=EB=93=9C=20=EA=B8=80=EB=9E=98=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewModel/AIAnswerViewModel.swift | 1 + ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift index 98ccb2cc..4246cfb4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/AIAnswerViewModel.swift @@ -79,6 +79,7 @@ extension AIAnswerViewModel { AIResultSubject.send(.failure(error)) AILoadingSubject.send(false) } + fetchAIAnswerTask = nil } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist index 9687993c..17810457 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist @@ -2,6 +2,8 @@ + UIDesignRequiresCompatibility + BASE_URL $(BASE_URL) CFBundleURLTypes From f28e1d83634713e16eeb90db7923daed6ba58c14 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 6 May 2026 22:28:57 +0900 Subject: [PATCH 028/231] =?UTF-8?q?chore:=20#429=20adjusted=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Home/View/Notice/NoticeCardsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift index c25be376..d97dcff6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift @@ -18,7 +18,7 @@ final class NoticeCardsView: BaseView { $0.contentInset = UIEdgeInsets( top: 0, left: 0, - bottom: 24, + bottom: 24.adjustedH, right: 0 ) } From 349d4ab47b0a4f66171c72aa2384cf5409cac68c Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:53:13 +0900 Subject: [PATCH 029/231] =?UTF-8?q?refactor:=20#428=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20DTO=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추후 서버 연결시 주석해제 --- .../CommonQuestAnswerDetailResponseDTO.swift | 46 ++++++++++++++++ .../CommonQuestAnswerRepliesResponseDTO.swift | 54 +++++++++++++++++++ .../Model/CommonQuestAnswersResponseDTO.swift | 24 +++++++-- 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift new file mode 100644 index 00000000..42fdd755 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift @@ -0,0 +1,46 @@ +// +// CommonQuestAnswerDetailResponseDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Foundation + +struct CommonQuestAnswerDetailResponseDTO: Decodable { + let question: String + let answer: CommonQuestAnswerResponseDTO + let comments: [CommonQuestCommentResponseDTO] +} + +struct CommonQuestCommentResponseDTO: Decodable { + let commentId: Int + let replyCount: Int + let writerId: Int + let writer: String + let profileIcon: String + let writtenAt: String + let content: String +} + +extension CommonQuestAnswerDetailResponseDTO { + func toEntity() -> [CommonQuestCommentEntity] { + comments.map { + $0.toEntity() + } + } +} + +extension CommonQuestCommentResponseDTO { + func toEntity() -> CommonQuestCommentEntity { + .init( + commentID: commentId, + replyCount: replyCount, + writerID: writerId, + writer: writer, + profileIcon: profileIcon, + writtenAt: writtenAt, + content: content + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift new file mode 100644 index 00000000..2c798e0a --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift @@ -0,0 +1,54 @@ +// +// CommonQuestAnswerRepliesResponseDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Foundation + +struct CommonQuestAnswerRepliesResponseDTO: Decodable { + let totalCount: Int + let comment: CommonQuestAnswerCommentResponseDTO + let replies: [CommonQuestAnswerReplyResponseDTO] +} + +struct CommonQuestAnswerCommentResponseDTO: Decodable { + let content: String + let writer: String + let createdAt: String + let profileIcon: String + let commentId: Int + let writerId: Int +} + +struct CommonQuestAnswerReplyResponseDTO: Decodable { + let content: String + let writer: String + let createdAt: String + let profileIcon: String + let commentId: Int + let writerId: Int +} + +extension CommonQuestAnswerRepliesResponseDTO { + func toEntity() -> [CommonQuestCommentEntity] { + replies.map { + $0.toEntity() + } + } +} + +extension CommonQuestAnswerReplyResponseDTO { + func toEntity() -> CommonQuestCommentEntity { + .init( + commentID: commentId, + replyCount: nil, + writerID: writerId, + writer: writer, + profileIcon: profileIcon, + writtenAt: createdAt, + content: content + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift index 8c3e1aae..32b563fc 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift @@ -16,12 +16,17 @@ struct CommonQuestAnswersResponseDTO: Decodable { } struct CommonQuestAnswerResponseDTO: Decodable { +// let likeCount: Int +// let commentCount: Int +// let isLiked: Bool let answerId: Int +// let userId: Int +// let writerId: String let writer: String + let writerId: Int let profileIcon: String let writtenAt: String let content: String - let writerId: Int } extension CommonQuestAnswersResponseDTO { @@ -41,13 +46,26 @@ extension CommonQuestAnswersResponseDTO { extension CommonQuestAnswerResponseDTO { func toEntity(userName: String) -> CommonQuestAnswerEntity { .init( +// isMyAnswer: userName == writerId ? true : false, +// answerID: answerId, +// writerID: writerId, +// profileIcon: profileIcon, +// writtenAt: writtenAt, +// content: content, +// userID: userId, +// likeCount: likeCount, +// commentCount: commentCount, +// isLiked: isLiked isMyAnswer: userName == writer ? true : false, answerID: answerId, - writer: writer, + writerID: writer, profileIcon: profileIcon, writtenAt: writtenAt, content: content, - writerID: writerId + userID: writerId, + likeCount: 3, + commentCount: 4, + isLiked: false, ) } } From 5cb40bfb8935d1acdf46c773f0eb610903b3e8e7 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:53:49 +0900 Subject: [PATCH 030/231] =?UTF-8?q?feat:=20#428=20=EC=9E=AC=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=B4=20answer=20entity=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20comment=20entity=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/CommonQuestAnswersEntity.swift | 21 +++++--- .../Entity/CommonQuestCommentEntity.swift | 49 +++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift index 8edb6a71..a0d251ff 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift @@ -20,11 +20,14 @@ struct CommonQuestAnswersEntity { struct CommonQuestAnswerEntity { let isMyAnswer: Bool let answerID: Int - let writer: String + let writerID: String let profileIcon: String let writtenAt: String let content: String - let writerID: Int + let userID: Int + let likeCount: Int + let commentCount: Int + let isLiked: Bool } extension CommonQuestAnswersEntity { @@ -35,11 +38,14 @@ extension CommonQuestAnswersEntity { CommonQuestAnswerEntity( isMyAnswer: false, answerID: $0, - writer: "유저\($0)", + writerID: "유저\($0)", profileIcon: profileIcons[$0 % 4], writtenAt: Date.now.toString(), content: "\($0)번째 테스트 답변", - writerID: 1 + userID: $0, + likeCount: 1, + commentCount: 1, + isLiked: false ) } @@ -61,11 +67,14 @@ extension CommonQuestAnswerEntity { .init( isMyAnswer: false, answerID: 1, - writer: "장원영", + writerID: "장원영", profileIcon: "SO_SO", writtenAt: "2025-10-12", content: "헤어진 지 벌써 일주일이 지났습니다. 처음에는 실감이 안 나서 눈물조차 나오지 않았어요. 그저 멍하니 천장만 바라보며 시간을 보냈습니다. 그런데 오늘 아침, 습관적으로 휴대폰을 확인하다가 더 이상 '굿모닝' 인사를 보낼 사람이 없다는 사실을 깨닫고 그제야 무너져 내렸습니다. 밥알이 모래알 같아서 잘 넘어가지도 않네요. 친구들은 시간이 약이라고, 더 좋은 사람 만날 거라고 위로하지만 지금 당장은 그 어떤 말도 귀에 들어오지 않습니다.", - writerID: 1 + userID: 12, + likeCount: 3, + commentCount: 4, + isLiked: false ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift new file mode 100644 index 00000000..3e97accb --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -0,0 +1,49 @@ +// +// CommonQuestCommentEntity.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Foundation + +struct CommonQuestCommentEntity { + let commentID: Int + let replyCount: Int? + let writerID: Int + let writer: String + let profileIcon: String + let writtenAt: String + let content: String +} + +extension CommonQuestCommentEntity { + static func toCommentListStub() -> [Self] { + let stub = toCommentStub() + return [stub, stub, stub, stub, stub] + } + + static func toCommentStub() -> Self { + .init( + commentID: 1, + replyCount: 3, + writerID: 2, + writer: "장원영", + profileIcon: "SADNESS", + writtenAt: "2026-02-19T02:09:43.735730", + content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ냐냐냐냐냐냐냐냐냐ㅑ냐냐냐냐냐냐ㅑㄴ냐냐ㅑ냐냐냐냔냐냐냐냐냐" + ) + } + + static func toReplyStub() -> Self { + .init( + commentID: 1, + replyCount: nil, + writerID: 2, + writer: "가을이", + profileIcon: "SELF_UNDERSTANDING", + writtenAt: "2026-02-19T02:09:43.735730", + content: "헤어짐" + ) + } +} From 840d3b03e26b534e0809002cb08abe7330b3c1f5 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:54:10 +0900 Subject: [PATCH 031/231] =?UTF-8?q?refactor:=20#428=20profile=20Icon=20Str?= =?UTF-8?q?ing=20->=20Image=20=EC=A0=84=EC=97=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Enum/ProfileIcon.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift new file mode 100644 index 00000000..da77f388 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift @@ -0,0 +1,32 @@ +// +// ProfileIcon.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import UIKit + +enum ProfileIcon: String, CaseIterable { + case sad = "SADNESS" + case selfUnderstanding = "SELF_UNDERSTANDING" + case soso = "SO_SO" + case relieved = "RELIEVED" + + var image: UIImage { + switch self { + case .sad: + return .sadnessBadge + case .selfUnderstanding: + return .selfUnderstandingBadge + case .soso: + return .sosoBadge + case .relieved: + return .relievedBadge + } + } + + static func image(for iconString: String) -> UIImage? { + ProfileIcon(rawValue: iconString)?.image + } +} From 809f5dc59de383e766b03b7c6eae3ad1bf20f405 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:54:40 +0900 Subject: [PATCH 032/231] =?UTF-8?q?feat:=20#428=20Textview=20line=20?= =?UTF-8?q?=EC=88=98=20=EA=B3=84=EC=82=B0=20=EC=9D=B5=EC=8A=A4=ED=85=90?= =?UTF-8?q?=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Extension/UITextView+.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift index 7cf1cc4d..74c2481a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift @@ -45,4 +45,23 @@ extension UITextView { color: color ) } + + func numberOfLine() -> Int { + guard !text.isEmpty else { return 0 } + let size = CGSize(width: frame.width, height: .infinity) + let estimatedSize = sizeThatFits(size) + + let lineHeight: CGFloat + if let paragraphStyle = attributedText?.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle, + paragraphStyle.minimumLineHeight > 0 { + lineHeight = paragraphStyle.minimumLineHeight + } else if let font = attributedText?.attribute(.font, at: 0, effectiveRange: nil) as? UIFont { + lineHeight = font.lineHeight + } else { + lineHeight = self.font?.lineHeight ?? 0 + } + + guard lineHeight > 0 else { return 0 } + return Int(estimatedSize.height / lineHeight) + } } From 55b01b13f6a79478b634018e4e53112997edfdee Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:55:05 +0900 Subject: [PATCH 033/231] =?UTF-8?q?style:=20#428=20=EB=8C=93=EA=B8=80=20UI?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommentTableViewCell.swift | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift new file mode 100644 index 00000000..43c42103 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -0,0 +1,210 @@ +// +// CommentCellTableViewCell.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/4/26. +// + +import UIKit + +final class CommentTableViewCell: UITableViewCell { + + private var replyCommentArrow = UIImageView() + private var replyCommentContainer = UIView() + private let profileIcon = UIImageView(image: .relievedBadge) + private let nicknameLabel = UILabel() + private let dateLabel = UILabel() + private let menuButton = UIImageView() + private let commentTextView = UITextView() + private var moreLabel = UILabel() + private var replyCountContainer = UIStackView() + private var replyCommentIcon = UIImageView() + private var replyCountLabel = UILabel() + + private var replyCount: Int = 0 + + override init( + style: UITableViewCell.CellStyle, + reuseIdentifier: String? + ) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + setUI() + setStyle() + setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUI() { + contentView.addSubviews(profileIcon, nicknameLabel, dateLabel, menuButton, commentTextView, moreLabel) + } + + private func setStyle() { + backgroundColor = .grayscale900 + selectionStyle = .none + + nicknameLabel.do { + $0.applyByeBooFont(style: .body5M14, color: .grayscale200) + } + + dateLabel.do { + $0.applyByeBooFont(style: .cap2R12, color: .grayscale400) + } + + menuButton.do { + $0.image = .menu + } + + commentTextView.do { + $0.isEditable = false + $0.isScrollEnabled = false + $0.isUserInteractionEnabled = false + $0.isSelectable = false + $0.backgroundColor = .clear + $0.textContainerInset = .zero + $0.textContainer.lineBreakMode = .byTruncatingTail + $0.textContainer.lineFragmentPadding = 0 + $0.applyByeBooFont(style: .body6R14, color: .grayscale100) + } + + moreLabel.do { + $0.applyByeBooFont(style: .body6R14, text: "더보기", color: .grayscale400) + $0.backgroundColor = .grayscale900 + } + + replyCountContainer.do { + $0.axis = .horizontal + $0.spacing = 4.adjustedW + } + + replyCommentIcon.do { + $0.image = .comment + } + + replyCountLabel.do { + $0.applyByeBooFont(style: .cap2R12, text: String(replyCount), color: .grayscale100) + } + } + + private func setLayout() { + profileIcon.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview() + $0.size.equalTo(20.adjustedH) + } + nicknameLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalTo(profileIcon.snp.trailing).offset(4.adjustedW) + } + dateLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(18.5.adjustedH) + $0.leading.equalTo(nicknameLabel.snp.trailing).offset(4) + } + menuButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.trailing.equalToSuperview() + $0.size.equalTo(24.adjustedH) + } + commentTextView.snp.makeConstraints { + $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) + $0.leading.equalTo(profileIcon.snp.leading).offset(20) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.lessThanOrEqualTo(105.adjustedH) + $0.bottom.equalToSuperview().inset(16.adjustedH) + } + moreLabel.snp.makeConstraints { + $0.bottom.equalTo(commentTextView.snp.bottom) + $0.trailing.equalTo(commentTextView.snp.trailing).offset(-20.adjustedW) + } + } + + private func setReplyLayout() { + contentView.addSubviews(replyCommentArrow, replyCountContainer) + replyCountContainer.addArrangedSubviews(replyCommentIcon, replyCountLabel) + + replyCommentArrow.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedH) + $0.size.equalTo(24.adjustedH) + } + replyCountContainer.snp.makeConstraints { + $0.top.equalTo(commentTextView.snp.bottom).offset(8.adjustedH) + $0.leading.equalTo(commentTextView.snp.leading) + $0.height.equalTo(20.adjustedH) + $0.bottom.equalToSuperview().inset(16.adjustedH) + } + replyCommentIcon.snp.makeConstraints { + $0.size.equalTo(20.adjustedH) + } + profileIcon.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalTo(replyCommentArrow.snp.trailing) + $0.size.equalTo(20.adjustedH) + } + commentTextView.snp.remakeConstraints { + $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) + $0.leading.equalTo(profileIcon.snp.leading).offset(20) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.lessThanOrEqualTo(105.adjustedH) + } + } +} + +extension CommentTableViewCell { + func configure( + replyCount: Int? = nil, + writer: String, + profileIcon: UIImage, + writtenAt: String, + content: String + ) { + + if let _ = replyCount { + setReplyLayout() + } + nicknameLabel.text = writer + self.profileIcon.image = profileIcon + dateLabel.text = writtenAt + commentTextView.applyTextViewStyle(style: .body6R14, text: content, color: .grayscale100) + + layoutIfNeeded() + + let numberOfLines = commentTextView.numberOfLine() + ByeBooLogger.debug("라인수 \(numberOfLines)") + applyStyleWhenHideText(numberOfLines) + } +} + +extension CommentTableViewCell { + private func applyStyleWhenHideText(_ numberOfLines: Int) { + moreLabel.isHidden = numberOfLines > 5 ? false : true + commentTextView.textContainer.maximumNumberOfLines = 5 + + let moreLabelWidth: CGFloat = moreLabel.intrinsicContentSize.width + let contentHeight = commentTextView.sizeThatFits( + CGSize(width: commentTextView.bounds.width, height: .infinity) + ).height + + + let lineHeight: CGFloat + if let paragraphStyle = commentTextView.attributedText?.attribute(.paragraphStyle, at: 0, effectiveRange: nil) + as? NSParagraphStyle, + paragraphStyle.minimumLineHeight > 0 { + lineHeight = paragraphStyle.minimumLineHeight + } else { + lineHeight = commentTextView.font?.lineHeight ?? 0 + } + + let exclusionRect = CGRect( + x: commentTextView.bounds.width - moreLabelWidth - 10, + y: contentHeight - lineHeight, + width: moreLabelWidth, + height: lineHeight + ) + ByeBooLogger.debug("가로 : \(exclusionRect.width), 세로: \(exclusionRect.height)" ) + commentTextView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionRect)] + } +} From 70c51087dd6521768faa206f73c32b547624b29f Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:58:44 +0900 Subject: [PATCH 034/231] =?UTF-8?q?refactor:=20#428=20entity=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20configure=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=82=B4=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CommonQuest/Cells/CommonQuestAnswerCell.swift | 6 ++++-- .../View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift | 3 ++- .../Quest/ViewController/CommonQuestViewController.swift | 7 +++---- 3 files changed, 9 insertions(+), 7 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 034016d9..4cf66ccc 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 @@ -91,14 +91,16 @@ extension CommonQuestAnswerCell { if let profileIcon { userIconView.image = profileIcon } - userNicknameLabel.text = answer.writer + userNicknameLabel.text = answer.writerID answerID = answer.answerID questContentView.configure( content: answer.content, writtenAt: writtenAt, isLiked: false, likeCount: 4, - commentCount: 3) + commentCount: 3, + showAllText: false + ) } func getAnswewrID() -> Int? { 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 9b33af32..650ce1ec 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 @@ -108,7 +108,8 @@ extension CommonQuestMyAnswerCell { writtenAt: writtenAt, isLiked: isLiked, likeCount: likeCount, - commentCount: commentCount + commentCount: commentCount, + showAllText: true ) } } 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 e6cfeb14..f3ca30c7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -145,10 +145,10 @@ extension CommonQuestViewController: UITableViewDelegate { question: viewModel.question, writtenAt: formattedWrittenAt, profileIcon: profileIcon, - nickname: answer.writer, + nickname: answer.writerID, content: answer.content, answerID: answer.answerID, - writerID: answer.writerID, + writerID: answer.userID, isMyAnswer: answer.isMyAnswer ) historyViewController.navigationItem.hidesBackButton = true @@ -162,7 +162,7 @@ extension CommonQuestViewController: UITableViewDelegate { _ tableView: UITableView, heightForRowAt indexPath: IndexPath ) -> CGFloat { - indexPath.row == 0 ? UITableView.automaticDimension : 171.adjustedH + UITableView.automaticDimension } func tableView( @@ -260,7 +260,6 @@ extension CommonQuestViewController: UITableViewDataSource { let profileIcon = viewModel.getProfileIcon(at: indexPath.row - 1) let writtenAt = viewModel.getWrittenAt(at: indexPath.row - 1) cell.questContentView.delegate = self - if let answer, let writtenAt { cell.bind( From cac59772d0042a3d01471bda93078dc7784021be Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:59:09 +0900 Subject: [PATCH 035/231] =?UTF-8?q?refactor:=20#428=20profileIcon=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/CommonQuestViewModel.swift | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) 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 06b6f30f..82f1dc2b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -90,27 +90,6 @@ extension CommonQuestViewModel: ViewModelType { } extension CommonQuestViewModel { - - private enum ProfileIcon: String, CaseIterable { - case sad = "SADNESS" - case selfUnderstanding = "SELF_UNDERSTANDING" - case soso = "SO_SO" - case relieved = "RELIEVED" - - var image: UIImage { - switch self { - case .sad: - return .sadnessBadge - case .selfUnderstanding: - return .selfUnderstandingBadge - case .soso: - return .sosoBadge - case .relieved: - return .relievedBadge - } - } - } - var question: String { commonQuest?.question ?? "" } @@ -141,17 +120,10 @@ extension CommonQuestViewModel { } return answers[index] } - + func getProfileIcon(at index: Int) -> UIImage? { - guard index >= 0 && index < answers.count else { - return nil - } - - let iconString = self.answers[index].profileIcon - let profileIcon = ProfileIcon.allCases - .first { $0.rawValue == iconString }? - .image - return profileIcon + guard index >= 0 && index < answers.count else { return nil } + return ProfileIcon.image(for: answers[index].profileIcon) } func getWrittenAt(at index: Int) -> String? { From ba83a4078db88a4f492b6973807f56dce6412d84 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:59:40 +0900 Subject: [PATCH 036/231] =?UTF-8?q?feat:=20#428=20comment=20Table=20View?= =?UTF-8?q?=20VC=EC=97=90=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 65 ++++++++++++++++--- .../CommonQuestHistoryViewModel.swift | 17 +++++ 2 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift 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 00dd03d9..12a21741 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -12,6 +12,8 @@ import Mixpanel final class CommonQuestHistoryViewController: BaseViewController { private let rootView = CommonQuestHistoryView() + private let viewModel = CommonQuestHistoryViewModel() + private var answerID: Int? private var answer: String? private var question: String? @@ -27,10 +29,10 @@ final class CommonQuestHistoryViewController: BaseViewController { super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = true addKeyboardObservers() - + Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } - + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) removeKeyboardObservers() @@ -53,6 +55,14 @@ final class CommonQuestHistoryViewController: BaseViewController { tapGestureRecognizer.cancelsTouchesInView = false rootView.scrollView.addGestureRecognizer(tapGestureRecognizer) } + + override func setDelegate() { + rootView.commentListView.do { + $0.delegate = self + $0.dataSource = self + $0.register(CommentTableViewCell.self) + } + } } extension CommonQuestHistoryViewController: BackNavigable { @@ -62,6 +72,41 @@ extension CommonQuestHistoryViewController: BackNavigable { } } +extension CommonQuestHistoryViewController: UITableViewDelegate { + +} + +extension CommonQuestHistoryViewController: UITableViewDataSource { + func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int) + -> Int { + ByeBooLogger.debug(viewModel.getCommentsCount()) + return viewModel.getCommentsCount() + } + + func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell( + withIdentifier: CommentTableViewCell.identifier, + for: indexPath) as? CommentTableViewCell else { return UITableViewCell() } + + let entity = viewModel.commentLists[indexPath.row] + ByeBooLogger.debug(entity) + cell.configure( + replyCount: entity.replyCount, + writer: entity.writer, + profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, + writtenAt: entity.writtenAt, + content: entity.content + ) + return cell + } + + +} extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { func didTapEdit( @@ -91,7 +136,7 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { writtenAt: writtenAt ) setDelegate(bottomSheet: commonQuestBottomSheet) - + if let sheet = commonQuestBottomSheet.sheetPresentationController{ sheet.detents = [.custom { _ in 224.adjustedH }] sheet.prefersGrabberVisible = true @@ -132,7 +177,7 @@ extension CommonQuestHistoryViewController { self.answer = content self.question = question self.writtenAt = writtenAt - + if let isMyAnswer { commonQuestArchiveType = isMyAnswer ? .mine : .other } @@ -171,7 +216,7 @@ extension CommonQuestHistoryViewController { private func dismissKeyboard() { view.endEditing(true) } - + private func addKeyboardObservers() { NotificationCenter.default.addObserver( self, @@ -186,27 +231,27 @@ extension CommonQuestHistoryViewController { object: nil ) } - + private func removeKeyboardObservers() { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) } - + @objc private func keyboardWillShow(_ notification: Notification) { guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - + UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: keyboardFrame.height) self.rootView.layoutIfNeeded() } } - + @objc private func keyboardWillHide(_ notification: Notification) { guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - + UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: 0) self.rootView.layoutIfNeeded() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift new file mode 100644 index 00000000..eb0625b5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -0,0 +1,17 @@ +// +// CommonQuestHistoryViewModel.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Combine +import Foundation + +final class CommonQuestHistoryViewModel { + private(set) var commentLists: [CommonQuestCommentEntity] = CommonQuestCommentEntity.toCommentListStub() + + func getCommentsCount() -> Int { + return commentLists.count + } +} From 575545101af75688db8d8081968e89e5596f46cc Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 20:00:11 +0900 Subject: [PATCH 037/231] =?UTF-8?q?fix:=20#428=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9E=AC=EC=82=AC=EC=9A=A9=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20UI=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Common/QuestContentView.swift | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 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 c5cedb0c..67a40bb9 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 @@ -11,10 +11,10 @@ protocol CommonQuestLikeCommentProtocol: AnyObject { func likeButtonDidTap() } -final class QuestContentView: BaseView { +final class QuestContentView: UIView { - private let answerContentLabel = UILabel() - private let writtenDateLabel: UILabel? = nil + private let answerContentTextView = UITextView() + private let writtenDateLabel = UILabel() private let likeCommentStackView = UIStackView() private let likeContainerView = UIStackView() @@ -28,8 +28,8 @@ final class QuestContentView: BaseView { private var likeCounts: Int = 0 - override init(frame: CGRect) { - super.init(frame: frame) + init() { + super.init(frame: .zero) setUI() setStyle() @@ -40,31 +40,30 @@ final class QuestContentView: BaseView { fatalError("init(coder:) has not been implemented") } - override func setUI() { + private func setUI() { self.addSubviews( - answerContentLabel, + answerContentTextView, + writtenDateLabel, likeCommentStackView ) likeCommentStackView.addArrangedSubviews(likeContainerView, commentContainerView) likeContainerView.addArrangedSubviews(likeButton, likeCountLabel) commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) - - if let writtenDateLabel { - self.addSubview(writtenDateLabel) - } } - override func setStyle() { - answerContentLabel.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 2 - ) - if let writtenDateLabel { - writtenDateLabel.applyByeBooFont( - style: .cap2R12, - color: .grayscale400 - ) + private func setStyle() { + answerContentTextView.do { + $0.applyByeBooFont(style: .body3R16, color: .grayscale100) + $0.isScrollEnabled = false + $0.isEditable = false + $0.isSelectable = false + $0.isUserInteractionEnabled = false + $0.backgroundColor = .clear + $0.textContainer.lineBreakMode = .byTruncatingTail + } + writtenDateLabel.do { + $0.applyByeBooFont(style: .cap2R12, color: .grayscale400) + $0.isHidden = true } likeCommentStackView.do { $0.axis = .horizontal @@ -92,21 +91,18 @@ final class QuestContentView: BaseView { } } - override func setLayout() { - answerContentLabel.snp.makeConstraints { + private func setLayout() { + answerContentTextView.snp.makeConstraints { $0.top.equalToSuperview() $0.horizontalEdges.equalToSuperview() - $0.height.equalTo(47.adjustedH) } - if let writtenDateLabel { - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview() - $0.height.equalTo(16.adjustedH) - } + writtenDateLabel.snp.makeConstraints { + $0.top.equalTo(answerContentTextView.snp.bottom).offset(20.adjustedH) + $0.leading.equalToSuperview() + $0.height.equalTo(16.adjustedH) } likeCommentStackView.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20) + $0.top.equalTo(answerContentTextView.snp.bottom).offset(20) $0.trailing.equalToSuperview() $0.bottom.equalToSuperview() } @@ -122,11 +118,16 @@ final class QuestContentView: BaseView { writtenAt: String? = nil, isLiked: Bool, likeCount: Int, - commentCount: Int + commentCount: Int, + showAllText: Bool ) { self.likeCounts = likeCount - answerContentLabel.text = content - if let writtenDateLabel { + answerContentTextView.do { + $0.textContainer.maximumNumberOfLines = showAllText ? 0 : 2 + $0.applyTextViewStyle(style: .body3R16, text: content, color: .grayscale100) + } + if let writtenAt { + writtenDateLabel.isHidden = false writtenDateLabel.text = writtenAt } likeButton.isSelected = isLiked From 4e56be090cd44a4252dbc394043c03f850e60104 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 20:10:09 +0900 Subject: [PATCH 038/231] style: #428 History View UI --- .../CommonQuest/CommonQuestHistoryView.swift | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) 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 e35145a4..95241ca0 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 @@ -24,10 +24,13 @@ final class CommonQuestHistoryView: BaseView { private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() private let questContentView = QuestContentView() - private let commentListView = UITableView() + private(set) var commentListView = SelfSizingTableView() private let commentTextView = CommentTextView() override func setStyle() { + scrollView.do { + $0.showsVerticalScrollIndicator = false + } commonQuestLabel.applyByeBooFont( style: .body6R14, text: "공통퀘스트", @@ -54,6 +57,9 @@ final class CommonQuestHistoryView: BaseView { $0.layer.cornerRadius = 12 $0.backgroundColor = .white5 } + commentListView.do { + $0.isScrollEnabled = false + } userNicknameLabel.applyByeBooFont( style: .body6R14, color: .grayscale200 @@ -67,7 +73,8 @@ final class CommonQuestHistoryView: BaseView { commonQuestLabel, dateLabel, questionView, - answerView + answerView, + commentListView ) questionView.addSubviews( questionMarkLabel, @@ -84,7 +91,7 @@ final class CommonQuestHistoryView: BaseView { scrollView.snp.makeConstraints { $0.top.equalTo(safeAreaLayoutGuide).offset(16.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview().inset(24.adjustedH) + $0.bottom.equalTo(commentTextView.snp.top) } contentView.snp.makeConstraints { $0.edges.equalTo(scrollView.contentLayoutGuide) @@ -117,7 +124,6 @@ final class CommonQuestHistoryView: BaseView { answerView.snp.makeConstraints { $0.top.equalTo(questionView.snp.bottom).offset(30.adjustedH) $0.horizontalEdges.equalToSuperview() - $0.bottom.equalToSuperview().inset(24.adjustedH) } profileIconImageView.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) @@ -134,6 +140,11 @@ final class CommonQuestHistoryView: BaseView { $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) } + commentListView.snp.makeConstraints { + $0.top.equalTo(answerView.snp.bottom).offset(24.adjustedH) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalToSuperview().inset(24.adjustedH) + } commentTextView.snp.makeConstraints { $0.horizontalEdges.equalToSuperview() commentTextViewBottomConstraint = $0.bottom.equalToSuperview().inset(34.adjustedH).constraint @@ -167,7 +178,8 @@ extension CommonQuestHistoryView { content: content, isLiked: isLiked, likeCount: likeCount, - commentCount: commentCount + commentCount: commentCount, + showAllText: true ) profileIconImageView.image = profileIcon From 5a5b38f98f23a1ec71873d17d6851a1b11b2fc77 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 20:10:27 +0900 Subject: [PATCH 039/231] =?UTF-8?q?feat:=20#428=20scrollView,=20tableView?= =?UTF-8?q?=20=EC=A4=91=EC=B2=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9D=B5?= =?UTF-8?q?=EC=8A=A4=ED=85=90=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Extension/UITableView+.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift index d3a196f4..fc2a002b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift @@ -7,8 +7,22 @@ import UIKit +final class SelfSizingTableView: UITableView { + override var contentSize: CGSize { + didSet { + if oldValue != contentSize { + invalidateIntrinsicContentSize() + } + } + } + + override var intrinsicContentSize: CGSize { + return contentSize + } +} + extension UITableView { - + func dequeueReusableCell(for indexPath: IndexPath) -> T { guard let cell = self.dequeueReusableCell( withIdentifier: T.identifier, From 5997863aca8ca1d178ab06261e57516b7e842cd6 Mon Sep 17 00:00:00 2001 From: yeonee Date: Fri, 8 May 2026 18:49:51 +0900 Subject: [PATCH 040/231] =?UTF-8?q?feat:=20#428=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EB=8D=94=EB=B3=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/CommonQuestCommentEntity.swift | 27 +++--- .../Cells/CommentTableViewCell.swift | 46 ++++++++-- .../CommonQuest/CommonQuestHistoryView.swift | 2 + .../CommonQuestHistoryViewController.swift | 87 +++++++++++++------ 4 files changed, 110 insertions(+), 52 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 3e97accb..99532755 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -7,7 +7,7 @@ import Foundation -struct CommonQuestCommentEntity { +struct CommonQuestCommentEntity: Hashable { let commentID: Int let replyCount: Int? let writerID: Int @@ -19,20 +19,17 @@ struct CommonQuestCommentEntity { extension CommonQuestCommentEntity { static func toCommentListStub() -> [Self] { - let stub = toCommentStub() - return [stub, stub, stub, stub, stub] - } - - static func toCommentStub() -> Self { - .init( - commentID: 1, - replyCount: 3, - writerID: 2, - writer: "장원영", - profileIcon: "SADNESS", - writtenAt: "2026-02-19T02:09:43.735730", - content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ냐냐냐냐냐냐냐냐냐ㅑ냐냐냐냐냐냐ㅑㄴ냐냐ㅑ냐냐냐냔냐냐냐냐냐" - ) + return (1...5).map { + .init( + commentID: $0, + replyCount: 3, + writerID: 2, + writer: "장원영", + profileIcon: "SADNESS", + writtenAt: "2026-02-19T02:09:43.735730", + content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ냐냐냐냐냐냐냐냐냐" + ) + } } static func toReplyStub() -> Self { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index 43c42103..edd122d2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -7,8 +7,14 @@ import UIKit +protocol CommentProtocol: AnyObject { + func moreLabelDidTap(commentID: Int) +} + final class CommentTableViewCell: UITableViewCell { + weak var delegate: CommentProtocol? + private var replyCommentArrow = UIImageView() private var replyCommentContainer = UIView() private let profileIcon = UIImageView(image: .relievedBadge) @@ -22,6 +28,9 @@ final class CommentTableViewCell: UITableViewCell { private var replyCountLabel = UILabel() private var replyCount: Int = 0 + private var commentID: Int = 0 + private var content: String = "" + private var didSetReplyLayout = false override init( style: UITableViewCell.CellStyle, @@ -73,6 +82,9 @@ final class CommentTableViewCell: UITableViewCell { moreLabel.do { $0.applyByeBooFont(style: .body6R14, text: "더보기", color: .grayscale400) $0.backgroundColor = .grayscale900 + $0.isUserInteractionEnabled = true + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(moreLabelDidTap(_:))) + $0.addGestureRecognizer(tapRecognizer) } replyCountContainer.do { @@ -112,7 +124,6 @@ final class CommentTableViewCell: UITableViewCell { $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) $0.leading.equalTo(profileIcon.snp.leading).offset(20) $0.trailing.equalToSuperview().inset(24.adjustedW) - $0.height.lessThanOrEqualTo(105.adjustedH) $0.bottom.equalToSuperview().inset(16.adjustedH) } moreLabel.snp.makeConstraints { @@ -122,6 +133,9 @@ final class CommentTableViewCell: UITableViewCell { } private func setReplyLayout() { + guard !didSetReplyLayout else { return } + didSetReplyLayout = true + contentView.addSubviews(replyCommentArrow, replyCountContainer) replyCountContainer.addArrangedSubviews(replyCommentIcon, replyCountLabel) @@ -148,33 +162,42 @@ final class CommentTableViewCell: UITableViewCell { $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) $0.leading.equalTo(profileIcon.snp.leading).offset(20) $0.trailing.equalToSuperview().inset(24.adjustedW) - $0.height.lessThanOrEqualTo(105.adjustedH) } } } extension CommentTableViewCell { func configure( + commentID: Int, replyCount: Int? = nil, writer: String, profileIcon: UIImage, writtenAt: String, - content: String + content: String, + showAllText: Bool ) { - if let _ = replyCount { setReplyLayout() } + + self.commentID = commentID nicknameLabel.text = writer self.profileIcon.image = profileIcon dateLabel.text = writtenAt + self.content = content commentTextView.applyTextViewStyle(style: .body6R14, text: content, color: .grayscale100) - + layoutIfNeeded() - - let numberOfLines = commentTextView.numberOfLine() - ByeBooLogger.debug("라인수 \(numberOfLines)") - applyStyleWhenHideText(numberOfLines) + + if showAllText { + commentTextView.textContainer.maximumNumberOfLines = 0 + commentTextView.textContainer.exclusionPaths = [] + commentTextView.invalidateIntrinsicContentSize() + moreLabel.isHidden = true + } else { + let numberOfLines = commentTextView.numberOfLine() + applyStyleWhenHideText(numberOfLines) + } } } @@ -207,4 +230,9 @@ extension CommentTableViewCell { ByeBooLogger.debug("가로 : \(exclusionRect.width), 세로: \(exclusionRect.height)" ) commentTextView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionRect)] } + + @objc + private func moreLabelDidTap(_ tapRecognizer: UITapGestureRecognizer) { + delegate?.moreLabelDidTap(commentID: commentID) + } } 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 95241ca0..d807133c 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 @@ -59,6 +59,8 @@ final class CommonQuestHistoryView: BaseView { } commentListView.do { $0.isScrollEnabled = false + $0.rowHeight = UITableView.automaticDimension + $0.estimatedRowHeight = 100 } userNicknameLabel.applyByeBooFont( style: .body6R14, 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 12a21741..f493cc70 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -9,8 +9,19 @@ import UIKit import Mixpanel +enum CommentSection: Hashable { + case main +} + +struct CommentItem: Hashable { + let entity: CommonQuestCommentEntity + var showAllText: Bool +} + final class CommonQuestHistoryViewController: BaseViewController { + private var dataSource: UITableViewDiffableDataSource! + private let rootView = CommonQuestHistoryView() private let viewModel = CommonQuestHistoryViewModel() @@ -48,6 +59,9 @@ final class CommonQuestHistoryViewController: BaseViewController { action: #selector(back), secondAction: #selector(bottomUp) ) + + configureDataSource() + applySnapshot() } override func setAddTarget() { @@ -59,7 +73,6 @@ final class CommonQuestHistoryViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { $0.delegate = self - $0.dataSource = self $0.register(CommentTableViewCell.self) } } @@ -76,37 +89,40 @@ extension CommonQuestHistoryViewController: UITableViewDelegate { } -extension CommonQuestHistoryViewController: UITableViewDataSource { - func tableView( - _ tableView: UITableView, - numberOfRowsInSection section: Int) - -> Int { - ByeBooLogger.debug(viewModel.getCommentsCount()) - return viewModel.getCommentsCount() +extension CommonQuestHistoryViewController { + private func configureDataSource() { + dataSource = UITableViewDiffableDataSource( + tableView: rootView.commentListView + ) { tableView, indexPath, item in + guard let cell = tableView.dequeueReusableCell( + withIdentifier: CommentTableViewCell.identifier, + for: indexPath + ) as? CommentTableViewCell else { return UITableViewCell() } + + let entity = item.entity + cell.configure( + commentID: entity.commentID, + replyCount: entity.replyCount, + writer: entity.writer, + profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, + writtenAt: entity.writtenAt, + content: entity.content, + showAllText: item.showAllText + ) + cell.delegate = self + return cell + } } - func tableView( - _ tableView: UITableView, - cellForRowAt indexPath: IndexPath - ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell( - withIdentifier: CommentTableViewCell.identifier, - for: indexPath) as? CommentTableViewCell else { return UITableViewCell() } - - let entity = viewModel.commentLists[indexPath.row] - ByeBooLogger.debug(entity) - cell.configure( - replyCount: entity.replyCount, - writer: entity.writer, - profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, - writtenAt: entity.writtenAt, - content: entity.content - ) - return cell + private func applySnapshot() { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + let items = viewModel.commentLists.map { CommentItem(entity: $0, showAllText: false) } + snapshot.appendItems(items, toSection: .main) + dataSource.apply(snapshot, animatingDifferences: false) } - - } + extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { func didTapEdit( @@ -161,6 +177,21 @@ extension CommonQuestHistoryViewController: DeleteCommonQuestDelegate { } } +extension CommonQuestHistoryViewController: CommentProtocol { + func moreLabelDidTap(commentID: Int) { + let updatedItems = dataSource.snapshot().itemIdentifiers.map { item -> CommentItem in + guard item.entity.commentID == commentID else { return item } + return CommentItem(entity: item.entity, showAllText: true) + } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(updatedItems, toSection: .main) + dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in + self?.rootView.commentListView.performBatchUpdates(nil) + } + } +} extension CommonQuestHistoryViewController { func configure( From cc361ac2e9f2b0597fc37f8db1daff1568da920b Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Sun, 10 May 2026 19:18:55 +0900 Subject: [PATCH 041/231] =?UTF-8?q?feat:=20#438=20=EB=B7=B0=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8C=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/FinishJourneyViewController.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewController/FinishJourneyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewController/FinishJourneyViewController.swift index 13b3d677..9f29a295 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewController/FinishJourneyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewController/FinishJourneyViewController.swift @@ -99,10 +99,7 @@ extension FinishJourneyViewController { extension FinishJourneyViewController: Dismissible { func close() { - if let tabBarController { - navigationController?.popViewController(animated: false) - ViewControllerUtils.changeSelectedIndex(index: 0) - } + navigationController?.popViewController(animated: false) } } From c4dbe52ede25a40797d5cdb154809cc54f16fd81 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:53:32 +0900 Subject: [PATCH 042/231] =?UTF-8?q?add:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IconSystem/replyArrow.imageset/Contents.json | 12 ++++++++++++ .../IconSystem/replyArrow.imageset/icon.svg | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json new file mode 100644 index 00000000..52ca28db --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg new file mode 100644 index 00000000..4f6764ab --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg @@ -0,0 +1,3 @@ + + + From 2da80d8ad8b866e9ca1d1e5cea2bdcc1f44971d0 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:53:58 +0900 Subject: [PATCH 043/231] =?UTF-8?q?chore:=20#428=20comment=20entity=20stub?= =?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 --- .../Entity/CommonQuestCommentEntity.swift | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 99532755..035331af 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -22,7 +22,7 @@ extension CommonQuestCommentEntity { return (1...5).map { .init( commentID: $0, - replyCount: 3, + replyCount: $0 - 1, writerID: 2, writer: "장원영", profileIcon: "SADNESS", @@ -32,6 +32,33 @@ extension CommonQuestCommentEntity { } } + static func toCommentStub() -> Self { + .init( + commentID: 1, + replyCount: 2, + writerID: 2, + writer: "가을이", + profileIcon: "SELF_UNDERSTANDING", + writtenAt: "2026-02-19T02:09:43.735730", + content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ냐냐냐냐냐냐냐냐냐" + ) + } + + static func toReplyListStub() -> [Self] { + return (1...5).map { + .init( + commentID: $0, + replyCount: nil, + writerID: 2, + writer: "안유진", + profileIcon: "SADNESS", + writtenAt: "2026-02-19T02:09:43.735730", + content: "헤어짐ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ" + ) + } + } + + static func toReplyStub() -> Self { .init( commentID: 1, From 0d248ecb7e0f7791193d780b64bc60c2bc85515b Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:54:29 +0900 Subject: [PATCH 044/231] =?UTF-8?q?feat:=20#428=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EB=8B=B5=EA=B8=80=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommentTableViewCell.swift | 92 +++++++++++++------ 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index edd122d2..eed1bea4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -9,6 +9,11 @@ import UIKit protocol CommentProtocol: AnyObject { func moreLabelDidTap(commentID: Int) + func replyIconDidTap(commentID: Int) +} + +extension CommentProtocol { + func replyIconDidTap(commentID: Int) { } } final class CommentTableViewCell: UITableViewCell { @@ -31,6 +36,7 @@ final class CommentTableViewCell: UITableViewCell { private var commentID: Int = 0 private var content: String = "" private var didSetReplyLayout = false + private var didSetCommentLayout = false override init( style: UITableViewCell.CellStyle, @@ -41,6 +47,7 @@ final class CommentTableViewCell: UITableViewCell { setUI() setStyle() setLayout() + setAddTarget() } required init?(coder: NSCoder) { @@ -55,6 +62,10 @@ final class CommentTableViewCell: UITableViewCell { backgroundColor = .grayscale900 selectionStyle = .none + replyCommentArrow.do { + $0.image = .replyArrow + } + nicknameLabel.do { $0.applyByeBooFont(style: .body5M14, color: .grayscale200) } @@ -83,8 +94,6 @@ final class CommentTableViewCell: UITableViewCell { $0.applyByeBooFont(style: .body6R14, text: "더보기", color: .grayscale400) $0.backgroundColor = .grayscale900 $0.isUserInteractionEnabled = true - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(moreLabelDidTap(_:))) - $0.addGestureRecognizer(tapRecognizer) } replyCountContainer.do { @@ -93,7 +102,9 @@ final class CommentTableViewCell: UITableViewCell { } replyCommentIcon.do { - $0.image = .comment + $0.image = .comment.withRenderingMode(.alwaysTemplate) + $0.isUserInteractionEnabled = true + $0.tintColor = .grayscale100 } replyCountLabel.do { @@ -102,11 +113,6 @@ final class CommentTableViewCell: UITableViewCell { } private func setLayout() { - profileIcon.snp.makeConstraints { - $0.top.equalToSuperview().inset(16.adjustedH) - $0.leading.equalToSuperview() - $0.size.equalTo(20.adjustedH) - } nicknameLabel.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) $0.leading.equalTo(profileIcon.snp.trailing).offset(4.adjustedW) @@ -120,29 +126,28 @@ final class CommentTableViewCell: UITableViewCell { $0.trailing.equalToSuperview() $0.size.equalTo(24.adjustedH) } - commentTextView.snp.makeConstraints { - $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) - $0.leading.equalTo(profileIcon.snp.leading).offset(20) - $0.trailing.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview().inset(16.adjustedH) - } moreLabel.snp.makeConstraints { $0.bottom.equalTo(commentTextView.snp.bottom) $0.trailing.equalTo(commentTextView.snp.trailing).offset(-20.adjustedW) } } - - private func setReplyLayout() { - guard !didSetReplyLayout else { return } - didSetReplyLayout = true + private func setCommentListLayout() { + guard !didSetCommentLayout else { return } + didSetCommentLayout = true - contentView.addSubviews(replyCommentArrow, replyCountContainer) + contentView.addSubview(replyCountContainer) replyCountContainer.addArrangedSubviews(replyCommentIcon, replyCountLabel) - replyCommentArrow.snp.makeConstraints { + profileIcon.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) - $0.leading.equalToSuperview().inset(24.adjustedH) - $0.size.equalTo(24.adjustedH) + $0.leading.equalToSuperview() + $0.size.equalTo(20.adjustedH) + } + commentTextView.snp.makeConstraints { + $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) + $0.leading.equalTo(profileIcon.snp.leading).offset(20) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(44.adjustedH) } replyCountContainer.snp.makeConstraints { $0.top.equalTo(commentTextView.snp.bottom).offset(8.adjustedH) @@ -153,6 +158,19 @@ final class CommentTableViewCell: UITableViewCell { replyCommentIcon.snp.makeConstraints { $0.size.equalTo(20.adjustedH) } + } + + private func setReplySheetLayout() { + guard !didSetReplyLayout else { return } + didSetReplyLayout = true + + contentView.addSubviews(replyCommentArrow) + + replyCommentArrow.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview() + $0.size.equalTo(24.adjustedH) + } profileIcon.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) $0.leading.equalTo(replyCommentArrow.snp.trailing) @@ -162,8 +180,17 @@ final class CommentTableViewCell: UITableViewCell { $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) $0.leading.equalTo(profileIcon.snp.leading).offset(20) $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(16.adjustedH) } } + + private func setAddTarget() { + let moreLabelTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(moreLabelDidTap(_:))) + moreLabel.addGestureRecognizer(moreLabelTapRecognizer) + + let replyTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(replyIconDidTap(_:))) + replyCommentIcon.addGestureRecognizer(replyTapRecognizer) + } } extension CommentTableViewCell { @@ -174,10 +201,18 @@ extension CommentTableViewCell { profileIcon: UIImage, writtenAt: String, content: String, - showAllText: Bool + showAllText: Bool, + isReplySheet: Bool ) { if let _ = replyCount { - setReplyLayout() + setCommentListLayout() + } else { + setReplySheetLayout() + } + + if isReplySheet { + replyCommentIcon.tintColor = .grayscale600 + replyCountLabel.textColor = .grayscale600 } self.commentID = commentID @@ -189,9 +224,10 @@ extension CommentTableViewCell { layoutIfNeeded() + commentTextView.textContainer.maximumNumberOfLines = 0 + commentTextView.textContainer.exclusionPaths = [] + if showAllText { - commentTextView.textContainer.maximumNumberOfLines = 0 - commentTextView.textContainer.exclusionPaths = [] commentTextView.invalidateIntrinsicContentSize() moreLabel.isHidden = true } else { @@ -235,4 +271,8 @@ extension CommentTableViewCell { private func moreLabelDidTap(_ tapRecognizer: UITapGestureRecognizer) { delegate?.moreLabelDidTap(commentID: commentID) } + + @objc private func replyIconDidTap(_ tapRecognizer: UITapGestureRecognizer) { + delegate?.replyIconDidTap(commentID: commentID) + } } From a3e1ea019c62b2124e0a80c415da926eba8bc8dc Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:54:55 +0900 Subject: [PATCH 045/231] =?UTF-8?q?style:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/CommonQuestReplyView.swift | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift new file mode 100644 index 00000000..8a2c149a --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift @@ -0,0 +1,76 @@ +// +// CommonQuestReplyView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import UIKit + +import SnapKit + +final class CommonQuestReplyView: BaseView { + private let headerView = UIView() + private(set) var backButton = UIButton() + private let headerLabel = UILabel() + private(set) var commentListView = UITableView() + private let commentTextView = CommentTextView() + + private var commentTextViewBottomConstraint: Constraint? + + override func setUI() { + addSubviews(headerView, commentListView, commentTextView) + headerView.addSubviews(backButton, headerLabel) + } + + override func setStyle() { + backgroundColor = .grayscale900 + + backButton.do { + $0.setImage(.left.withRenderingMode(.alwaysTemplate), for: .normal) + $0.tintColor = .white + } + + headerLabel.do { + $0.applyByeBooFont(style: .sub1Sb20, text: "답글", color: .white) + } + + commentListView.do { + $0.backgroundColor = .grayscale900 + $0.showsVerticalScrollIndicator = false + } + } + + override func setLayout() { + headerView.snp.makeConstraints { + $0.top.equalToSuperview().inset(26.adjustedH) + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(40.adjustedH) + } + backButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedH) + $0.size.equalTo(24.adjustedH) + } + headerLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.centerX.equalToSuperview() + } + commentListView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom).offset(6.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(20.adjustedW) + $0.bottom.equalTo(commentTextView.snp.top) + } + commentTextView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + commentTextViewBottomConstraint = $0.bottom.equalToSuperview().inset(34.adjustedH).constraint + } + } +} + +extension CommonQuestReplyView { + func updateLayout(keyboardHeight: CGFloat) { + let inset = keyboardHeight > 0 ? keyboardHeight : 34.adjustedH + commentTextViewBottomConstraint?.update(inset: inset) + } +} From 34610bf7a6abf1e15f4d881eb5278531341e732a Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:56:06 +0900 Subject: [PATCH 046/231] =?UTF-8?q?feat:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EB=B7=B0=20=EB=AA=A8=EB=8D=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/CommonQuestReplyViewModel.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift new file mode 100644 index 00000000..e0d5c0cd --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift @@ -0,0 +1,18 @@ +// +// CommonQuestReplyViewModel.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import Foundation + +final class CommonQuestReplyViewModel { + func getCommentContent() -> CommonQuestCommentEntity { + return CommonQuestCommentEntity.toCommentStub() + } + + func getReplyList() -> [CommonQuestCommentEntity] { + return CommonQuestCommentEntity.toReplyListStub() + } +} From cd2ae9b51f863caff9809f6569ed7d41bd44049a Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 22:09:49 +0900 Subject: [PATCH 047/231] =?UTF-8?q?refactor:=20#428=20=ED=82=A4=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 77 ++++++------------- .../Protocol/KeyboardHandleProtocol.swift | 41 ++++++++++ 2 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift 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 f493cc70..a1c9578e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -9,15 +9,6 @@ import UIKit import Mixpanel -enum CommentSection: Hashable { - case main -} - -struct CommentItem: Hashable { - let entity: CommonQuestCommentEntity - var showAllText: Bool -} - final class CommonQuestHistoryViewController: BaseViewController { private var dataSource: UITableViewDiffableDataSource! @@ -44,11 +35,6 @@ final class CommonQuestHistoryViewController: BaseViewController { Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - removeKeyboardObservers() - } - override func viewDidLoad() { super.viewDidLoad() @@ -64,6 +50,11 @@ final class CommonQuestHistoryViewController: BaseViewController { applySnapshot() } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardObservers() + } + override func setAddTarget() { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) tapGestureRecognizer.cancelsTouchesInView = false @@ -72,7 +63,6 @@ final class CommonQuestHistoryViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { - $0.delegate = self $0.register(CommentTableViewCell.self) } } @@ -85,10 +75,6 @@ extension CommonQuestHistoryViewController: BackNavigable { } } -extension CommonQuestHistoryViewController: UITableViewDelegate { - -} - extension CommonQuestHistoryViewController { private func configureDataSource() { dataSource = UITableViewDiffableDataSource( @@ -107,7 +93,8 @@ extension CommonQuestHistoryViewController { profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, writtenAt: entity.writtenAt, content: entity.content, - showAllText: item.showAllText + showAllText: item.showAllText, + isReplySheet: false ) cell.delegate = self return cell @@ -191,7 +178,18 @@ extension CommonQuestHistoryViewController: CommentProtocol { self?.rootView.commentListView.performBatchUpdates(nil) } } + + func replyIconDidTap(commentID: Int) { + let viewController = CommonQuestReplyViewController() + if let sheet = viewController.sheetPresentationController{ + sheet.detents = [.large()] + sheet.prefersGrabberVisible = true + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + } + self.present(viewController, animated: true) + } } + extension CommonQuestHistoryViewController { func configure( @@ -247,42 +245,17 @@ extension CommonQuestHistoryViewController { private func dismissKeyboard() { view.endEditing(true) } - - private func addKeyboardObservers() { - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardWillShow), - name: UIResponder.keyboardWillShowNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardWillHide), - name: UIResponder.keyboardWillHideNotification, - object: nil - ) - } - - private func removeKeyboardObservers() { - NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) - } - - @objc private func keyboardWillShow(_ notification: Notification) { - guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, - let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double - else { return } - +} + +extension CommonQuestHistoryViewController: KeyboardHandleProtocol { + func keyboardWillShow(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { - self.rootView.updateLayout(keyboardHeight: keyboardFrame.height) + self.rootView.updateLayout(keyboardHeight: height) self.rootView.layoutIfNeeded() } } - - @objc private func keyboardWillHide(_ notification: Notification) { - guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double - else { return } - + + func keyboardWillHide(duration: Double) { UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: 0) self.rootView.layoutIfNeeded() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift new file mode 100644 index 00000000..07fcee60 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift @@ -0,0 +1,41 @@ +// +// KeyboardHandlable+.swift +// ByeBoo-iOS +// + +import UIKit + +protocol KeyboardHandleProtocol: UIViewController { + func keyboardWillShow(height: CGFloat, duration: Double) + func keyboardWillHide(duration: Double) +} + +extension KeyboardHandleProtocol { + func addKeyboardObservers() { + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillShowNotification, + object: nil, + queue: .main + ) { [weak self] notification in + guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, + let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + self?.keyboardWillShow(height: keyboardFrame.height, duration: duration) + } + + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillHideNotification, + object: nil, + queue: .main + ) { [weak self] notification in + guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + self?.keyboardWillHide(duration: duration) + } + } + + func removeKeyboardObservers() { + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) + } +} From d2f185f91d2337ca1890ac9cf09cabf20f668076 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 22:10:16 +0900 Subject: [PATCH 048/231] =?UTF-8?q?feat:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/CommentDataSource.swift | 22 +++ .../CommonQuestReplyViewController.swift | 133 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift new file mode 100644 index 00000000..3b32e980 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift @@ -0,0 +1,22 @@ +// +// CommentDataSource.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import Foundation + +enum CommentSection: Hashable { + case main +} + +enum ReplySection: Hashable { + case comment + case replies +} + +struct CommentItem: Hashable { + let entity: CommonQuestCommentEntity + var showAllText: Bool +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift new file mode 100644 index 00000000..b968e122 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -0,0 +1,133 @@ +// +// CommonQuestReplyViewController.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import UIKit + +final class CommonQuestReplyViewController: BaseViewController { + + private let rootView = CommonQuestReplyView() + private let viewModel = CommonQuestReplyViewModel() + + private var dataSource: UITableViewDiffableDataSource! + + override func loadView() { + view = rootView + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.tabBarController?.tabBar.isHidden = true + addKeyboardObservers() + } + + override func viewDidLoad() { + super.viewDidLoad() + + configureDataSource() + applySnapshot() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardObservers() + } + + override func setAddTarget() { + rootView.backButton.addTarget(self, action: #selector(backButtonDidTap), for: .touchUpInside) + } + + override func setDelegate() { + rootView.commentListView.do { + $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "comment") + $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "reply") + } + } +} + +extension CommonQuestReplyViewController { + private func configureDataSource() { + dataSource = UITableViewDiffableDataSource( + tableView: rootView.commentListView + ) { tableView, indexPath, item in + let identifier = item.entity.replyCount != nil ? "comment" : "reply" + guard let cell = tableView.dequeueReusableCell( + withIdentifier: identifier, + for: indexPath + ) as? CommentTableViewCell else { return UITableViewCell() } + + let entity = item.entity + cell.configure( + commentID: entity.commentID, + replyCount: entity.replyCount, + writer: entity.writer, + profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, + writtenAt: entity.writtenAt, + content: entity.content, + showAllText: item.showAllText, + isReplySheet: true + ) + cell.delegate = self + return cell + } + } + + private func applySnapshot() { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.comment, .replies]) + let commentItem = CommentItem(entity: viewModel.getCommentContent(), showAllText: false) + snapshot.appendItems([commentItem], toSection: .comment) + + let replyItems = viewModel.getReplyList().map { CommentItem(entity: $0, showAllText: false) } + snapshot.appendItems(replyItems, toSection: .replies) + + dataSource.apply(snapshot, animatingDifferences: false) + } + + @objc + private func backButtonDidTap() { + self.dismiss(animated: true) + } +} + +extension CommonQuestReplyViewController: CommentProtocol { + func moreLabelDidTap(commentID: Int) { + let currentSnapshot = dataSource.snapshot() + + let commentItems = currentSnapshot.itemIdentifiers(inSection: .comment).map { item -> CommentItem in + guard item.entity.commentID == commentID else { return item } + return CommentItem(entity: item.entity, showAllText: true) + } + let replyItems = currentSnapshot.itemIdentifiers(inSection: .replies).map { item -> CommentItem in + guard item.entity.commentID == commentID else { return item } + return CommentItem(entity: item.entity, showAllText: true) + } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.comment, .replies]) + snapshot.appendItems(commentItems, toSection: .comment) + snapshot.appendItems(replyItems, toSection: .replies) + dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in + self?.rootView.commentListView.performBatchUpdates(nil) + } + } +} + +extension CommonQuestReplyViewController: KeyboardHandleProtocol { + func keyboardWillShow(height: CGFloat, duration: Double) { + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: height) + self.rootView.layoutIfNeeded() + } + } + + func keyboardWillHide(duration: Double) { + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: 0) + self.rootView.layoutIfNeeded() + } + } +} From ee5f919a5dfaa45f9650ef4e898aa354ee21f7c5 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 22:30:26 +0900 Subject: [PATCH 049/231] =?UTF-8?q?style:=20#428=20separator=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestHistoryViewController.swift | 1 + .../Quest/ViewController/CommonQuestReplyViewController.swift | 1 + 2 files changed, 2 insertions(+) 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 a1c9578e..ba8a7828 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -63,6 +63,7 @@ final class CommonQuestHistoryViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { + $0.separatorStyle = .none $0.register(CommentTableViewCell.self) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index b968e122..620c2f03 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -42,6 +42,7 @@ final class CommonQuestReplyViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { + $0.separatorStyle = .none $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "comment") $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "reply") } From fdca78ced6024be668cabd1a33fab9ac736f6755 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 23:07:25 +0900 Subject: [PATCH 050/231] =?UTF-8?q?fix:=20#428=20stub=20id=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 035331af..16c286e6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -45,7 +45,7 @@ extension CommonQuestCommentEntity { } static func toReplyListStub() -> [Self] { - return (1...5).map { + return (6...10).map { .init( commentID: $0, replyCount: nil, From d8fa92015a2fa5f70296229c8548162cdead8aab Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 23:21:55 +0900 Subject: [PATCH 051/231] =?UTF-8?q?refactor:=20#428=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Common/CommentTextFieldView.swift | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift index db3fef89..dab606a8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift @@ -124,16 +124,10 @@ extension CommentTextView: UITextViewDelegate { let maxHeight = 105.adjustedH let fittingHeight = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: .infinity)).height - if fittingHeight > maxHeight { - if !textView.isScrollEnabled { - textView.isScrollEnabled = true - } - } else { - if textView.isScrollEnabled { - textView.isScrollEnabled = false - textView.contentOffset = .zero - } - } + + let shouldScroll = fittingHeight > maxHeight + textView.isScrollEnabled = shouldScroll + if !shouldScroll { textView.contentOffset = .zero } } func textViewDidEndEditing(_ textView: UITextView) { From 35423f37395a725e014628bc334dbd54ced323cb Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 19 May 2026 18:01:29 +0900 Subject: [PATCH 052/231] =?UTF-8?q?refactor:=20#428=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=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 --- .../Cells/CommentTableViewCell.swift | 2 +- .../CommonQuest/Common/QuestContentView.swift | 20 ++++--------------- .../CommonQuestHistoryViewController.swift | 9 +-------- .../CommonQuestReplyViewController.swift | 9 +-------- .../Protocol/KeyboardHandleProtocol.swift | 15 +++++++++----- 5 files changed, 17 insertions(+), 38 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index eed1bea4..8704e6e0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -204,7 +204,7 @@ extension CommentTableViewCell { showAllText: Bool, isReplySheet: Bool ) { - if let _ = replyCount { + if replyCount != nil { setCommentListLayout() } else { setReplySheetLayout() 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 67a40bb9..c405fcc8 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 @@ -11,7 +11,7 @@ protocol CommonQuestLikeCommentProtocol: AnyObject { func likeButtonDidTap() } -final class QuestContentView: UIView { +final class QuestContentView: BaseView { private let answerContentTextView = UITextView() private let writtenDateLabel = UILabel() @@ -28,19 +28,7 @@ final class QuestContentView: UIView { private var likeCounts: Int = 0 - init() { - super.init(frame: .zero) - - setUI() - setStyle() - setLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setUI() { + override func setUI() { self.addSubviews( answerContentTextView, writtenDateLabel, @@ -51,7 +39,7 @@ final class QuestContentView: UIView { commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) } - private func setStyle() { + override func setStyle() { answerContentTextView.do { $0.applyByeBooFont(style: .body3R16, color: .grayscale100) $0.isScrollEnabled = false @@ -91,7 +79,7 @@ final class QuestContentView: UIView { } } - private func setLayout() { + override func setLayout() { answerContentTextView.snp.makeConstraints { $0.top.equalToSuperview() $0.horizontalEdges.equalToSuperview() 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 ba8a7828..0abf8f73 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -249,17 +249,10 @@ extension CommonQuestHistoryViewController { } extension CommonQuestHistoryViewController: KeyboardHandleProtocol { - func keyboardWillShow(height: CGFloat, duration: Double) { + func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: height) self.rootView.layoutIfNeeded() } } - - func keyboardWillHide(duration: Double) { - UIView.animate(withDuration: duration) { - self.rootView.updateLayout(keyboardHeight: 0) - self.rootView.layoutIfNeeded() - } - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 620c2f03..5c492016 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -118,17 +118,10 @@ extension CommonQuestReplyViewController: CommentProtocol { } extension CommonQuestReplyViewController: KeyboardHandleProtocol { - func keyboardWillShow(height: CGFloat, duration: Double) { + func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: height) self.rootView.layoutIfNeeded() } } - - func keyboardWillHide(duration: Double) { - UIView.animate(withDuration: duration) { - self.rootView.updateLayout(keyboardHeight: 0) - self.rootView.layoutIfNeeded() - } - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift index 07fcee60..3d98320b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift @@ -5,9 +5,14 @@ import UIKit -protocol KeyboardHandleProtocol: UIViewController { - func keyboardWillShow(height: CGFloat, duration: Double) - func keyboardWillHide(duration: Double) +protocol KeyboardHandleProtocol where Self: UIViewController { + func keyboardWillShowOrHide(height: CGFloat, duration: Double) +} + +extension KeyboardHandleProtocol { + func keyboardWillShowOrHide(duration: Double) { + keyboardWillShowOrHide(height: 0, duration: duration) + } } extension KeyboardHandleProtocol { @@ -20,7 +25,7 @@ extension KeyboardHandleProtocol { guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - self?.keyboardWillShow(height: keyboardFrame.height, duration: duration) + self?.keyboardWillShowOrHide(height: keyboardFrame.height, duration: duration) } NotificationCenter.default.addObserver( @@ -30,7 +35,7 @@ extension KeyboardHandleProtocol { ) { [weak self] notification in guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - self?.keyboardWillHide(duration: duration) + self?.keyboardWillShowOrHide(duration: duration) } } From f2ead4cf0effb5e971bfa789b3fc944e4a04515a Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 19 May 2026 18:33:24 +0900 Subject: [PATCH 053/231] =?UTF-8?q?refactor:=20#441=20isOnboardingComplete?= =?UTF-8?q?d=EB=A5=BC=20isRegistered=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/Persistence/UserDefaultsKey.swift | 1 - .../ByeBoo-iOS/Data/Repository/AuthRepository.swift | 8 ++++---- .../ByeBoo-iOS/Data/Repository/UsersRepository.swift | 2 +- ByeBoo-iOS/ByeBooTests/Feature/Login/AuthTests.swift | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/UserDefaultsKey.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/UserDefaultsKey.swift index d9432c00..741bdc25 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/UserDefaultsKey.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/UserDefaultsKey.swift @@ -10,7 +10,6 @@ import Foundation enum UserDefaultsKey: String, CaseIterable { case userName case userID - case isOnboardingCompleted case isHelperShown case isRegistered case loginPlatform diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift index b558ea0d..c25722e2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift @@ -97,12 +97,12 @@ struct DefaultAuthRepository: AuthInterface { } func autoLogin() async throws -> Bool { - let isOnboardingCompleted: Bool = userDefaultsService.load(key: .isOnboardingCompleted) ?? false - ByeBooLogger.debug("온보딩 여부 \(isOnboardingCompleted)") + let isRegistered: Bool = userDefaultsService.load(key: .isRegistered) ?? false + ByeBooLogger.debug("온보딩 여부 \(isRegistered)") if !keychainService.load(key: .accessToken).isEmpty && !keychainService.load(key: .refreshToken).isEmpty - && isOnboardingCompleted + && isRegistered { ByeBooLogger.debug("정보 있음") try await tokenService.reissue() @@ -136,7 +136,7 @@ struct DefaultAuthRepository: AuthInterface { } clearKeychain() - removeUserInfo(excludedKeys: [.isOnboardingCompleted, .isHelperShown, .hasEnterMyPage]) + removeUserInfo(excludedKeys: [.isRegistered, .isHelperShown, .hasEnterMyPage]) return true } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift index 8d3e6a01..74174372 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift @@ -50,7 +50,7 @@ struct DefaultUsersRepository: UsersInterface { ) let _ = userDefaultsService.save(result.id, key: .userID) let _ = userDefaultsService.save(result.name, key: .userName) - let _ = userDefaultsService.save(true, key: .isOnboardingCompleted) + let _ = userDefaultsService.save(true, key: .isRegistered) let _ = userDefaultsService.save(false, key: .hasEnterMyPage) let _ = userDefaultsService.save(false, key: .alarmEnabled) diff --git a/ByeBoo-iOS/ByeBooTests/Feature/Login/AuthTests.swift b/ByeBoo-iOS/ByeBooTests/Feature/Login/AuthTests.swift index 0b783bad..c5b53e1c 100644 --- a/ByeBoo-iOS/ByeBooTests/Feature/Login/AuthTests.swift +++ b/ByeBoo-iOS/ByeBooTests/Feature/Login/AuthTests.swift @@ -15,14 +15,14 @@ struct AuthTests { private let networkService = MockNetworkService(userAPI: MockUserAPI(isAvailable: true)) @Test("🏁 isOnboardingCompleted가 true일 때 ✅ 자동로그인 success") - func isOnboardingCompleted_true__autoLogin_success() async throws { + func isRegistered_true__autoLogin_success() async throws { // Given let authRepository = MockAuthRepository( network: networkService, userDefaultsService: userDefaultsService, keychainService: keychainService ) - let _ = userDefaultsService.save(true, key: .isOnboardingCompleted) + let _ = userDefaultsService.save(true, key: .isRegistered) let autoLoginUseCase = DefaultAutoLoginUseCase(repository: authRepository) // When From 6a4c56447acbb52dfcc7dea7ef55024cdec3f040 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:58:10 +0900 Subject: [PATCH 054/231] =?UTF-8?q?refactor:=20#437=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B0=8F=20API,=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC,=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift | 2 +- ...otificationAPI.swift => NotificationTokenAPI.swift} | 4 ++-- .../ByeBoo-iOS/Data/Repository/AuthRepository.swift | 4 ++-- ...ository.swift => NotificationTokenRepository.swift} | 10 +++++----- ...nterface.swift => NotificationTokenInterface.swift} | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/{NotificationAPI.swift => NotificationTokenAPI.swift} (95%) rename ByeBoo-iOS/ByeBoo-iOS/Data/Repository/{NotificationRepository.swift => NotificationTokenRepository.swift} (89%) rename ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/{NotificationInterface.swift => NotificationTokenInterface.swift} (88%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift b/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift index 1bce5697..5ab4cde4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift @@ -48,7 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { - guard let notificationRepository = DIContainer.shared.resolve(type: DefaultNotificationRepository.self) else { + guard let notificationRepository = DIContainer.shared.resolve(type: DefaultNotificationTokenRepository.self) else { return } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift similarity index 95% rename from ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift rename to ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift index c77364c0..acb0f6e3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift @@ -9,13 +9,13 @@ import Foundation import Alamofire -enum NotificationAPI { +enum NotificationTokenAPI { case saveToken(dto: FCMTokenDTO) case updateToken(dto: FCMTokenDTO) case deleteToken(dto: FCMTokenDTO) } -extension NotificationAPI: EndPoint { +extension NotificationTokenAPI: EndPoint { var basePath: String { return "/api/v1/notification-tokens" diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift index c25722e2..394f3506 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift @@ -89,7 +89,7 @@ struct DefaultAuthRepository: AuthInterface { let fcmToken = try await Messaging.messaging().token() let fcmTokenDTO = FCMTokenDTO(token: fcmToken) try await network.request( - NotificationAPI.saveToken(dto: fcmTokenDTO) + NotificationTokenAPI.saveToken(dto: fcmTokenDTO) ) } catch (let error) { ByeBooLogger.error(error) @@ -127,7 +127,7 @@ struct DefaultAuthRepository: AuthInterface { return false } try await network.request( - NotificationAPI.deleteToken(dto: .init(token: fcmToken)) + NotificationTokenAPI.deleteToken(dto: .init(token: fcmToken)) ) } catch (let error) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift similarity index 89% rename from ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift rename to ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift index 4a136854..47063b8e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift @@ -5,7 +5,7 @@ // Created by APPLE on 11/22/25. // -struct DefaultNotificationRepository: NotificationInterface { +struct DefaultNotificationTokenRepository: NotificationTokenInterface { private let network: NetworkService private let userDefaultsService: UserDefaultService @@ -38,7 +38,7 @@ struct DefaultNotificationRepository: NotificationInterface { let fcmTokenDTO = createDTO(token: token) try await network.request( - NotificationAPI.saveToken(dto: fcmTokenDTO) + NotificationTokenAPI.saveToken(dto: fcmTokenDTO) ) saveToken(token: token) } @@ -53,7 +53,7 @@ struct DefaultNotificationRepository: NotificationInterface { let fcmTokenDTO = createDTO(token: token) try await network.request( - NotificationAPI.updateToken(dto: fcmTokenDTO) + NotificationTokenAPI.updateToken(dto: fcmTokenDTO) ) saveToken(token: token) } @@ -66,7 +66,7 @@ struct DefaultNotificationRepository: NotificationInterface { let fcmTokenDTO = createDTO(token: token) let accessToken = keychainService.load(key: .accessToken) try await network.request( - NotificationAPI.deleteToken(dto: fcmTokenDTO) + NotificationTokenAPI.deleteToken(dto: fcmTokenDTO) ) let _ = userDefaultsService.delete(key: .fcmToken) } @@ -76,7 +76,7 @@ struct DefaultNotificationRepository: NotificationInterface { } } -final class MockNotificationRepository: NotificationInterface { +final class MockNotificationTokenRepository: NotificationTokenInterface { private let userDefaultsService: UserDefaultService var sendTokenCalled = false diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationTokenInterface.swift similarity index 88% rename from ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift rename to ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationTokenInterface.swift index 05cc0709..3c92561a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationTokenInterface.swift @@ -5,7 +5,7 @@ // Created by APPLE on 11/26/25. // -protocol NotificationInterface { +protocol NotificationTokenInterface { func loadToken() -> String? func sendToken(token: String) async throws func saveToken(token: String) From 23d3ef2654e58e3cd323171fd754e447d4917a17 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:15:54 +0900 Subject: [PATCH 055/231] =?UTF-8?q?feat:=20#437=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/DataDependencyAssembler.swift | 4 ++ .../Model/NotificationListReponseDTO.swift | 41 ++++++++++++ .../Network/EndPoint/NotificationAPI.swift | 63 +++++++++++++++++++ .../Repository/NotificationRepository.swift | 23 +++++++ .../Domain/DomainDependencyAssembler.swift | 17 +++-- .../Entity/NotificationListEntity.swift | 20 ++++++ .../Interface/NotificationInterface.swift | 10 +++ .../FetchNotificationListUseCase.swift | 23 +++++++ .../PresentationDependencyAssembler.swift | 7 ++- 9 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift index 4304c2bb..d5393fbd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift @@ -67,6 +67,10 @@ struct DataDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: ReportsInterface.self) { _ in return DefaultReportsRepository(networkService: networkService) } + + DIContainer.shared.register(type: NotificationInterface.self) { _ in + return DefaultNotificationRepository(networkService: networkService) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift new file mode 100644 index 00000000..db9191f4 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift @@ -0,0 +1,41 @@ +// +// NotificationListReponseDTO.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +struct NotificationListReponseDTO: Decodable { + let notifications: [NotificationResponseDTO] +} + +struct NotificationResponseDTO: Decodable { + let notificationID: Int + let notificationType: String + let title: String + let content: String + let isRead: Bool + let createdAt: String + let landingURL: String +} + +extension NotificationListReponseDTO { + func toEntity() -> NotificationListEntity { + let notifications = notifications.map { $0.toEntity() } + return .init(notifications: notifications) + } +} + +extension NotificationResponseDTO { + func toEntity() -> NotificationEntity { + .init( + notificationID: notificationID, + notificationType: notificationType, + title: title, + content: content, + isRead: isRead, + createdAt: createdAt, + landingURL: landingURL + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift new file mode 100644 index 00000000..34a270f3 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -0,0 +1,63 @@ +// +// NotificationAPI.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +import Foundation + +import Alamofire + +enum NotificationAPI { + case fetchNotificationList +} + +extension NotificationAPI: EndPoint { + + var basePath: String { + return "/api/v1/notifications" + } + + var path: String { + switch self { + case .fetchNotificationList: + "" + } + } + + var method: HTTPMethod { + switch self { + case .fetchNotificationList: + .get + } + } + + var headers: HeaderType { + switch self { + case .fetchNotificationList: + .withAuth + } + } + + var parameterEncoding: ParameterEncoding { + switch self { + case .fetchNotificationList: + JSONEncoding.default + } + } + + var queryParameters: [String : String]? { + switch self { + case .fetchNotificationList: + nil + } + } + + var bodyParameters: Parameters? { + switch self { + case .fetchNotificationList: + nil + } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift new file mode 100644 index 00000000..26c704b5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -0,0 +1,23 @@ +// +// NotificationRepository.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +struct DefaultNotificationRepository: NotificationInterface { + + private let networkService: NetworkService + + init(networkService: NetworkService) { + self.networkService = networkService + } + + func fetchNotifications() async throws -> NotificationListEntity { + let result = try await networkService.request( + NotificationAPI.fetchNotificationList, + decodingType: NotificationListReponseDTO.self + ) + return result.toEntity() + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index b79e9a4f..44a6f4bd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -23,10 +23,11 @@ struct DomainDependencyAssembler: DependencyAssembler { let forbiddenWordRepository = DIContainer.shared.resolve(type: ForbiddenWordInterface.self), let commonQuestRepository = DIContainer.shared.resolve(type: CommonQuestInterface.self), let blocksRepository = DIContainer.shared.resolve(type: BlocksInterface.self), - let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self) else { - ByeBooLogger.error(ByeBooError.DIFailedError) - return - } + let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self), + let notificationRepository = DIContainer.shared.resolve(type: NotificationInterface.self) else { + ByeBooLogger.error(ByeBooError.DIFailedError) + return + } DIContainer.shared.register(type: FetchUserJourneyUseCase.self) { _ in return DefaultFetchUserJourneyUseCase(repository: userRepository) @@ -171,7 +172,7 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchCommonQuestMyAnswersUseCase.self) { _ in return DefaultFetchCommonQuestMyAnswersUseCase(repository: userRepository) } - + DIContainer.shared.register(type: FetchAIAnswerUseCase.self) { _ in return DefaultFetchAIAnswerUseCase(repository: questRepository) } @@ -187,7 +188,7 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: UpdateCommonQuestUseCase.self) { _ in return DefaultUpdateCommonQuestUseCase(repository: commonQuestRepository) } - + DIContainer.shared.register(type: BlockUserUseCase.self) { _ in return DefaultBlockUserCase(repository: blocksRepository) } @@ -207,5 +208,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: DeleteCommonQuestUseCase.self) { _ in return DefaultDeleteCommonQuestUseCase(repository: commonQuestRepository) } + + DIContainer.shared.register(type: FetchNotificationListUseCase.self) { _ in + return DefaultFetchNotificationListUseCase(repository: notificationRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift new file mode 100644 index 00000000..42f7f4e8 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -0,0 +1,20 @@ +// +// NotificationListEntity.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +struct NotificationListEntity { + let notifications: [NotificationEntity] +} + +struct NotificationEntity { + let notificationID: Int + let notificationType: String + let title: String + let content: String + let isRead: Bool + let createdAt: String + let landingURL: String +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift new file mode 100644 index 00000000..05b07428 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift @@ -0,0 +1,10 @@ +// +// NotificationInterface.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +protocol NotificationInterface { + func fetchNotifications() async throws -> NotificationListEntity +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift new file mode 100644 index 00000000..bb0dec12 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift @@ -0,0 +1,23 @@ +// +// FetchNotificationListUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +protocol FetchNotificationListUseCase { + func execute() async throws -> NotificationListEntity +} + +struct DefaultFetchNotificationListUseCase: FetchNotificationListUseCase { + + private let repository: NotificationInterface + + init(repository: NotificationInterface) { + self.repository = repository + } + + func execute() async throws -> NotificationListEntity { + try await repository.fetchNotifications() + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 8ecf838a..79511f67 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -107,7 +107,9 @@ struct PresentationDependencyAssembler: DependencyAssembler { let setHelperUseCase = container.resolve(type: SetHelperUseCase.self), let fetchUserJourneyUseCase = container.resolve(type: FetchUserJourneyUseCase.self), let getUserNameUseCase = container.resolve(type: GetUserNameUseCase.self), - let getHelperUseCase = container.resolve(type: GetHelperUseCase.self) else { + let getHelperUseCase = container.resolve(type: GetHelperUseCase.self), + let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) + else { ByeBooLogger.error(ByeBooError.DIFailedError) return } @@ -118,7 +120,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { fetchUserJourneyUseCase: fetchUserJourneyUseCase, getUserNameUseCase: getUserNameUseCase, setHelperUseCase: setHelperUseCase, - getHelperUseCase: getHelperUseCase + getHelperUseCase: getHelperUseCase, + fetchNotificationListUseCase: fetchNotificationListUseCase ) } From 41ab7ecd464083971e966dbd92a04d4936f9f535 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:17:14 +0900 Subject: [PATCH 056/231] =?UTF-8?q?feat:=20#437=20=ED=99=88=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=EC=97=90=EC=84=9C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/HomeViewModel.swift | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index 18458a7e..fd16ee7a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -22,13 +22,15 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() - + private var notificationResultSubject = PassthroughSubject, Never>() + private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase private let fetchUserJourneyUseCase: FetchUserJourneyUseCase private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase + private let fetchNotificationListUseCase: FetchNotificationListUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -36,7 +38,8 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase + getHelperUseCase: GetHelperUseCase, + fetchNotificationListUseCase: FetchNotificationListUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -44,13 +47,15 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase + self.fetchNotificationListUseCase = fetchNotificationListUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher(), + notificationResult: notificationResultSubject.eraseToAnyPublisher() ) } } @@ -137,7 +142,6 @@ extension HomeViewModel { } private func isHelperShown(state: HomeState) { - if !getHelperUseCase.execute() && state == .beforeJourneyStart { isHelperShownResultSubject.send(false) } else { @@ -148,4 +152,13 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } + + private func fetchNotificationList() async { + do { + let notifications = try await fetchNotificationListUseCase.execute() + notificationResultSubject.send(.success(notifications)) + } catch { + notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) + } + } } From ab1284d8cc00599387959baea288c546b4285005 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:17:49 +0900 Subject: [PATCH 057/231] =?UTF-8?q?refactor:=20#437=20=ED=99=88=20?= =?UTF-8?q?=EB=B7=B0=EB=AA=A8=EB=8D=B8=20viewWillAppear=20=ED=8A=B8?= =?UTF-8?q?=EB=A6=AC=EA=B1=B0=20=EC=8B=9C=20=EA=B5=AC=EC=A1=B0=EC=A0=81=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/HomeViewModel.swift | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index fd16ee7a..0d0d7869 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -72,17 +72,24 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> + let notificationResult: AnyPublisher, Never> } func action(_ trigger: Input) { switch trigger { case .viewWillAppear: - // TODO: 구조적 동시성 반영 - fetchDialogue() - fetchStatus() - fetchJourney() - getUserResult() + + Task { + await withTaskGroup(of: Void.self) { group in + group.addTask { await self.fetchDialogue() } + group.addTask { await self.fetchStatus() } + group.addTask { await self.fetchJourney() } + group.addTask { await self.fetchNotificationList() } + + await group.waitForAll() + } + } case .helperDidTap: setHelperShown() } @@ -90,49 +97,43 @@ extension HomeViewModel: ViewModelType { } extension HomeViewModel { - private func fetchDialogue() { - Task { - do { - let dialogues = try await fetchCharacterDialogueUseCase.execute() - characterResultSubject.send(.success(dialogues)) - } catch { - characterResultSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) + private func fetchDialogue() async { + do { + let dialogues = try await fetchCharacterDialogueUseCase.execute() + characterResultSubject.send(.success(dialogues)) + } catch { + characterResultSubject.send( + .failure( + error as? ByeBooError ?? ByeBooError.unknownError ) - } + ) } } - private func fetchStatus() { - Task { - do { - let status = try await fetchQuestStatusUseCase.execute() - homeStateResultSubject.send(.success(status)) - isHelperShown(state: status.currentStatus) - ByeBooLogger.debug("home status: \(status)") - } catch { - if let error = error as? ByeBooError { - homeStateResultSubject.send(.failure(error)) - } - isHelperShown(state: .beforeJourneyStart) + private func fetchStatus() async { + do { + let status = try await fetchQuestStatusUseCase.execute() + homeStateResultSubject.send(.success(status)) + isHelperShown(state: status.currentStatus) + ByeBooLogger.debug("home status: \(status)") + } catch { + if let error = error as? ByeBooError { + homeStateResultSubject.send(.failure(error)) } + isHelperShown(state: .beforeJourneyStart) } } - private func fetchJourney() { - Task { - do { - let journey = try await fetchUserJourneyUseCase.execute() - journeyResultSubject.send(.success(journey)) - } catch { - journeyResultSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) + private func fetchJourney() async { + do { + let journey = try await fetchUserJourneyUseCase.execute() + journeyResultSubject.send(.success(journey)) + } catch { + journeyResultSubject.send( + .failure( + error as? ByeBooError ?? ByeBooError.unknownError ) - } + ) } } From b1c6e973c9f8b39c0e1b5f7b357221b3dfd6497d Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:24:23 +0900 Subject: [PATCH 058/231] =?UTF-8?q?refactor:=20#444=20dateformatter=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Model/Util/ServerDateFormatter.swift | 23 +++++++++++++++++++ .../ViewModel/CommonQuestViewModel.swift | 22 ++---------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift index 5c70828d..25f2e112 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift @@ -12,6 +12,10 @@ final class ServerDateFormatter { static let shared = ServerDateFormatter() private let formatter: DateFormatter + private static let minute: TimeInterval = 60 + private static let hour: TimeInterval = 3600 + private static let day: TimeInterval = 86400 + private init() { self.formatter = DateFormatter() formatter.do { @@ -25,4 +29,23 @@ final class ServerDateFormatter { guard let string = string else { return nil } return formatter.date(from: string) } + + func relativeTimeString(from dateString: String) -> String? { + guard let date = formatter.date(from: dateString) else { return nil } + + let diff = Date().timeIntervalSince(date) + + switch diff { + case .. String? { - guard index >= 0 && index < answers.count, - let writtenAt = DateFormatter.toDetailDate(from: answers[index].writtenAt) - else { - return nil - } - - let diffTime = Date().timeIntervalSince(writtenAt) - - switch diffTime { - case ..= 0 && index < answers.count else { return nil } + return ServerDateFormatter.shared.relativeTimeString(from: answers[index].writtenAt) } } From b096655e260cb9d7da527012569f7808aa0f1668 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:25:04 +0900 Subject: [PATCH 059/231] =?UTF-8?q?chore:=20#444=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 3 +++ .../PresentationDependencyAssembler.swift | 11 +++++++++++ .../Presentation/ViewControllerFactory.swift | 6 +++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index b79e9a4f..462e36cc 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -207,5 +207,8 @@ 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) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 8ecf838a..8b693e49 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -349,5 +349,16 @@ struct PresentationDependencyAssembler: DependencyAssembler { deleteBlockedUserUseCase: deleteBlockedUserUseCase ) } + + DIContainer.shared.register(type: CommonQuestHistoryViewModel.self) { container in + guard let fetchCommonQuestDetailUseCase = container.resolve(type: FetchCommonQuestDetailUseCase.self) else { + ByeBooLogger.error(ByeBooError.DIFailedError) + return + } + + return CommonQuestHistoryViewModel( + fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase + ) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index dd356892..05cd59e3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -187,7 +187,11 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { } func makeCommonQuestHistoryViewController() -> CommonQuestHistoryViewController { - .init() + guard let viewModel = DIContainer.shared.resolve(type: CommonQuestHistoryViewModel.self) else { + DIErrorHandle() + fatalError() + } + return CommonQuestHistoryViewController(viewModel: viewModel) } func makeCommonQuestMyAnswersViewController() -> CommonQuestMyAnswersViewController { From f7419248467c7d43aa5707f653b9c9fb8da102f7 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:27:08 +0900 Subject: [PATCH 060/231] feat: #444 DTO --- .../CommonQuestAnswerDetailResponseDTO.swift | 13 +++++++- .../Model/CommonQuestAnswersResponseDTO.swift | 30 ++++++------------- .../CommonQuestMyAnswersResponseDTO.swift | 12 +++++--- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift index 42fdd755..689ce275 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift @@ -9,10 +9,21 @@ import Foundation struct CommonQuestAnswerDetailResponseDTO: Decodable { let question: String - let answer: CommonQuestAnswerResponseDTO + let answer: CommonQuestAnswerSimpleResponseDTO let comments: [CommonQuestCommentResponseDTO] } +struct CommonQuestAnswerSimpleResponseDTO: Decodable { + let content: String + let writerId: Int + let writer: String + let profileIcon: String + let likeCount: Int + let commentCount: Int + let isLiked: Bool + let writtenAt: String +} + struct CommonQuestCommentResponseDTO: Decodable { let commentId: Int let replyCount: Int diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift index 32b563fc..2c69fcf1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift @@ -16,14 +16,12 @@ struct CommonQuestAnswersResponseDTO: Decodable { } struct CommonQuestAnswerResponseDTO: Decodable { -// let likeCount: Int -// let commentCount: Int -// let isLiked: Bool + let likeCount: Int + let commentCount: Int + let isLiked: Bool let answerId: Int -// let userId: Int -// let writerId: String - let writer: String let writerId: Int + let writer: String let profileIcon: String let writtenAt: String let content: String @@ -46,26 +44,16 @@ extension CommonQuestAnswersResponseDTO { extension CommonQuestAnswerResponseDTO { func toEntity(userName: String) -> CommonQuestAnswerEntity { .init( -// isMyAnswer: userName == writerId ? true : false, -// answerID: answerId, -// writerID: writerId, -// profileIcon: profileIcon, -// writtenAt: writtenAt, -// content: content, -// userID: userId, -// likeCount: likeCount, -// commentCount: commentCount, -// isLiked: isLiked isMyAnswer: userName == writer ? true : false, answerID: answerId, - writerID: writer, + writerID: writerId, + writer: writer, profileIcon: profileIcon, writtenAt: writtenAt, content: content, - userID: writerId, - likeCount: 3, - commentCount: 4, - isLiked: false, + likeCount: likeCount, + commentCount: commentCount, + isLiked: isLiked ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift index 2a1ad8fb..c87e8ce0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift @@ -19,22 +19,26 @@ struct CommonQuestMyAnswerResponseDTO: Decodable { } extension CommonQuestMyAnswersResponseDTO { - func toEntity() -> CommonQuestMyAnswersEntity { + func toEntity(userName: String) -> CommonQuestMyAnswersEntity { .init( hasNext: hasNext, nextCursor: nextCursor, - answers: answers.map { $0.toEntity() + answers: answers.map { $0.toEntity(userName: userName) }) } } extension CommonQuestMyAnswerResponseDTO { - func toEntity() -> CommonQuestMyAnswerEntity { + func toEntity(userName: String) -> CommonQuestMyAnswerEntity { .init( answerID: answerId, writtenAt: writtenAt, content: content, - question: question + question: question, + nickname: userName, + isLiked: false, + likeCount: 2, + commentCount: 3 // TODO: 서버수정되면 수정하기 ) } } From 1fb3a3fbad2a1b5965ecda1566a569ec5c231585 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:27:55 +0900 Subject: [PATCH 061/231] =?UTF-8?q?feat:=20#444=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=80=98=EC=8A=A4=ED=8A=B8=20API=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/EndPoint/CommonQuestAPI.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift index 2bfd5f07..1a83bec4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift @@ -12,6 +12,7 @@ import Alamofire enum CommonQuestAPI { case postCommonQuest(questID: Int, dto: SaveCommonQuestRequestDTO) case fetchCommonQuest(date: String, cursor: Int?) + case fetchCommonQuestDetail(asnwerID: Int) case updateCommonQuest(answerID: Int, dto: UpdateCommonQuestRequestDTO) case deleteCommonQuest(answerID: Int) } @@ -19,7 +20,12 @@ enum CommonQuestAPI { extension CommonQuestAPI: EndPoint { var basePath: String { - return "/api/v1/common-quests" + switch self { + case .fetchCommonQuest, .fetchCommonQuestDetail: + return "/api/v2/common-quests" + case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest: + return "/api/v1/common-quests" + } } var path: String { @@ -28,7 +34,7 @@ extension CommonQuestAPI: EndPoint { return "/\(questID)" case .fetchCommonQuest: return "" - case .updateCommonQuest(let answerID, _), .deleteCommonQuest(let answerID): + case .updateCommonQuest(let answerID, _), .deleteCommonQuest(let answerID), .fetchCommonQuestDetail(let answerID): return "/\(answerID)" } } @@ -37,7 +43,7 @@ extension CommonQuestAPI: EndPoint { switch self { case .postCommonQuest: return .post - case .fetchCommonQuest: + case .fetchCommonQuest, .fetchCommonQuestDetail: return .get case .updateCommonQuest: return .patch @@ -48,7 +54,7 @@ extension CommonQuestAPI: EndPoint { var headers: HeaderType { switch self { - case .postCommonQuest, .fetchCommonQuest, .updateCommonQuest, .deleteCommonQuest: + case .postCommonQuest, .fetchCommonQuest, .updateCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: return .withAuth } } @@ -57,14 +63,14 @@ extension CommonQuestAPI: EndPoint { switch self { case .postCommonQuest, .updateCommonQuest: return JSONEncoding.default - case .fetchCommonQuest, .deleteCommonQuest: + case .fetchCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: return URLEncoding.default } } var queryParameters: [String : String]? { switch self { - case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest: + case .postCommonQuest, .updateCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: return nil case .fetchCommonQuest(let date, let cursor): if let cursor { @@ -81,7 +87,7 @@ extension CommonQuestAPI: EndPoint { switch self { case .postCommonQuest(_, let dto): return try? dto.toDictionary() - case .fetchCommonQuest, .deleteCommonQuest: + case .fetchCommonQuest, .deleteCommonQuest, .fetchCommonQuestDetail: return nil case .updateCommonQuest(_, let dto): return try? dto.toDictionary() From 9669d2e8b4a96ee1d1d0584bde4bfa72b9a2c1f4 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:28:59 +0900 Subject: [PATCH 062/231] =?UTF-8?q?refactor:=20#444=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/CommonQuestAnswersEntity.swift | 12 ++++---- .../Entity/CommonQuestMyAnswersEntity.swift | 30 +++++++++++++++---- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift index a0d251ff..b26bf296 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift @@ -20,11 +20,11 @@ struct CommonQuestAnswersEntity { struct CommonQuestAnswerEntity { let isMyAnswer: Bool let answerID: Int - let writerID: String + let writerID: Int + let writer: String let profileIcon: String let writtenAt: String let content: String - let userID: Int let likeCount: Int let commentCount: Int let isLiked: Bool @@ -38,11 +38,11 @@ extension CommonQuestAnswersEntity { CommonQuestAnswerEntity( isMyAnswer: false, answerID: $0, - writerID: "유저\($0)", + writerID: $0, + writer: "유저\($0)", profileIcon: profileIcons[$0 % 4], writtenAt: Date.now.toString(), content: "\($0)번째 테스트 답변", - userID: $0, likeCount: 1, commentCount: 1, isLiked: false @@ -67,11 +67,11 @@ extension CommonQuestAnswerEntity { .init( isMyAnswer: false, answerID: 1, - writerID: "장원영", + writerID: 12, + writer: "장원영", profileIcon: "SO_SO", writtenAt: "2025-10-12", content: "헤어진 지 벌써 일주일이 지났습니다. 처음에는 실감이 안 나서 눈물조차 나오지 않았어요. 그저 멍하니 천장만 바라보며 시간을 보냈습니다. 그런데 오늘 아침, 습관적으로 휴대폰을 확인하다가 더 이상 '굿모닝' 인사를 보낼 사람이 없다는 사실을 깨닫고 그제야 무너져 내렸습니다. 밥알이 모래알 같아서 잘 넘어가지도 않네요. 친구들은 시간이 약이라고, 더 좋은 사람 만날 거라고 위로하지만 지금 당장은 그 어떤 말도 귀에 들어오지 않습니다.", - userID: 12, likeCount: 3, commentCount: 4, isLiked: false diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift index bc49f98d..e083f1ab 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift @@ -18,6 +18,10 @@ struct CommonQuestMyAnswerEntity { let writtenAt: String let content: String let question: String + let nickname: String + let isLiked: Bool + let likeCount: Int + let commentCount: Int } extension CommonQuestMyAnswersEntity { @@ -27,7 +31,11 @@ extension CommonQuestMyAnswersEntity { answerID: $0, writtenAt: Date.now.toString(), content: "\($0)번째 테스트 답변", - question: "\($0)번째 질문" + question: "\($0)번째 질문", + nickname: "냔", + isLiked: false, + likeCount: 2, + commentCount: 3 ) } @@ -47,10 +55,22 @@ extension CommonQuestMyAnswerEntity { static func stubs() -> [Self] { [ - .init(answerID: 1, writtenAt: "2025-01-11", content: "content", question: "question"), - .init(answerID: 2, writtenAt: "2025-01-12", content: "content", question: "question"), - .init(answerID: 3, writtenAt: "2025-01-13", content: "content", question: "question"), - .init(answerID: 4, writtenAt: "2025-01-14", content: "content", question: "question") + .init( + answerID: 1, writtenAt: "2025-01-11", content: "content", + question: "question", nickname: "가을", isLiked: false, likeCount: 1, commentCount: 2 + ), + .init( + answerID: 2, writtenAt: "2025-01-12", content: "content", + question: "question", nickname: "원영", isLiked: false, likeCount: 1, commentCount: 2 + ), + .init( + answerID: 3, writtenAt: "2025-01-13", content: "content", + question: "question", nickname: "리즈", isLiked: false, likeCount: 1, commentCount: 2 + ), + .init( + answerID: 4, writtenAt: "2025-01-14", content: "content", + question: "question", nickname: "유진", isLiked: false, likeCount: 1, commentCount: 2 + ) ] } } From 4cfaed1e5d5d22e91ec11123bcf0ad37f275358b Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:35:06 +0900 Subject: [PATCH 063/231] =?UTF-8?q?refactor:=20#444=20=EB=82=B4=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EB=AA=A8=EC=95=84=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=EC=97=90=20username=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift index 74174372..2efd4249 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift @@ -144,11 +144,12 @@ struct DefaultUsersRepository: UsersInterface { } func fetchMyCommonQuestAnswers(cursor: Int?) async throws -> CommonQuestMyAnswersEntity { + let userName: String = userDefaultsService.load(key: .userName) ?? "" let result = try await network.request( UsersAPI.fetchCommonQuestAnswers(cursor: cursor), decodingType: CommonQuestMyAnswersResponseDTO.self ) - return result.toEntity() + return result.toEntity(userName: userName) } var alarmEnabled: Bool { From 0e4b1472740d4587b17396905712021c4c806841 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:35:53 +0900 Subject: [PATCH 064/231] =?UTF-8?q?feat:=20#444=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=80=98=EC=8A=A4=ED=8A=B8=20v2=20api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/CommonQuestRepository.swift | 9 ++++ .../Interface/CommonQuestInterface.swift | 1 + .../FetchCommonQuestDetailUseCase.swift | 25 +++++++++ .../CommonQuestHistoryViewController.swift | 53 +++++++++++++++---- .../CommonQuestHistoryViewModel.swift | 52 ++++++++++++++++-- 5 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index c74198ca..fc21ac6b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -64,4 +64,13 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { CommonQuestAPI.deleteCommonQuest(answerID: answerID) ) } + + func fetchCommonQuestDetail(answerID: Int) async throws -> [CommonQuestCommentEntity] { + let commonQuestDetail = try await network.request( + CommonQuestAPI.fetchCommonQuestDetail(asnwerID: answerID), + decodingType: CommonQuestAnswerDetailResponseDTO.self + ) + + return commonQuestDetail.toEntity() + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift index 599821de..5bcab1aa 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift @@ -12,4 +12,5 @@ protocol CommonQuestInterface { func fetchCommonQuest(date: String, cursor: Int?) async throws -> CommonQuestAnswersEntity func updateCommonQuest(answerID: Int, answer: String) async throws func deleteCommonQuest(answerID: Int) async throws + func fetchCommonQuestDetail(answerID: Int) async throws -> [CommonQuestCommentEntity] } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift new file mode 100644 index 00000000..809016bb --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift @@ -0,0 +1,25 @@ +// +// FetchCommonQuestDetailUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/26/26. +// + +import Foundation + +protocol FetchCommonQuestDetailUseCase { + func execute(answerID: Int) async throws -> [CommonQuestCommentEntity] +} + +struct DefaultFetchCommonQuestDetailUseCase: FetchCommonQuestDetailUseCase { + + private let repository: CommonQuestInterface + + init(repository: CommonQuestInterface) { + self.repository = repository + } + + func execute(answerID: Int) async throws -> [CommonQuestCommentEntity] { + return try await repository.fetchCommonQuestDetail(answerID: 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 0abf8f73..d440453c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -5,6 +5,7 @@ // Created by APPLE on 2/24/26. // +import Combine import UIKit import Mixpanel @@ -12,9 +13,19 @@ import Mixpanel final class CommonQuestHistoryViewController: BaseViewController { private var dataSource: UITableViewDiffableDataSource! + private var cancellable = Set() private let rootView = CommonQuestHistoryView() - private let viewModel = CommonQuestHistoryViewModel() + private let viewModel: CommonQuestHistoryViewModel + + init(viewModel: CommonQuestHistoryViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } private var answerID: Int? private var answer: String? @@ -32,6 +43,9 @@ final class CommonQuestHistoryViewController: BaseViewController { self.tabBarController?.tabBar.isHidden = true addKeyboardObservers() + viewModel.action(.viewWillAppear(answerID: answerID ?? 0)) + bind() + Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } @@ -47,7 +61,7 @@ final class CommonQuestHistoryViewController: BaseViewController { ) configureDataSource() - applySnapshot() +// applySnapshot() } override func viewWillDisappear(_ animated: Bool) { @@ -92,7 +106,7 @@ extension CommonQuestHistoryViewController { replyCount: entity.replyCount, writer: entity.writer, profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, - writtenAt: entity.writtenAt, + writtenAt: ServerDateFormatter.shared.relativeTimeString(from: entity.writtenAt) ?? "", content: entity.content, showAllText: item.showAllText, isReplySheet: false @@ -102,10 +116,10 @@ extension CommonQuestHistoryViewController { } } - private func applySnapshot() { + private func applySnapshot(commentList: [CommonQuestCommentEntity]) { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) - let items = viewModel.commentLists.map { CommentItem(entity: $0, showAllText: false) } + let items = commentList.map { CommentItem(entity: $0, showAllText: false) } snapshot.appendItems(items, toSection: .main) dataSource.apply(snapshot, animatingDifferences: false) } @@ -189,6 +203,10 @@ extension CommonQuestHistoryViewController: CommentProtocol { } self.present(viewController, animated: true) } + + func menuButtonDidTap(commentID: Int) { + + } } extension CommonQuestHistoryViewController { @@ -201,7 +219,10 @@ extension CommonQuestHistoryViewController { content: String, answerID: Int? = nil, writerID: Int? = nil, - isMyAnswer: Bool? = nil + isMyAnswer: Bool? = nil, + isLiked: Bool, + likeCount: Int, + commentCount: Int ) { self.answerID = answerID self.answer = content @@ -222,9 +243,9 @@ extension CommonQuestHistoryViewController { profileIcon: profileIcon, nickname: nickname, content: content, - isLiked: false, - likeCount: 4, - commentCount: 5 + isLiked: isLiked, + likeCount: likeCount, + commentCount: commentCount ) } } @@ -246,6 +267,20 @@ extension CommonQuestHistoryViewController { private func dismissKeyboard() { view.endEditing(true) } + + private func bind() { + viewModel.output.fetchCommonQuestCommentsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let commentList): + self?.applySnapshot(commentList: commentList) + case .failure(let error): + ByeBooLogger.debug(error) + } + } + .store(in: &cancellable) + } } extension CommonQuestHistoryViewController: KeyboardHandleProtocol { 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 eb0625b5..7dfd486a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -9,9 +9,55 @@ import Combine import Foundation final class CommonQuestHistoryViewModel { - private(set) var commentLists: [CommonQuestCommentEntity] = CommonQuestCommentEntity.toCommentListStub() + private var cancellables = Set() - func getCommentsCount() -> Int { - return commentLists.count + private(set) var output: Output + + private let fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase + + private let fetchCommentListSubject: PassthroughSubject, Never> = .init() + + init( + fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase + ) { + self.fetchCommonQuestCommentsUseCase = fetchCommonQuestCommentsUseCase + + output = Output( + fetchCommonQuestCommentsPublisher: fetchCommentListSubject.eraseToAnyPublisher() + ) + } +} + +extension CommonQuestHistoryViewModel: ViewModelType { + enum Input { + case viewWillAppear(answerID: Int) + } + + struct Output { + let fetchCommonQuestCommentsPublisher: AnyPublisher, Never> + } + + func action(_ trigger: Input) { + switch trigger { + case .viewWillAppear(let answerID): + fetchCommonQuestComments(answerID: answerID) + } + } +} + +extension CommonQuestHistoryViewModel { + private func fetchCommonQuestComments(answerID: Int) { + Task { + do { + let commentsList = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) + ByeBooLogger.debug("댓글 list \(commentsList)") + fetchCommentListSubject.send(.success(commentsList)) + } catch { + guard let error = error as? ByeBooError else { + return + } + fetchCommentListSubject.send(.failure(error)) + } + } } } From 1a4f2323936f2b60bed4e42d12cd48849af4bdb1 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 6 Jun 2026 00:36:19 +0900 Subject: [PATCH 065/231] =?UTF-8?q?refactor:=20#444=20mock=20->=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Cells/CommonQuestAnswerCell.swift | 8 ++++---- .../CommonQuestMyAnswersViewController.swift | 13 ++++++++----- .../ViewController/CommonQuestViewController.swift | 9 ++++++--- .../ViewModel/CommonQuestMyAnswerViewModel.swift | 6 +++++- 4 files changed, 23 insertions(+), 13 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 4cf66ccc..1ea3ffef 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 @@ -91,14 +91,14 @@ extension CommonQuestAnswerCell { if let profileIcon { userIconView.image = profileIcon } - userNicknameLabel.text = answer.writerID + userNicknameLabel.text = answer.writer answerID = answer.answerID questContentView.configure( content: answer.content, writtenAt: writtenAt, - isLiked: false, - likeCount: 4, - commentCount: 3, + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.likeCount, showAllText: false ) } 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 0e592468..bd372b0c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -116,9 +116,12 @@ extension CommonQuestMyAnswersViewController: UITableViewDelegate { question: answer.question, writtenAt: answer.writtenAt, profileIcon: .relievedBadge, - nickname: "냐냐냐", + nickname: answer.nickname, content: answer.content, - answerID: answer.answerID + answerID: answer.answerID, + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.commentCount ) self.navigationController?.pushViewController(historyViewController, animated: false) @@ -201,9 +204,9 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { question: answer.question, content: answer.content, writtenAt: answer.writtenAt, - isLiked: true, - likeCount: 3, - commentCount: 3 + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.commentCount ) return cell } 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 f3ca30c7..303c2547 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -145,11 +145,14 @@ extension CommonQuestViewController: UITableViewDelegate { question: viewModel.question, writtenAt: formattedWrittenAt, profileIcon: profileIcon, - nickname: answer.writerID, + nickname: answer.writer, content: answer.content, answerID: answer.answerID, - writerID: answer.userID, - isMyAnswer: answer.isMyAnswer + writerID: answer.writerID, + isMyAnswer: answer.isMyAnswer, + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.commentCount ) historyViewController.navigationItem.hidesBackButton = true self.navigationController?.pushViewController( 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 7622a3cb..d2ef0037 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -103,7 +103,11 @@ extension CommonQuestMyAnswerViewModel { answerID: answer.answerID, writtenAt: displayDate, content: answer.content, - question: answer.question + question: answer.question, + nickname: answer.nickname, + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.commentCount ) } } From 863544a6097f0c62f7c26eb3e40b4e512705b06a Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 6 Jun 2026 12:36:46 +0900 Subject: [PATCH 066/231] =?UTF-8?q?chore:=20#437=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EB=B9=84=EC=A0=80=EB=8B=9D=20=ED=8C=8C=EC=9D=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index 814438d8..2387f417 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -329,7 +329,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS 1780581426"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -349,7 +349,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Prod.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 10; DEVELOPMENT_TEAM = ""; @@ -372,7 +372,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.heartz.ByeBoo-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS 1780581426"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From a59529b2d7c0db280261eefb67e6dc6ea09d6b77 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:35:14 +0900 Subject: [PATCH 067/231] =?UTF-8?q?chore:=20#437=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Home/View/Notice/NoticeCardCell.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index dc597d0a..7c26b48a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -86,12 +86,12 @@ extension NoticeCardCell { iconImage: UIImage, title: String, subtitle: String, - writtenTiem: String + writtenTime: String ) { self.backgroundColor = backgroundColor noticeImageView.image = iconImage titleLabel.text = title subtitleLabel.text = subtitle - writtenTimeLabel.text = writtenTiem + writtenTimeLabel.text = writtenTime } } From 944801b88ebc0d04f5de3ff82e57a92ca246c184 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:36:09 +0900 Subject: [PATCH 068/231] =?UTF-8?q?refactor:=20#437=20NoticesViewControlle?= =?UTF-8?q?r=20=EC=83=9D=EC=84=B1=EC=9E=90=EC=97=90=EC=84=9C=20=EC=95=8C?= =?UTF-8?q?=EB=9E=8C=20=EC=9C=A0=EB=AC=B4=20=EC=A3=BC=EC=9E=85=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 11 +++-------- .../ViewController/NoticesViewController.swift | 18 ++++++++++++++---- .../Feature/Home/ViewModel/HomeViewModel.swift | 2 ++ .../Presentation/ViewControllerFactory.swift | 4 ++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 89abd74c..97cf8858 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -22,7 +22,6 @@ final class HomeViewController: BaseViewController { private var isFirstVisit: Bool = true private var journeyType: JourneyType = .recording private var isAnimating: Bool = false - private var isExistNotice: Bool? init(viewModel: HomeViewModel) { self.viewModel = viewModel @@ -61,9 +60,6 @@ final class HomeViewController: BaseViewController { if isFirstVisit { isFirstVisit.toggle() } self.navigationController?.setNavigationBarHidden(true, animated: false) - // 추후 API 연동 시 뷰모델 액션을 호출하여 알림 유무를 판단할 예정 - rootView.headerView.updateNotice(isExist: true) - isExistNotice = true } override func setAddTarget() { @@ -121,11 +117,10 @@ extension HomeViewController { } @objc - private func noticeButtonDidTap() { - guard let isExistNotice else { return } - - let viewController = ViewControllerFactory.shared.makeNoticesViewController(isExistNotice: isExistNotice) + private func noticeButtonDidTap() { + let viewController = ViewControllerFactory.shared.makeNoticesViewController() viewController.hidesBottomBarWhenPushed = true + viewController.configure(notificationList: viewModel.notifications) self.navigationController?.setNavigationBarHidden(false, animated: false) self.navigationController?.pushViewController(viewController, animated: false) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift index 78046246..cd33f35d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift @@ -9,11 +9,10 @@ import UIKit final class NoticesViewController: BaseViewController { - private let isExistNotice: Bool private let rootView = NoticesView() + private var notificationList: NotificationListEntity? - init(isExistNotice: Bool) { - self.isExistNotice = isExistNotice + init() { super.init(nibName: nil, bundle: nil) } @@ -35,7 +34,11 @@ final class NoticesViewController: BaseViewController { action: #selector(back) ) - rootView.contentView.decideNoticeContent(isExistNotice: isExistNotice) + guard let notifications = notificationList?.notifications else { + return + } + + rootView.contentView.decideNoticeContent(isExistNotice: !notifications.isEmpty) } override func setAddTarget() { @@ -68,6 +71,13 @@ extension NoticesViewController { private func readAllButtonDidTap() {} } +extension NoticesViewController { + + func configure(notificationList: NotificationListEntity?) { + self.notificationList = notificationList + } +} + extension NoticesViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index 0d0d7869..af9768c4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -15,6 +15,7 @@ final class HomeViewModel { var dialoguesResult: Result { characterResultSubject.value } + private(set) var notifications: NotificationListEntity? private(set) var output: Output private var characterResultSubject = CurrentValueSubject, Never>(.failure(ByeBooError.noData)) @@ -157,6 +158,7 @@ extension HomeViewModel { private func fetchNotificationList() async { do { let notifications = try await fetchNotificationListUseCase.execute() + self.notifications = notifications notificationResultSubject.send(.success(notifications)) } catch { notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index dd356892..d4293e4b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -225,8 +225,8 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { return .init(viewModel: viewModel) } - func makeNoticesViewController(isExistNotice: Bool) -> NoticesViewController { - .init(isExistNotice: isExistNotice) + func makeNoticesViewController() -> NoticesViewController { + .init() } } From 7907672df46c1a137a22556a9fc75581041e2e02 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:32:26 +0900 Subject: [PATCH 069/231] =?UTF-8?q?refactor:=20#437=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=86=A0=EC=BD=9C=EC=97=90=20=EC=A0=95=EC=9D=98=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=93=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Presentation/ViewControllerFactory.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index d4293e4b..0dd5b375 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -26,6 +26,14 @@ protocol ViewControllerFactoryProtocol { func makeWriteActiveTypeQuestViewController() -> WriteActiveTypeQuestViewController func makeFinishJourneyViewController() -> FinishJourneyViewController func makeCommonQuestBottomSheetViewController() -> CommonQuestBottomSheetViewController + func makeCompletedQuestsViewController() -> CompletedQuestsViewController + func makeParentQuestViewController() -> ParentQuestViewController + func makeCommonQuestViewController() -> CommonQuestViewController + func makeCommonQuestHistoryViewController() -> CommonQuestHistoryViewController + func makeCommonQuestMyAnswersViewController() -> CommonQuestMyAnswersViewController + func makeBlockedUserListViewController() -> BlockedkUserListViewController + func makeAIAnswerViewController() -> AIAnswerViewController + func makeNotificationsViewController() -> NotificationsViewController } final class ViewControllerFactory: ViewControllerFactoryProtocol { From 1a6a86c9546eaf4112cecdf460483d2b17ec0e26 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:35:37 +0900 Subject: [PATCH 070/231] =?UTF-8?q?feat:=20#437=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B0=A8=EC=9D=B4=EB=A5=BC=20=EA=B3=84=EC=82=B0=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=A4=ED=95=91=ED=95=98=EB=8A=94=20=EC=9C=A0?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/DomainDependencyAssembler.swift | 4 ++ .../UseCase/FormatElapsedTimeUseCase.swift | 40 +++++++++++++++++++ .../ViewModel/CommonQuestViewModel.swift | 31 ++++---------- .../PresentationDependencyAssembler.swift | 15 ++++++- 4 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 44a6f4bd..acb630a6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -212,5 +212,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchNotificationListUseCase.self) { _ in return DefaultFetchNotificationListUseCase(repository: notificationRepository) } + + DIContainer.shared.register(type: FormatElapsedTimeUseCase.self) { _ in + return DefaultFormatElapsedTimeUseCase() + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift new file mode 100644 index 00000000..23d14196 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift @@ -0,0 +1,40 @@ +// +// FormatElapsedTimeUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +import Foundation + +protocol FormatElapsedTimeUseCase { + func execute(from timeString: String) -> String? +} + +struct DefaultFormatElapsedTimeUseCase: FormatElapsedTimeUseCase { + + private let minute: Double = 60 + private let hour: Double = 3600 + private let day: Double = 86400 + + func execute(from timeString: String) -> String? { + guard let time = DateFormatter.toDetailDate(from: timeString) else { + return nil + } + + let diffTime = Date().timeIntervalSince(time) + + switch diffTime { + case ..() private let commonQuestSubject = PassthroughSubject, Never>.init() private let fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase - private let minute: Double = 60 - private let hour: Double = 3600 - private let day: Double = 86400 + private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase private(set) var output: Output private var commonQuest: CommonQuestAnswersEntity? @@ -24,8 +22,12 @@ final class CommonQuestViewModel { private var nextCursor: Int? = nil private var currentDate: String = DateFormatter.toAPIDateString(from: .now) - init(fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase) { + init( + fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase, + formatElapsedTimeUseCase: FormatElapsedTimeUseCase + ) { self.fetchCommonQuestByDateUseCase = fetchCommonQuestByDateUseCase + self.formatElapsedTimeUseCase = formatElapsedTimeUseCase self.output = Output( commonQuestPublisher: commonQuestSubject.eraseToAnyPublisher() ) @@ -127,25 +129,6 @@ extension CommonQuestViewModel { } func getWrittenAt(at index: Int) -> String? { - guard index >= 0 && index < answers.count, - let writtenAt = DateFormatter.toDetailDate(from: answers[index].writtenAt) - else { - return nil - } - - let diffTime = Date().timeIntervalSince(writtenAt) - - switch diffTime { - case .. Date: Sun, 7 Jun 2026 11:36:56 +0900 Subject: [PATCH 071/231] =?UTF-8?q?refactor:=20#437=20=EB=B7=B0=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20NotificationsViewModel=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 2 +- ...wift => NotificationsViewController.swift} | 42 +++++++++++++------ .../ViewModel/NotificationsViewModel.swift | 23 ++++++++++ .../Presentation/ViewControllerFactory.swift | 9 +++- 4 files changed, 60 insertions(+), 16 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/{NoticesViewController.swift => NotificationsViewController.swift} (65%) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 97cf8858..297a3b5c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -118,7 +118,7 @@ extension HomeViewController { @objc private func noticeButtonDidTap() { - let viewController = ViewControllerFactory.shared.makeNoticesViewController() + let viewController = ViewControllerFactory.shared.makeNotificationsViewController() viewController.hidesBottomBarWhenPushed = true viewController.configure(notificationList: viewModel.notifications) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift similarity index 65% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index cd33f35d..cc0cc1b4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -1,5 +1,5 @@ // -// NoticesViewController.swift +// NotificationsViewController.swift // ByeBoo-iOS // // Created by APPLE on 4/30/26. @@ -7,12 +7,14 @@ import UIKit -final class NoticesViewController: BaseViewController { +final class NotificationsViewController: BaseViewController { private let rootView = NoticesView() + private let viewModel: NotificationsViewModel private var notificationList: NotificationListEntity? - init() { + init(viewModel: NotificationsViewModel) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -33,7 +35,7 @@ final class NoticesViewController: BaseViewController { type: .titleAndBack("알림"), action: #selector(back) ) - + guard let notifications = notificationList?.notifications else { return } @@ -58,31 +60,30 @@ final class NoticesViewController: BaseViewController { } } -extension NoticesViewController: BackNavigable { +extension NotificationsViewController: BackNavigable { func back() { self.navigationController?.popViewController(animated: false) } } -extension NoticesViewController { +extension NotificationsViewController { @objc private func readAllButtonDidTap() {} } -extension NoticesViewController { +extension NotificationsViewController { func configure(notificationList: NotificationListEntity?) { self.notificationList = notificationList } } -extension NoticesViewController: UITableViewDataSource { +extension NotificationsViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - // 실제로는 알림 개수만큼 설정 - 2 + notificationList?.notifications.count ?? 0 } func tableView( @@ -97,16 +98,31 @@ extension NoticesViewController: UITableViewDataSource { cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: NoticeCardCell.identifier, for: indexPath) as? NoticeCardCell + guard + let cell = tableView.dequeueReusableCell( + withIdentifier: NoticeCardCell.identifier, + for: indexPath + ) as? NoticeCardCell, + let notification = notificationList?.notifications[indexPath.section], + let notificationType = notification.notificationType, + let writtenTime = viewModel.formatElapsedTime(from: notification.createdAt) else { return UITableViewCell() } - + + cell.bind( + isRead: notification.isRead, + notificationType: notificationType, + title: notification.title, + subtitle: notification.content, + writtenTime: writtenTime + ) + return cell } } -extension NoticesViewController: UITableViewDelegate { +extension NotificationsViewController: UITableViewDelegate { func tableView( _ tableView: UITableView, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift new file mode 100644 index 00000000..6c3d8997 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -0,0 +1,23 @@ +// +// NotificationsViewModel.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +final class NotificationsViewModel { + + private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase + + init(formatElapsedTimeUseCase: FormatElapsedTimeUseCase) { + self.formatElapsedTimeUseCase = formatElapsedTimeUseCase + } + + func formatElapsedTime(from timeString: String) -> String? { + guard let formattedTime = formatElapsedTimeUseCase.execute(from: timeString) else { + return nil + } + + return formattedTime + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index 0dd5b375..7c5371d5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -233,8 +233,13 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { return .init(viewModel: viewModel) } - func makeNoticesViewController() -> NoticesViewController { - .init() + func makeNotificationsViewController() -> NotificationsViewController { + guard let viewModel = DIContainer.shared.resolve(type: NotificationsViewModel.self) else { + DIErrorHandle() + fatalError() + } + + return .init(viewModel: viewModel) } } From 10e7d9c03602721c222a0eac3887c0feac869e03 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:37:32 +0900 Subject: [PATCH 072/231] =?UTF-8?q?feat:=20#437=20NotificationType=20?= =?UTF-8?q?=EC=97=B4=EA=B1=B0=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Enum/NotificationType+Data.swift | 24 +++++++++++++++++++ .../Model/NotificationListReponseDTO.swift | 2 +- .../Domain/Entity/Enum/NotificationType.swift | 12 ++++++++++ .../Home/View/Notice/NoticeCardCell.swift | 17 +++++++++---- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift new file mode 100644 index 00000000..c1cb649c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift @@ -0,0 +1,24 @@ +// +// NotificationType+Data.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +extension NotificationType { + + var responseKey: String { + switch self { + case .questOpen: + "QUEST_OPEN" + case .comment: + "COMMENT" + case .like: + "LIKE" + } + } + + static func keyToEnum(_ key: String) -> Self? { + allCases.first { $0.responseKey == key } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift index db9191f4..28542da7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift @@ -30,7 +30,7 @@ extension NotificationResponseDTO { func toEntity() -> NotificationEntity { .init( notificationID: notificationID, - notificationType: notificationType, + notificationType: NotificationType.keyToEnum(notificationType), title: title, content: content, isRead: isRead, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift new file mode 100644 index 00000000..6fba51e5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift @@ -0,0 +1,12 @@ +// +// NotificationType.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +enum NotificationType: CaseIterable { + case questOpen + case comment + case like +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index 7c26b48a..b36128f8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -82,16 +82,25 @@ final class NoticeCardCell: UITableViewCell { extension NoticeCardCell { func bind( - backgroundColor: UIColor, - iconImage: UIImage, + isRead: Bool, + notificationType: NotificationType, title: String, subtitle: String, writtenTime: String ) { - self.backgroundColor = backgroundColor - noticeImageView.image = iconImage + self.backgroundColor = isRead ? .primary30020 : .white5 + noticeImageView.image = mapToImage(for: notificationType) titleLabel.text = title subtitleLabel.text = subtitle writtenTimeLabel.text = writtenTime } + + private func mapToImage(for notificationType: NotificationType) -> UIImage { + switch notificationType { + case .questOpen: + return .myOn + case .comment, .like: + return .commonJourney + } + } } From 8e1277ee8670fc09151e1c7328fc326134701fce Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:37:48 +0900 Subject: [PATCH 073/231] =?UTF-8?q?feat:=20#437=20NotificationListEntity?= =?UTF-8?q?=20stub=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/NotificationListEntity.swift | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift index 42f7f4e8..fd4dcc33 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -11,10 +11,100 @@ struct NotificationListEntity { struct NotificationEntity { let notificationID: Int - let notificationType: String + let notificationType: NotificationType? let title: String let content: String let isRead: Bool let createdAt: String let landingURL: String } + +extension NotificationListEntity { + static func stub() -> Self { + .init( + notifications: [ + NotificationEntity( + notificationID: 9, + notificationType: .questOpen, + title: "오늘의 퀘스트 오픈 🌱", + content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + isRead: false, + createdAt: "2026-06-07T11:29:00.735730", + landingURL: "myapp://quest/24" + ), + NotificationEntity( + notificationID: 8, + notificationType: .comment, + title: "공통여정에 답변이 달렸어요 💬", + content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", + isRead: true, + createdAt: "2026-06-07T08:35:43.735730", + landingURL: "myapp://common-quests/24" + ), + NotificationEntity( + notificationID: 7, + notificationType: .like, + title: "공통여정에 답변에 공감이 달렸어요 ❤️", + content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", + isRead: true, + createdAt: "2026-06-06T15:35:43.735730", + landingURL: "myapp://common-quests/23" + ), + NotificationEntity( + notificationID: 6, + notificationType: .questOpen, + title: "오늘의 퀘스트 오픈 🌱", + content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + isRead: false, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://quest/24" + ), + NotificationEntity( + notificationID: 5, + notificationType: .comment, + title: "공통여정에 답변이 달렸어요 💬", + content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/24" + ), + NotificationEntity( + notificationID: 4, + notificationType: .like, + title: "공통여정에 답변에 공감이 달렸어요 ❤️", + content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/23" + ), + NotificationEntity( + notificationID: 3, + notificationType: .questOpen, + title: "오늘의 퀘스트 오픈 🌱", + content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + isRead: false, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://quest/24" + ), + NotificationEntity( + notificationID: 2, + notificationType: .comment, + title: "공통여정에 답변이 달렸어요 💬", + content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/24" + ), + NotificationEntity( + notificationID: 1, + notificationType: .like, + title: "공통여정에 답변에 공감이 달렸어요 ❤️", + content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/23" + ) + ] + ) + } +} From 2e29eb136adbc79842c2a5bcd0742fbd6fb397f6 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:38:11 +0900 Subject: [PATCH 074/231] =?UTF-8?q?refactor:=20#437=20bind=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EA=B5=AC=EB=8F=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 297a3b5c..4ab1d259 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -47,6 +47,9 @@ final class HomeViewController: BaseViewController { super.viewWillAppear(animated) viewModel.action(.viewWillAppear) + if isFirstVisit { isFirstVisit.toggle() } + + self.navigationController?.setNavigationBarHidden(true, animated: false) let property = HomeEvents.HomePageProperty( isFirstPageView: isFirstVisit, @@ -56,10 +59,6 @@ final class HomeViewController: BaseViewController { event: HomeEvents.Name.homePageView, properties: property.dictionary ) - - if isFirstVisit { isFirstVisit.toggle() } - - self.navigationController?.setNavigationBarHidden(true, animated: false) } override func setAddTarget() { @@ -147,6 +146,13 @@ extension HomeViewController { extension HomeViewController: ToastPresentable, ToastErrorHandler { private func bind() { + bindCharacter() + bindHomeState() + bindHelper() + bindNotifications() + } + + private func bindCharacter() { viewModel.output.characterResult .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -158,7 +164,9 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } } .store(in: &cancellables) - + } + + private func bindHomeState() { Publishers.CombineLatest3( viewModel.output.userResult, viewModel.output.journeyResult, @@ -188,7 +196,9 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } } .store(in: &cancellables) - + } + + private func bindHelper() { viewModel.output.helperResult .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -198,4 +208,19 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } .store(in: &cancellables) } + + private func bindNotifications() { + viewModel.output.notificationResult + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let notificationList): + let isAllRead = notificationList.notifications.allSatisfy { $0.isRead } + self?.rootView.headerView.updateNotice(isExist: !isAllRead) + case .failure(let error): + self?.handleError(error) + } + } + .store(in: &cancellables) + } } From f1b6327b4cde29996422afde0710314cf5f91d69 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:38:18 +0900 Subject: [PATCH 075/231] =?UTF-8?q?chore:=20#437=20=EC=A4=84=EB=B0=94?= =?UTF-8?q?=EA=BF=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift | 1 + 1 file changed, 1 insertion(+) 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 7622a3cb..63cba471 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -13,6 +13,7 @@ final class CommonQuestMyAnswerViewModel { private let cancellables = Set() private let nameSubject = PassthroughSubject, Never>.init() private let answersSubject = PassthroughSubject, Never>.init() + private let getUserNameUseCase: GetUserNameUseCase private let fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase From 10207eac89a0acb70f5234810286bd16666aadc9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:09:39 +0900 Subject: [PATCH 076/231] =?UTF-8?q?refactor:=20#437=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=85=80=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=ED=9A=8C?= =?UTF-8?q?=EC=83=89=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Home/View/Notice/NoticeCardCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index b36128f8..1ba1773a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -29,6 +29,7 @@ final class NoticeCardCell: UITableViewCell { private func setStyle() { self.do { $0.layer.cornerRadius = 12 + $0.selectionStyle = .none } titleLabel.applyByeBooFont( style: .body1Sb16, From fb3554f6f7f9d54f239bd884dc8aab715c469d62 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:16:20 +0900 Subject: [PATCH 077/231] =?UTF-8?q?add:=20#446=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=EC=96=B4=20=EC=9E=90=EB=8F=99=20=EB=B0=B0=EC=A0=95=20yml=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/auto_assign.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/auto_assign.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 00000000..a5b934a4 --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,19 @@ +# Set to true to add reviewers to pull requests +addReviewers: true + +# Set to true to add assignees to pull requests +addAssignees: author + +# A list of reviewers to be added to pull requests (GitHub user name) +reviewers: + - y-eonee + - dev-domo + - juri123123 + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +skipKeywords: + - wip + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +numberOfReviewers: 0 From 7c756dd6d97050c28dabe0acdd7050edeb1b687d Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:53:38 +0900 Subject: [PATCH 078/231] =?UTF-8?q?del:=20#446=20yml=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/auto_assign.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/auto_assign.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml deleted file mode 100644 index a5b934a4..00000000 --- a/.github/auto_assign.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Set to true to add reviewers to pull requests -addReviewers: true - -# Set to true to add assignees to pull requests -addAssignees: author - -# A list of reviewers to be added to pull requests (GitHub user name) -reviewers: - - y-eonee - - dev-domo - - juri123123 - -# A list of keywords to be skipped the process that add reviewers if pull requests include it -skipKeywords: - - wip - -# A number of reviewers added to the pull request -# Set 0 to add all the reviewers (default: 0) -numberOfReviewers: 0 From 9d7b8da705bb13b3b28c863a9f6ca1127dfa5222 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:54:38 +0900 Subject: [PATCH 079/231] =?UTF-8?q?add:=20#446=20code=20owners=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..b2c9e3a4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @y-eonee @dev-domo @juri123123 From 5f40a43b8d95409cdcf84e600788cddb43f021a9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:13:17 +0900 Subject: [PATCH 080/231] =?UTF-8?q?chore:=20#437=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repository/NotificationTokenRepository.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift index 47063b8e..98b3050d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift @@ -1,5 +1,5 @@ // -// NotificationRepositoru.swift +// NotificationTokenRepository.swift // ByeBoo-iOS // // Created by APPLE on 11/22/25. From 4cf0b318a5fc6fbd9cbfc38f1c2e41b7ab248006 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:19:33 +0900 Subject: [PATCH 081/231] =?UTF-8?q?refactor:=20#437=20TaskGroup=20->=20asy?= =?UTF-8?q?nc=20let=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Home/ViewModel/HomeViewModel.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index af9768c4..f0a664b5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -82,15 +82,14 @@ extension HomeViewModel: ViewModelType { getUserResult() Task { - await withTaskGroup(of: Void.self) { group in - group.addTask { await self.fetchDialogue() } - group.addTask { await self.fetchStatus() } - group.addTask { await self.fetchJourney() } - group.addTask { await self.fetchNotificationList() } - - await group.waitForAll() - } + async let dialogue: Void = fetchDialogue() + async let status: Void = fetchStatus() + async let journey: Void = fetchJourney() + async let notificationList: Void = fetchNotificationList() + + let _ = await (dialogue, status, journey, notificationList) } + case .helperDidTap: setHelperShown() } From cf9eb095b81f5e64c989d90f66dba48a0bc75a6a Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:44:07 +0900 Subject: [PATCH 082/231] =?UTF-8?q?chore:=20#437=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...stReponseDTO.swift => NotificationListResponseDTO.swift} | 6 +++--- .../ByeBoo-iOS/Data/Repository/NotificationRepository.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Data/Model/{NotificationListReponseDTO.swift => NotificationListResponseDTO.swift} (87%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift similarity index 87% rename from ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift rename to ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift index 28542da7..9c2cb343 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift @@ -1,11 +1,11 @@ // -// NotificationListReponseDTO.swift +// NotificationListResponseDTO.swift // ByeBoo-iOS // // Created by 더스틴 on 6/5/26. // -struct NotificationListReponseDTO: Decodable { +struct NotificationListResponseDTO: Decodable { let notifications: [NotificationResponseDTO] } @@ -19,7 +19,7 @@ struct NotificationResponseDTO: Decodable { let landingURL: String } -extension NotificationListReponseDTO { +extension NotificationListResponseDTO { func toEntity() -> NotificationListEntity { let notifications = notifications.map { $0.toEntity() } return .init(notifications: notifications) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift index 26c704b5..c93aefa3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -16,7 +16,7 @@ struct DefaultNotificationRepository: NotificationInterface { func fetchNotifications() async throws -> NotificationListEntity { let result = try await networkService.request( NotificationAPI.fetchNotificationList, - decodingType: NotificationListReponseDTO.self + decodingType: NotificationListResponseDTO.self ) return result.toEntity() } From 7c82b60adfc675166200d796ba631a5aa0180b4d Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:44:58 +0900 Subject: [PATCH 083/231] =?UTF-8?q?chore:=20#437=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift index acb0f6e3..ea727164 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift @@ -1,5 +1,5 @@ // -// NotificationAPI.swift +// NotificationTokenAPI.swift // ByeBoo-iOS // // Created by APPLE on 11/22/25. From a1ea571dbf542c286e34538c656fe0f124d026e8 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 15:20:33 +0900 Subject: [PATCH 084/231] =?UTF-8?q?feat:=20#444=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EB=8B=B5=EA=B8=80=20=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestAnswerDetailResponseDTO.swift | 9 +- .../CommonQuestAnswerRepliesResponseDTO.swift | 7 +- .../Repository/CommonQuestRepository.swift | 3 +- .../Entity/CommonQuestCommentEntity.swift | 5 + ...CommonQuestBottomSheetViewController.swift | 8 +- .../Enum/CommonQuestArchiveType.swift | 15 ++- .../Cells/CommentTableViewCell.swift | 22 ++++- .../CommonQuestHistoryViewController.swift | 94 +++++++++++++------ .../CommonQuestReplyViewController.swift | 18 ++++ 9 files changed, 133 insertions(+), 48 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift index 689ce275..0143cd8e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift @@ -35,16 +35,17 @@ struct CommonQuestCommentResponseDTO: Decodable { } extension CommonQuestAnswerDetailResponseDTO { - func toEntity() -> [CommonQuestCommentEntity] { + func toEntity(userName: String) -> [CommonQuestCommentEntity] { comments.map { - $0.toEntity() + $0.toEntity(userName: userName) } } } extension CommonQuestCommentResponseDTO { - func toEntity() -> CommonQuestCommentEntity { - .init( + func toEntity(userName: String) -> CommonQuestCommentEntity { + return .init( + isMyComment: userName == writer ? true : false, commentID: commentId, replyCount: replyCount, writerID: writerId, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift index 2c798e0a..46a28640 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift @@ -32,16 +32,17 @@ struct CommonQuestAnswerReplyResponseDTO: Decodable { } extension CommonQuestAnswerRepliesResponseDTO { - func toEntity() -> [CommonQuestCommentEntity] { + func toEntity(userName: String) -> [CommonQuestCommentEntity] { replies.map { - $0.toEntity() + $0.toEntity(userName: userName) } } } extension CommonQuestAnswerReplyResponseDTO { - func toEntity() -> CommonQuestCommentEntity { + func toEntity(userName: String) -> CommonQuestCommentEntity { .init( + isMyComment: userName == writer ? true : false, commentID: commentId, replyCount: nil, writerID: writerId, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index fc21ac6b..b795211c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -66,11 +66,12 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { } func fetchCommonQuestDetail(answerID: Int) async throws -> [CommonQuestCommentEntity] { + let userName: String = userDefaultsService.load(key: .userName) ?? "" let commonQuestDetail = try await network.request( CommonQuestAPI.fetchCommonQuestDetail(asnwerID: answerID), decodingType: CommonQuestAnswerDetailResponseDTO.self ) - return commonQuestDetail.toEntity() + return commonQuestDetail.toEntity(userName: userName) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 16c286e6..5b2f4794 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -8,6 +8,7 @@ import Foundation struct CommonQuestCommentEntity: Hashable { + let isMyComment: Bool let commentID: Int let replyCount: Int? let writerID: Int @@ -21,6 +22,7 @@ extension CommonQuestCommentEntity { static func toCommentListStub() -> [Self] { return (1...5).map { .init( + isMyComment: false, commentID: $0, replyCount: $0 - 1, writerID: 2, @@ -34,6 +36,7 @@ extension CommonQuestCommentEntity { static func toCommentStub() -> Self { .init( + isMyComment: false, commentID: 1, replyCount: 2, writerID: 2, @@ -47,6 +50,7 @@ extension CommonQuestCommentEntity { static func toReplyListStub() -> [Self] { return (6...10).map { .init( + isMyComment: false, commentID: $0, replyCount: nil, writerID: 2, @@ -61,6 +65,7 @@ extension CommonQuestCommentEntity { static func toReplyStub() -> Self { .init( + isMyComment: false, commentID: 1, replyCount: nil, writerID: 2, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index e3191e81..59a1f192 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -30,7 +30,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { private let yesAnswer = "예" private let noAnswer = "아니오" - private var rootView = CommonQuestBottomSheetView(sheetType: .other) + private var rootView = CommonQuestBottomSheetView(sheetType: .otherAnswer) private let viewModel: CommonQuestBottomSheetViewModel private var writerID: Int = 0 private var cancellables = Set() @@ -77,7 +77,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { extension CommonQuestBottomSheetViewController { - func configure( + func configureWhenEdit( sheeetType: CommonQuestArchiveType, answerID: Int? = nil, answer: String? = nil, @@ -92,9 +92,9 @@ extension CommonQuestBottomSheetViewController { } - func configure(sheeetType: CommonQuestArchiveType, writerID: Int) { + func configure(sheeetType: CommonQuestArchiveType, targetID: Int) { self.sheetType = sheeetType - self.writerID = writerID + self.writerID = targetID if let sheetType { rootView = CommonQuestBottomSheetView(sheetType: sheetType) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift index bde3216d..b236e120 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift @@ -9,8 +9,10 @@ import Foundation import UIKit enum CommonQuestArchiveType { - case mine - case other + case myAnswer + case otherAnswer + case myComment + case otherComment enum Action { case edit, delete, report, block @@ -25,16 +27,21 @@ enum CommonQuestArchiveType { var items: [Item] { switch self { - case .mine: + case .myAnswer, .myComment: return [ Item(title: "수정하기", icon: .edit, color: .white, action: .edit), Item(title: "삭제하기", icon: .trash, color: .error300, action: .delete) ] - case .other: + case .otherAnswer: return [ Item(title: "사용자 차단하기", icon: .block, color: .white, action: .block), Item(title: "게시글 신고하기", icon: .report, color: .error300, action: .report) ] + case .otherComment: + return [ + Item(title: "차단하기", icon: .edit, color: .white, action: .edit), + Item(title: "신고하기", icon: .trash, color: .error300, action: .delete) + ] } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index 8704e6e0..0e4ba3bb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -10,6 +10,7 @@ import UIKit protocol CommentProtocol: AnyObject { func moreLabelDidTap(commentID: Int) func replyIconDidTap(commentID: Int) + func menuButtonDidTap(commentID: Int, isMyComment: Bool) } extension CommentProtocol { @@ -25,7 +26,7 @@ final class CommentTableViewCell: UITableViewCell { private let profileIcon = UIImageView(image: .relievedBadge) private let nicknameLabel = UILabel() private let dateLabel = UILabel() - private let menuButton = UIImageView() + private let menuButton = UIButton() private let commentTextView = UITextView() private var moreLabel = UILabel() private var replyCountContainer = UIStackView() @@ -38,6 +39,8 @@ final class CommentTableViewCell: UITableViewCell { private var didSetReplyLayout = false private var didSetCommentLayout = false + private var isMyComment: Bool = false + override init( style: UITableViewCell.CellStyle, reuseIdentifier: String? @@ -75,7 +78,7 @@ final class CommentTableViewCell: UITableViewCell { } menuButton.do { - $0.image = .menu + $0.setImage(.menu, for: .normal) } commentTextView.do { @@ -131,6 +134,7 @@ final class CommentTableViewCell: UITableViewCell { $0.trailing.equalTo(commentTextView.snp.trailing).offset(-20.adjustedW) } } + private func setCommentListLayout() { guard !didSetCommentLayout else { return } didSetCommentLayout = true @@ -190,11 +194,14 @@ final class CommentTableViewCell: UITableViewCell { let replyTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(replyIconDidTap(_:))) replyCommentIcon.addGestureRecognizer(replyTapRecognizer) + + menuButton.addTarget(self, action: #selector(menuButtonDidTap), for: .touchUpInside) } } extension CommentTableViewCell { func configure( + isMyComment: Bool, commentID: Int, replyCount: Int? = nil, writer: String, @@ -214,7 +221,8 @@ extension CommentTableViewCell { replyCommentIcon.tintColor = .grayscale600 replyCountLabel.textColor = .grayscale600 } - + + self.isMyComment = isMyComment self.commentID = commentID nicknameLabel.text = writer self.profileIcon.image = profileIcon @@ -272,7 +280,13 @@ extension CommentTableViewCell { delegate?.moreLabelDidTap(commentID: commentID) } - @objc private func replyIconDidTap(_ tapRecognizer: UITapGestureRecognizer) { + @objc + private func replyIconDidTap(_ tapRecognizer: UITapGestureRecognizer) { delegate?.replyIconDidTap(commentID: commentID) } + + @objc + private func menuButtonDidTap() { + delegate?.menuButtonDidTap(commentID: commentID, isMyComment: isMyComment) + } } 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 d440453c..4d867869 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -27,12 +27,19 @@ final class CommonQuestHistoryViewController: BaseViewController { fatalError("init(coder:) has not been implemented") } - private var answerID: Int? + private var isMyAnswer: Bool = false + + private var nickname: String = "" + private var profileIcon: UIImage? + var answerID: Int? private var answer: String? private var question: String? private var writtenAt: String? - private var commonQuestArchiveType: CommonQuestArchiveType = .mine + private var commonQuestArchiveType: CommonQuestArchiveType = .myAnswer private var writerID: Int = 0 + private var isLiked: Bool = false + private var likeCount: Int = 0 + private var commentCount: Int = 0 override func loadView() { view = rootView @@ -102,6 +109,7 @@ extension CommonQuestHistoryViewController { let entity = item.entity cell.configure( + isMyComment: entity.isMyComment, commentID: entity.commentID, replyCount: entity.replyCount, writer: entity.writer, @@ -137,31 +145,24 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { writeCommonQuestViewController.navigationItem.hidesBackButton = true writeCommonQuestViewController.questScope = .common writeCommonQuestViewController.configureToEdit( - nil, .question, question, answerID, answer, writtenAt + questNumber: nil, + questType: .question, + questionTitle: question, + answerID: answerID, + answer: answer, + writtenAt: writtenAt, + nickname: nickname, + profileIcon: profileIcon ?? .relievedBadge, + isLiked: isLiked, + likeCount: likeCount, + commentCount: commentCount ) self.navigationController?.pushViewController(writeCommonQuestViewController, animated: false) } @objc private func bottomUp() { - let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() - commonQuestBottomSheet.configure(sheeetType: commonQuestArchiveType, writerID: writerID) - commonQuestBottomSheet.configure( - sheeetType: commonQuestArchiveType, - answerID: answerID, - answer: answer, - question: question, - writtenAt: writtenAt - ) - setDelegate(bottomSheet: commonQuestBottomSheet) - - if let sheet = commonQuestBottomSheet.sheetPresentationController{ - sheet.detents = [.custom { _ in 224.adjustedH }] - sheet.prefersGrabberVisible = true - sheet.prefersScrollingExpandsWhenScrolledToEdge = false - sheet.preferredCornerRadius = 8 - } - self.present(commonQuestBottomSheet, animated: true) + commonBottomSheetUp() } private func setDelegate(bottomSheet: CommonQuestBottomSheetViewController) { @@ -204,8 +205,8 @@ extension CommonQuestHistoryViewController: CommentProtocol { self.present(viewController, animated: true) } - func menuButtonDidTap(commentID: Int) { - + func menuButtonDidTap(commentID: Int, isMyComment: Bool) { + commonBottomSheetUp(commentID: commentID, isMyComment: isMyComment) } } @@ -219,24 +220,29 @@ extension CommonQuestHistoryViewController { content: String, answerID: Int? = nil, writerID: Int? = nil, - isMyAnswer: Bool? = nil, + isMyAnswer: Bool, isLiked: Bool, likeCount: Int, commentCount: Int ) { + self.nickname = nickname + self.profileIcon = profileIcon self.answerID = answerID self.answer = content self.question = question self.writtenAt = writtenAt + self.isMyAnswer = isMyAnswer - if let isMyAnswer { - commonQuestArchiveType = isMyAnswer ? .mine : .other - } + commonQuestArchiveType = isMyAnswer ? .myAnswer : .otherAnswer if let writerID { self.writerID = writerID } - + + self.isLiked = isLiked + self.likeCount = likeCount + self.commentCount = commentCount + rootView.configure( question: question, writtenAt: writtenAt, @@ -281,6 +287,38 @@ extension CommonQuestHistoryViewController { } .store(in: &cancellable) } + + private func commonBottomSheetUp(commentID: Int? = nil, isMyComment: Bool? = nil) { + let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() + if isMyAnswer { + commonQuestBottomSheet.configureWhenEdit( + sheeetType: commonQuestArchiveType, + answerID: answerID, + answer: answer, + question: question, + writtenAt: writtenAt + ) + } + + if let commentID, let isMyComment { + commonQuestBottomSheet.configure( + sheeetType: isMyComment ? .myComment : .otherComment , + targetID: writerID + ) + } else { + commonQuestBottomSheet.configure(sheeetType: commonQuestArchiveType, targetID: writerID) + } + + setDelegate(bottomSheet: commonQuestBottomSheet) + + if let sheet = commonQuestBottomSheet.sheetPresentationController{ + sheet.detents = [.custom { _ in 224.adjustedH }] + sheet.prefersGrabberVisible = true + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + sheet.preferredCornerRadius = 8 + } + self.present(commonQuestBottomSheet, animated: true) + } } extension CommonQuestHistoryViewController: KeyboardHandleProtocol { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 5c492016..3305826e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -62,6 +62,7 @@ extension CommonQuestReplyViewController { let entity = item.entity cell.configure( + isMyComment: entity.isMyComment, commentID: entity.commentID, replyCount: entity.replyCount, writer: entity.writer, @@ -95,6 +96,23 @@ extension CommonQuestReplyViewController { } extension CommonQuestReplyViewController: CommentProtocol { + func menuButtonDidTap(commentID: Int, isMyComment: Bool) { + let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() + + commonQuestBottomSheet.configure( + sheeetType: isMyComment ? .myComment : .otherComment , + targetID: commentID + ) + + if let sheet = commonQuestBottomSheet.sheetPresentationController{ + sheet.detents = [.custom { _ in 224.adjustedH }] + sheet.prefersGrabberVisible = true + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + sheet.preferredCornerRadius = 8 + } + self.present(commonQuestBottomSheet, animated: true) + } + func moreLabelDidTap(commentID: Int) { let currentSnapshot = dataSource.snapshot() From 2c18831252b6156590c8517b7b14e61d5abaddd3 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 16:58:23 +0900 Subject: [PATCH 085/231] =?UTF-8?q?refactor:=20#444=20viewModel=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=20id=EB=A7=8C=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/CommonQuestViewModel.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) 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 bef44192..8dcf1c04 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -113,14 +113,14 @@ extension CommonQuestViewModel { var isUserAnswered: Bool { commonQuest?.isAnswered ?? false } - + func getAnswer(at index: Int) -> CommonQuestAnswerEntity? { guard index >= 0 && index < answers.count else { return nil } return answers[index] } - + func getProfileIcon(at index: Int) -> UIImage? { guard index >= 0 && index < answers.count else { return nil } return ProfileIcon.image(for: answers[index].profileIcon) @@ -131,3 +131,19 @@ extension CommonQuestViewModel { return ServerDateFormatter.shared.relativeTimeString(from: answers[index].writtenAt) } } + +extension CommonQuestViewModel { + struct CommonQuestID { + let answerID: Int + let writerID: Int + } + + func getCommonQuestID(at index: Int) -> CommonQuestID? { + guard index >= 0 && index < answers.count else { + return nil + } + let answer = answers[index] + return CommonQuestID(answerID: answer.answerID, writerID: answer.writerID) + } + +} From f18357f49786d5f76acc28ed843ab5a9e7dded5e Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 17:00:59 +0900 Subject: [PATCH 086/231] =?UTF-8?q?feat:=20#444=20=EB=82=B4=EB=8B=B5?= =?UTF-8?q?=EB=B3=80=20=EC=A1=B0=ED=9A=8C=20v2=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Model/CommonQuestMyAnswersResponseDTO.swift | 7 +++++-- ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swift | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift index c87e8ce0..0389e4a7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift @@ -16,6 +16,9 @@ struct CommonQuestMyAnswerResponseDTO: Decodable { let writtenAt: String let content: String let question: String + let likeCount: Int + let commentCount: Int + let isLiked: Bool } extension CommonQuestMyAnswersResponseDTO { @@ -37,8 +40,8 @@ extension CommonQuestMyAnswerResponseDTO { question: question, nickname: userName, isLiked: false, - likeCount: 2, - commentCount: 3 // TODO: 서버수정되면 수정하기 + likeCount: likeCount, + commentCount: commentCount ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swift index eea17785..75f6dd26 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/UsersAPI.swift @@ -24,9 +24,9 @@ extension UsersAPI: EndPoint { var basePath: String { switch self { case .journey, .character, .count, .start, - .modifyName, .updateNotificationPermission, .fetchCommonQuestAnswers: + .modifyName, .updateNotificationPermission: return "/api/v1/users" - case .sendUser: + case .sendUser, .fetchCommonQuestAnswers: return "/api/v2/users" } } From cf3d0670bd338ee20eef037ebf9b473a1e869f1d Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 17:02:06 +0900 Subject: [PATCH 087/231] =?UTF-8?q?feat:=20#444=20history=EC=97=90?= =?UTF-8?q?=EC=84=9C=20api=20=ED=98=B8=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 데이터를 넘겨받는 방식이 아닌 서버에서 데이터를 받아옴 --- .../CommonQuestAnswerDetailResponseDTO.swift | 26 +++- .../Repository/CommonQuestRepository.swift | 2 +- .../Domain/CommonQuestDetailEntity.swift | 26 ++++ .../Interface/CommonQuestInterface.swift | 2 +- .../FetchCommonQuestDetailUseCase.swift | 4 +- .../CommonQuestHistoryViewController.swift | 124 ++++++++---------- 6 files changed, 108 insertions(+), 76 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/CommonQuestDetailEntity.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift index 0143cd8e..cd86b562 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift @@ -35,10 +35,28 @@ struct CommonQuestCommentResponseDTO: Decodable { } extension CommonQuestAnswerDetailResponseDTO { - func toEntity(userName: String) -> [CommonQuestCommentEntity] { - comments.map { - $0.toEntity(userName: userName) - } + func toEntity(userName: String) -> CommonQuestDetailEntity { + .init( + question: question, + answer: answer.toEntity(userName: userName), + comments: comments.map { $0.toEntity(userName: userName) } + ) + } +} + +extension CommonQuestAnswerSimpleResponseDTO { + func toEntity(userName: String) -> CommonQuestAnswerDetailEntity { + .init( + isMyAnswer: userName == writer ? true : false, + content: content, + writerID: writerId, + writer: writer, + profileIcon: profileIcon, + likeCount: likeCount, + commentCount: commentCount, + isLiked: isLiked, + writtenAt: writtenAt + ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index b795211c..8c0930bf 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -65,7 +65,7 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { ) } - func fetchCommonQuestDetail(answerID: Int) async throws -> [CommonQuestCommentEntity] { + func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity { let userName: String = userDefaultsService.load(key: .userName) ?? "" let commonQuestDetail = try await network.request( CommonQuestAPI.fetchCommonQuestDetail(asnwerID: answerID), diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/CommonQuestDetailEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/CommonQuestDetailEntity.swift new file mode 100644 index 00000000..07ec2e18 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/CommonQuestDetailEntity.swift @@ -0,0 +1,26 @@ +// +// CommonQuestDetailEntity.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/9/26. +// + +import Foundation + +struct CommonQuestDetailEntity { + let question: String + let answer: CommonQuestAnswerDetailEntity + let comments: [CommonQuestCommentEntity] +} + +struct CommonQuestAnswerDetailEntity { + let isMyAnswer: Bool + let content: String + let writerID: Int + let writer: String + let profileIcon: String + let likeCount: Int + let commentCount: Int + let isLiked: Bool + let writtenAt: String +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift index 5bcab1aa..d2647f63 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift @@ -12,5 +12,5 @@ protocol CommonQuestInterface { func fetchCommonQuest(date: String, cursor: Int?) async throws -> CommonQuestAnswersEntity func updateCommonQuest(answerID: Int, answer: String) async throws func deleteCommonQuest(answerID: Int) async throws - func fetchCommonQuestDetail(answerID: Int) async throws -> [CommonQuestCommentEntity] + func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift index 809016bb..d79f3958 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestDetailUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol FetchCommonQuestDetailUseCase { - func execute(answerID: Int) async throws -> [CommonQuestCommentEntity] + func execute(answerID: Int) async throws -> CommonQuestDetailEntity } struct DefaultFetchCommonQuestDetailUseCase: FetchCommonQuestDetailUseCase { @@ -19,7 +19,7 @@ struct DefaultFetchCommonQuestDetailUseCase: FetchCommonQuestDetailUseCase { self.repository = repository } - func execute(answerID: Int) async throws -> [CommonQuestCommentEntity] { + func execute(answerID: Int) async throws -> CommonQuestDetailEntity { return try await repository.fetchCommonQuestDetail(answerID: 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 4d867869..65d7a632 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -27,16 +27,15 @@ final class CommonQuestHistoryViewController: BaseViewController { fatalError("init(coder:) has not been implemented") } - private var isMyAnswer: Bool = false + private var answerID: Int = 0 + private var writerID: Int = 0 + private var isMyAnswer: Bool = false private var nickname: String = "" private var profileIcon: UIImage? - var answerID: Int? - private var answer: String? - private var question: String? - private var writtenAt: String? - private var commonQuestArchiveType: CommonQuestArchiveType = .myAnswer - private var writerID: Int = 0 + private var answer: String = "" + private var question: String = "" + private var writtenAt: String = "" private var isLiked: Bool = false private var likeCount: Int = 0 private var commentCount: Int = 0 @@ -50,8 +49,7 @@ final class CommonQuestHistoryViewController: BaseViewController { self.tabBarController?.tabBar.isHidden = true addKeyboardObservers() - viewModel.action(.viewWillAppear(answerID: answerID ?? 0)) - bind() + viewModel.action(.viewWillAppear(answerID: answerID)) Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } @@ -67,6 +65,8 @@ final class CommonQuestHistoryViewController: BaseViewController { secondAction: #selector(bottomUp) ) + bind() + configureDataSource() // applySnapshot() } @@ -90,6 +90,15 @@ final class CommonQuestHistoryViewController: BaseViewController { } } +extension CommonQuestHistoryViewController { + func configure(writerID: Int? = nil, answerID: Int) { + if let writerID { + self.writerID = writerID + } + self.answerID = answerID + } +} + extension CommonQuestHistoryViewController: BackNavigable { func back() { @@ -210,52 +219,6 @@ extension CommonQuestHistoryViewController: CommentProtocol { } } -extension CommonQuestHistoryViewController { - - func configure( - question: String, - writtenAt: String, - profileIcon: UIImage, - nickname: String, - content: String, - answerID: Int? = nil, - writerID: Int? = nil, - isMyAnswer: Bool, - isLiked: Bool, - likeCount: Int, - commentCount: Int - ) { - self.nickname = nickname - self.profileIcon = profileIcon - self.answerID = answerID - self.answer = content - self.question = question - self.writtenAt = writtenAt - self.isMyAnswer = isMyAnswer - - commonQuestArchiveType = isMyAnswer ? .myAnswer : .otherAnswer - - if let writerID { - self.writerID = writerID - } - - self.isLiked = isLiked - self.likeCount = likeCount - self.commentCount = commentCount - - rootView.configure( - question: question, - writtenAt: writtenAt, - profileIcon: profileIcon, - nickname: nickname, - content: content, - isLiked: isLiked, - likeCount: likeCount, - commentCount: commentCount - ) - } -} - extension CommonQuestHistoryViewController: BlockReportProtocol { func completeBlockReport(type: CommonQuestArchiveType.Action) { ViewControllerUtils.changeQuestTabWithIndex(index: 1) { @@ -279,8 +242,9 @@ extension CommonQuestHistoryViewController { .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { - case .success(let commentList): - self?.applySnapshot(commentList: commentList) + case .success(let entity): + self?.bindData(entity: entity) + self?.applySnapshot(commentList: entity.comments) case .failure(let error): ByeBooLogger.debug(error) } @@ -288,25 +252,49 @@ extension CommonQuestHistoryViewController { .store(in: &cancellable) } + private func bindData(entity: CommonQuestDetailEntity) { + let answer = entity.answer + self.nickname = answer.writer + self.answer = answer.content + self.question = entity.question + self.isLiked = answer.isLiked + self.likeCount = answer.likeCount + self.commentCount = answer.commentCount + self.isMyAnswer = answer.isMyAnswer + + rootView.configure( + question: entity.question, + writtenAt: ServerDateFormatter.shared.relativeTimeString(from: answer.writtenAt) ?? "", //TODO: ViewModel로 수정 + profileIcon: ProfileIcon.image(for: answer.profileIcon) ?? .relievedBadge, + nickname: answer.writer, + content: answer.content, + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.commentCount + ) + } + private func commonBottomSheetUp(commentID: Int? = nil, isMyComment: Bool? = nil) { let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() - if isMyAnswer { - commonQuestBottomSheet.configureWhenEdit( - sheeetType: commonQuestArchiveType, - answerID: answerID, - answer: answer, - question: question, - writtenAt: writtenAt - ) - } - if let commentID, let isMyComment { + if let commentID, let isMyComment{ commonQuestBottomSheet.configure( sheeetType: isMyComment ? .myComment : .otherComment , targetID: writerID ) } else { - commonQuestBottomSheet.configure(sheeetType: commonQuestArchiveType, targetID: writerID) + let sheetType: CommonQuestArchiveType = isMyAnswer ? .myAnswer : .otherAnswer + commonQuestBottomSheet.configure(sheeetType: sheetType, targetID: writerID) + + if isMyAnswer { + commonQuestBottomSheet.configureWhenEdit( + sheeetType: sheetType, + answerID: answerID, + answer: answer, + question: question, + writtenAt: writtenAt + ) + } } setDelegate(bottomSheet: commonQuestBottomSheet) From 62a959eb51d95cc09d48d311238e048ef7d75d1e Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 17:02:25 +0900 Subject: [PATCH 088/231] =?UTF-8?q?fix:=20#444=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=9E=98=EB=AA=BB=20=EB=B0=94=EC=9D=B8=EB=94=A9?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=9E=88=EB=8D=98=20=EA=B2=83=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1ea3ffef..ea9ea4a4 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 @@ -98,7 +98,7 @@ extension CommonQuestAnswerCell { writtenAt: writtenAt, isLiked: answer.isLiked, likeCount: answer.likeCount, - commentCount: answer.likeCount, + commentCount: answer.commentCount, showAllText: false ) } From 32a8609762bfb5988d6dd8ae71b2c16733a1b68c Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 17:06:14 +0900 Subject: [PATCH 089/231] =?UTF-8?q?refactor:=20#444=20historyVC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20configure=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 9 ++------ .../CommonQuestMyAnswersViewController.swift | 12 +--------- .../CommonQuestViewController.swift | 23 ++----------------- ...WriteQuestionTypeQuestViewController.swift | 20 ++++++---------- .../CommonQuestHistoryViewModel.swift | 10 ++++---- 5 files changed, 17 insertions(+), 57 deletions(-) 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 65d7a632..77326b8f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -159,12 +159,7 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { questionTitle: question, answerID: answerID, answer: answer, - writtenAt: writtenAt, - nickname: nickname, - profileIcon: profileIcon ?? .relievedBadge, - isLiked: isLiked, - likeCount: likeCount, - commentCount: commentCount + writtenAt: writtenAt ) self.navigationController?.pushViewController(writeCommonQuestViewController, animated: false) } @@ -264,7 +259,7 @@ extension CommonQuestHistoryViewController { rootView.configure( question: entity.question, - writtenAt: ServerDateFormatter.shared.relativeTimeString(from: answer.writtenAt) ?? "", //TODO: ViewModel로 수정 + writtenAt: ServerDateFormatter.shared.relativeTimeString(from: answer.writtenAt) ?? "", //TODO: ViewModel로 수정 profileIcon: ProfileIcon.image(for: answer.profileIcon) ?? .relievedBadge, nickname: answer.writer, content: answer.content, 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 bd372b0c..acfa8a41 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -112,17 +112,7 @@ extension CommonQuestMyAnswersViewController: UITableViewDelegate { let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() historyViewController.navigationItem.hidesBackButton = true - historyViewController.configure( - question: answer.question, - writtenAt: answer.writtenAt, - profileIcon: .relievedBadge, - nickname: answer.nickname, - content: answer.content, - answerID: answer.answerID, - isLiked: answer.isLiked, - likeCount: answer.likeCount, - commentCount: answer.commentCount - ) + historyViewController.configure(answerID: answer.answerID) self.navigationController?.pushViewController(historyViewController, animated: false) } 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 303c2547..f1e1fe6c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -129,31 +129,12 @@ extension CommonQuestViewController: UITableViewDelegate { } let answerIndex = indexPath.row - 1 - guard let answer = viewModel.getAnswer(at: answerIndex) else { + guard let answer = viewModel.getCommonQuestID(at: answerIndex) else { return } - let formattedWrittenAt = DateFormatter.toDetailDate(from: answer.writtenAt).map { - DateFormatter.toDisplayDateString(from: $0) - } - - guard let formattedWrittenAt else { return } - guard let profileIcon = viewModel.getProfileIcon(at: answerIndex) else { return } - let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() - historyViewController.configure( - question: viewModel.question, - writtenAt: formattedWrittenAt, - profileIcon: profileIcon, - nickname: answer.writer, - content: answer.content, - answerID: answer.answerID, - writerID: answer.writerID, - isMyAnswer: answer.isMyAnswer, - isLiked: answer.isLiked, - likeCount: answer.likeCount, - commentCount: answer.commentCount - ) + historyViewController.configure(writerID: answer.writerID, answerID: answer.answerID) historyViewController.navigationItem.hidesBackButton = true self.navigationController?.pushViewController( historyViewController, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift index e027c8dd..31478da8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift @@ -214,13 +214,7 @@ extension WriteQuestionTypeQuestViewController: ToastPresentable, ToastErrorHand let previousVC = viewControllers[viewControllers.count - 2] if let historyVC = previousVC as? CommonQuestHistoryViewController { - historyVC.configure( - question: questTitle, - writtenAt: writtenAt, - profileIcon: .relievedBadge, // TODO: 서버 수정 후 고치기 - nickname: "냐냐냐", - content: self.rootView.questTextField.textView.text - ) + historyVC.configure(answerID: answerID) } } @@ -268,12 +262,12 @@ extension WriteQuestionTypeQuestViewController { } func configureToEdit( - _ questNumber: Int?, - _ questType: QuestType, - _ questionTitle: String?, - _ answerID: Int?, - _ answer: String, - _ writtenAt: String + questNumber: Int?, + questType: QuestType, + questionTitle: String?, + answerID: Int?, + answer: String, + writtenAt: String ) { guard let answerID else { return } 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 7dfd486a..9ee0e2cf 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -15,7 +15,7 @@ final class CommonQuestHistoryViewModel { private let fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase - private let fetchCommentListSubject: PassthroughSubject, Never> = .init() + private let fetchCommentListSubject: PassthroughSubject, Never> = .init() init( fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase @@ -34,7 +34,7 @@ extension CommonQuestHistoryViewModel: ViewModelType { } struct Output { - let fetchCommonQuestCommentsPublisher: AnyPublisher, Never> + let fetchCommonQuestCommentsPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -49,9 +49,9 @@ extension CommonQuestHistoryViewModel { private func fetchCommonQuestComments(answerID: Int) { Task { do { - let commentsList = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) - ByeBooLogger.debug("댓글 list \(commentsList)") - fetchCommentListSubject.send(.success(commentsList)) + let entity = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) + ByeBooLogger.debug("entity \(entity)") + fetchCommentListSubject.send(.success(entity)) } catch { guard let error = error as? ByeBooError else { return From a58e524718845616634a0efcca81325d198fbf25 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 18:09:35 +0900 Subject: [PATCH 090/231] =?UTF-8?q?chore:=20#444=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=ED=8F=B4=EB=8D=94=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/{ => Entity}/CommonQuestDetailEntity.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Domain/{ => Entity}/CommonQuestDetailEntity.swift (100%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/CommonQuestDetailEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestDetailEntity.swift similarity index 100% rename from ByeBoo-iOS/ByeBoo-iOS/Domain/CommonQuestDetailEntity.swift rename to ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestDetailEntity.swift From 4bf96be9c50818cb5c179698fd4b5a3ddb6ec591 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 18:13:56 +0900 Subject: [PATCH 091/231] =?UTF-8?q?refactor:=20#444=20answerID=EB=A7=8C=20?= =?UTF-8?q?configure=ED=95=98=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 --- .../CommonQuestHistoryViewController.swift | 6 ++--- .../CommonQuestViewController.swift | 4 ++-- .../ViewModel/CommonQuestViewModel.swift | 23 ++++++------------- 3 files changed, 11 insertions(+), 22 deletions(-) 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 77326b8f..ecb97062 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -91,10 +91,7 @@ final class CommonQuestHistoryViewController: BaseViewController { } extension CommonQuestHistoryViewController { - func configure(writerID: Int? = nil, answerID: Int) { - if let writerID { - self.writerID = writerID - } + func configure(answerID: Int) { self.answerID = answerID } } @@ -249,6 +246,7 @@ extension CommonQuestHistoryViewController { private func bindData(entity: CommonQuestDetailEntity) { let answer = entity.answer + self.writerID = answer.writerID self.nickname = answer.writer self.answer = answer.content self.question = entity.question 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 f1e1fe6c..48dbb117 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -129,12 +129,12 @@ extension CommonQuestViewController: UITableViewDelegate { } let answerIndex = indexPath.row - 1 - guard let answer = viewModel.getCommonQuestID(at: answerIndex) else { + guard let answerID = viewModel.getAnswerID(at: answerIndex) else { return } let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() - historyViewController.configure(writerID: answer.writerID, answerID: answer.answerID) + historyViewController.configure(answerID: answerID) historyViewController.navigationItem.hidesBackButton = true self.navigationController?.pushViewController( historyViewController, 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 8dcf1c04..ddcabe8e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -121,6 +121,13 @@ extension CommonQuestViewModel { return answers[index] } + func getAnswerID(at index: Int) -> Int? { + guard index >= 0 && index < answers.count else { + return nil + } + return answers[index].answerID + } + func getProfileIcon(at index: Int) -> UIImage? { guard index >= 0 && index < answers.count else { return nil } return ProfileIcon.image(for: answers[index].profileIcon) @@ -131,19 +138,3 @@ extension CommonQuestViewModel { return ServerDateFormatter.shared.relativeTimeString(from: answers[index].writtenAt) } } - -extension CommonQuestViewModel { - struct CommonQuestID { - let answerID: Int - let writerID: Int - } - - func getCommonQuestID(at index: Int) -> CommonQuestID? { - guard index >= 0 && index < answers.count else { - return nil - } - let answer = answers[index] - return CommonQuestID(answerID: answer.answerID, writerID: answer.writerID) - } - -} From 24ae6cbd1d0c59dbeeacce33c41ed9a4df899062 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 18:22:37 +0900 Subject: [PATCH 092/231] =?UTF-8?q?refactor:=20#444=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/CommonQuestHistoryViewController.swift | 2 +- .../Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 ecb97062..6342bbf0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -230,7 +230,7 @@ extension CommonQuestHistoryViewController { } private func bind() { - viewModel.output.fetchCommonQuestCommentsPublisher + viewModel.output.fetchCommonQuestDetailPublisher .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { 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 9ee0e2cf..9849cdf3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -23,7 +23,7 @@ final class CommonQuestHistoryViewModel { self.fetchCommonQuestCommentsUseCase = fetchCommonQuestCommentsUseCase output = Output( - fetchCommonQuestCommentsPublisher: fetchCommentListSubject.eraseToAnyPublisher() + fetchCommonQuestDetailPublisher: fetchCommentListSubject.eraseToAnyPublisher() ) } } @@ -34,7 +34,7 @@ extension CommonQuestHistoryViewModel: ViewModelType { } struct Output { - let fetchCommonQuestCommentsPublisher: AnyPublisher, Never> + let fetchCommonQuestDetailPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { From 1b2aebdb94e9c2ab5bc898de080db755ffffbda5 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 9 Jun 2026 18:34:52 +0900 Subject: [PATCH 093/231] =?UTF-8?q?refactor:=20#444=20=EB=B0=94=ED=85=80?= =?UTF-8?q?=EC=8B=9C=ED=8A=B8=20=EB=9D=84=EC=9A=B0=EA=B8=B0=20extension=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extension/UIViewController+.swift | 20 +++++++++++++++++++ .../CommonQuestHistoryViewController.swift | 9 +-------- .../CommonQuestReplyViewController.swift | 8 +------- .../WriteActiveTypeQuestViewController.swift | 8 +------- ...WriteQuestionTypeQuestViewController.swift | 8 +------- 5 files changed, 24 insertions(+), 29 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UIViewController+.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UIViewController+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UIViewController+.swift new file mode 100644 index 00000000..211947f1 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UIViewController+.swift @@ -0,0 +1,20 @@ +// +// UIViewController+.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/9/26. +// + +import UIKit + +extension UIViewController { + func presentBottomSheet(_ viewController: UIViewController, height: CGFloat) { + if let sheet = viewController.sheetPresentationController { + sheet.detents = [.custom { _ in height }] + sheet.prefersGrabberVisible = true + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + sheet.preferredCornerRadius = 8 + } + self.present(viewController, animated: true) + } +} 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 6342bbf0..fdad1083 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -291,14 +291,7 @@ extension CommonQuestHistoryViewController { } setDelegate(bottomSheet: commonQuestBottomSheet) - - if let sheet = commonQuestBottomSheet.sheetPresentationController{ - sheet.detents = [.custom { _ in 224.adjustedH }] - sheet.prefersGrabberVisible = true - sheet.prefersScrollingExpandsWhenScrolledToEdge = false - sheet.preferredCornerRadius = 8 - } - self.present(commonQuestBottomSheet, animated: true) + commonQuestBottomSheet.presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 3305826e..d7a1b231 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -104,13 +104,7 @@ extension CommonQuestReplyViewController: CommentProtocol { targetID: commentID ) - if let sheet = commonQuestBottomSheet.sheetPresentationController{ - sheet.detents = [.custom { _ in 224.adjustedH }] - sheet.prefersGrabberVisible = true - sheet.prefersScrollingExpandsWhenScrolledToEdge = false - sheet.preferredCornerRadius = 8 - } - self.present(commonQuestBottomSheet, animated: true) + commonQuestBottomSheet.presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) } func moreLabelDidTap(commentID: Int) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift index b20bf368..e147c59f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift @@ -92,13 +92,7 @@ final class WriteActiveTypeQuestViewController: WriteQuestBaseViewController Date: Tue, 9 Jun 2026 18:38:11 +0900 Subject: [PATCH 094/231] =?UTF-8?q?fix:=20#444=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestHistoryViewController.swift | 2 +- .../Quest/ViewController/CommonQuestReplyViewController.swift | 2 +- .../ViewController/WriteActiveTypeQuestViewController.swift | 2 +- .../ViewController/WriteQuestionTypeQuestViewController.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 fdad1083..e7f544a4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -291,7 +291,7 @@ extension CommonQuestHistoryViewController { } setDelegate(bottomSheet: commonQuestBottomSheet) - commonQuestBottomSheet.presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) + presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index d7a1b231..e7436453 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -104,7 +104,7 @@ extension CommonQuestReplyViewController: CommentProtocol { targetID: commentID ) - commonQuestBottomSheet.presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) + presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) } func moreLabelDidTap(commentID: Int) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift index e147c59f..e87a6f90 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift @@ -92,7 +92,7 @@ final class WriteActiveTypeQuestViewController: WriteQuestBaseViewController Date: Wed, 10 Jun 2026 01:44:50 +0900 Subject: [PATCH 095/231] =?UTF-8?q?refactor:=20#444=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=9E=98=EB=B9=97=20=EB=A6=AC=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 --- .../ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift | 4 ++-- .../ViewController/CommonQuestHistoryViewController.swift | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift index b236e120..e8bba503 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift @@ -39,8 +39,8 @@ enum CommonQuestArchiveType { ] case .otherComment: return [ - Item(title: "차단하기", icon: .edit, color: .white, action: .edit), - Item(title: "신고하기", icon: .trash, color: .error300, action: .delete) + Item(title: "차단하기", icon: .block, color: .white, action: .edit), + Item(title: "신고하기", icon: .report, color: .error300, action: .delete) ] } } 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 e7f544a4..7e649bef 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -254,6 +254,7 @@ extension CommonQuestHistoryViewController { self.likeCount = answer.likeCount self.commentCount = answer.commentCount self.isMyAnswer = answer.isMyAnswer + self.writtenAt = answer.writtenAt rootView.configure( question: entity.question, @@ -273,7 +274,7 @@ extension CommonQuestHistoryViewController { if let commentID, let isMyComment{ commonQuestBottomSheet.configure( sheeetType: isMyComment ? .myComment : .otherComment , - targetID: writerID + targetID: commentID ) } else { let sheetType: CommonQuestArchiveType = isMyAnswer ? .myAnswer : .otherAnswer From 1c1c7f73e6252bba5276f0a449d39f8078f03197 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 01:53:45 +0900 Subject: [PATCH 096/231] =?UTF-8?q?refactor:=20#444=20userName=20->=20user?= =?UTF-8?q?ID=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/CommonQuestAnswerDetailResponseDTO.swift | 14 +++++++------- .../Data/Model/CommonQuestAnswersResponseDTO.swift | 8 ++++---- .../Model/CommonQuestMyAnswersResponseDTO.swift | 9 ++++----- .../Data/Repository/CommonQuestRepository.swift | 8 ++++---- .../Domain/Entity/CommonQuestMyAnswersEntity.swift | 10 ++++------ .../ViewModel/CommonQuestMyAnswerViewModel.swift | 1 - 6 files changed, 23 insertions(+), 27 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift index cd86b562..b4ab2523 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift @@ -35,19 +35,19 @@ struct CommonQuestCommentResponseDTO: Decodable { } extension CommonQuestAnswerDetailResponseDTO { - func toEntity(userName: String) -> CommonQuestDetailEntity { + func toEntity(userID: Int) -> CommonQuestDetailEntity { .init( question: question, - answer: answer.toEntity(userName: userName), - comments: comments.map { $0.toEntity(userName: userName) } + answer: answer.toEntity(userID: userID), + comments: comments.map { $0.toEntity(userID: userID) } ) } } extension CommonQuestAnswerSimpleResponseDTO { - func toEntity(userName: String) -> CommonQuestAnswerDetailEntity { + func toEntity(userID: Int) -> CommonQuestAnswerDetailEntity { .init( - isMyAnswer: userName == writer ? true : false, + isMyAnswer: userID == writerId ? true : false, content: content, writerID: writerId, writer: writer, @@ -61,9 +61,9 @@ extension CommonQuestAnswerSimpleResponseDTO { } extension CommonQuestCommentResponseDTO { - func toEntity(userName: String) -> CommonQuestCommentEntity { + func toEntity(userID: Int) -> CommonQuestCommentEntity { return .init( - isMyComment: userName == writer ? true : false, + isMyComment: userID == writerId ? true : false, commentID: commentId, replyCount: replyCount, writerID: writerId, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift index 2c69fcf1..2f0e05dd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift @@ -28,7 +28,7 @@ struct CommonQuestAnswerResponseDTO: Decodable { } extension CommonQuestAnswersResponseDTO { - func toEntity(userName: String) -> CommonQuestAnswersEntity { + func toEntity(userID: Int) -> CommonQuestAnswersEntity { .init( question: question, questID: questId, @@ -36,15 +36,15 @@ extension CommonQuestAnswersResponseDTO { isAnswered: isAnswered, hasNext: hasNext, nextCursor: nil, - answers: answers.map { $0.toEntity(userName: userName) } + answers: answers.map { $0.toEntity(userID: userID) } ) } } extension CommonQuestAnswerResponseDTO { - func toEntity(userName: String) -> CommonQuestAnswerEntity { + func toEntity(userID: Int) -> CommonQuestAnswerEntity { .init( - isMyAnswer: userName == writer ? true : false, + isMyAnswer: userID == writerId ? true : false, answerID: answerId, writerID: writerId, writer: writer, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift index 0389e4a7..7b5a7968 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift @@ -26,20 +26,19 @@ extension CommonQuestMyAnswersResponseDTO { .init( hasNext: hasNext, nextCursor: nextCursor, - answers: answers.map { $0.toEntity(userName: userName) - }) + answers: answers.map { $0.toEntity()} + ) } } extension CommonQuestMyAnswerResponseDTO { - func toEntity(userName: String) -> CommonQuestMyAnswerEntity { + func toEntity() -> CommonQuestMyAnswerEntity { .init( answerID: answerId, writtenAt: writtenAt, content: content, question: question, - nickname: userName, - isLiked: false, + isLiked: isLiked, likeCount: likeCount, commentCount: commentCount ) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index 8c0930bf..9057aacd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -38,7 +38,7 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { date: String, cursor: Int? ) async throws -> CommonQuestAnswersEntity { - let userName: String = userDefaultsService.load(key: .userName) ?? "" + let userID: Int = userDefaultsService.load(key: .userID) ?? 0 let commonQuest = try await network.request( CommonQuestAPI.fetchCommonQuest( date: date, @@ -46,7 +46,7 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { ), decodingType: CommonQuestAnswersResponseDTO.self ) - return commonQuest.toEntity(userName: userName) + return commonQuest.toEntity(userID: userID) } func updateCommonQuest(answerID: Int, answer: String) async throws { @@ -66,12 +66,12 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { } func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity { - let userName: String = userDefaultsService.load(key: .userName) ?? "" + let userID: Int = userDefaultsService.load(key: .userID) ?? 0 let commonQuestDetail = try await network.request( CommonQuestAPI.fetchCommonQuestDetail(asnwerID: answerID), decodingType: CommonQuestAnswerDetailResponseDTO.self ) - return commonQuestDetail.toEntity(userName: userName) + return commonQuestDetail.toEntity(userID: userID) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift index e083f1ab..bd4d0b9f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestMyAnswersEntity.swift @@ -18,7 +18,6 @@ struct CommonQuestMyAnswerEntity { let writtenAt: String let content: String let question: String - let nickname: String let isLiked: Bool let likeCount: Int let commentCount: Int @@ -32,7 +31,6 @@ extension CommonQuestMyAnswersEntity { writtenAt: Date.now.toString(), content: "\($0)번째 테스트 답변", question: "\($0)번째 질문", - nickname: "냔", isLiked: false, likeCount: 2, commentCount: 3 @@ -57,19 +55,19 @@ extension CommonQuestMyAnswerEntity { [ .init( answerID: 1, writtenAt: "2025-01-11", content: "content", - question: "question", nickname: "가을", isLiked: false, likeCount: 1, commentCount: 2 + question: "question", isLiked: false, likeCount: 1, commentCount: 2 ), .init( answerID: 2, writtenAt: "2025-01-12", content: "content", - question: "question", nickname: "원영", isLiked: false, likeCount: 1, commentCount: 2 + question: "question", isLiked: false, likeCount: 1, commentCount: 2 ), .init( answerID: 3, writtenAt: "2025-01-13", content: "content", - question: "question", nickname: "리즈", isLiked: false, likeCount: 1, commentCount: 2 + question: "question", isLiked: false, likeCount: 1, commentCount: 2 ), .init( answerID: 4, writtenAt: "2025-01-14", content: "content", - question: "question", nickname: "유진", isLiked: false, likeCount: 1, commentCount: 2 + question: "question", isLiked: false, likeCount: 1, commentCount: 2 ) ] } 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 d2ef0037..13b7f1e9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -104,7 +104,6 @@ extension CommonQuestMyAnswerViewModel { writtenAt: displayDate, content: answer.content, question: answer.question, - nickname: answer.nickname, isLiked: answer.isLiked, likeCount: answer.likeCount, commentCount: answer.commentCount From 28550aa61e054b90b62f86d4b205d537a7d02c7d Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 01:56:48 +0900 Subject: [PATCH 097/231] =?UTF-8?q?refactor:=20#444=20username=20->=20user?= =?UTF-8?q?ID=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Model/CommonQuestMyAnswersResponseDTO.swift | 4 ++-- ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift index 7b5a7968..58ee68db 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestMyAnswersResponseDTO.swift @@ -22,11 +22,11 @@ struct CommonQuestMyAnswerResponseDTO: Decodable { } extension CommonQuestMyAnswersResponseDTO { - func toEntity(userName: String) -> CommonQuestMyAnswersEntity { + func toEntity(userID: Int) -> CommonQuestMyAnswersEntity { .init( hasNext: hasNext, nextCursor: nextCursor, - answers: answers.map { $0.toEntity()} + answers: answers.map { $0.toEntity() } ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift index 2efd4249..e7db2260 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/UsersRepository.swift @@ -144,12 +144,12 @@ struct DefaultUsersRepository: UsersInterface { } func fetchMyCommonQuestAnswers(cursor: Int?) async throws -> CommonQuestMyAnswersEntity { - let userName: String = userDefaultsService.load(key: .userName) ?? "" + let userID: Int = userDefaultsService.load(key: .userID) ?? 0 let result = try await network.request( UsersAPI.fetchCommonQuestAnswers(cursor: cursor), decodingType: CommonQuestMyAnswersResponseDTO.self ) - return result.toEntity(userName: userName) + return result.toEntity(userID: userID) } var alarmEnabled: Bool { From e903aabbe31dd58a77ca38fa07bd70cc61bf641d Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 01:58:14 +0900 Subject: [PATCH 098/231] =?UTF-8?q?fix:=20#444=20=EC=9E=98=EB=AA=BB=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=ED=95=9C=20=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift index e8bba503..06b462a6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift @@ -39,8 +39,8 @@ enum CommonQuestArchiveType { ] case .otherComment: return [ - Item(title: "차단하기", icon: .block, color: .white, action: .edit), - Item(title: "신고하기", icon: .report, color: .error300, action: .delete) + Item(title: "차단하기", icon: .block, color: .white, action: .block), + Item(title: "신고하기", icon: .report, color: .error300, action: .report) ] } } From 0d2b301aa09372f25300d4758d208354a90dbb82 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 16:02:56 +0900 Subject: [PATCH 099/231] =?UTF-8?q?refactor:=20#444=20viewmodel=EC=97=90?= =?UTF-8?q?=EC=84=9C=20entity=EB=A5=BC=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=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 --- .../CommonQuestHistoryViewController.swift | 32 +++++-------------- .../CommonQuestHistoryViewModel.swift | 30 +++++++++++------ 2 files changed, 29 insertions(+), 33 deletions(-) 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 7e649bef..8ff99038 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -30,16 +30,6 @@ final class CommonQuestHistoryViewController: BaseViewController { private var answerID: Int = 0 private var writerID: Int = 0 - private var isMyAnswer: Bool = false - private var nickname: String = "" - private var profileIcon: UIImage? - private var answer: String = "" - private var question: String = "" - private var writtenAt: String = "" - private var isLiked: Bool = false - private var likeCount: Int = 0 - private var commentCount: Int = 0 - override func loadView() { view = rootView } @@ -68,7 +58,6 @@ final class CommonQuestHistoryViewController: BaseViewController { bind() configureDataSource() -// applySnapshot() } override func viewWillDisappear(_ animated: Bool) { @@ -247,14 +236,6 @@ extension CommonQuestHistoryViewController { private func bindData(entity: CommonQuestDetailEntity) { let answer = entity.answer self.writerID = answer.writerID - self.nickname = answer.writer - self.answer = answer.content - self.question = entity.question - self.isLiked = answer.isLiked - self.likeCount = answer.likeCount - self.commentCount = answer.commentCount - self.isMyAnswer = answer.isMyAnswer - self.writtenAt = answer.writtenAt rootView.configure( question: entity.question, @@ -277,16 +258,19 @@ extension CommonQuestHistoryViewController { targetID: commentID ) } else { - let sheetType: CommonQuestArchiveType = isMyAnswer ? .myAnswer : .otherAnswer + let entity = viewModel.detailEntity + guard let entity else { return } + + let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer commonQuestBottomSheet.configure(sheeetType: sheetType, targetID: writerID) - if isMyAnswer { + if entity.isMyAnswer { commonQuestBottomSheet.configureWhenEdit( sheeetType: sheetType, answerID: answerID, - answer: answer, - question: question, - writtenAt: writtenAt + answer: entity.content, + question: viewModel.question, + writtenAt: entity.writtenAt ) } } 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 9849cdf3..9c0d1fe6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -9,14 +9,14 @@ import Combine import Foundation final class CommonQuestHistoryViewModel { - private var cancellables = Set() - - private(set) var output: Output - private let fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase private let fetchCommentListSubject: PassthroughSubject, Never> = .init() + private var entity: CommonQuestDetailEntity? = nil + private var cancellables = Set() + private(set) var output: Output + init( fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase ) { @@ -45,17 +45,29 @@ extension CommonQuestHistoryViewModel: ViewModelType { } } +extension CommonQuestHistoryViewModel { + var question: String { + return entity?.question ?? "" + } + + var detailEntity: CommonQuestAnswerDetailEntity? { + return entity?.answer + } + + var commentList: [CommonQuestCommentEntity]? { + return entity?.comments + } +} + extension CommonQuestHistoryViewModel { private func fetchCommonQuestComments(answerID: Int) { Task { do { - let entity = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) + entity = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) + guard let entity else { return } ByeBooLogger.debug("entity \(entity)") fetchCommentListSubject.send(.success(entity)) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { fetchCommentListSubject.send(.failure(error)) } } From 33d19a813f0b23b300463f2bac53fb79edd83b57 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 16:03:04 +0900 Subject: [PATCH 100/231] =?UTF-8?q?refactor:=20#444=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EA=B5=AC=EB=AC=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/Service/NetworkInterceptor.swift | 2 +- .../CommonQuestBottomSheetViewModel.swift | 20 +++++---------- .../Home/ViewModel/CardJourneyViewModel.swift | 5 +--- .../Home/ViewModel/HomeViewModel.swift | 16 +++--------- .../ViewModel/InformationViewModel.swift | 4 +-- .../Login/ViewModel/LoginViewModel.swift | 7 ++---- .../Login/ViewModel/SplashViewModel.swift | 7 ++---- .../ViewModel/BlockedUserListViewModel.swift | 14 +++-------- .../ViewModel/LookBackJourneyViewModel.swift | 7 ++---- .../MyPage/ViewModel/MyPageViewModel.swift | 25 ++++++------------- .../ViewModel/NewJourneyViewModel.swift | 7 ++---- .../ViewModel/CompletedQuestsViewModel.swift | 8 ++---- .../ViewModel/ArchiveQuestViewModel.swift | 5 +--- .../CommonQuestMyAnswerViewModel.swift | 4 +-- .../ViewModel/CommonQuestViewModel.swift | 4 +-- .../ProgressingQuestsViewModel.swift | 12 +++------ .../Quest/ViewModel/QuestStartViewModel.swift | 9 +++---- .../Quest/ViewModel/QuestTipViewModel.swift | 5 +--- .../ViewModel/WriteQuestTypeViewModel.swift | 25 ++++--------------- 19 files changed, 54 insertions(+), 132 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift index 4f75ab3a..86c3de0a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/Service/NetworkInterceptor.swift @@ -57,7 +57,7 @@ final class NetworkInterceptor: RequestInterceptor { try await self.tokenService.reissue() ByeBooLogger.debug("401 Error -> 토큰 재발급 성공") completion(.retry) - } catch { + } catch(let error as ByeBooError) { completion(.doNotRetryWithError(error)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift index 9083ca6a..9bcaaa2e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift @@ -69,8 +69,8 @@ extension CommonQuestBottomSheetViewModel { do { try await blockUserUseCase.execute(userID: userID) blockUserSubject.send(.success(())) - } catch { - sendError(error: error) + } catch(let error as ByeBooError) { + blockUserSubject.send(.failure(error)) } } } @@ -80,8 +80,8 @@ extension CommonQuestBottomSheetViewModel { do { try await reportCommonQuestUseCase.execute(answerID: answerID) reportQuestSubject.send(.success(())) - } catch { - sendError(error: error) + } catch(let error as ByeBooError) { + reportQuestSubject.send(.failure(error)) } } } @@ -91,17 +91,9 @@ extension CommonQuestBottomSheetViewModel { do { try await deleteCommonQuestUseCase.execute(answerID: answerID) deleteQuestSubject.send(.success(())) - } catch { - sendError(error: error) + } catch(let error as ByeBooError){ + deleteQuestSubject.send(.failure(error)) } } } - - private func sendError(error: Error) { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) - reportQuestSubject.send(.failure(error)) - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift index 0ae58e94..09a60df1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/CardJourneyViewModel.swift @@ -60,10 +60,7 @@ extension CardJourneyViewModel { let journey = try await fetchUserJourneyUseCase.execute() journeyResultSubject.send(.success(journey)) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { journeyResultSubject.send(.failure(error)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index 18458a7e..a0539fd5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -90,12 +90,8 @@ extension HomeViewModel { do { let dialogues = try await fetchCharacterDialogueUseCase.execute() characterResultSubject.send(.success(dialogues)) - } catch { - characterResultSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) - ) + } catch(let error as ByeBooError) { + characterResultSubject.send(.failure(error)) } } } @@ -121,12 +117,8 @@ extension HomeViewModel { do { let journey = try await fetchUserJourneyUseCase.execute() journeyResultSubject.send(.success(journey)) - } catch { - journeyResultSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) - ) + } catch(let error as ByeBooError) { + journeyResultSubject.send(.failure(error)) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift index 00f5a582..fa4b7c0b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Information/ViewModel/InformationViewModel.swift @@ -58,8 +58,8 @@ final class InformationViewModel { questStyle: questStyle.key ) getUserName() - } catch { - userInformationSubject.send(.failure(error as! ByeBooError)) + } catch(let error as ByeBooError) { + userInformationSubject.send(.failure(error)) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift index 17329ca4..1c2b74c3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift @@ -66,11 +66,8 @@ extension LoginViewModel { socialLoginAuthSubject.send(.success(())) getIsRegistered() getUserID() - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) socialLoginAuthSubject.send(.failure(error)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift index bbfc1993..4e880c44 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift @@ -60,13 +60,10 @@ extension SplashViewModel { ByeBooLogger.debug("자동로그인 실패") autoLoginSubject.send(.failure((.noData))) } - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { autoLoginSubject.send(.failure((.noData))) ByeBooLogger.debug(ByeBooError.networkConnect) - ByeBooLogger.error(error as ByeBooError) + ByeBooLogger.error(error) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift index 08c369fb..037ca3fb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/BlockedUserListViewModel.swift @@ -70,11 +70,8 @@ extension BlockedUserListViewModel { self.blockedUsers = try await getBlockedUsersListUseCase.execute() ByeBooLogger.debug("차단한 유저 \(blockedUsers)") getBlockedUsersListSubject.send(.success(blockedUsers)) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) getBlockedUsersListSubject.send(.failure(error)) } } @@ -87,11 +84,8 @@ extension BlockedUserListViewModel { try await deleteBlockedUserUseCase.execute(blockID: blockID) ByeBooLogger.debug("차단 완료 \(blockID)") deleteBlockUserSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) deleteBlockUserSubject.send(.failure(error)) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/LookBackJourneyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/LookBackJourneyViewModel.swift index ea43cc4b..438e65ae 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/LookBackJourneyViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/LookBackJourneyViewModel.swift @@ -51,11 +51,8 @@ extension LookBackJourneyViewModel { do { let journeys = try await useCase.execute() lookBackJourneySubject.send(.success(journeys)) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) lookBackJourneySubject.send(.failure(error)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/MyPageViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/MyPageViewModel.swift index c76e2098..ccca3b78 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/MyPageViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/MyPageViewModel.swift @@ -99,13 +99,10 @@ extension MyPageViewModel { private func logout() { Task { do { - try await logoutUseCase.execute() + let _ = try await logoutUseCase.execute() logoutResultSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) logoutResultSubject.send(.failure(error)) } } @@ -114,13 +111,10 @@ extension MyPageViewModel { private func withdraw() { Task { do { - try await withdrawUseCase.execute() + let _ = try await withdrawUseCase.execute() withdrawResultSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) withdrawResultSubject.send(.failure(error)) } } @@ -131,11 +125,8 @@ extension MyPageViewModel { do { let result = try await changeNotificationPermissionUseCase.execute() notificationResultSubject.send(.success(result)) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) notificationResultSubject.send(.failure(error)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/NewJourneyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/NewJourneyViewModel.swift index 221f883b..2b5e7b73 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/NewJourneyViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/MyPage/ViewModel/NewJourneyViewModel.swift @@ -52,11 +52,8 @@ extension NewJourneyViewModel { do { let journeys = try await getNewJourneyUseCase.execute() newJourneySubject.send(.success(journeys)) - } catch { - guard let error = error as? ByeBooError else { - return - } - ByeBooLogger.error(error as ByeBooError) + } catch(let error as ByeBooError) { + ByeBooLogger.error(error) newJourneySubject.send(.failure(error)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift index 7749f2ef..ab7441d2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Offboarding/ViewModel/CompletedQuestsViewModel.swift @@ -47,12 +47,8 @@ final class CompletedQuestsViewModel { ) self.questsEntity = completedQuestsEntity questsSubject.send(.success(completedQuestsEntity)) - } catch { - questsSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) - ) + } catch(let error as ByeBooError) { + questsSubject.send(.failure(error)) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ArchiveQuestViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ArchiveQuestViewModel.swift index f3765e2a..c841366c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ArchiveQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ArchiveQuestViewModel.swift @@ -69,10 +69,7 @@ extension ArchiveQuestViewModel { guard let entity = self.entity else { return } resultSubject.send(.success(entity)) loadingSubject.send(false) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { resultSubject.send(.failure(error)) loadingSubject.send(false) } 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 13b7f1e9..a41dafa8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -53,8 +53,8 @@ final class CommonQuestMyAnswerViewModel { answers.append(contentsOf: result.answers) answersSubject.send(.success(())) - } catch { - answersSubject.send(.failure(error as! ByeBooError)) + } catch(let error as ByeBooError) { + answersSubject.send(.failure(error)) } } } 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 ddcabe8e..1c84b4e6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -52,8 +52,8 @@ final class CommonQuestViewModel { } commonQuestSubject.send(.success(())) - } catch { - commonQuestSubject.send(.failure(error as! ByeBooError)) + } catch(let error as ByeBooError) { + commonQuestSubject.send(.failure(error)) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift index 60ea152a..983ee264 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift @@ -58,12 +58,8 @@ final class ProgressingQuestsViewModel { let journeyEntity = try await fetchUserJourneyUseCase.execute() journeySubject.send(.success(journeyEntity)) loadingSubject.send(false) - } catch { - journeySubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) - ) + } catch(let error as ByeBooError) { + journeySubject.send(.failure(error)) loadingSubject.send(false) } } @@ -77,8 +73,8 @@ final class ProgressingQuestsViewModel { self.setQuestTimer() questsSubject.send(.success(questsEntity)) loadingSubject.send(false) - } catch { - questsSubject.send(.failure(error as! ByeBooError)) + } catch(let error as ByeBooError) { + questsSubject.send(.failure(error)) loadingSubject.send(false) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift index 8ec6d920..ac62c7e8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestStartViewModel.swift @@ -77,8 +77,8 @@ extension QuestStartViewModel { do { try await startJourneyUseCase.execute() startResultSubject.send(.success(true)) - } catch { - startResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) + } catch(let error as ByeBooError){ + startResultSubject.send(.failure(error)) } } } @@ -88,10 +88,7 @@ extension QuestStartViewModel { do { let _ = try await postJourneyUseCase.execute(journey: JourneyType.titleToEnum(journey) ?? .recording) startResultSubject.send(.success(true)) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { ByeBooLogger.error(error as ByeBooError) startResultSubject.send(.failure((error))) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift index 9a205790..f2883af2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/QuestTipViewModel.swift @@ -51,10 +51,7 @@ extension QuestTipViewModel { do { let tips = try await useCase.fetchQuestTips(questID: questID) questTipSubject.send((.success(tips))) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { ByeBooLogger.error(error as ByeBooError) questTipSubject.send(.failure(error)) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift index b69bbec4..32300d20 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift @@ -131,10 +131,7 @@ extension WriteQuestionTypeViewModel { do { let questInfo = try await getQuestInfoUseCase.execute(questID: questID) questInfoResultSubject.send(.success(questInfo)) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { questInfoResultSubject.send(.failure(error)) } } @@ -145,10 +142,7 @@ extension WriteQuestionTypeViewModel { do { try await saveQuestTypeUseCase.execute(questID: questID, answer: answer, emotionState: emotionState) didSuccessPostSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { didSuccessPostSubject.send(.failure(error as ByeBooError)) ByeBooLogger.error(error) } @@ -160,10 +154,7 @@ extension WriteQuestionTypeViewModel { do { try await editQuestTypeUseCase.execute(questID: questID, answer: answer) didSuccessEditSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { didSuccessEditSubject.send(.failure(error as ByeBooError)) ByeBooLogger.error(error) } @@ -175,10 +166,7 @@ extension WriteQuestionTypeViewModel { do { try await saveCommonQuestUseCase.execute(questID: questID, answer: answer) didSuccessPostSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { didSuccessPostSubject.send(.failure(error as ByeBooError)) ByeBooLogger.error(error) } @@ -200,10 +188,7 @@ extension WriteQuestionTypeViewModel { do { try await updateCommonQuestUseCase.execute(answerID: answerID, answer: answer) didSucessUpdateSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } + } catch(let error as ByeBooError) { didSuccessPostSubject.send(.failure(error as ByeBooError)) ByeBooLogger.error(error) } From a86838ebfbe33e233bf49b4bedb23261182e12e0 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:25:26 +0900 Subject: [PATCH 101/231] =?UTF-8?q?feat:=20#450=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/EndPoint/NotificationAPI.swift | 13 +++- .../Repository/NotificationRepository.swift | 4 ++ .../Domain/DomainDependencyAssembler.swift | 4 ++ .../Interface/NotificationInterface.swift | 1 + .../UseCase/ReadNotificationUseCase.swift | 23 +++++++ .../ViewModel/NotificationsViewModel.swift | 66 +++++++++++++++++-- .../PresentationDependencyAssembler.swift | 8 ++- 7 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadNotificationUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift index 34a270f3..2042230b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -11,6 +11,7 @@ import Alamofire enum NotificationAPI { case fetchNotificationList + case readNotification(notificationID: Int) } extension NotificationAPI: EndPoint { @@ -23,6 +24,8 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList: "" + case .readNotification(let notificationID): + "/\(notificationID)/read" } } @@ -30,12 +33,14 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList: .get + case .readNotification: + .patch } } var headers: HeaderType { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .readNotification: .withAuth } } @@ -44,19 +49,21 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList: JSONEncoding.default + case .readNotification: + URLEncoding.default } } var queryParameters: [String : String]? { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .readNotification: nil } } var bodyParameters: Parameters? { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .readNotification: nil } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift index c93aefa3..7b18b797 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -20,4 +20,8 @@ struct DefaultNotificationRepository: NotificationInterface { ) return result.toEntity() } + + func readNotification(for notificationID: Int) async throws { + try await networkService.request(NotificationAPI.readNotification(notificationID: notificationID)) + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index acb630a6..8b8b2b48 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -216,5 +216,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FormatElapsedTimeUseCase.self) { _ in return DefaultFormatElapsedTimeUseCase() } + + DIContainer.shared.register(type: ReadNotificationUseCase.self) { _ in + return DefaultReadNotificationUseCase(repository: notificationRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift index 05b07428..0302b28f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift @@ -7,4 +7,5 @@ protocol NotificationInterface { func fetchNotifications() async throws -> NotificationListEntity + func readNotification(for notificationID: Int) async throws } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadNotificationUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadNotificationUseCase.swift new file mode 100644 index 00000000..f09c58bc --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadNotificationUseCase.swift @@ -0,0 +1,23 @@ +// +// ReadNotificationUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/8/26. +// + +protocol ReadNotificationUseCase { + func execute(for notificationID: Int) async throws +} + +struct DefaultReadNotificationUseCase: ReadNotificationUseCase { + + private let repository: NotificationInterface + + init(repository: NotificationInterface) { + self.repository = repository + } + + func execute(for notificationID: Int) async throws { + try await repository.readNotification(for: notificationID) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 6c3d8997..e71cfd42 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -5,19 +5,75 @@ // Created by 더스틴 on 6/7/26. // -final class NotificationsViewModel { +import Combine + +final class NotificationsViewModel: ViewModelType { private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase + private let readNotificationUseCase: ReadNotificationUseCase + + private var cancellables: Set = .init() + private var formatElapsedTimeSubject: PassthroughSubject, Never> = .init() + private var readNotificationSubject: PassthroughSubject, Never> = .init() + private(set) var output: Output - init(formatElapsedTimeUseCase: FormatElapsedTimeUseCase) { + init( + formatElapsedTimeUseCase: FormatElapsedTimeUseCase, + readNotificationUseCase: ReadNotificationUseCase + ) { self.formatElapsedTimeUseCase = formatElapsedTimeUseCase + self.readNotificationUseCase = readNotificationUseCase + self.output = Output( + formattedTimeResult: formatElapsedTimeSubject.eraseToAnyPublisher(), + readNotificationResult: readNotificationSubject.eraseToAnyPublisher() + ) + } + + func action(_ trigger: Input) { + switch trigger { + case .dequeueCell(let rawTime): + formatElapsedTime(from: rawTime) + case .notificationDidTap(let notificationID): + readNotification(for: notificationID) + } + } +} + +extension NotificationsViewModel { + + enum Input { + case dequeueCell(rawTime: String) + case notificationDidTap(notificationID: Int) + } + + struct Output { + let formattedTimeResult: AnyPublisher, Never> + let readNotificationResult: AnyPublisher, Never> } +} + +extension NotificationsViewModel { - func formatElapsedTime(from timeString: String) -> String? { + func formatElapsedTime(from timeString: String) { guard let formattedTime = formatElapsedTimeUseCase.execute(from: timeString) else { - return nil + formatElapsedTimeSubject.send(.failure(.dateFormatError)) + return } - return formattedTime + formatElapsedTimeSubject.send(.success(formattedTime)) + } + + func readNotification(for notificationID: Int) { + Task { + do { + try await readNotificationUseCase.execute(for: notificationID) + readNotificationSubject.send(.success(())) + } catch { + guard let error = error as? ByeBooError else { + return + } + readNotificationSubject.send(.failure(error)) + } + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 7b6d5b43..15409b26 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -356,12 +356,16 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { + guard let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self), + let readNotificationUseCase = container.resolve(type: ReadNotificationUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } - return NotificationsViewModel(formatElapsedTimeUseCase: formatElapsedTimeUseCase) + return NotificationsViewModel( + formatElapsedTimeUseCase: formatElapsedTimeUseCase, + readNotificationUseCase: readNotificationUseCase + ) } } } From 3414aedfc021b4e0ddc04e891398103fd11237f7 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:06:50 +0900 Subject: [PATCH 102/231] =?UTF-8?q?setting:=20#450=20URLScheme=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist index 4f14f7a3..c6593c1b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist @@ -2,8 +2,6 @@ - UIDesignRequiresCompatibility - BASE_URL $(BASE_URL) CFBundleURLTypes @@ -16,6 +14,16 @@ kakao$(KAKAO_NATIVE_APP_KEY) + + CFBundleTypeRole + Editor + CFBundleURLName + com.heartz.ByeBoo-iOS + CFBundleURLSchemes + + myapp + + KAKAO_NATIVE_APP_KEY $(KAKAO_NATIVE_APP_KEY) From c54b59fade3f446db323204bae064c8ddea00ab4 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:02:17 +0900 Subject: [PATCH 103/231] =?UTF-8?q?chore:=20#450=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift index a6762f3c..8baf24ac 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/QuestsRepository.swift @@ -250,7 +250,6 @@ final class MockQuestsRepository: QuestsInterface { } func fetchAIAnswer(questID: Int) async throws -> AIAnswerEntity { -// throw ByeBooError.unknownError .stub() } From 1a709663fbe01aae862f73a5a40cc4bd12b012d2 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:03:25 +0900 Subject: [PATCH 104/231] =?UTF-8?q?refactor:=20#450=20stub=EC=97=90=20?= =?UTF-8?q?=EC=8B=A4=EC=A0=9C=20=ED=80=98=EC=8A=A4=ED=8A=B8=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EC=99=80=20answerID=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/NotificationListEntity.swift | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift index fd4dcc33..d1392bf0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -27,10 +27,10 @@ extension NotificationListEntity { notificationID: 9, notificationType: .questOpen, title: "오늘의 퀘스트 오픈 🌱", - content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + content: "3번째 퀘스트가 오픈됐어요, 시작해볼까요?", isRead: false, createdAt: "2026-06-07T11:29:00.735730", - landingURL: "myapp://quest/24" + landingURL: "myapp://quest/3" ), NotificationEntity( notificationID: 8, @@ -39,7 +39,7 @@ extension NotificationListEntity { content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", isRead: true, createdAt: "2026-06-07T08:35:43.735730", - landingURL: "myapp://common-quests/24" + landingURL: "myapp://common-quests/486" ), NotificationEntity( notificationID: 7, @@ -48,16 +48,16 @@ extension NotificationListEntity { content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", isRead: true, createdAt: "2026-06-06T15:35:43.735730", - landingURL: "myapp://common-quests/23" + landingURL: "myapp://common-quests/486" ), NotificationEntity( notificationID: 6, notificationType: .questOpen, title: "오늘의 퀘스트 오픈 🌱", - content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + content: "3번째 퀘스트가 오픈됐어요, 시작해볼까요?", isRead: false, createdAt: "2026-02-19T02:09:43.735730", - landingURL: "myapp://quest/24" + landingURL: "myapp://quest/3" ), NotificationEntity( notificationID: 5, @@ -66,7 +66,7 @@ extension NotificationListEntity { content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", isRead: true, createdAt: "2026-02-19T02:09:43.735730", - landingURL: "myapp://common-quests/24" + landingURL: "myapp://common-quests/486" ), NotificationEntity( notificationID: 4, @@ -75,34 +75,7 @@ extension NotificationListEntity { content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", isRead: true, createdAt: "2026-02-19T02:09:43.735730", - landingURL: "myapp://common-quests/23" - ), - NotificationEntity( - notificationID: 3, - notificationType: .questOpen, - title: "오늘의 퀘스트 오픈 🌱", - content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", - isRead: false, - createdAt: "2026-02-19T02:09:43.735730", - landingURL: "myapp://quest/24" - ), - NotificationEntity( - notificationID: 2, - notificationType: .comment, - title: "공통여정에 답변이 달렸어요 💬", - content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", - isRead: true, - createdAt: "2026-02-19T02:09:43.735730", - landingURL: "myapp://common-quests/24" - ), - NotificationEntity( - notificationID: 1, - notificationType: .like, - title: "공통여정에 답변에 공감이 달렸어요 ❤️", - content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", - isRead: true, - createdAt: "2026-02-19T02:09:43.735730", - landingURL: "myapp://common-quests/23" + landingURL: "myapp://common-quests/486" ) ] ) From a58430d78a809b5afdf72682240f24c8cac7f181 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:16:13 +0900 Subject: [PATCH 105/231] =?UTF-8?q?refactor:=20#450=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20Notificat?= =?UTF-8?q?ionViewModel=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 17 ------- .../NotificationsViewController.swift | 48 +++++++++++++------ .../Home/ViewModel/HomeViewModel.swift | 24 ++-------- .../ViewModel/NotificationsViewModel.swift | 44 ++++++++++++++++- 4 files changed, 81 insertions(+), 52 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 4ab1d259..c940e92f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -119,7 +119,6 @@ extension HomeViewController { private func noticeButtonDidTap() { let viewController = ViewControllerFactory.shared.makeNotificationsViewController() viewController.hidesBottomBarWhenPushed = true - viewController.configure(notificationList: viewModel.notifications) self.navigationController?.setNavigationBarHidden(false, animated: false) self.navigationController?.pushViewController(viewController, animated: false) @@ -149,7 +148,6 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { bindCharacter() bindHomeState() bindHelper() - bindNotifications() } private func bindCharacter() { @@ -208,19 +206,4 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } .store(in: &cancellables) } - - private func bindNotifications() { - viewModel.output.notificationResult - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - switch result { - case .success(let notificationList): - let isAllRead = notificationList.notifications.allSatisfy { $0.isRead } - self?.rootView.headerView.updateNotice(isExist: !isAllRead) - case .failure(let error): - self?.handleError(error) - } - } - .store(in: &cancellables) - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index cc0cc1b4..92269f38 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -5,13 +5,14 @@ // Created by APPLE on 4/30/26. // +import Combine import UIKit final class NotificationsViewController: BaseViewController { private let rootView = NoticesView() private let viewModel: NotificationsViewModel - private var notificationList: NotificationListEntity? + private var cancellables = Set() init(viewModel: NotificationsViewModel) { self.viewModel = viewModel @@ -26,6 +27,12 @@ final class NotificationsViewController: BaseViewController { view = rootView } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewModel.action(.viewWillAppear) + } + override func viewDidLoad() { super.viewDidLoad() @@ -35,12 +42,8 @@ final class NotificationsViewController: BaseViewController { type: .titleAndBack("알림"), action: #selector(back) ) - - guard let notifications = notificationList?.notifications else { - return - } - - rootView.contentView.decideNoticeContent(isExistNotice: !notifications.isEmpty) + + bind() } override func setAddTarget() { @@ -75,15 +78,26 @@ extension NotificationsViewController { extension NotificationsViewController { - func configure(notificationList: NotificationListEntity?) { - self.notificationList = notificationList + func bind() { + viewModel.output.notificationListResult + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let notificationList): + self?.rootView.contentView.decideNoticeContent(isExistNotice: !notificationList.notifications.isEmpty) + self?.rootView.contentView.noticeCardsView.cardTableView.reloadData() + case .failure(let error): + ByeBooLogger.error(error) + } + } + .store(in: &cancellables) } } extension NotificationsViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - notificationList?.notifications.count ?? 0 + viewModel.notificationList?.notifications.count ?? 0 } func tableView( @@ -103,9 +117,8 @@ extension NotificationsViewController: UITableViewDataSource { withIdentifier: NoticeCardCell.identifier, for: indexPath ) as? NoticeCardCell, - let notification = notificationList?.notifications[indexPath.section], - let notificationType = notification.notificationType, - let writtenTime = viewModel.formatElapsedTime(from: notification.createdAt) + let notification = viewModel.notificationList?.notifications[indexPath.section], + let notificationType = notification.notificationType else { return UITableViewCell() } @@ -115,7 +128,7 @@ extension NotificationsViewController: UITableViewDataSource { notificationType: notificationType, title: notification.title, subtitle: notification.content, - writtenTime: writtenTime + writtenTime: "" ) return cell @@ -124,6 +137,13 @@ extension NotificationsViewController: UITableViewDataSource { extension NotificationsViewController: UITableViewDelegate { + func tableView( + _ tableView: UITableView, + didSelectRowAt indexPath: IndexPath + ) { + viewModel.move(from: self, at: indexPath.section) + } + func tableView( _ tableView: UITableView, viewForHeaderInSection section: Int diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index f0a664b5..b2f3b589 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -23,7 +23,7 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() - private var notificationResultSubject = PassthroughSubject, Never>() + private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase @@ -31,7 +31,6 @@ final class HomeViewModel { private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase - private let fetchNotificationListUseCase: FetchNotificationListUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -39,8 +38,7 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase, - fetchNotificationListUseCase: FetchNotificationListUseCase + getHelperUseCase: GetHelperUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -48,15 +46,13 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase - self.fetchNotificationListUseCase = fetchNotificationListUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher(), - notificationResult: notificationResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher() ) } } @@ -73,7 +69,6 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> - let notificationResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -85,9 +80,8 @@ extension HomeViewModel: ViewModelType { async let dialogue: Void = fetchDialogue() async let status: Void = fetchStatus() async let journey: Void = fetchJourney() - async let notificationList: Void = fetchNotificationList() - let _ = await (dialogue, status, journey, notificationList) + let _ = await (dialogue, status, journey) } case .helperDidTap: @@ -153,14 +147,4 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } - - private func fetchNotificationList() async { - do { - let notifications = try await fetchNotificationListUseCase.execute() - self.notifications = notifications - notificationResultSubject.send(.success(notifications)) - } catch { - notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) - } - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index e71cfd42..04b6aaba 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -6,24 +6,32 @@ // import Combine +import UIKit final class NotificationsViewModel: ViewModelType { + private let fetchNotificationListUseCase: FetchNotificationListUseCase private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase private let readNotificationUseCase: ReadNotificationUseCase + private(set) var notificationList: NotificationListEntity? private var cancellables: Set = .init() + private(set) var output: Output + + private var notificationResultSubject = PassthroughSubject, Never>() private var formatElapsedTimeSubject: PassthroughSubject, Never> = .init() private var readNotificationSubject: PassthroughSubject, Never> = .init() - private(set) var output: Output init( + fetchNotificationListUseCase: FetchNotificationListUseCase, formatElapsedTimeUseCase: FormatElapsedTimeUseCase, readNotificationUseCase: ReadNotificationUseCase ) { + self.fetchNotificationListUseCase = fetchNotificationListUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase self.readNotificationUseCase = readNotificationUseCase self.output = Output( + notificationListResult: notificationResultSubject.eraseToAnyPublisher(), formattedTimeResult: formatElapsedTimeSubject.eraseToAnyPublisher(), readNotificationResult: readNotificationSubject.eraseToAnyPublisher() ) @@ -31,6 +39,8 @@ final class NotificationsViewModel: ViewModelType { func action(_ trigger: Input) { switch trigger { + case .viewWillAppear: + fetchNotificationList() case .dequeueCell(let rawTime): formatElapsedTime(from: rawTime) case .notificationDidTap(let notificationID): @@ -42,11 +52,13 @@ final class NotificationsViewModel: ViewModelType { extension NotificationsViewModel { enum Input { + case viewWillAppear case dequeueCell(rawTime: String) case notificationDidTap(notificationID: Int) } struct Output { + let notificationListResult: AnyPublisher, Never> let formattedTimeResult: AnyPublisher, Never> let readNotificationResult: AnyPublisher, Never> } @@ -54,6 +66,21 @@ extension NotificationsViewModel { extension NotificationsViewModel { + func fetchNotificationList() { + Task { + do { + let notificationList = try await fetchNotificationListUseCase.execute() + self.notificationList = NotificationListEntity.stub() + notificationResultSubject.send(.success(NotificationListEntity.stub())) + } catch { + guard let error = error as? ByeBooError else { + return + } + notificationResultSubject.send(.failure(error)) + } + } + } + func formatElapsedTime(from timeString: String) { guard let formattedTime = formatElapsedTimeUseCase.execute(from: timeString) else { formatElapsedTimeSubject.send(.failure(.dateFormatError)) @@ -76,4 +103,19 @@ extension NotificationsViewModel { } } } + + func move(from rootViewController: UIViewController, at index: Int) { + guard let landingURL = getLandingURL(at: index), + let destination = DeepLinkParser.parse(from: landingURL) + else { + return + } + + let coordinator = DeepLinkCoordinator(rootViewController: rootViewController) + coordinator.navigate(to: destination) + } + + private func getLandingURL(at index: Int) -> String? { + notificationList?.notifications[index].landingURL + } } From de25d558574dfbddb71b2658f3747b11e64e8e74 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:37:35 +0900 Subject: [PATCH 106/231] =?UTF-8?q?refactor:=20#450=20CommonQuestHistoryVi?= =?UTF-8?q?ewController=EC=97=90=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 11 ++++++++++- .../PresentationDependencyAssembler.swift | 10 +++++----- .../Presentation/ViewControllerFactory.swift | 6 +++++- 3 files changed, 20 insertions(+), 7 deletions(-) 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 0abf8f73..fa1005b0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -14,7 +14,7 @@ final class CommonQuestHistoryViewController: BaseViewController { private var dataSource: UITableViewDiffableDataSource! private let rootView = CommonQuestHistoryView() - private let viewModel = CommonQuestHistoryViewModel() + private let viewModel: CommonQuestHistoryViewModel private var answerID: Int? private var answer: String? @@ -23,6 +23,15 @@ final class CommonQuestHistoryViewController: BaseViewController { private var commonQuestArchiveType: CommonQuestArchiveType = .mine private var writerID: Int = 0 + init(viewModel: CommonQuestHistoryViewModel) { + self.viewModel = viewModel + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func loadView() { view = rootView } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 15409b26..cb745c1b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -107,8 +107,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { let setHelperUseCase = container.resolve(type: SetHelperUseCase.self), let fetchUserJourneyUseCase = container.resolve(type: FetchUserJourneyUseCase.self), let getUserNameUseCase = container.resolve(type: GetUserNameUseCase.self), - let getHelperUseCase = container.resolve(type: GetHelperUseCase.self), - let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) + let getHelperUseCase = container.resolve(type: GetHelperUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -120,8 +119,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { fetchUserJourneyUseCase: fetchUserJourneyUseCase, getUserNameUseCase: getUserNameUseCase, setHelperUseCase: setHelperUseCase, - getHelperUseCase: getHelperUseCase, - fetchNotificationListUseCase: fetchNotificationListUseCase + getHelperUseCase: getHelperUseCase ) } @@ -356,13 +354,15 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self), + guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self), + let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self), let readNotificationUseCase = container.resolve(type: ReadNotificationUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } return NotificationsViewModel( + fetchNotificationListUseCase: fetchNotificationListUseCase, formatElapsedTimeUseCase: formatElapsedTimeUseCase, readNotificationUseCase: readNotificationUseCase ) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index 7c5371d5..aceab3e1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -195,7 +195,11 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { } func makeCommonQuestHistoryViewController() -> CommonQuestHistoryViewController { - .init() + guard let viewModel = DIContainer.shared.resolve(type: CommonQuestHistoryViewModel.self) else { + DIErrorHandle() + fatalError() + } + return .init(viewModel: viewModel) } func makeCommonQuestMyAnswersViewController() -> CommonQuestMyAnswersViewController { From d96e4eb2f04be7f056dc7841915a7f26cd38efd0 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:48:09 +0900 Subject: [PATCH 107/231] =?UTF-8?q?chore:=20#450=20answer=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift | 2 +- .../ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift index 1a83bec4..3d189f5e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift @@ -12,7 +12,7 @@ import Alamofire enum CommonQuestAPI { case postCommonQuest(questID: Int, dto: SaveCommonQuestRequestDTO) case fetchCommonQuest(date: String, cursor: Int?) - case fetchCommonQuestDetail(asnwerID: Int) + case fetchCommonQuestDetail(answerID: Int) case updateCommonQuest(answerID: Int, dto: UpdateCommonQuestRequestDTO) case deleteCommonQuest(answerID: Int) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift index 9057aacd..b036605c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -68,7 +68,7 @@ struct DefaultCommonQuestRepository: CommonQuestInterface { func fetchCommonQuestDetail(answerID: Int) async throws -> CommonQuestDetailEntity { let userID: Int = userDefaultsService.load(key: .userID) ?? 0 let commonQuestDetail = try await network.request( - CommonQuestAPI.fetchCommonQuestDetail(asnwerID: answerID), + CommonQuestAPI.fetchCommonQuestDetail(answerID: answerID), decodingType: CommonQuestAnswerDetailResponseDTO.self ) From 371511a320a433876c9f27e0b224ad31a9e06455 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:48:48 +0900 Subject: [PATCH 108/231] =?UTF-8?q?chore:=20#450=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 9 --------- 1 file changed, 9 deletions(-) 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 da6a76ae..8ff99038 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -30,15 +30,6 @@ final class CommonQuestHistoryViewController: BaseViewController { private var answerID: Int = 0 private var writerID: Int = 0 - init(viewModel: CommonQuestHistoryViewModel) { - self.viewModel = viewModel - super.init() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - override func loadView() { view = rootView } From 4b468c9291da32eb462e23cb95d1a2bb957a843f Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:31:29 +0900 Subject: [PATCH 109/231] =?UTF-8?q?feat:=20#450=20=EB=94=A5=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeepLink/DeepLinkCoordinator.swift | 25 +++++++ .../Presentation/DeepLink/DeepLinkHost.swift | 11 +++ .../DeepLink/DeepLinkParser.swift | 65 ++++++++++++++++++ .../CommonQuestDeepLinkDestination.swift | 41 ++++++++++++ .../Destination/DeepLinkDestination.swift | 12 ++++ .../QuestDeepLinkDestination.swift | 67 +++++++++++++++++++ 6 files changed, 221 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift new file mode 100644 index 00000000..a6f97deb --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift @@ -0,0 +1,25 @@ +// +// DeepLinkCoordinator.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/11/26. +// + +import UIKit + +struct DeepLinkCoordinator { + + private weak var rootViewController: UIViewController? + + init(rootViewController: UIViewController?) { + self.rootViewController = rootViewController + } + + func navigate(to destination: DeepLinkDestination) { + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) { + + destination.navigate(from: window) + } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift new file mode 100644 index 00000000..9ce00c70 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift @@ -0,0 +1,11 @@ +// +// DeepLinkHost.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/17/26. +// + +enum DeepLinkHost: String { + case quest = "quest" + case commonQuest = "common-quests" +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift new file mode 100644 index 00000000..af49f16a --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift @@ -0,0 +1,65 @@ +// +// DeepLinkParser.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/11/26. +// + +import Foundation + +struct DeepLinkParser { + + private static let scheme = "myapp" + private static let idIndex = 1 + + static func parse(from urlString: String) -> DeepLinkDestination? { + guard let url = validateURL(from: urlString), + let host = validateHost(from: url), + let id = extractID(from: url) + else { + return nil + } + + return destination(for: host, id: id) + } +} + +private extension DeepLinkParser { + + static func validateURL(from urlString: String) -> URL? { + guard let url = URL(string: urlString), + url.scheme == scheme + else { + return nil + } + + return url + } + + static func validateHost(from url: URL) -> DeepLinkHost? { + guard let hostString = url.host, + let host = DeepLinkHost(rawValue: hostString) + else { + return nil + } + + return host + } + + static func extractID(from url: URL) -> Int? { + guard url.pathComponents.indices.contains(idIndex) else { + return nil + } + + return Int(url.pathComponents[idIndex]) + } + + static func destination(for host: DeepLinkHost, id: Int) -> DeepLinkDestination { + switch host { + case .quest: + QuestDeepLinkDestination(questNumber: id) + case .commonQuest: + CommonQuestAnswerDeepLinkDestination(answerID: id) + } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift new file mode 100644 index 00000000..d1c7eaba --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift @@ -0,0 +1,41 @@ +// +// CommonQuestDeepLinkDestination.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/18/26. +// + +import UIKit + +struct CommonQuestAnswerDeepLinkDestination: DeepLinkDestination { + let answerID: Int + + func navigate(from window: UIWindow) { + guard let tabBarController = window.rootViewController as? ByeBooTabBar else { + return + } + + ViewControllerUtils.setRootViewController( + window: window, + viewController: tabBarController, + withAnimation: false + ) + pushToHistory(from: tabBarController) + } +} + +private extension CommonQuestAnswerDeepLinkDestination { + + func pushToHistory(from tabBarController: ByeBooTabBar) { + tabBarController.selectedIndex = 1 + + guard let navigationController = tabBarController.viewControllers?[1] as? UINavigationController else { + return + } + + let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() + historyViewController.configure(answerID: answerID) + + navigationController.pushViewController(historyViewController, animated: true) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift new file mode 100644 index 00000000..a9600ad5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift @@ -0,0 +1,12 @@ +// +// DeepLinkDestination.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/17/26. +// + +import UIKit + +protocol DeepLinkDestination { + func navigate(from window: UIWindow) +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift new file mode 100644 index 00000000..98934242 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift @@ -0,0 +1,67 @@ +// +// QuestDeepLinkDestination.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/18/26. +// + +import UIKit + +struct QuestDeepLinkDestination: DeepLinkDestination { + private let questIndex = 1 + let questNumber: Int + + func navigate(from window: UIWindow) { + let tabBarController = ViewControllerFactory.shared.makeByeBooTabBar() + tabBarController.selectedIndex = questIndex + + guard let questCheckViewController = findQuestCheckViewController(from: tabBarController) else { + return + } + + questCheckViewController.pendingQuestNumber = questNumber + + ViewControllerUtils.setRootViewController( + window: window, + viewController: tabBarController, + withAnimation: false + ) + } + + private func findQuestCheckViewController(from tabBarController: ByeBooTabBar) -> QuestCheckViewController? { + guard let navigationController = findNavigationController(from: tabBarController), + let parentQuestViewController = findParentQuestViewController(from: navigationController), + let questCheckViewController = findQuestCheckViewController(from: parentQuestViewController) + else { + return nil + } + + selectParentQuestTab(for: parentQuestViewController, target: questCheckViewController) + + return questCheckViewController + } +} + +private extension QuestDeepLinkDestination { + + func findNavigationController(from tabBarController: ByeBooTabBar) -> UINavigationController? { + tabBarController.viewControllers?[1] as? UINavigationController + } + + func findParentQuestViewController(from navigationController: UINavigationController) -> ParentQuestViewController? { + navigationController.viewControllers.first as? ParentQuestViewController + } + + func findQuestCheckViewController(from parentQuestViewController: ParentQuestViewController) -> QuestCheckViewController? { + parentQuestViewController.controllers.first(where: { $0 is QuestCheckViewController }) as? QuestCheckViewController + } + + func selectParentQuestTab( + for parentQuestVC: ParentQuestViewController, + target questCheckVC: QuestCheckViewController + ) { + if let targetIndex = parentQuestVC.controllers.firstIndex(of: questCheckVC) { + parentQuestVC.selectTab(index: targetIndex) + } + } +} From b2eb47c116eccd6799a94fc77471f32768615616 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:37:27 +0900 Subject: [PATCH 110/231] =?UTF-8?q?refactor:=20#450=20=ED=98=84=EC=9E=AC?= =?UTF-8?q?=20=ED=80=98=EC=8A=A4=ED=8A=B8=20=EB=B2=88=ED=98=B8=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EB=8A=94=20=ED=80=98=EC=8A=A4=ED=8A=B8=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/QuestCheckViewController.swift | 10 ++++++++++ .../ViewModel/ProgressingQuestsViewModel.swift | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift index 52f2b5b6..0466e275 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift @@ -23,6 +23,7 @@ final class QuestCheckViewController: BaseViewController { private var journeyType: JourneyType = .recording private var isFirst: Bool = true + var pendingQuestNumber: Int? init(viewModel: ProgressingQuestsViewModel) { self.viewModel = viewModel @@ -107,6 +108,10 @@ extension QuestCheckViewController: ToastPresentable, ToastErrorHandler { switch (name, journey, quests) { case let (.success(name), .success(journey), .success(quests)): self?.updateQuestMainUI(name: name, journey: journey, quests: quests) + if let questNumber = self?.pendingQuestNumber { + self?.presentCurrentQuestModal(questNumber: questNumber) + self?.pendingQuestNumber = nil + } case (.success(_), .success(_), .failure(_)): self?.coordinator?.moveQuestStart() case let (.success(name), .failure(.notFound), .failure(_)): @@ -219,6 +224,11 @@ extension QuestCheckViewController { private func goBoriButtonDidTap() { coordinator?.moveFinishQuest() } + + func presentCurrentQuestModal(questNumber: Int) { + let quest = viewModel.findQuest(questNumber: questNumber) + coordinator?.presentQuestModal(quest: quest) + } } extension QuestCheckViewController: UICollectionViewDelegate { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift index 983ee264..eefb2b9a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/ProgressingQuestsViewModel.swift @@ -87,7 +87,7 @@ final class ProgressingQuestsViewModel { questOpenTime: questsEntity?.questOpenTime, currentTime: questsEntity?.currentTime ) - + timeCancellabels?.cancel() timeCancellabels = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() @@ -95,7 +95,7 @@ final class ProgressingQuestsViewModel { guard let self = self else { return } - + if remainingSeconds > 0 { remainingSeconds -= 1 let time = self.formatRemainingTime(seconds: remainingSeconds) @@ -178,6 +178,16 @@ extension ProgressingQuestsViewModel { } return .ongoing } + + func findQuest(questNumber: Int) -> QuestEntity? { + for (sectionIndex, step) in steps.enumerated() { + if let itemIndex = step.quests.firstIndex(where: { $0.questNumber == questNumber }) { + return getQuest(section: sectionIndex, item: itemIndex) + } + } + + return nil + } } extension ProgressingQuestsViewModel: ViewModelType { From ebd03d6eb787914019b875e7d124d64b2d681afe Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:37:52 +0900 Subject: [PATCH 111/231] =?UTF-8?q?refactor:=20#450=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift b/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift index 70136271..e4af7dc7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift @@ -28,6 +28,7 @@ enum ByeBooError: Error, LocalizedError, Equatable { case fileNotFound case nicknameViolation case questViolation + case dateFormatError var errorDescription: String? { switch self { @@ -71,6 +72,8 @@ enum ByeBooError: Error, LocalizedError, Equatable { return "비속어나 부적절한 단어가 포함된 닉네임" case .questViolation: return "비속어나 부적절한 단어가 포함된 답변" + case .dateFormatError: + return "날짜 문자열 형식이 올바르지 않음" } } } From 7b0c7d28d81efb85658abde56a47a5df507d2013 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:38:18 +0900 Subject: [PATCH 112/231] =?UTF-8?q?refactor:=20#450=20controllers=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=A0=9C=EC=96=B4=EC=9E=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/ParentQuestViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index 92e6cca4..fe9441e5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -11,7 +11,7 @@ final class ParentQuestViewController: BaseViewController, ToastPres private let tabBar: TopTabBar private let containerView = UIView() - private let controllers: [UIViewController] + private(set) var controllers: [UIViewController] private var currentViewController: UIViewController? init(items: T.AllCases) { From a8179adb9937c4b46e3214150216e393154cdcb4 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:39:01 +0900 Subject: [PATCH 113/231] =?UTF-8?q?refactor:=20#450=20ByeBooTabBar=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Presentation/ViewControllerFactory.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index aceab3e1..7ad73461 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -34,6 +34,7 @@ protocol ViewControllerFactoryProtocol { func makeBlockedUserListViewController() -> BlockedkUserListViewController func makeAIAnswerViewController() -> AIAnswerViewController func makeNotificationsViewController() -> NotificationsViewController + func makeByeBooTabBar() -> ByeBooTabBar } final class ViewControllerFactory: ViewControllerFactoryProtocol { @@ -245,6 +246,10 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { return .init(viewModel: viewModel) } + + func makeByeBooTabBar() -> ByeBooTabBar { + .init() + } } extension ViewControllerFactory { From 0f13f8a8d512c72df29e42615622e841fde96312 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:08:24 +0900 Subject: [PATCH 114/231] =?UTF-8?q?refactor:=20#450=20=EC=BD=94=EB=94=94?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=84=B0=20=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeepLink/DeepLinkCoordinator.swift | 25 ------------------- .../DeepLink/DeepLinkParser.swift | 8 +++--- ...ommonQuestAnswerDeepLinkCoordinator.swift} | 6 ++--- ...nation.swift => DeepLinkCoordinator.swift} | 4 +-- ...n.swift => QuestDeepLinkCoordinator.swift} | 6 ++--- 5 files changed, 12 insertions(+), 37 deletions(-) delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift rename ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/{CommonQuestDeepLinkDestination.swift => CommonQuestAnswerDeepLinkCoordinator.swift} (86%) rename ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/{DeepLinkDestination.swift => DeepLinkCoordinator.swift} (66%) rename ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/{QuestDeepLinkDestination.swift => QuestDeepLinkCoordinator.swift} (94%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift deleted file mode 100644 index a6f97deb..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkCoordinator.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// DeepLinkCoordinator.swift -// ByeBoo-iOS -// -// Created by 더스틴 on 6/11/26. -// - -import UIKit - -struct DeepLinkCoordinator { - - private weak var rootViewController: UIViewController? - - init(rootViewController: UIViewController?) { - self.rootViewController = rootViewController - } - - func navigate(to destination: DeepLinkDestination) { - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first(where: { $0.isKeyWindow }) { - - destination.navigate(from: window) - } - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift index af49f16a..703a5d43 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift @@ -12,7 +12,7 @@ struct DeepLinkParser { private static let scheme = "myapp" private static let idIndex = 1 - static func parse(from urlString: String) -> DeepLinkDestination? { + static func parse(from urlString: String) -> DeepLinkCoordinator? { guard let url = validateURL(from: urlString), let host = validateHost(from: url), let id = extractID(from: url) @@ -54,12 +54,12 @@ private extension DeepLinkParser { return Int(url.pathComponents[idIndex]) } - static func destination(for host: DeepLinkHost, id: Int) -> DeepLinkDestination { + static func destination(for host: DeepLinkHost, id: Int) -> DeepLinkCoordinator { switch host { case .quest: - QuestDeepLinkDestination(questNumber: id) + QuestDeepLinkCoordinator(questNumber: id) case .commonQuest: - CommonQuestAnswerDeepLinkDestination(answerID: id) + CommonQuestAnswerDeepLinkCoordinator(answerID: id) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift similarity index 86% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift index d1c7eaba..be66be2d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestDeepLinkDestination.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift @@ -1,5 +1,5 @@ // -// CommonQuestDeepLinkDestination.swift +// CommonQuestAnswerDeepLinkCoordinator.swift // ByeBoo-iOS // // Created by 더스틴 on 6/18/26. @@ -7,7 +7,7 @@ import UIKit -struct CommonQuestAnswerDeepLinkDestination: DeepLinkDestination { +struct CommonQuestAnswerDeepLinkCoordinator: DeepLinkCoordinator { let answerID: Int func navigate(from window: UIWindow) { @@ -24,7 +24,7 @@ struct CommonQuestAnswerDeepLinkDestination: DeepLinkDestination { } } -private extension CommonQuestAnswerDeepLinkDestination { +private extension CommonQuestAnswerDeepLinkCoordinator { func pushToHistory(from tabBarController: ByeBooTabBar) { tabBarController.selectedIndex = 1 diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift similarity index 66% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift index a9600ad5..142bd295 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkDestination.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift @@ -1,5 +1,5 @@ // -// DeepLinkDestination.swift +// DeepLinkCoordinator.swift // ByeBoo-iOS // // Created by 더스틴 on 6/17/26. @@ -7,6 +7,6 @@ import UIKit -protocol DeepLinkDestination { +protocol DeepLinkCoordinator { func navigate(from window: UIWindow) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift similarity index 94% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift index 98934242..2d6bac54 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkDestination.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift @@ -1,5 +1,5 @@ // -// QuestDeepLinkDestination.swift +// QuestDeepLinkCoordinator.swift // ByeBoo-iOS // // Created by 더스틴 on 6/18/26. @@ -7,7 +7,7 @@ import UIKit -struct QuestDeepLinkDestination: DeepLinkDestination { +struct QuestDeepLinkCoordinator: DeepLinkCoordinator { private let questIndex = 1 let questNumber: Int @@ -42,7 +42,7 @@ struct QuestDeepLinkDestination: DeepLinkDestination { } } -private extension QuestDeepLinkDestination { +private extension QuestDeepLinkCoordinator { func findNavigationController(from tabBarController: ByeBooTabBar) -> UINavigationController? { tabBarController.viewControllers?[1] as? UINavigationController From ace31639a682b0182984a764da727ff818f005c1 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:08:56 +0900 Subject: [PATCH 115/231] =?UTF-8?q?refactor:=20#450=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=9D=BD=EA=B8=B0=20API=20=ED=98=B8=EC=B6=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsViewController.swift | 1 + .../ViewModel/NotificationsViewModel.swift | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index 92269f38..4b98d8b4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -141,6 +141,7 @@ extension NotificationsViewController: UITableViewDelegate { _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { + viewModel.action(.notificationDidTap(at: indexPath.section)) viewModel.move(from: self, at: indexPath.section) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 04b6aaba..239b5fcb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -43,8 +43,8 @@ final class NotificationsViewModel: ViewModelType { fetchNotificationList() case .dequeueCell(let rawTime): formatElapsedTime(from: rawTime) - case .notificationDidTap(let notificationID): - readNotification(for: notificationID) + case .notificationDidTap(let index): + readNotification(at: index) } } } @@ -54,7 +54,7 @@ extension NotificationsViewModel { enum Input { case viewWillAppear case dequeueCell(rawTime: String) - case notificationDidTap(notificationID: Int) + case notificationDidTap(at: Int) } struct Output { @@ -90,7 +90,11 @@ extension NotificationsViewModel { formatElapsedTimeSubject.send(.success(formattedTime)) } - func readNotification(for notificationID: Int) { + func readNotification(at index: Int) { + guard let notificationID = notificationList?.notifications[index].notificationID else { + return + } + Task { do { try await readNotificationUseCase.execute(for: notificationID) @@ -110,9 +114,12 @@ extension NotificationsViewModel { else { return } - - let coordinator = DeepLinkCoordinator(rootViewController: rootViewController) - coordinator.navigate(to: destination) + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) { + + destination.navigate(from: window) + } } private func getLandingURL(at index: Int) -> String? { From 1f187c9482a68889ab3b4ab1331601aa940a7751 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:09:08 +0900 Subject: [PATCH 116/231] =?UTF-8?q?chore:=20#450=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A4=84=EB=B0=94=EA=BF=88=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Home/ViewModel/HomeViewModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index b2f3b589..d3af648a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -24,7 +24,6 @@ final class HomeViewModel { private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() - private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase private let fetchUserJourneyUseCase: FetchUserJourneyUseCase From a20c86dd6fb0ab442bf62aa195b0789cdd72a185 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:12:00 +0900 Subject: [PATCH 117/231] =?UTF-8?q?refactor:=20#450=20UINavigationControll?= =?UTF-8?q?er=EB=A5=BC=20=EC=B0=BE=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Destination/CommonQuestAnswerDeepLinkCoordinator.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift index be66be2d..a98d3390 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift @@ -29,7 +29,7 @@ private extension CommonQuestAnswerDeepLinkCoordinator { func pushToHistory(from tabBarController: ByeBooTabBar) { tabBarController.selectedIndex = 1 - guard let navigationController = tabBarController.viewControllers?[1] as? UINavigationController else { + guard let navigationController = findNavigationController(from: tabBarController) else { return } @@ -38,4 +38,8 @@ private extension CommonQuestAnswerDeepLinkCoordinator { navigationController.pushViewController(historyViewController, animated: true) } + + func findNavigationController(from tabBarController: ByeBooTabBar) -> UINavigationController? { + tabBarController.viewControllers?[1] as? UINavigationController + } } From 27fe4d11ce883bf9d82ecdfa2ff281c1784af95f Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:19:03 +0900 Subject: [PATCH 118/231] =?UTF-8?q?refactor:=20#437=20=ED=99=88=20?= =?UTF-8?q?=EB=B7=B0=EB=AA=A8=EB=8D=B8=EC=97=90=EC=84=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/HomeViewModel.swift | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index f0a664b5..d3af648a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -23,7 +23,6 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() - private var notificationResultSubject = PassthroughSubject, Never>() private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase @@ -31,7 +30,6 @@ final class HomeViewModel { private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase - private let fetchNotificationListUseCase: FetchNotificationListUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -39,8 +37,7 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase, - fetchNotificationListUseCase: FetchNotificationListUseCase + getHelperUseCase: GetHelperUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -48,15 +45,13 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase - self.fetchNotificationListUseCase = fetchNotificationListUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher(), - notificationResult: notificationResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher() ) } } @@ -73,7 +68,6 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> - let notificationResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -85,9 +79,8 @@ extension HomeViewModel: ViewModelType { async let dialogue: Void = fetchDialogue() async let status: Void = fetchStatus() async let journey: Void = fetchJourney() - async let notificationList: Void = fetchNotificationList() - let _ = await (dialogue, status, journey, notificationList) + let _ = await (dialogue, status, journey) } case .helperDidTap: @@ -153,14 +146,4 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } - - private func fetchNotificationList() async { - do { - let notifications = try await fetchNotificationListUseCase.execute() - self.notifications = notifications - notificationResultSubject.send(.success(notifications)) - } catch { - notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) - } - } } From e505dbd62929b83d5d28e3c05904541a02f41ef9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:22:26 +0900 Subject: [PATCH 119/231] =?UTF-8?q?feat:=20#437=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B4=80=EB=A0=A8=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 4 ++++ .../Presentation/PresentationDependencyAssembler.swift | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index acb630a6..8eeb3e5d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -216,5 +216,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FormatElapsedTimeUseCase.self) { _ in return DefaultFormatElapsedTimeUseCase() } + + DIContainer.shared.register(type: FetchHasUnreadNotificationUseCase.self) { _ in + return DefaultFetchHasUnreadNotificationUseCase(repository: notificationRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 7b6d5b43..95f1f426 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -108,7 +108,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { let fetchUserJourneyUseCase = container.resolve(type: FetchUserJourneyUseCase.self), let getUserNameUseCase = container.resolve(type: GetUserNameUseCase.self), let getHelperUseCase = container.resolve(type: GetHelperUseCase.self), - let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) + let fetchHasUnreadNotificationUseCase = container.resolve(type: FetchHasUnreadNotificationUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -121,7 +121,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { getUserNameUseCase: getUserNameUseCase, setHelperUseCase: setHelperUseCase, getHelperUseCase: getHelperUseCase, - fetchNotificationListUseCase: fetchNotificationListUseCase + fetchHasUnreadNotificationUseCase: fetchHasUnreadNotificationUseCase ) } @@ -356,12 +356,12 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { + guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } - return NotificationsViewModel(formatElapsedTimeUseCase: formatElapsedTimeUseCase) + return NotificationsViewModel(fetchNotificationListUseCase: fetchNotificationListUseCase) } } } From 58b5c94fe6e93e416e0d3d73fde01124f32ce613 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:23:12 +0900 Subject: [PATCH 120/231] =?UTF-8?q?feat:=20#450=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?-=20Presentation=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 14 +++++------ .../Home/ViewModel/HomeViewModel.swift | 25 ++++++++++++++++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 4ab1d259..09fa25a3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -116,10 +116,9 @@ extension HomeViewController { } @objc - private func noticeButtonDidTap() { + private func noticeButtonDidTap() { let viewController = ViewControllerFactory.shared.makeNotificationsViewController() viewController.hidesBottomBarWhenPushed = true - viewController.configure(notificationList: viewModel.notifications) self.navigationController?.setNavigationBarHidden(false, animated: false) self.navigationController?.pushViewController(viewController, animated: false) @@ -149,7 +148,7 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { bindCharacter() bindHomeState() bindHelper() - bindNotifications() + bindHasNotification() } private func bindCharacter() { @@ -209,14 +208,13 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { .store(in: &cancellables) } - private func bindNotifications() { - viewModel.output.notificationResult + private func bindHasNotification() { + viewModel.output.hasNotifcationResult .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { - case .success(let notificationList): - let isAllRead = notificationList.notifications.allSatisfy { $0.isRead } - self?.rootView.headerView.updateNotice(isExist: !isAllRead) + case .success(let entity): + self?.rootView.headerView.updateNotice(isExist: entity.hasUnread) case .failure(let error): self?.handleError(error) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index d3af648a..36fd3a65 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -15,7 +15,6 @@ final class HomeViewModel { var dialoguesResult: Result { characterResultSubject.value } - private(set) var notifications: NotificationListEntity? private(set) var output: Output private var characterResultSubject = CurrentValueSubject, Never>(.failure(ByeBooError.noData)) @@ -23,6 +22,7 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() + private var hasNotificationResultSubject = PassthroughSubject,Never>() private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase @@ -30,6 +30,7 @@ final class HomeViewModel { private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase + private let fetchHasUnreadNotificationUseCase: FetchHasUnreadNotificationUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -37,7 +38,8 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase + getHelperUseCase: GetHelperUseCase, + fetchHasUnreadNotificationUseCase: FetchHasUnreadNotificationUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -45,13 +47,15 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase + self.fetchHasUnreadNotificationUseCase = fetchHasUnreadNotificationUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher(), + hasNotifcationResult: hasNotificationResultSubject.eraseToAnyPublisher() ) } } @@ -68,6 +72,7 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> + let hasNotifcationResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -79,6 +84,7 @@ extension HomeViewModel: ViewModelType { async let dialogue: Void = fetchDialogue() async let status: Void = fetchStatus() async let journey: Void = fetchJourney() + async let hasNotification: Void = fetchHasUnreadNotification() let _ = await (dialogue, status, journey) } @@ -146,4 +152,17 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } + + private func fetchHasUnreadNotification() async { + do { + let result = try await fetchHasUnreadNotificationUseCase.execute() + hasNotificationResultSubject.send(.success(result)) + } catch { + hasNotificationResultSubject.send( + .failure( + error as? ByeBooError ?? ByeBooError.unknownError + ) + ) + } + } } From 3ee2f63a33fdff8c10e32b5382ebefb8fea61676 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:23:24 +0900 Subject: [PATCH 121/231] =?UTF-8?q?feat:=20#450=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?-=20Domain=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/HasUnreadNotificationEntity.swift | 10 ++++++++ .../Interface/NotificationInterface.swift | 1 + .../FetchHasUnreadNotificationUseCase.swift | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift new file mode 100644 index 00000000..ce047066 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift @@ -0,0 +1,10 @@ +// +// HasUnreadNotificationEntity.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/19/26. +// + +struct HasUnreadNotificationEntity { + let hasUnread: Bool +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift index 05b07428..3640970f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift @@ -7,4 +7,5 @@ protocol NotificationInterface { func fetchNotifications() async throws -> NotificationListEntity + func fetchHasUnreadNotification() async throws -> HasUnreadNotificationEntity } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift new file mode 100644 index 00000000..cde37b86 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift @@ -0,0 +1,24 @@ +// +// FetchHasUnreadNotificationUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/19/26. +// + +protocol FetchHasUnreadNotificationUseCase { + func execute() async throws -> HasUnreadNotificationEntity +} + +struct DefaultFetchHasUnreadNotificationUseCase: FetchHasUnreadNotificationUseCase { + + private let repository: NotificationInterface + + init(repository: NotificationInterface) { + self.repository = repository + } + + func execute() async throws -> HasUnreadNotificationEntity { + let result = try await repository.fetchHasUnreadNotification() + return result + } +} From 481c0b800233dce8538ba5898ec4bb2b6db2ed1b Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:23:31 +0900 Subject: [PATCH 122/231] =?UTF-8?q?feat:=20#450=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?-=20Data=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/HasUnreadNotificationResponseDTO.swift | 16 ++++++++++++++++ .../Data/Network/EndPoint/NotificationAPI.swift | 13 ++++++++----- .../Data/Repository/NotificationRepository.swift | 8 ++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift new file mode 100644 index 00000000..fdc8f184 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift @@ -0,0 +1,16 @@ +// +// HasUnreadNotificationResponseDTO.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/19/26. +// + +struct HasUnreadNotificationResponseDTO: Decodable { + let hasUnread: Bool +} + +extension HasUnreadNotificationResponseDTO { + func toEntity() -> HasUnreadNotificationEntity { + .init(hasUnread: hasUnread) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift index 34a270f3..97dc1a49 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -11,6 +11,7 @@ import Alamofire enum NotificationAPI { case fetchNotificationList + case fetchUnreadNotification } extension NotificationAPI: EndPoint { @@ -23,40 +24,42 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList: "" + case .fetchUnreadNotification: + "/unread/status" } } var method: HTTPMethod { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: .get } } var headers: HeaderType { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: .withAuth } } var parameterEncoding: ParameterEncoding { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: JSONEncoding.default } } var queryParameters: [String : String]? { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: nil } } var bodyParameters: Parameters? { switch self { - case .fetchNotificationList: + case .fetchNotificationList ,.fetchUnreadNotification: nil } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift index c93aefa3..91419050 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -20,4 +20,12 @@ struct DefaultNotificationRepository: NotificationInterface { ) return result.toEntity() } + + func fetchHasUnreadNotification() async throws -> HasUnreadNotificationEntity { + let result = try await networkService.request( + NotificationAPI.fetchUnreadNotification, + decodingType: HasUnreadNotificationResponseDTO.self + ) + return result.toEntity() + } } From a62c0a6d209d04b4b6f563613d0a7e29a9ae5b41 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:40:03 +0900 Subject: [PATCH 123/231] =?UTF-8?q?refactor:=20#437=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20NotificationViewModel=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsViewController.swift | 41 +++++++---- .../ViewModel/NotificationsViewModel.swift | 68 +++++++++++++++++-- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index cc0cc1b4..954fb040 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -5,13 +5,14 @@ // Created by APPLE on 4/30/26. // +import Combine import UIKit final class NotificationsViewController: BaseViewController { private let rootView = NoticesView() private let viewModel: NotificationsViewModel - private var notificationList: NotificationListEntity? + private var cancellables = Set() init(viewModel: NotificationsViewModel) { self.viewModel = viewModel @@ -26,6 +27,11 @@ final class NotificationsViewController: BaseViewController { view = rootView } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + viewModel.action(.viewWillAppear) + } + override func viewDidLoad() { super.viewDidLoad() @@ -36,11 +42,7 @@ final class NotificationsViewController: BaseViewController { action: #selector(back) ) - guard let notifications = notificationList?.notifications else { - return - } - - rootView.contentView.decideNoticeContent(isExistNotice: !notifications.isEmpty) + bind() } override func setAddTarget() { @@ -67,23 +69,34 @@ extension NotificationsViewController: BackNavigable { } } -extension NotificationsViewController { +extension NotificationsViewController: ToastPresentable, ToastErrorHandler { - @objc - private func readAllButtonDidTap() {} + func bind() { + viewModel.output.notificationList + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let notificationList): + self?.rootView.contentView.decideNoticeContent(isExistNotice: !notificationList.notifications.isEmpty) + self?.rootView.contentView.noticeCardsView.cardTableView.reloadData() + case .failure(let error): + self?.handleError(error) + } + } + .store(in: &cancellables) + } } extension NotificationsViewController { - func configure(notificationList: NotificationListEntity?) { - self.notificationList = notificationList - } + @objc + private func readAllButtonDidTap() {} } extension NotificationsViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - notificationList?.notifications.count ?? 0 + viewModel.notificatinosCount } func tableView( @@ -103,7 +116,7 @@ extension NotificationsViewController: UITableViewDataSource { withIdentifier: NoticeCardCell.identifier, for: indexPath ) as? NoticeCardCell, - let notification = notificationList?.notifications[indexPath.section], + let notification = viewModel.getNotification(at: indexPath.section), let notificationType = notification.notificationType, let writtenTime = viewModel.formatElapsedTime(from: notification.createdAt) else { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 6c3d8997..0cf4afa3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -5,19 +5,75 @@ // Created by 더스틴 on 6/7/26. // +import Combine + final class NotificationsViewModel { + private var cancellables = Set() + private var notifications: [NotificationEntity]? + + private let fetchNotificationListUseCase: FetchNotificationListUseCase private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase - init(formatElapsedTimeUseCase: FormatElapsedTimeUseCase) { + private(set) var output: Output + private var notificationListSubject = PassthroughSubject, Never>() + + init( + fetchNotificationListUseCase: FetchNotificationListUseCase, + formatElapsedTimeUseCase: FormatElapsedTimeUseCase + ) { + self.fetchNotificationListUseCase = fetchNotificationListUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase + self.output = .init(notificationList: notificationListSubject.eraseToAnyPublisher()) + } +} + +extension NotificationsViewModel: ViewModelType { + enum Input { + case viewWillAppear } - func formatElapsedTime(from timeString: String) -> String? { - guard let formattedTime = formatElapsedTimeUseCase.execute(from: timeString) else { - return nil + struct Output { + let notificationList: AnyPublisher, Never> + } + + func action(_ trigger: Input) { + switch trigger { + case .viewWillAppear: + fetchNotificationList() } - - return formattedTime + } +} + +extension NotificationsViewModel { + + func fetchNotificationList() { + Task { + do { + let notificationList = try await fetchNotificationListUseCase.execute() + self.notifications = notificationList.notifications + notificationListSubject.send(.success(notificationList)) + } catch { + guard let error = error as? ByeBooError else { + return + } + notificationListSubject.send(.failure(error)) + } + } + } + + func formatElapsedTime(from timeString: String) -> String? { + formatElapsedTimeUseCase.execute(from: timeString) + } +} + +extension NotificationsViewModel { + + var notificatinosCount: Int { + notifications?.count ?? 0 + } + + func getNotification(at index: Int) -> NotificationEntity? { + notifications?[index] } } From a6dd04df79a3201e361ba4c51adfe69adf04db36 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:40:39 +0900 Subject: [PATCH 124/231] =?UTF-8?q?refactor:=20#437=20NotificationsViewMod?= =?UTF-8?q?el=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20FormatElaspedTimeUseCase?= =?UTF-8?q?=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/PresentationDependencyAssembler.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 95f1f426..a653c1d6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -356,12 +356,16 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) else { + guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self), + let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } - return NotificationsViewModel(fetchNotificationListUseCase: fetchNotificationListUseCase) + return NotificationsViewModel( + fetchNotificationListUseCase: fetchNotificationListUseCase, + formatElapsedTimeUseCase: formatElapsedTimeUseCase + ) } } } From b792d9d1fe181365d38d446359a5cd484a26273b Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:37:39 +0900 Subject: [PATCH 125/231] =?UTF-8?q?fix:=20#437=20DTO=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Model/NotificationListResponseDTO.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift index 9c2cb343..0bead5b2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift @@ -10,13 +10,13 @@ struct NotificationListResponseDTO: Decodable { } struct NotificationResponseDTO: Decodable { - let notificationID: Int - let notificationType: String - let title: String + let notificationId: Int let content: String + let title: String let isRead: Bool let createdAt: String - let landingURL: String + let landingUrl: String + let notificationType: String } extension NotificationListResponseDTO { @@ -29,13 +29,13 @@ extension NotificationListResponseDTO { extension NotificationResponseDTO { func toEntity() -> NotificationEntity { .init( - notificationID: notificationID, + notificationID: notificationId, notificationType: NotificationType.keyToEnum(notificationType), title: title, content: content, isRead: isRead, createdAt: createdAt, - landingURL: landingURL + landingURL: landingUrl ) } } From 36b89548b50255d98af45fd04c3892160c4a6eb6 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:29:01 +0900 Subject: [PATCH 126/231] =?UTF-8?q?fix:=20#450=20switch-case=EB=AC=B8=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/EndPoint/NotificationAPI.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift index eb2ac98a..b9adf64b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -43,8 +43,7 @@ extension NotificationAPI: EndPoint { var headers: HeaderType { switch self { - case .fetchNotificationList, .fetchUnreadNotification: - case .fetchNotificationList, .readNotification: + case .fetchNotificationList, .fetchUnreadNotification, .readNotification: .withAuth } } @@ -60,16 +59,14 @@ extension NotificationAPI: EndPoint { var queryParameters: [String : String]? { switch self { - case .fetchNotificationList, .fetchUnreadNotification: - case .fetchNotificationList, .readNotification: + case .fetchNotificationList, .fetchUnreadNotification, .readNotification: nil } } var bodyParameters: Parameters? { switch self { - case .fetchNotificationList ,.fetchUnreadNotification: - case .fetchNotificationList, .readNotification: + case .fetchNotificationList, .fetchUnreadNotification, .readNotification: nil } } From 82ef944044845a701a75c193a928600af38115a5 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:29:17 +0900 Subject: [PATCH 127/231] =?UTF-8?q?refactor:=20#450=20readNotificationUseC?= =?UTF-8?q?ase=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/NotificationsViewModel.swift | 82 ++++++++++--------- .../PresentationDependencyAssembler.swift | 7 +- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 38bcc18a..b296cb08 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -6,6 +6,7 @@ // import Combine +import UIKit final class NotificationsViewModel { @@ -15,17 +16,19 @@ final class NotificationsViewModel { private let fetchNotificationListUseCase: FetchNotificationListUseCase private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase private let readNotificationUseCase: ReadNotificationUseCase - private(set) var notificationList: NotificationListEntity? private(set) var output: Output private var notificationListSubject = PassthroughSubject, Never>() + private var readNotificationSubject = PassthroughSubject, Never>() init( fetchNotificationListUseCase: FetchNotificationListUseCase, - formatElapsedTimeUseCase: FormatElapsedTimeUseCase + formatElapsedTimeUseCase: FormatElapsedTimeUseCase, + readNotificationUseCase: ReadNotificationUseCase ) { self.fetchNotificationListUseCase = fetchNotificationListUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase + self.readNotificationUseCase = readNotificationUseCase self.output = .init(notificationList: notificationListSubject.eraseToAnyPublisher()) } } @@ -33,6 +36,7 @@ final class NotificationsViewModel { extension NotificationsViewModel: ViewModelType { enum Input { case viewWillAppear + case notificationDidTap(at: Int) } struct Output { @@ -43,44 +47,10 @@ extension NotificationsViewModel: ViewModelType { switch trigger { case .viewWillAppear: fetchNotificationList() + case .notificationDidTap(let index): + readNotification(at: index) } } - - func readNotification(at index: Int) { - guard let notificationID = notificationList?.notifications[index].notificationID else { - return - } - - Task { - do { - try await readNotificationUseCase.execute(for: notificationID) - readNotificationSubject.send(.success(())) - } catch { - guard let error = error as? ByeBooError else { - return - } - readNotificationSubject.send(.failure(error)) - } - } - } - - func move(from rootViewController: UIViewController, at index: Int) { - guard let landingURL = getLandingURL(at: index), - let destination = DeepLinkParser.parse(from: landingURL) - else { - return - } - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first(where: { $0.isKeyWindow }) { - - destination.navigate(from: window) - } - } - - private func getLandingURL(at index: Int) -> String? { - notificationList?.notifications[index].landingURL - } } extension NotificationsViewModel { @@ -103,6 +73,24 @@ extension NotificationsViewModel { func formatElapsedTime(from timeString: String) -> String? { formatElapsedTimeUseCase.execute(from: timeString) } + + func readNotification(at index: Int) { + guard let notificationID = notifications?[index].notificationID else { + return + } + + Task { + do { + try await readNotificationUseCase.execute(for: notificationID) + readNotificationSubject.send(.success(())) + } catch { + guard let error = error as? ByeBooError else { + return + } + readNotificationSubject.send(.failure(error)) + } + } + } } extension NotificationsViewModel { @@ -114,4 +102,22 @@ extension NotificationsViewModel { func getNotification(at index: Int) -> NotificationEntity? { notifications?[index] } + + func move(from rootViewController: UIViewController, at index: Int) { + guard let landingURL = getLandingURL(at: index), + let destination = DeepLinkParser.parse(from: landingURL) + else { + return + } + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) { + + destination.navigate(from: window) + } + } + + private func getLandingURL(at index: Int) -> String? { + notifications?[index].landingURL + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index a0c7ccc8..cc777d7f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -357,14 +357,17 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: NotificationsViewModel.self) { container in guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self), - let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { + let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self), + let readNotificationUseCase = container.resolve(type: ReadNotificationUseCase.self) + else { ByeBooLogger.error(ByeBooError.DIFailedError) return } return NotificationsViewModel( fetchNotificationListUseCase: fetchNotificationListUseCase, - formatElapsedTimeUseCase: formatElapsedTimeUseCase + formatElapsedTimeUseCase: formatElapsedTimeUseCase, + readNotificationUseCase: readNotificationUseCase ) } From 64b64babe9825768f8cbe626662a91d782ddc1f6 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:29:32 +0900 Subject: [PATCH 128/231] =?UTF-8?q?chore:=20#450=20=EC=8B=A4=EC=A0=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=8B=9C=EA=B0=84=20=EC=82=BD=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewController/NotificationsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index bf176ff3..db379e9a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -128,7 +128,7 @@ extension NotificationsViewController: UITableViewDataSource { notificationType: notificationType, title: notification.title, subtitle: notification.content, - writtenTime: "" + writtenTime: writtenTime ) return cell From 8505cf6df5b27f2a6d3562cf07cd37c1a7a1ef21 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:29:55 +0900 Subject: [PATCH 129/231] =?UTF-8?q?refactor:=20#450=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=9D=BD=EA=B8=B0=20=EC=97=AC=EB=B6=80=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20NoticeCardCell=20=EB=B0=B0=EA=B2=BD=EC=83=89=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Home/View/Notice/NoticeCardCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index 1ba1773a..bf999b6a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -89,7 +89,7 @@ extension NoticeCardCell { subtitle: String, writtenTime: String ) { - self.backgroundColor = isRead ? .primary30020 : .white5 + self.backgroundColor = isRead ? .white5 : .primary30020 noticeImageView.image = mapToImage(for: notificationType) titleLabel.text = title subtitleLabel.text = subtitle From d1fb90dba30bbfdb6040634c159957661b1055e1 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:00:35 +0900 Subject: [PATCH 130/231] =?UTF-8?q?refactor:=20#450=20QuestDeepLinkCoordin?= =?UTF-8?q?ator=20=ED=94=84=EB=A1=9C=ED=86=A0=EC=BD=9C=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20=EC=BD=94=EB=94=94?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=84=B0=20=EC=9E=AC=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommonQuestAnswerDeepLinkCoordinator.swift | 42 ++++++++++++ .../MyJourneyDeepLinkCoordinator.swift | 32 +++++++++ .../Protocol/DeepLinkCoordinator.swift | 19 ++++++ .../Protocol/QuestDeepLinkCoordinator.swift | 47 +++++++++++++ ...CommonQuestAnswerDeepLinkCoordinator.swift | 45 ------------- .../Destination/DeepLinkCoordinator.swift | 12 ---- .../QuestDeepLinkCoordinator.swift | 67 ------------------- 7 files changed, 140 insertions(+), 124 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/QuestDeepLinkCoordinator.swift delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift new file mode 100644 index 00000000..efa22589 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift @@ -0,0 +1,42 @@ +// +// CommonQuestAnswerDeepLinkCoordinator.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/18/26. +// + +import UIKit + +struct CommonQuestAnswerDeepLinkCoordinator: QuestDeepLinkCoordinator { + + let answerID: Int + + func navigate(from window: UIWindow) { + let tabBarController = ViewControllerFactory.shared.makeByeBooTabBar() + tabBarController.selectedIndex = questViewIndex + + ViewControllerUtils.setRootViewController( + window: window, + viewController: tabBarController, + withAnimation: false + ) + + guard let navigationController = findNavigationController(from: tabBarController, index: questViewIndex), + let _: CommonQuestViewController = selectedQuestTab(from: navigationController) + else { + return + } + + pushToHistory(from: navigationController) + } +} + +private extension CommonQuestAnswerDeepLinkCoordinator { + + func pushToHistory(from navigationController: UINavigationController) { + let historyVC = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() + historyVC.configure(answerID: answerID) + + navigationController.pushViewController(historyVC, animated: false) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift new file mode 100644 index 00000000..5aa773e6 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift @@ -0,0 +1,32 @@ +// +// MyJourneyDeepLinkCoordinator.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/18/26. +// + +import UIKit + +struct MyJourneyDeepLinkCoordinator: QuestDeepLinkCoordinator { + + let questNumber: Int + + func navigate(from window: UIWindow) { + let tabBarController = ViewControllerFactory.shared.makeByeBooTabBar() + tabBarController.selectedIndex = questViewIndex + + ViewControllerUtils.setRootViewController( + window: window, + viewController: tabBarController, + withAnimation: false + ) + + guard let navigationController = findNavigationController(from: tabBarController, index: questViewIndex), + let questCheckVC: QuestCheckViewController = selectedQuestTab(from: navigationController) + else { + return + } + + questCheckVC.setPendingQuestNumber(questNumber) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift new file mode 100644 index 00000000..2f76b568 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift @@ -0,0 +1,19 @@ +// +// DeepLinkCoordinator.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/17/26. +// + +import UIKit + +protocol DeepLinkCoordinator { + func navigate(from window: UIWindow) + func findNavigationController(from tabBarController: ByeBooTabBar, index: Int) -> UINavigationController? +} + +extension DeepLinkCoordinator { + func findNavigationController(from tabBarController: ByeBooTabBar, index: Int) -> UINavigationController? { + tabBarController.viewControllers?[index] as? UINavigationController + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/QuestDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/QuestDeepLinkCoordinator.swift new file mode 100644 index 00000000..d26d9a8c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/QuestDeepLinkCoordinator.swift @@ -0,0 +1,47 @@ +// +// QuestDeepLinkCoordinator.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/20/26. +// + +import UIKit + +protocol QuestDeepLinkCoordinator: DeepLinkCoordinator { + + typealias QuestHostVC = ParentQuestViewController + + var questViewIndex: Int { get } + func selectedQuestTab(from navigationController: UINavigationController) -> T? +} + +extension QuestDeepLinkCoordinator { + + var questViewIndex: Int { 1 } + + func selectedQuestTab( + from navigationController: UINavigationController + ) -> T? { + guard let questHostVC = navigationController.viewControllers.first as? QuestHostVC, + let targetVC: T = questHostVC.controllers.first(where: { $0 is T }) as? T + else { + return nil + } + + selectQuestTab(for: questHostVC, target: targetVC) + + return targetVC + } +} + +private extension QuestDeepLinkCoordinator { + + func selectQuestTab( + for questHostVC: QuestHostVC, + target targetVC: T + ) { + if let targetIndex = questHostVC.controllers.firstIndex(of: targetVC as! UIViewController) { + questHostVC.selectTab(index: targetIndex) + } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift deleted file mode 100644 index a98d3390..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/CommonQuestAnswerDeepLinkCoordinator.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// CommonQuestAnswerDeepLinkCoordinator.swift -// ByeBoo-iOS -// -// Created by 더스틴 on 6/18/26. -// - -import UIKit - -struct CommonQuestAnswerDeepLinkCoordinator: DeepLinkCoordinator { - let answerID: Int - - func navigate(from window: UIWindow) { - guard let tabBarController = window.rootViewController as? ByeBooTabBar else { - return - } - - ViewControllerUtils.setRootViewController( - window: window, - viewController: tabBarController, - withAnimation: false - ) - pushToHistory(from: tabBarController) - } -} - -private extension CommonQuestAnswerDeepLinkCoordinator { - - func pushToHistory(from tabBarController: ByeBooTabBar) { - tabBarController.selectedIndex = 1 - - guard let navigationController = findNavigationController(from: tabBarController) else { - return - } - - let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() - historyViewController.configure(answerID: answerID) - - navigationController.pushViewController(historyViewController, animated: true) - } - - func findNavigationController(from tabBarController: ByeBooTabBar) -> UINavigationController? { - tabBarController.viewControllers?[1] as? UINavigationController - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift deleted file mode 100644 index 142bd295..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/DeepLinkCoordinator.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// DeepLinkCoordinator.swift -// ByeBoo-iOS -// -// Created by 더스틴 on 6/17/26. -// - -import UIKit - -protocol DeepLinkCoordinator { - func navigate(from window: UIWindow) -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift deleted file mode 100644 index 2d6bac54..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Destination/QuestDeepLinkCoordinator.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// QuestDeepLinkCoordinator.swift -// ByeBoo-iOS -// -// Created by 더스틴 on 6/18/26. -// - -import UIKit - -struct QuestDeepLinkCoordinator: DeepLinkCoordinator { - private let questIndex = 1 - let questNumber: Int - - func navigate(from window: UIWindow) { - let tabBarController = ViewControllerFactory.shared.makeByeBooTabBar() - tabBarController.selectedIndex = questIndex - - guard let questCheckViewController = findQuestCheckViewController(from: tabBarController) else { - return - } - - questCheckViewController.pendingQuestNumber = questNumber - - ViewControllerUtils.setRootViewController( - window: window, - viewController: tabBarController, - withAnimation: false - ) - } - - private func findQuestCheckViewController(from tabBarController: ByeBooTabBar) -> QuestCheckViewController? { - guard let navigationController = findNavigationController(from: tabBarController), - let parentQuestViewController = findParentQuestViewController(from: navigationController), - let questCheckViewController = findQuestCheckViewController(from: parentQuestViewController) - else { - return nil - } - - selectParentQuestTab(for: parentQuestViewController, target: questCheckViewController) - - return questCheckViewController - } -} - -private extension QuestDeepLinkCoordinator { - - func findNavigationController(from tabBarController: ByeBooTabBar) -> UINavigationController? { - tabBarController.viewControllers?[1] as? UINavigationController - } - - func findParentQuestViewController(from navigationController: UINavigationController) -> ParentQuestViewController? { - navigationController.viewControllers.first as? ParentQuestViewController - } - - func findQuestCheckViewController(from parentQuestViewController: ParentQuestViewController) -> QuestCheckViewController? { - parentQuestViewController.controllers.first(where: { $0 is QuestCheckViewController }) as? QuestCheckViewController - } - - func selectParentQuestTab( - for parentQuestVC: ParentQuestViewController, - target questCheckVC: QuestCheckViewController - ) { - if let targetIndex = parentQuestVC.controllers.firstIndex(of: questCheckVC) { - parentQuestVC.selectTab(index: targetIndex) - } - } -} From 14ea370a06b26185ddca2441957a9462d7871c65 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:01:01 +0900 Subject: [PATCH 131/231] =?UTF-8?q?refactor:=20#450=20destination=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20DeepLinkParser=20->=20De?= =?UTF-8?q?epLinkHost=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/DeepLink/DeepLinkHost.swift | 9 +++++++++ .../Presentation/DeepLink/DeepLinkParser.swift | 13 ++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift index 9ce00c70..92aedbac 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkHost.swift @@ -8,4 +8,13 @@ enum DeepLinkHost: String { case quest = "quest" case commonQuest = "common-quests" + + func destination(id: Int) -> DeepLinkCoordinator { + switch self { + case .quest: + MyJourneyDeepLinkCoordinator(questNumber: id) + case .commonQuest: + CommonQuestAnswerDeepLinkCoordinator(answerID: id) + } + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift index 703a5d43..d6715f51 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift @@ -7,7 +7,7 @@ import Foundation -struct DeepLinkParser { +enum DeepLinkParser { private static let scheme = "myapp" private static let idIndex = 1 @@ -20,7 +20,7 @@ struct DeepLinkParser { return nil } - return destination(for: host, id: id) + return host.destination(id: id) } } @@ -53,13 +53,4 @@ private extension DeepLinkParser { return Int(url.pathComponents[idIndex]) } - - static func destination(for host: DeepLinkHost, id: Int) -> DeepLinkCoordinator { - switch host { - case .quest: - QuestDeepLinkCoordinator(questNumber: id) - case .commonQuest: - CommonQuestAnswerDeepLinkCoordinator(answerID: id) - } - } } From 2a4f90f78078ac018e216c6132ce391fb9faecec Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:01:32 +0900 Subject: [PATCH 132/231] =?UTF-8?q?refactor:=20#450=20=EA=B5=AC=EB=8F=85?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsViewController.swift | 6 +++--- .../ViewModel/NotificationsViewModel.swift | 19 +++++++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index db379e9a..59835282 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -71,8 +71,8 @@ extension NotificationsViewController: BackNavigable { extension NotificationsViewController: ToastPresentable, ToastErrorHandler { - func bind() { - viewModel.output.notificationList + private func bind() { + viewModel.output.notificationListResult .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { @@ -142,7 +142,7 @@ extension NotificationsViewController: UITableViewDelegate { didSelectRowAt indexPath: IndexPath ) { viewModel.action(.notificationDidTap(at: indexPath.section)) - viewModel.move(from: self, at: indexPath.section) + viewModel.handleNotification(at: indexPath.section) } func tableView( diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index b296cb08..536d2bc4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -19,7 +19,6 @@ final class NotificationsViewModel { private(set) var output: Output private var notificationListSubject = PassthroughSubject, Never>() - private var readNotificationSubject = PassthroughSubject, Never>() init( fetchNotificationListUseCase: FetchNotificationListUseCase, @@ -29,7 +28,7 @@ final class NotificationsViewModel { self.fetchNotificationListUseCase = fetchNotificationListUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase self.readNotificationUseCase = readNotificationUseCase - self.output = .init(notificationList: notificationListSubject.eraseToAnyPublisher()) + self.output = .init(notificationListResult: notificationListSubject.eraseToAnyPublisher()) } } @@ -40,7 +39,7 @@ extension NotificationsViewModel: ViewModelType { } struct Output { - let notificationList: AnyPublisher, Never> + let notificationListResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -59,8 +58,8 @@ extension NotificationsViewModel { Task { do { let notificationList = try await fetchNotificationListUseCase.execute() - self.notifications = notificationList.notifications - notificationListSubject.send(.success(notificationList)) + self.notifications = NotificationListEntity.stub().notifications + notificationListSubject.send(.success(NotificationListEntity.stub())) } catch { guard let error = error as? ByeBooError else { return @@ -81,13 +80,9 @@ extension NotificationsViewModel { Task { do { - try await readNotificationUseCase.execute(for: notificationID) - readNotificationSubject.send(.success(())) + let _ = try await readNotificationUseCase.execute(for: notificationID) } catch { - guard let error = error as? ByeBooError else { - return - } - readNotificationSubject.send(.failure(error)) + ByeBooLogger.error(error) } } } @@ -103,7 +98,7 @@ extension NotificationsViewModel { notifications?[index] } - func move(from rootViewController: UIViewController, at index: Int) { + func handleNotification(at index: Int) { guard let landingURL = getLandingURL(at: index), let destination = DeepLinkParser.parse(from: landingURL) else { From 7d23dcf916407dc9ea4f6f5a0617489d913db0ef Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:02:05 +0900 Subject: [PATCH 133/231] =?UTF-8?q?refactor:=20#450=20=EB=B7=B0=20?= =?UTF-8?q?=EC=83=9D=EB=AA=85=EC=A3=BC=EA=B8=B0=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20selectedIndex=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParentQuestViewController.swift | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index fe9441e5..3e43534c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -13,6 +13,7 @@ final class ParentQuestViewController: BaseViewController, ToastPres private let containerView = UIView() private(set) var controllers: [UIViewController] private var currentViewController: UIViewController? + private var selectedIndex: Int = 0 init(items: T.AllCases) { self.tabBar = TopTabBar(items: Array(items)) @@ -27,6 +28,15 @@ final class ParentQuestViewController: BaseViewController, ToastPres override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(true) tabBarController?.tabBar.isHidden = false + + guard controllers.indices.contains(selectedIndex) else { return } + + show(controllers[selectedIndex]) + tabBar.select(index: selectedIndex) + } + + override func viewWillDisappear(_ animated: Bool) { + selectedIndex = 0 } override func viewDidLoad() { @@ -35,17 +45,12 @@ final class ParentQuestViewController: BaseViewController, ToastPres setLayout() bind() - if let controller = controllers.first { - show(controller) - } - NotificationCenter.default.addObserver( - self, - selector: #selector(handleToast(_:)), - name: .showToastMessage, - object: nil + self, + selector: #selector(handleToast(_:)), + name: .showToastMessage, + object: nil ) - } override func setView() { @@ -57,6 +62,8 @@ final class ParentQuestViewController: BaseViewController, ToastPres func selectTab(index: Int) { guard controllers.indices.contains(index) else { return } + + selectedIndex = index show(controllers[index]) tabBar.select(index: index) } From b8849389afaf604f9c9c4cafa8a28ac71ce01a6a Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:02:31 +0900 Subject: [PATCH 134/231] =?UTF-8?q?refactor:=20#450=20pendingQuestNumber?= =?UTF-8?q?=EB=A5=BC=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=EC=84=9C=EB=A7=8C=20=EB=B3=80=EA=B2=BD=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=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 --- .../QuestCheckViewController.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift index 0466e275..d1049a8a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift @@ -23,7 +23,7 @@ final class QuestCheckViewController: BaseViewController { private var journeyType: JourneyType = .recording private var isFirst: Bool = true - var pendingQuestNumber: Int? + private var pendingQuestNumber: Int? init(viewModel: ProgressingQuestsViewModel) { self.viewModel = viewModel @@ -163,6 +163,15 @@ extension QuestCheckViewController: ToastPresentable, ToastErrorHandler { extension QuestCheckViewController { + func presentCurrentQuestModal(questNumber: Int) { + let quest = viewModel.findQuest(questNumber: questNumber) + coordinator?.presentQuestModal(quest: quest) + } + + func setPendingQuestNumber(_ questNumber: Int) { + self.pendingQuestNumber = questNumber + } + private func updateQuestMainUI( name: String, journey: JourneyEntity, @@ -220,15 +229,11 @@ extension QuestCheckViewController { } extension QuestCheckViewController { + @objc private func goBoriButtonDidTap() { coordinator?.moveFinishQuest() } - - func presentCurrentQuestModal(questNumber: Int) { - let quest = viewModel.findQuest(questNumber: questNumber) - coordinator?.presentQuestModal(quest: quest) - } } extension QuestCheckViewController: UICollectionViewDelegate { From 680538a37c6412b934f99ca46fbfe0e97e13146e Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:40:18 +0900 Subject: [PATCH 135/231] =?UTF-8?q?refactor:=20#450=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=9D=BD=EA=B8=B0=20API=EB=A5=BC=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=ED=95=9C=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=98=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 --- .../Feature/Home/ViewModel/NotificationsViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 536d2bc4..e07278d2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -58,8 +58,8 @@ extension NotificationsViewModel { Task { do { let notificationList = try await fetchNotificationListUseCase.execute() - self.notifications = NotificationListEntity.stub().notifications - notificationListSubject.send(.success(NotificationListEntity.stub())) + self.notifications = notificationList.notifications + notificationListSubject.send(.success(notificationList)) } catch { guard let error = error as? ByeBooError else { return From 1308cba982cac77ba25677ec24ae0c5c28a99ec5 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:40:36 +0900 Subject: [PATCH 136/231] =?UTF-8?q?refactor:=20#450=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=20=EC=9D=BD=EC=9D=80=20=EC=95=8C=EB=A6=BC=EC=9D=B4=EB=9D=BC?= =?UTF-8?q?=EB=A9=B4=20API=EB=A5=BC=20=ED=98=B8=EC=B6=9C=ED=95=98=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=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 --- .../Feature/Home/ViewModel/NotificationsViewModel.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index e07278d2..8c336eb4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -74,13 +74,15 @@ extension NotificationsViewModel { } func readNotification(at index: Int) { - guard let notificationID = notifications?[index].notificationID else { + guard let notification = notifications?[index], + !notification.isRead + else { return } Task { do { - let _ = try await readNotificationUseCase.execute(for: notificationID) + let _ = try await readNotificationUseCase.execute(for: notification.notificationID) } catch { ByeBooLogger.error(error) } From 0193330886d2a4ce78172bda09e742f212554447 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:42:04 +0900 Subject: [PATCH 137/231] =?UTF-8?q?chore:=20#450=20=EC=9C=A0=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=A4=91=EB=B3=B5=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index dcf28457..a91a27dd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -228,9 +228,5 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchHasUnreadNotificationUseCase.self) { _ in return DefaultFetchHasUnreadNotificationUseCase(repository: notificationRepository) } - - DIContainer.shared.register(type: FetchCommonQuestDetailUseCase.self) { _ in - return DefaultFetchCommonQuestDetailUseCase(repository: commonQuestRepository) - } } } From 847213bdb4e051ca263c3235f9921526b1ef15df Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:44:42 +0900 Subject: [PATCH 138/231] =?UTF-8?q?refactor:=20#450=20index=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/Protocol/DeepLinkCoordinator.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift index 2f76b568..28b21125 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/Protocol/DeepLinkCoordinator.swift @@ -14,6 +14,12 @@ protocol DeepLinkCoordinator { extension DeepLinkCoordinator { func findNavigationController(from tabBarController: ByeBooTabBar, index: Int) -> UINavigationController? { - tabBarController.viewControllers?[index] as? UINavigationController + guard let controllers = tabBarController.viewControllers, + controllers.indices.contains(index) + else { + return nil + } + + return tabBarController.viewControllers?[index] as? UINavigationController } } From 813b7952142796671db66af5124e2732df61d018 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:48:22 +0900 Subject: [PATCH 139/231] =?UTF-8?q?refactor:=20#450=20landingURL=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift index d6715f51..2f6bd16c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/DeepLinkParser.swift @@ -47,10 +47,13 @@ private extension DeepLinkParser { } static func extractID(from url: URL) -> Int? { - guard url.pathComponents.indices.contains(idIndex) else { + let pathComponents = url.pathComponents + guard pathComponents.count == 2, + let id = Int(pathComponents[idIndex]) + else { return nil } - return Int(url.pathComponents[idIndex]) + return id } } From 04a40aa69dd2ecaec34c4de4970376c78cf63008 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:50:14 +0900 Subject: [PATCH 140/231] =?UTF-8?q?refactor:=20#450=20NotificationsViewMod?= =?UTF-8?q?el=EC=97=90=EC=84=9C=20index=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/NotificationsViewModel.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 8c336eb4..5cfed277 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -57,9 +57,9 @@ extension NotificationsViewModel { func fetchNotificationList() { Task { do { - let notificationList = try await fetchNotificationListUseCase.execute() - self.notifications = notificationList.notifications - notificationListSubject.send(.success(notificationList)) + //let notificationList = try await fetchNotificationListUseCase.execute() + self.notifications = NotificationListEntity.stub().notifications + notificationListSubject.send(.success(NotificationListEntity.stub())) } catch { guard let error = error as? ByeBooError else { return @@ -97,7 +97,13 @@ extension NotificationsViewModel { } func getNotification(at index: Int) -> NotificationEntity? { - notifications?[index] + guard let notifications, + notifications.indices.contains(index) + else { + return nil + } + + return notifications[index] } func handleNotification(at index: Int) { @@ -115,6 +121,7 @@ extension NotificationsViewModel { } private func getLandingURL(at index: Int) -> String? { - notifications?[index].landingURL + let notification = getNotification(at: index) + return notification?.landingURL } } From 7609ff4a7fcd60cc637a2b5c42a2fb02ca3589cf Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:51:33 +0900 Subject: [PATCH 141/231] =?UTF-8?q?refactor:=20#340=20=EC=83=81=EC=9C=84?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20viewWillDisAppear=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewController/ParentQuestViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index 3e43534c..41115759 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -36,6 +36,7 @@ final class ParentQuestViewController: BaseViewController, ToastPres } override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) selectedIndex = 0 } From 0f3fb7a8cfc1e758e4a0dbc6ac9913ee26fc19cd Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:52:10 +0900 Subject: [PATCH 142/231] =?UTF-8?q?Revert=20"refactor:=20#340=20=EC=83=81?= =?UTF-8?q?=EC=9C=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=20viewWillDisAppear=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7609ff4a7fcd60cc637a2b5c42a2fb02ca3589cf. --- .../Feature/Quest/ViewController/ParentQuestViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index 41115759..3e43534c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -36,7 +36,6 @@ final class ParentQuestViewController: BaseViewController, ToastPres } override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) selectedIndex = 0 } From c987addd9ac453f6260f5423bde08a27aef2c12c Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:52:51 +0900 Subject: [PATCH 143/231] =?UTF-8?q?refactor:=20#450=20viewWillDisappear=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=98=B8=EC=B6=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewController/ParentQuestViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index 3e43534c..41115759 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -36,6 +36,7 @@ final class ParentQuestViewController: BaseViewController, ToastPres } override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) selectedIndex = 0 } From c517ad06277de9db06fd86b3e3cf4c761fcbb49c Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:53:55 +0900 Subject: [PATCH 144/231] =?UTF-8?q?chore:=20#450=20NotificationViewModel?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EB=93=B1=EB=A1=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PresentationDependencyAssembler.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index cc777d7f..8307ebc8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -381,20 +381,5 @@ struct PresentationDependencyAssembler: DependencyAssembler { fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase ) } - - DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self), - let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self), - let readNotificationUseCase = container.resolve(type: ReadNotificationUseCase.self) else { - ByeBooLogger.error(ByeBooError.DIFailedError) - return - } - - return NotificationsViewModel( - fetchNotificationListUseCase: fetchNotificationListUseCase, - formatElapsedTimeUseCase: formatElapsedTimeUseCase, - readNotificationUseCase: readNotificationUseCase - ) - } } } From c395e37211aa8b21bae9b24c926776f73302b5a2 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:32:19 +0900 Subject: [PATCH 145/231] =?UTF-8?q?refactor:=20#450=20stub=EC=9D=B4=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20=EC=8B=A4=EC=A0=9C=20API=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=98=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 --- .../Feature/Home/ViewModel/NotificationsViewModel.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 5cfed277..8bacbf81 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -57,9 +57,9 @@ extension NotificationsViewModel { func fetchNotificationList() { Task { do { - //let notificationList = try await fetchNotificationListUseCase.execute() - self.notifications = NotificationListEntity.stub().notifications - notificationListSubject.send(.success(NotificationListEntity.stub())) + let notificationList = try await fetchNotificationListUseCase.execute() + self.notifications = notificationList.notifications + notificationListSubject.send(.success(notificationList)) } catch { guard let error = error as? ByeBooError else { return From 3a38c72d91473604614bf7fe5f7094ee7cfdbc58 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:33:08 +0900 Subject: [PATCH 146/231] =?UTF-8?q?refactor:=20#450=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=84=B0=EC=B9=98=20=EC=8B=9C=20API=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EC=97=90=20=EC=9D=B4=EC=96=B4=20handleNotification=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=8F=84=20=ED=98=B8=EC=B6=9C=EB=90=98?= =?UTF-8?q?=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 --- .../NotificationsViewController.swift | 1 - .../ViewModel/NotificationsViewModel.swift | 20 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index 59835282..1d0323fa 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -142,7 +142,6 @@ extension NotificationsViewController: UITableViewDelegate { didSelectRowAt indexPath: IndexPath ) { viewModel.action(.notificationDidTap(at: indexPath.section)) - viewModel.handleNotification(at: indexPath.section) } func tableView( diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 8bacbf81..415660c7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -74,19 +74,21 @@ extension NotificationsViewModel { } func readNotification(at index: Int) { - guard let notification = notifications?[index], - !notification.isRead - else { + guard let notification = notifications?[index] else { return } - Task { - do { - let _ = try await readNotificationUseCase.execute(for: notification.notificationID) - } catch { - ByeBooLogger.error(error) + if !notification.isRead { + Task { + do { + let _ = try await readNotificationUseCase.execute(for: notification.notificationID) + } catch { + ByeBooLogger.error(error) + } } } + + handleNotification(at: index) } } @@ -106,7 +108,7 @@ extension NotificationsViewModel { return notifications[index] } - func handleNotification(at index: Int) { + private func handleNotification(at index: Int) { guard let landingURL = getLandingURL(at: index), let destination = DeepLinkParser.parse(from: landingURL) else { From 34d39e940f0a36fda07597890f64e89675f876d8 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:34:35 +0900 Subject: [PATCH 147/231] =?UTF-8?q?refactor:=20#450=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestAnswerDeepLinkCoordinator.swift | 4 ++++ .../Coordinator/MyJourneyDeepLinkCoordinator.swift | 4 ++++ .../ViewController/QuestCheckViewController.swift | 10 +++++----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift index efa22589..11fe14fd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/CommonQuestAnswerDeepLinkCoordinator.swift @@ -11,6 +11,10 @@ struct CommonQuestAnswerDeepLinkCoordinator: QuestDeepLinkCoordinator { let answerID: Int + init(answerID: Int) { + self.answerID = answerID + } + func navigate(from window: UIWindow) { let tabBarController = ViewControllerFactory.shared.makeByeBooTabBar() tabBarController.selectedIndex = questViewIndex diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift index 5aa773e6..afafe4b5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/DeepLink/Coordinator/MyJourneyDeepLinkCoordinator.swift @@ -11,6 +11,10 @@ struct MyJourneyDeepLinkCoordinator: QuestDeepLinkCoordinator { let questNumber: Int + init(questNumber: Int) { + self.questNumber = questNumber + } + func navigate(from window: UIWindow) { let tabBarController = ViewControllerFactory.shared.makeByeBooTabBar() tabBarController.selectedIndex = questViewIndex diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift index d1049a8a..872f7916 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/QuestCheckViewController.swift @@ -163,11 +163,6 @@ extension QuestCheckViewController: ToastPresentable, ToastErrorHandler { extension QuestCheckViewController { - func presentCurrentQuestModal(questNumber: Int) { - let quest = viewModel.findQuest(questNumber: questNumber) - coordinator?.presentQuestModal(quest: quest) - } - func setPendingQuestNumber(_ questNumber: Int) { self.pendingQuestNumber = questNumber } @@ -226,6 +221,11 @@ extension QuestCheckViewController { let collectionView = questsCheckView.questCollectionView collectionView.scrollToHeader(at: sectionIndex) } + + private func presentCurrentQuestModal(questNumber: Int) { + let quest = viewModel.findQuest(questNumber: questNumber) + coordinator?.presentQuestModal(quest: quest) + } } extension QuestCheckViewController { From c409d4abaf1ee68097a9b87fa9cea01342f19a72 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:08:40 +0900 Subject: [PATCH 148/231] =?UTF-8?q?setting:=20#450=20UIDesignRequiresCompa?= =?UTF-8?q?tibility=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist index c6593c1b..1efb405a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist @@ -2,6 +2,8 @@ + UIDesignRequiresCompatibility + BASE_URL $(BASE_URL) CFBundleURLTypes From d9ed373c81d7f16852c2d9fe571a88cbd6c95d8f Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:28:49 +0900 Subject: [PATCH 149/231] =?UTF-8?q?feat:=20#453=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=80=98=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=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 | 27 ++++++++++++++++- 2 files changed, 54 insertions(+), 3 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 1cb6b759..eb605a3e 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 formatElapsedTimeUseCase: FormatElapsedTimeUseCase private(set) var output: Output @@ -24,12 +27,15 @@ final class CommonQuestViewModel { init( fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase, + postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase formatElapsedTimeUseCase: FormatElapsedTimeUseCase ) { self.fetchCommonQuestByDateUseCase = fetchCommonQuestByDateUseCase + self.postCommonQuestLikeUseCase = postCommonQuestLikeUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase self.output = Output( - commonQuestPublisher: commonQuestSubject.eraseToAnyPublisher() + commonQuestPublisher: commonQuestSubject.eraseToAnyPublisher(), + commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } @@ -59,6 +65,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 { @@ -67,10 +84,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) { @@ -87,6 +106,8 @@ extension CommonQuestViewModel: ViewModelType { return } fetchCommonQuestByDate(date: currentDate, cursor: nextCursor) + case .likeButtonDidTap(let answerID): + postCommonQuestLike(answerID: answerID) } } } @@ -129,6 +150,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 13831ce158ea024146aad2623dd13a3d40dafe22 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:29:39 +0900 Subject: [PATCH 150/231] =?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 1220262da56e7b13bb5d9b320f23ce3c9f6d83cc Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:29:59 +0900 Subject: [PATCH 151/231] =?UTF-8?q?feat:=20#453=20=EA=B3=B5=EA=B0=90=20api?= =?UTF-8?q?=20=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 8b38adfe53596fe8b4883ca5c1ae87290bc130b5 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:30:28 +0900 Subject: [PATCH 152/231] =?UTF-8?q?feat:=20#453=20=EA=B3=B5=EA=B0=90=20API?= =?UTF-8?q?=20UseCase?= 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 3d189f5e..a9eb6c9d 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(answerID: 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 b036605c..2a96a856 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 b4528fddf014c53c9fcffe2829fd6254113d8657 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:30:40 +0900 Subject: [PATCH 153/231] =?UTF-8?q?chore:=20#453=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 4 ++++ .../Presentation/PresentationDependencyAssembler.swift | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index a91a27dd..76036b54 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -213,6 +213,10 @@ struct DomainDependencyAssembler: DependencyAssembler { return DefaultFetchCommonQuestDetailUseCase(repository: commonQuestRepository) } + DIContainer.shared.register(type: PostCommonQuestLikeUseCase.self) { _ in + return DefaultPostCommonQuestLikeUseCase(repository: commonQuestRepository) + } + DIContainer.shared.register(type: FetchNotificationListUseCase.self) { _ in return DefaultFetchNotificationListUseCase(repository: notificationRepository) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 8307ebc8..5e2903ca 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -290,6 +290,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: CommonQuestViewModel.self) { container in guard let fetchCommonQuestByDateUseCase = container.resolve(type: FetchCommonQuestByDateUseCase.self), + let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -298,6 +299,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { return CommonQuestViewModel( fetchCommonQuestByDateUseCase: fetchCommonQuestByDateUseCase, formatElapsedTimeUseCase: formatElapsedTimeUseCase + postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } From eab29fb352ac1f003693d4a9680d2b9a3e6dd388 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 17:44:32 +0900 Subject: [PATCH 154/231] =?UTF-8?q?feat:=20#453=20=EB=82=B4=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=ED=80=98=EC=8A=A4=ED=8A=B8=20=EB=AA=A8=EC=95=84?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EA=B3=B5=EA=B0=90=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestMyAnswersViewController.swift | 32 +++++++++++++++++-- .../CommonQuestMyAnswerViewModel.swift | 30 +++++++++++++++-- .../PresentationDependencyAssembler.swift | 6 ++-- 3 files changed, 62 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 d4de135f..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,9 +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? @@ -25,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() ) } @@ -59,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 { @@ -66,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) { @@ -80,6 +100,8 @@ extension CommonQuestMyAnswerViewModel: ViewModelType { fetchUserCommonQuestAnswers() case .scrollAnswer: fetchUserCommonQuestAnswers(cursor: nextCursor) + case .likeButtonDidTap(let answerID): + postCommonQuestLike(answerID: answerID) } } } @@ -90,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 5e2903ca..14e46a32 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -305,14 +305,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 21c9ecde35973b7131e389377ad9e99a6a134f7f Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 10 Jun 2026 18:01:10 +0900 Subject: [PATCH 155/231] =?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 14e46a32..12fed643 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -376,13 +376,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 0d1875035cb4a417cb0362c74a043f3166c3ca0c Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 14 Jun 2026 15:42:50 +0900 Subject: [PATCH 156/231] =?UTF-8?q?refactor:=20#453=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=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 2a96a856..8cfe7511 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 eb605a3e..9d18f9ac 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 @@ -24,6 +24,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, @@ -67,13 +68,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 } } } @@ -89,7 +98,7 @@ extension CommonQuestViewModel: ViewModelType { struct Output { let commonQuestPublisher: AnyPublisher, Never> - let commonQuestLikeCountPublisher: AnyPublisher, Never> + let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { From 33dfb8ab9016cbef58240e18bcd8a43744dd25cf Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 20 Jun 2026 00:37:41 +0900 Subject: [PATCH 157/231] =?UTF-8?q?chore:=20#453=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=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 9d18f9ac..518c5c4e 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() From dfbda4f3e8f13cc0cb22333b49fe75772ae6ac1b Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 23 Jun 2026 17:26:23 +0900 Subject: [PATCH 158/231] =?UTF-8?q?chore:=20#453=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=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 --- .../Quest/ViewModel/CommonQuestMyAnswerViewModel.swift | 6 ++++-- .../Feature/Quest/ViewModel/CommonQuestViewModel.swift | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) 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 6a2ed058..f6098b4e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -71,12 +71,14 @@ final class CommonQuestMyAnswerViewModel { private func postCommonQuestLike(answerID: Int) { likeTasks[answerID]?.cancel() - + likeTasks[answerID] = Task { do { let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) - guard !Task.isCancelled else { return } + try Task.checkCancellation() likeCountSubject.send(.success((answerID: 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 518c5c4e..c73a6970 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -73,8 +73,10 @@ final class CommonQuestViewModel { likeTasks[answerID] = Task { do { let entity = try await postCommonQuestLikeUseCase.execute(answerID: answerID) - guard !Task.isCancelled else { return } + try Task.checkCancellation() likeCountSubject.send(.success((answerID: answerID, entity))) + } catch is CancellationError { + ByeBooLogger.debug("Task 취소됨") } catch { guard let error = error as? ByeBooError else { return From d1a8a3fedec3dc11f247829b6ba797fed671be9c Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 23 Jun 2026 17:33:43 +0900 Subject: [PATCH 159/231] =?UTF-8?q?fix:=20#453=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj | 4 ++-- .../Feature/Quest/ViewModel/CommonQuestViewModel.swift | 2 +- .../Presentation/PresentationDependencyAssembler.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index 2387f417..7ab4201c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -329,7 +329,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS 1780581426"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -372,7 +372,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS 1780581426"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 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 c73a6970..1bee4d55 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -28,7 +28,7 @@ final class CommonQuestViewModel { init( fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase, - postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase + postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase, formatElapsedTimeUseCase: FormatElapsedTimeUseCase ) { self.fetchCommonQuestByDateUseCase = fetchCommonQuestByDateUseCase diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 12fed643..47717b00 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -290,7 +290,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: CommonQuestViewModel.self) { container in guard let fetchCommonQuestByDateUseCase = container.resolve(type: FetchCommonQuestByDateUseCase.self), - let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { + let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self), let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -298,8 +298,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { return CommonQuestViewModel( fetchCommonQuestByDateUseCase: fetchCommonQuestByDateUseCase, + postCommonQuestLikeUseCase: postCommonQuestLikeUseCase, formatElapsedTimeUseCase: formatElapsedTimeUseCase - postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } From a9832c679cf6eb6e5d58174c74517d2444271e92 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:22:00 +0900 Subject: [PATCH 160/231] =?UTF-8?q?feat:=20#459=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=AA=A8=EB=91=90=20=EC=9D=BD=EA=B8=B0=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20-=20=EA=B4=80=EB=A0=A8=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 4 ++++ .../Presentation/PresentationDependencyAssembler.swift | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 76036b54..10f9f403 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -232,5 +232,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchHasUnreadNotificationUseCase.self) { _ in return DefaultFetchHasUnreadNotificationUseCase(repository: notificationRepository) } + + DIContainer.shared.register(type: ReadAllNotificationsUseCase.self) { _ in + return DefaultReadAllNotificationsUseCase(repository: notificationRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 47717b00..8689338e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -362,7 +362,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: NotificationsViewModel.self) { container in guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self), let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self), - let readNotificationUseCase = container.resolve(type: ReadNotificationUseCase.self) + let readNotificationUseCase = container.resolve(type: ReadNotificationUseCase.self), + let readAllNotificationsUseCase = container.resolve(type: ReadAllNotificationsUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -371,7 +372,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { return NotificationsViewModel( fetchNotificationListUseCase: fetchNotificationListUseCase, formatElapsedTimeUseCase: formatElapsedTimeUseCase, - readNotificationUseCase: readNotificationUseCase + readNotificationUseCase: readNotificationUseCase, + readAllNotificationsUseCase: readAllNotificationsUseCase ) } From b091ef631976e625476f20afb3509088cf595941 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:22:33 +0900 Subject: [PATCH 161/231] =?UTF-8?q?feat:=20#459=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=AA=A8=EB=91=90=20=EC=9D=BD=EA=B8=B0=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20-=20Presentation=20=EC=98=81=EC=97=AD=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsViewController.swift | 23 +++++++++++- .../ViewModel/NotificationsViewModel.swift | 36 +++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index 1d0323fa..d6315d04 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -72,6 +72,11 @@ extension NotificationsViewController: BackNavigable { extension NotificationsViewController: ToastPresentable, ToastErrorHandler { private func bind() { + bindNotificationList() + bindAllNotificationsRead() + } + + private func bindNotificationList() { viewModel.output.notificationListResult .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -85,12 +90,28 @@ extension NotificationsViewController: ToastPresentable, ToastErrorHandler { } .store(in: &cancellables) } + + private func bindAllNotificationsRead() { + viewModel.output.readAllNotificationsResult + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success: + self?.rootView.contentView.noticeCardsView.cardTableView.reloadData() + case .failure(let error): + self?.handleError(error) + } + } + .store(in: &cancellables) + } } extension NotificationsViewController { @objc - private func readAllButtonDidTap() {} + private func readAllButtonDidTap() { + viewModel.action(.readAllNotificationsDidTap) + } } extension NotificationsViewController: UITableViewDataSource { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 415660c7..8cf959c3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -16,19 +16,26 @@ final class NotificationsViewModel { private let fetchNotificationListUseCase: FetchNotificationListUseCase private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase private let readNotificationUseCase: ReadNotificationUseCase + private let readAllNotificationsUseCase: ReadAllNotificationsUseCase private(set) var output: Output private var notificationListSubject = PassthroughSubject, Never>() + private var readAllNotificationsSubject = PassthroughSubject, Never>() init( fetchNotificationListUseCase: FetchNotificationListUseCase, formatElapsedTimeUseCase: FormatElapsedTimeUseCase, - readNotificationUseCase: ReadNotificationUseCase + readNotificationUseCase: ReadNotificationUseCase, + readAllNotificationsUseCase: ReadAllNotificationsUseCase ) { self.fetchNotificationListUseCase = fetchNotificationListUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase self.readNotificationUseCase = readNotificationUseCase - self.output = .init(notificationListResult: notificationListSubject.eraseToAnyPublisher()) + self.readAllNotificationsUseCase = readAllNotificationsUseCase + self.output = .init( + notificationListResult: notificationListSubject.eraseToAnyPublisher(), + readAllNotificationsResult: readAllNotificationsSubject.eraseToAnyPublisher() + ) } } @@ -36,10 +43,12 @@ extension NotificationsViewModel: ViewModelType { enum Input { case viewWillAppear case notificationDidTap(at: Int) + case readAllNotificationsDidTap } struct Output { let notificationListResult: AnyPublisher, Never> + let readAllNotificationsResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -48,6 +57,8 @@ extension NotificationsViewModel: ViewModelType { fetchNotificationList() case .notificationDidTap(let index): readNotification(at: index) + case .readAllNotificationsDidTap: + readAllNotifications() } } } @@ -90,6 +101,27 @@ extension NotificationsViewModel { handleNotification(at: index) } + + func readAllNotifications() { + guard let isAllRead = notifications?.allSatisfy({ $0.isRead }), + !isAllRead + else { + return + } + + Task { + do { + let _ = try await readAllNotificationsUseCase.execute() + self.notifications = notifications?.map { $0.toRead() } + readAllNotificationsSubject.send(.success(())) + } catch { + guard let error = error as? ByeBooError else { + return + } + readAllNotificationsSubject.send(.failure(error)) + } + } + } } extension NotificationsViewModel { From a74586b629756f5365a5f3e09c4452f72a992dcf Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:22:41 +0900 Subject: [PATCH 162/231] =?UTF-8?q?feat:=20#459=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=AA=A8=EB=91=90=20=EC=9D=BD=EA=B8=B0=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20-=20Domain=20=EC=98=81=EC=97=AD=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/NotificationListEntity.swift | 14 +++++++++++ .../Interface/NotificationInterface.swift | 1 + .../UseCase/ReadAllNotificationsUseCase.swift | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadAllNotificationsUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift index d1392bf0..852c50eb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -81,3 +81,17 @@ extension NotificationListEntity { ) } } + +extension NotificationEntity { + func toRead() -> Self { + .init( + notificationID: notificationID, + notificationType: notificationType, + title: title, + content: content, + isRead: true, + createdAt: createdAt, + landingURL: landingURL + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift index a6f6155d..c835dd3d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift @@ -9,4 +9,5 @@ protocol NotificationInterface { func fetchNotifications() async throws -> NotificationListEntity func fetchHasUnreadNotification() async throws -> HasUnreadNotificationEntity func readNotification(for notificationID: Int) async throws + func readAllNotifications() async throws } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadAllNotificationsUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadAllNotificationsUseCase.swift new file mode 100644 index 00000000..d7657f98 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReadAllNotificationsUseCase.swift @@ -0,0 +1,23 @@ +// +// ReadAllNotificationsUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/23/26. +// + +protocol ReadAllNotificationsUseCase { + func execute() async throws +} + +struct DefaultReadAllNotificationsUseCase: ReadAllNotificationsUseCase { + + private let repository: NotificationInterface + + init(repository: NotificationInterface) { + self.repository = repository + } + + func execute() async throws { + try await repository.readAllNotifications() + } +} From 7bccc23ac34ca4172c24bb82f324dca269c3897c Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:22:47 +0900 Subject: [PATCH 163/231] =?UTF-8?q?feat:=20#459=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=AA=A8=EB=91=90=20=EC=9D=BD=EA=B8=B0=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20-=20Data=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/EndPoint/NotificationAPI.swift | 13 ++++++++----- .../Data/Repository/NotificationRepository.swift | 4 ++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift index b9adf64b..a1bdc65c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -13,6 +13,7 @@ enum NotificationAPI { case fetchNotificationList case fetchUnreadNotification case readNotification(notificationID: Int) + case readAllNotifications } extension NotificationAPI: EndPoint { @@ -29,6 +30,8 @@ extension NotificationAPI: EndPoint { "/unread/status" case .readNotification(let notificationID): "/\(notificationID)/read" + case .readAllNotifications: + "/read-all" } } @@ -36,14 +39,14 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList, .fetchUnreadNotification: .get - case .readNotification: + case .readNotification, .readAllNotifications: .patch } } var headers: HeaderType { switch self { - case .fetchNotificationList, .fetchUnreadNotification, .readNotification: + case .fetchNotificationList, .fetchUnreadNotification, .readNotification, .readAllNotifications: .withAuth } } @@ -52,21 +55,21 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList, .fetchUnreadNotification: JSONEncoding.default - case .readNotification: + case .readNotification, .readAllNotifications: URLEncoding.default } } var queryParameters: [String : String]? { switch self { - case .fetchNotificationList, .fetchUnreadNotification, .readNotification: + case .fetchNotificationList, .fetchUnreadNotification, .readNotification, .readAllNotifications: nil } } var bodyParameters: Parameters? { switch self { - case .fetchNotificationList, .fetchUnreadNotification, .readNotification: + case .fetchNotificationList, .fetchUnreadNotification, .readNotification, .readAllNotifications: nil } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift index be2402bb..b50b7030 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -32,4 +32,8 @@ struct DefaultNotificationRepository: NotificationInterface { func readNotification(for notificationID: Int) async throws { try await networkService.request(NotificationAPI.readNotification(notificationID: notificationID)) } + + func readAllNotifications() async throws { + try await networkService.request(NotificationAPI.readAllNotifications) + } } From d70da46ea42669cfe12bbac0fc3ac297c36ccac2 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:50:34 +0900 Subject: [PATCH 164/231] =?UTF-8?q?refactor:=20#459=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95(toRead=20->=20markAsRea?= =?UTF-8?q?d)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift | 2 +- .../Feature/Home/ViewModel/NotificationsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift index 852c50eb..47a4a9d5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -83,7 +83,7 @@ extension NotificationListEntity { } extension NotificationEntity { - func toRead() -> Self { + func markAsRead() -> Self { .init( notificationID: notificationID, notificationType: notificationType, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 8cf959c3..9fe195b1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -112,7 +112,7 @@ extension NotificationsViewModel { Task { do { let _ = try await readAllNotificationsUseCase.execute() - self.notifications = notifications?.map { $0.toRead() } + self.notifications = notifications?.map { $0.markAsRead() } readAllNotificationsSubject.send(.success(())) } catch { guard let error = error as? ByeBooError else { From 189b8e6855a6691b85abc95268d63b8232e6e3cb Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 01:03:32 +0900 Subject: [PATCH 165/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EB=8B=AC=EA=B8=B0=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/CommonQuestCommentRequestDTO.swift | 13 ++++++ .../PostCommonQuestCommentUseCase.swift | 24 ++++++++++ ...tFieldView.swift => CommentTextView.swift} | 19 +++++++- .../CommonQuest/Common/QuestContentView.swift | 6 +-- .../CommonQuest/CommonQuestHistoryView.swift | 2 +- .../CommonQuestHistoryViewController.swift | 23 ++++++++-- .../CommonQuestHistoryViewModel.swift | 45 ++++++++++++++----- 7 files changed, 113 insertions(+), 19 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestCommentRequestDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift rename ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/{CommentTextFieldView.swift => CommentTextView.swift} (94%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestCommentRequestDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestCommentRequestDTO.swift new file mode 100644 index 00000000..faeebacd --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestCommentRequestDTO.swift @@ -0,0 +1,13 @@ +// +// CommonQuestCommentRequestDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +struct CommonQuestCommentRequestDTO: Encodable { + let content: String + let targetId: Int +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift new file mode 100644 index 00000000..00f09021 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift @@ -0,0 +1,24 @@ +// +// PostCommonQuestCommentUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +protocol PostCommonQuestCommentUseCase { + func execute(content: String, targetID: Int) async throws +} + +struct DefaultPostCommonQuestCommentUseCase: PostCommonQuestCommentUseCase { + private let repository: CommentInterface + + init(repository: CommentInterface) { + self.repository = repository + } + + func execute(content: String, targetID: Int) async throws { + try await repository.postComment(content: content, targetID: targetID) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift similarity index 94% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift index dab606a8..cc8e2424 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift @@ -1,5 +1,5 @@ // -// CommentTextFieldView.swift +// CommentTextView.swift // ByeBoo-iOS // // Created by 이나연 on 5/4/26. @@ -7,7 +7,14 @@ import UIKit +protocol CommonQuestCommentProtcol: AnyObject { + func postComment(content: String) +} + final class CommentTextView: BaseView { + + weak var delegate: CommonQuestCommentProtcol? + private let topBorderLine = UIView() private let textFieldContainer = UIView() private let textView = UITextView() @@ -69,6 +76,7 @@ final class CommentTextView: BaseView { confirmButton.do { $0.applyByeBooFont(style: .body2M16, text: "완료", color: .grayscale600) + $0.addTarget(self, action: #selector(confirmButtonDidTap), for: .touchUpInside) } } @@ -136,6 +144,14 @@ extension CommentTextView: UITextViewDelegate { } } +extension CommentTextView { + @objc + private func confirmButtonDidTap() { + delegate?.postComment(content: textView.text) + endEditing(true) + } +} + extension CommentTextView { private func updateTextViewLayoutWhenEditing() { textView.textContainer.maximumNumberOfLines = 0 @@ -160,6 +176,7 @@ extension CommentTextView { } countConfirmContainer.snp.makeConstraints { + $0.leading.equalToSuperview() $0.trailing.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(8.adjustedH) $0.height.equalTo(40.adjustedH) 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 8306dc88..cded9918 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 @@ -7,8 +7,8 @@ import UIKit -protocol CommonQuestLikeCommentProtocol: AnyObject { - func likeButtonDidTap(answerID: Int) +protocol CommonQuestLikeProtocol: AnyObject { + func likeButtonDidTap() } final class QuestContentView: BaseView { @@ -24,7 +24,7 @@ final class QuestContentView: BaseView { private let commentIcon = UIImageView() private let commentCountLabel = UILabel() - weak var delegate: CommonQuestLikeCommentProtocol? + weak var delegate: CommonQuestLikeProtocol? private var answerID: Int = 0 private var likeCounts: Int = 0 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 0554be48..54b11367 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 @@ -25,7 +25,7 @@ final class CommonQuestHistoryView: BaseView { private let userNicknameLabel = UILabel() private(set) var questContentView = QuestContentView() private(set) var commentListView = SelfSizingTableView() - private let commentTextView = CommentTextView() + private(set) var commentTextView = CommentTextView() override func setStyle() { scrollView.do { 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 8079f645..31c50198 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -39,7 +39,7 @@ final class CommonQuestHistoryViewController: BaseViewController { self.tabBarController?.tabBar.isHidden = true addKeyboardObservers() - viewModel.action(.viewWillAppear(answerID: answerID)) + viewModel.action(.fetchQuestDetail(answerID: answerID)) Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } @@ -76,7 +76,7 @@ final class CommonQuestHistoryViewController: BaseViewController { $0.separatorStyle = .none $0.register(CommentTableViewCell.self) } - rootView.questContentView.delegate = self + rootView.commentTextView.delegate = self } } @@ -87,7 +87,6 @@ extension CommonQuestHistoryViewController { } extension CommonQuestHistoryViewController: BackNavigable { - func back() { self.navigationController?.popViewController(animated: false) } @@ -213,6 +212,12 @@ extension CommonQuestHistoryViewController: BlockReportProtocol { } } +extension CommonQuestHistoryViewController: CommonQuestCommentProtcol { + func postComment(content: String) { + viewModel.action(.postComment(answerID: answerID, content: content)) + } +} + extension CommonQuestHistoryViewController { @objc private func dismissKeyboard() { @@ -245,6 +250,18 @@ extension CommonQuestHistoryViewController { } } .store(in: &cancellable) + + viewModel.output.postCommentPublisher + .receive(on: DispatchQueue.main) + .sink { result in + switch result { + case .success: + ByeBooLogger.debug("댓글 입력 성공") + case .failure(let error): + ByeBooLogger.debug(error) + } + } + .store(in: &cancellable) } private func bindData(entity: CommonQuestDetailEntity) { 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 2edfd06d..c8c85bd6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -11,9 +11,11 @@ import Foundation final class CommonQuestHistoryViewModel { private let fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase + private let postCommentUseCase: PostCommonQuestCommentUseCase - private let fetchCommentListSubject: PassthroughSubject, Never> = .init() private let likeCountSubject = PassthroughSubject, Never>.init() + private let fetchQuestDetailSubject: PassthroughSubject, Never> = .init() + private let postCommentSubject: PassthroughSubject, Never> = .init() private var entity: CommonQuestDetailEntity? = nil private var cancellables = Set() @@ -22,13 +24,16 @@ final class CommonQuestHistoryViewModel { init( fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase, + postCommentUseCase: PostCommonQuestCommentUseCase postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase ) { self.fetchCommonQuestCommentsUseCase = fetchCommonQuestCommentsUseCase + self.postCommentUseCase = postCommentUseCase self.postCommonQuestLikeUseCase = postCommonQuestLikeUseCase output = Output( - fetchCommonQuestDetailPublisher: fetchCommentListSubject.eraseToAnyPublisher(), + fetchCommonQuestDetailPublisher: fetchQuestDetailSubject.eraseToAnyPublisher(), + postCommentPublisher: postCommentSubject.eraseToAnyPublisher() commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } @@ -36,19 +41,25 @@ final class CommonQuestHistoryViewModel { extension CommonQuestHistoryViewModel: ViewModelType { enum Input { - case viewWillAppear(answerID: Int) + case fetchQuestDetail(answerID: Int) + case postComment(answerID: Int, content: String) case likeButtonDidTap(answerID: Int) } struct Output { let fetchCommonQuestDetailPublisher: AnyPublisher, Never> + let postCommentPublisher: AnyPublisher,Never> let commonQuestLikeCountPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { switch trigger { - case .viewWillAppear(let answerID): - fetchCommonQuestComments(answerID: answerID) + case .fetchQuestDetail(let answerID): + Task { + await fetchCommonQuestComments(answerID: answerID) + } + case .postComment(let answerID, let content): + postComment(answerID: answerID, content: content) case .likeButtonDidTap(let answerID): postCommonQuestLike(answerID: answerID) } @@ -70,15 +81,27 @@ extension CommonQuestHistoryViewModel { } extension CommonQuestHistoryViewModel { - private func fetchCommonQuestComments(answerID: Int) { + private func fetchCommonQuestComments(answerID: Int) async { + do { + entity = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) + guard let entity else { return } + ByeBooLogger.debug("entity \(entity)") + fetchQuestDetailSubject.send(.success(entity)) + } catch { + guard let error = error as? ByeBooError else { return } + fetchQuestDetailSubject.send(.failure(error)) + } + } + + private func postComment(answerID: Int, content: String) { Task { do { - entity = try await fetchCommonQuestCommentsUseCase.execute(answerID: answerID) - guard let entity else { return } - ByeBooLogger.debug("entity \(entity)") - fetchCommentListSubject.send(.success(entity)) + _ = try await postCommentUseCase.execute(content: content, targetID: answerID) + ByeBooLogger.debug("content: \(content)") + await fetchCommonQuestComments(answerID: answerID) + postCommentSubject.send(.success(())) } catch(let error as ByeBooError) { - fetchCommentListSubject.send(.failure(error)) + postCommentSubject.send(.failure(error)) } } } From 06a9cfd4ff283d5c60a5fb58ebd415bd159b7cfc Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 01:03:59 +0900 Subject: [PATCH 166/231] =?UTF-8?q?chore:=20#456=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/DataDependencyAssembler.swift | 3 +++ .../Domain/DomainDependencyAssembler.swift | 15 +++++++++++---- .../PresentationDependencyAssembler.swift | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift index d5393fbd..5edff9b5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift @@ -71,6 +71,9 @@ struct DataDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: NotificationInterface.self) { _ in return DefaultNotificationRepository(networkService: networkService) } + DIContainer.shared.register(type: CommentInterface.self) { _ in + return DefaultCommentRepository(network: networkService) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 10f9f403..486ddcf6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -23,11 +23,12 @@ struct DomainDependencyAssembler: DependencyAssembler { let forbiddenWordRepository = DIContainer.shared.resolve(type: ForbiddenWordInterface.self), let commonQuestRepository = DIContainer.shared.resolve(type: CommonQuestInterface.self), let blocksRepository = DIContainer.shared.resolve(type: BlocksInterface.self), - let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self), let notificationRepository = DIContainer.shared.resolve(type: NotificationInterface.self) else { - ByeBooLogger.error(ByeBooError.DIFailedError) - return - } + let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self), + let commentRepository = DIContainer.shared.resolve(type: CommentInterface.self) else { + ByeBooLogger.error(ByeBooError.DIFailedError) + return + } DIContainer.shared.register(type: FetchUserJourneyUseCase.self) { _ in return DefaultFetchUserJourneyUseCase(repository: userRepository) @@ -212,6 +213,12 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchCommonQuestDetailUseCase.self) { _ in return DefaultFetchCommonQuestDetailUseCase(repository: commonQuestRepository) } + DIContainer.shared.register(type: PostCommonQuestCommentUseCase.self) { _ in + return DefaultPostCommonQuestCommentUseCase(repository: commentRepository) + } + DIContainer.shared.register(type: PostCommonQuestReplyUseCase.self) { _ in + return DefaultPostCommonQuestReplyUseCase(repository: commentRepository) + } 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 8689338e..724b942e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -379,6 +379,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: CommonQuestHistoryViewModel.self) { container in guard let fetchCommonQuestDetailUseCase = container.resolve(type: FetchCommonQuestDetailUseCase.self), + let postCommentUsecase = container.resolve(type: PostCommonQuestCommentUseCase.self) else { let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -386,6 +387,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { return CommonQuestHistoryViewModel( fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase, + postCommentUseCase: postCommentUsecase postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } From 02d4957732b9fe870ff2e78861dcaf49351fec6e Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:51:13 +0900 Subject: [PATCH 167/231] =?UTF-8?q?chore:=20#456=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/DataDependencyAssembler.swift | 5 ++++- .../Domain/DomainDependencyAssembler.swift | 3 +++ .../PresentationDependencyAssembler.swift | 13 +++++++++++++ .../Presentation/ViewControllerFactory.swift | 9 +++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift index 5edff9b5..38543a4b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift @@ -72,7 +72,10 @@ struct DataDependencyAssembler: DependencyAssembler { return DefaultNotificationRepository(networkService: networkService) } DIContainer.shared.register(type: CommentInterface.self) { _ in - return DefaultCommentRepository(network: networkService) + return DefaultCommentRepository( + network: networkService, + userDefaultService: userDefaultService + ) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 486ddcf6..4c93212f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -239,6 +239,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchHasUnreadNotificationUseCase.self) { _ in return DefaultFetchHasUnreadNotificationUseCase(repository: notificationRepository) } + DIContainer.shared.register(type: FetchCommonQuestRepliesUseCase.self) { _ in + return DefaultFetchCommonQuestRepliesUseCase(repository: commentRepository) + } DIContainer.shared.register(type: ReadAllNotificationsUseCase.self) { _ in return DefaultReadAllNotificationsUseCase(repository: notificationRepository) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 724b942e..d1603f45 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -391,5 +391,18 @@ struct PresentationDependencyAssembler: DependencyAssembler { postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } + + DIContainer.shared.register(type: CommonQuestReplyViewModel.self) { container in + guard let fetchReplyUseCase = container.resolve(type: FetchCommonQuestRepliesUseCase.self), + let postReplyUseCase = container.resolve(type: PostCommonQuestReplyUseCase.self) else { + ByeBooLogger.error(ByeBooError.DIFailedError) + return + } + + return CommonQuestReplyViewModel( + fetchRepliesUseCase: fetchReplyUseCase, + postReplyUseCase: postReplyUseCase + ) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index 7ad73461..05799ec3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -250,6 +250,15 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { func makeByeBooTabBar() -> ByeBooTabBar { .init() } + + func makeCommonQuestReplyViewController() -> CommonQuestReplyViewController { + guard let viewModel = DIContainer.shared.resolve(type: CommonQuestReplyViewModel.self) else { + DIErrorHandle() + fatalError() + } + + return .init(viewModel: viewModel) + } } extension ViewControllerFactory { From 850fc1a2e2877836fcdfb1dc422268bb8581214f Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:51:38 +0900 Subject: [PATCH 168/231] =?UTF-8?q?fix:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=ED=85=8D=EC=8A=A4=ED=8A=B8=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/View/CommonQuest/Common/CommentTextView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift index cc8e2424..a2816eb3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift @@ -149,6 +149,7 @@ extension CommentTextView { private func confirmButtonDidTap() { delegate?.postComment(content: textView.text) endEditing(true) + textView.text = "" } } From ce8ae3dd68dbe34f98e743561e5ede7b52f8c347 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:52:35 +0900 Subject: [PATCH 169/231] =?UTF-8?q?feat:=20#456=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20DTO,=20Entity=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestAnswerRepliesResponseDTO.swift | 26 +++++++------------ .../Model/CommonQuestReplyRequestDTO.swift | 12 +++++++++ .../Entity/CommonQuestReplyListEntity.swift | 13 ++++++++++ 3 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestReplyRequestDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestReplyListEntity.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift index 46a28640..fd5a96bd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift @@ -10,7 +10,7 @@ import Foundation struct CommonQuestAnswerRepliesResponseDTO: Decodable { let totalCount: Int let comment: CommonQuestAnswerCommentResponseDTO - let replies: [CommonQuestAnswerReplyResponseDTO] + let replies: [CommonQuestAnswerCommentResponseDTO] } struct CommonQuestAnswerCommentResponseDTO: Decodable { @@ -22,27 +22,19 @@ struct CommonQuestAnswerCommentResponseDTO: Decodable { let writerId: Int } -struct CommonQuestAnswerReplyResponseDTO: Decodable { - let content: String - let writer: String - let createdAt: String - let profileIcon: String - let commentId: Int - let writerId: Int -} - extension CommonQuestAnswerRepliesResponseDTO { - func toEntity(userName: String) -> [CommonQuestCommentEntity] { - replies.map { - $0.toEntity(userName: userName) - } + func toEntity(userID: Int) -> CommonQuestReplyListEntity { + .init( + totalCount: totalCount, + replies: replies.map { $0.toEntity(userID: userID) } + ) } } -extension CommonQuestAnswerReplyResponseDTO { - func toEntity(userName: String) -> CommonQuestCommentEntity { +extension CommonQuestAnswerCommentResponseDTO { + func toEntity(userID: Int) -> CommonQuestCommentEntity { .init( - isMyComment: userName == writer ? true : false, + isMyComment: userID == writerId ? true : false, commentID: commentId, replyCount: nil, writerID: writerId, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestReplyRequestDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestReplyRequestDTO.swift new file mode 100644 index 00000000..87a48405 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestReplyRequestDTO.swift @@ -0,0 +1,12 @@ +// +// CommonQuestReplyRequestDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +struct CommonQuestReplyRequestDTO: Encodable { + let content: String +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestReplyListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestReplyListEntity.swift new file mode 100644 index 00000000..d0413ab1 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestReplyListEntity.swift @@ -0,0 +1,13 @@ +// +// CommonQuestReplyListEntity.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/15/26. +// + +import Foundation + +struct CommonQuestReplyListEntity { + let totalCount: Int + let replies: [CommonQuestCommentEntity] +} From ffc5247edf643c7b997314206c04ac03566e9cea Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:53:00 +0900 Subject: [PATCH 170/231] =?UTF-8?q?feat:=20#456=20CommentAPI=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=EB=B3=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/EndPoint/CommentAPI.swift | 71 +++++++++++++++++++ .../Data/Repository/CommentRepository.swift | 41 +++++++++++ .../Domain/Interface/CommentInterface.swift | 14 ++++ .../FetchCommonQuestRepliesUseCase.swift | 24 +++++++ .../UseCase/PostCommonQuestReplyUseCase.swift | 24 +++++++ 5 files changed, 174 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestRepliesUseCase.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestReplyUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift new file mode 100644 index 00000000..3de789dc --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift @@ -0,0 +1,71 @@ +// +// CommentAPI.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +import Alamofire + +enum CommentAPI { + case postComment(dto: CommonQuestCommentRequestDTO) + case postReply(commentID: Int, dto: CommonQuestReplyRequestDTO) + case fetchReplies(commentID: Int) +} + +extension CommentAPI: EndPoint { + var basePath: String { + return "/api/v1/comments" + } + + var path: String { + switch self { + case .postComment: + return "" + case .postReply(let commentID, _), .fetchReplies(let commentID): + return "/\(commentID)/replies" + } + } + + var method: Alamofire.HTTPMethod { + switch self { + case .postComment, .postReply: + return .post + case .fetchReplies: + return .get + } + } + + var headers: HeaderType { + switch self { + case .postComment, .postReply, .fetchReplies: + return .withAuth + } + } + + var parameterEncoding: any Alamofire.ParameterEncoding { + switch self { + case .postComment, .postReply: + return JSONEncoding.default + case .fetchReplies: + return URLEncoding.default + } + } + + var queryParameters: [String : String]? { + nil + } + + var bodyParameters: Alamofire.Parameters? { + switch self { + case .postComment(let dto): + return try? dto.toDictionary() + case .postReply(_, let dto): + return try? dto.toDictionary() + case .fetchReplies: + return nil + } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift new file mode 100644 index 00000000..a2e1d01b --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift @@ -0,0 +1,41 @@ +// +// CommentRepository.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +struct DefaultCommentRepository: CommentInterface { + + private let network: NetworkService + private let userDefaultServcie: UserDefaultService + + init( + network: NetworkService, + userDefaultService: UserDefaultService + ) { + self.network = network + self.userDefaultServcie = userDefaultService + } + + func postComment(content: String, targetID: Int) async throws { + let postCommentRequestDTO: CommonQuestCommentRequestDTO = .init(content: content, targetId: targetID) + let _ = try await network.request(CommentAPI.postComment(dto: postCommentRequestDTO)) + } + + func postReply(content: String, commentID: Int) async throws { + let postReplyRequestDTO: CommonQuestReplyRequestDTO = .init(content: content) + let _ = try await network.request(CommentAPI.postReply(commentID: commentID, dto: postReplyRequestDTO)) + } + + func fetchReplies(commentID: Int) async throws -> CommonQuestReplyListEntity { + let userID: Int = userDefaultServcie.load(key: .userID) ?? 0 + let result = try await network.request( + CommentAPI.fetchReplies(commentID: commentID), + decodingType: CommonQuestAnswerRepliesResponseDTO.self + ) + return result.toEntity(userID: userID) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift new file mode 100644 index 00000000..787f5f86 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift @@ -0,0 +1,14 @@ +// +// CommentInterface.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +protocol CommentInterface { + func postComment(content: String, targetID: Int) async throws + func postReply(content: String, commentID: Int) async throws + func fetchReplies(commentID: Int) async throws -> CommonQuestReplyListEntity +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestRepliesUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestRepliesUseCase.swift new file mode 100644 index 00000000..c89dbc67 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchCommonQuestRepliesUseCase.swift @@ -0,0 +1,24 @@ +// +// FetchCommonQuestRepliesUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/15/26. +// + +import Foundation + +protocol FetchCommonQuestRepliesUseCase { + func execute(commentID: Int) async throws -> CommonQuestReplyListEntity +} + +struct DefaultFetchCommonQuestRepliesUseCase: FetchCommonQuestRepliesUseCase { + private let repository: CommentInterface + + init(repository: CommentInterface) { + self.repository = repository + } + + func execute(commentID: Int) async throws -> CommonQuestReplyListEntity { + return try await repository.fetchReplies(commentID: commentID) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestReplyUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestReplyUseCase.swift new file mode 100644 index 00000000..d52440b9 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestReplyUseCase.swift @@ -0,0 +1,24 @@ +// +// PostCommonQuestReplyUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/14/26. +// + +import Foundation + +protocol PostCommonQuestReplyUseCase { + func execute(content: String, commentID: Int) async throws +} + +struct DefaultPostCommonQuestReplyUseCase: PostCommonQuestReplyUseCase { + private let repository: CommentInterface + + init(repository: CommentInterface) { + self.repository = repository + } + + func execute(content: String, commentID: Int) async throws { + try await repository.postReply(content: content, commentID: commentID) + } +} From 334c8df08844125c39da46348e62af2a2352adb4 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:53:18 +0900 Subject: [PATCH 171/231] =?UTF-8?q?feat:=20#456=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EB=8B=B5=EA=B8=80=20=EB=8B=AC?= =?UTF-8?q?=EA=B8=B0=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/CommonQuestReplyView.swift | 2 +- .../CommonQuestReplyViewController.swift | 82 +++++++++++++++++-- .../ViewModel/CommonQuestReplyViewModel.swift | 74 +++++++++++++++++ 3 files changed, 151 insertions(+), 7 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift index 8a2c149a..5d1a9d56 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift @@ -14,7 +14,7 @@ final class CommonQuestReplyView: BaseView { private(set) var backButton = UIButton() private let headerLabel = UILabel() private(set) var commentListView = UITableView() - private let commentTextView = CommentTextView() + private(set) var commentTextView = CommentTextView() private var commentTextViewBottomConstraint: Constraint? diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index e7436453..f8fbe248 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -7,12 +7,30 @@ import UIKit +import Combine + final class CommonQuestReplyViewController: BaseViewController { private let rootView = CommonQuestReplyView() - private let viewModel = CommonQuestReplyViewModel() + private let viewModel: CommonQuestReplyViewModel + private var commentEntity: CommonQuestCommentEntity? = nil + private var commentID: Int = 0 + + var onReplyCountChanged: ((Int, Int) -> Void)? + private var dataSource: UITableViewDiffableDataSource! + private var cancellable = Set() + + + init(viewModel: CommonQuestReplyViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } override func loadView() { view = rootView @@ -22,13 +40,15 @@ final class CommonQuestReplyViewController: BaseViewController { super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = true addKeyboardObservers() + + viewModel.action(.fetchReplyList(commentID: commentID)) } override func viewDidLoad() { super.viewDidLoad() configureDataSource() - applySnapshot() + bind() } override func viewWillDisappear(_ animated: Bool) { @@ -46,6 +66,48 @@ final class CommonQuestReplyViewController: BaseViewController { $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "comment") $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "reply") } + rootView.commentTextView.delegate = self + } +} + +extension CommonQuestReplyViewController { + func configure( + entity: CommonQuestCommentEntity, + commentID: Int + ) { + self.commentEntity = entity + self.commentID = commentID + } + + private func bind() { + viewModel.output.fetchReplyListPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let replyList): + if let cell = self?.rootView.commentListView.cellForRow(at: IndexPath(row: 0, section: 0)) as? CommentTableViewCell { + cell.updateReplyCount(replyCount: replyList.totalCount) + } + self?.applySnapshot(entity: replyList.replies) + self?.onReplyCountChanged?(self?.commentID ?? 0, replyList.totalCount) + case .failure(let error): + ByeBooLogger.debug(error) + } + } + .store(in: &cancellable) + + viewModel.output.postReplyPublisher + .receive(on: DispatchQueue.main) + .sink { result in + switch result { + case .success: + ByeBooLogger.debug("답글 입력 성공") + case .failure(let error): + ByeBooLogger.debug(error) + } + } + .store(in: &cancellable) + } } @@ -67,7 +129,7 @@ extension CommonQuestReplyViewController { replyCount: entity.replyCount, writer: entity.writer, profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, - writtenAt: entity.writtenAt, + writtenAt: ServerDateFormatter.shared.relativeTimeString(from: entity.writtenAt) ?? "", content: entity.content, showAllText: item.showAllText, isReplySheet: true @@ -77,13 +139,15 @@ extension CommonQuestReplyViewController { } } - private func applySnapshot() { + private func applySnapshot(entity: [CommonQuestCommentEntity]) { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.comment, .replies]) - let commentItem = CommentItem(entity: viewModel.getCommentContent(), showAllText: false) + + guard let commentEntity else { return } + let commentItem = CommentItem(entity: commentEntity, showAllText: false) snapshot.appendItems([commentItem], toSection: .comment) - let replyItems = viewModel.getReplyList().map { CommentItem(entity: $0, showAllText: false) } + let replyItems = entity.map { CommentItem(entity: $0, showAllText: false) } snapshot.appendItems(replyItems, toSection: .replies) dataSource.apply(snapshot, animatingDifferences: false) @@ -129,6 +193,12 @@ extension CommonQuestReplyViewController: CommentProtocol { } } +extension CommonQuestReplyViewController: CommonQuestCommentProtcol { + func postComment(content: String) { + viewModel.action(.postReply(commentID: commentID, content: content)) + } +} + extension CommonQuestReplyViewController: KeyboardHandleProtocol { func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift index e0d5c0cd..46250dc3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift @@ -7,7 +7,31 @@ import Foundation +import Combine + final class CommonQuestReplyViewModel { + + private let fetchRepliesUseCase: FetchCommonQuestRepliesUseCase + private let postReplyUseCase: PostCommonQuestReplyUseCase + + private let fetchRepliesSubject: PassthroughSubject, Never> = .init() + private let postReplySubject: PassthroughSubject, Never> = .init() + private var cancellables = Set() + private(set) var output: Output + + init( + fetchRepliesUseCase: FetchCommonQuestRepliesUseCase, + postReplyUseCase: PostCommonQuestReplyUseCase + ) { + self.fetchRepliesUseCase = fetchRepliesUseCase + self.postReplyUseCase = postReplyUseCase + + output = Output( + fetchReplyListPublisher: fetchRepliesSubject.eraseToAnyPublisher(), + postReplyPublisher: postReplySubject.eraseToAnyPublisher() + ) + } + func getCommentContent() -> CommonQuestCommentEntity { return CommonQuestCommentEntity.toCommentStub() } @@ -16,3 +40,53 @@ final class CommonQuestReplyViewModel { return CommonQuestCommentEntity.toReplyListStub() } } + +extension CommonQuestReplyViewModel { + enum Input { + case fetchReplyList(commentID: Int) + case postReply(commentID: Int, content: String) + } + + struct Output { + let fetchReplyListPublisher: AnyPublisher, Never> + let postReplyPublisher: AnyPublisher,Never> + } + + func action(_ trigger: Input) { + switch trigger { + case .fetchReplyList(let commentID): + Task { + await fetchReplyList(commentID: commentID) + } + case .postReply(let commentID, let content): + postReply(commentID: commentID, content: content) + } + } +} + +extension CommonQuestReplyViewModel { + private func fetchReplyList(commentID: Int) async { + do { + let result = try await fetchRepliesUseCase.execute(commentID: commentID) + ByeBooLogger.debug("답글 목록 \(result)") + fetchRepliesSubject.send(.success(result)) + } catch { + guard let error = error as? ByeBooError else { return } + fetchRepliesSubject.send(.failure(error)) + } + } + + private func postReply(commentID: Int, content: String) { + Task { + do { + _ = try await postReplyUseCase.execute(content: content, commentID: commentID ) + ByeBooLogger.debug("답글 달기 성공") + await fetchReplyList(commentID: commentID) + postReplySubject.send(.success(())) + } catch { + guard let error = error as? ByeBooError else { return } + postReplySubject.send(.failure(error)) + } + } + } +} From 0ac729131ce6eda88fa96597ceb90d7c9afeabc8 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:53:36 +0900 Subject: [PATCH 172/231] =?UTF-8?q?feat:=20#456=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EC=B9=B4=EC=9A=B4=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CommonQuest/Cells/CommentTableViewCell.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index 0e4ba3bb..9fa1dafb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -211,8 +211,9 @@ extension CommentTableViewCell { showAllText: Bool, isReplySheet: Bool ) { - if replyCount != nil { + if let replyCount { setCommentListLayout() + updateReplyCount(replyCount: replyCount) } else { setReplySheetLayout() } @@ -243,6 +244,11 @@ extension CommentTableViewCell { applyStyleWhenHideText(numberOfLines) } } + + func updateReplyCount(replyCount: Int) { + self.replyCount = replyCount + replyCountLabel.text = "\(replyCount)" + } } extension CommentTableViewCell { From 651facdde04ba115ca59b5847201a1ca218809ee Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:54:08 +0900 Subject: [PATCH 173/231] =?UTF-8?q?fix:=20#456=20=EB=B0=94=ED=85=80?= =?UTF-8?q?=EC=8B=9C=ED=8A=B8=EA=B0=80=20=EB=8B=AB=ED=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=83=EC=9D=84=20=EC=BD=9C=EB=B0=B1=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=20history=EC=97=90=EC=84=9C=20=EB=8B=B5?= =?UTF-8?q?=EA=B8=80=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 31c50198..d883bfd6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -186,7 +186,21 @@ extension CommonQuestHistoryViewController: CommentProtocol { } func replyIconDidTap(commentID: Int) { - let viewController = CommonQuestReplyViewController() + let viewController = ViewControllerFactory.shared.makeCommonQuestReplyViewController() + let entity = viewModel.getComment(commentID: commentID) + guard let entity else { return } + viewController.configure(entity: entity, commentID: commentID) + + viewController.onReplyCountChanged = { [weak self] commentID, newCount in + guard let self else { return } + let item = dataSource.snapshot().itemIdentifiers.first { $0.entity.commentID == commentID } + guard let item, + let indexPath = dataSource.indexPath(for: item), + let cell = rootView.commentListView.cellForRow(at: indexPath) as? CommentTableViewCell + else { return } + cell.updateReplyCount(replyCount: newCount) + } + if let sheet = viewController.sheetPresentationController{ sheet.detents = [.large()] sheet.prefersGrabberVisible = true From b1216ae825767c9ebacb10834ace03cf5c17d853 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 15 Jun 2026 03:54:26 +0900 Subject: [PATCH 174/231] =?UTF-8?q?feat:=20#456=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=EC=BD=94=EB=A9=98=ED=8A=B8=20=EC=95=84=EC=9D=B4=EB=94=94=20?= =?UTF-8?q?=EB=B9=84=EA=B5=90=ED=95=98=EC=97=AC=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EB=A6=AC=ED=84=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift | 4 ++++ 1 file changed, 4 insertions(+) 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 c8c85bd6..53686dbb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -78,6 +78,10 @@ extension CommonQuestHistoryViewModel { var commentList: [CommonQuestCommentEntity]? { return entity?.comments } + + func getComment(commentID: Int) -> CommonQuestCommentEntity? { + entity?.comments.first { $0.commentID == commentID } + } } extension CommonQuestHistoryViewModel { From 41fb69f23bcaa418c1c3886c1be2d56fe67f42d0 Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 16 Jun 2026 17:14:52 +0900 Subject: [PATCH 175/231] =?UTF-8?q?chore:=20#456=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestReplyViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index f8fbe248..32fdeac8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -5,9 +5,8 @@ // Created by 이나연 on 5/8/26. // -import UIKit - import Combine +import UIKit final class CommonQuestReplyViewController: BaseViewController { From 5d7a962a618b109ed46a20df93225b643d087cf4 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 22 Jun 2026 02:02:14 +0900 Subject: [PATCH 176/231] =?UTF-8?q?feat:=20#456=20=EC=8B=A0=EA=B3=A0=20v2?= =?UTF-8?q?=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/Model/ReportRequestDTO.swift | 13 +++++++++++++ .../Data/Network/EndPoint/ReportsAPI.swift | 16 ++++++++-------- .../Data/Repository/ReportsRepository.swift | 8 ++++---- .../Domain/Interface/ReportsInterface.swift | 2 +- .../ReportsCommonQuestAnswerUseCase.swift | 6 +++--- .../Enum/CommonQuestTargetType.swift | 13 +++++++++++++ 6 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/ReportRequestDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestTargetType.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/ReportRequestDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/ReportRequestDTO.swift new file mode 100644 index 00000000..b99f56c6 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/ReportRequestDTO.swift @@ -0,0 +1,13 @@ +// +// ReportRequestDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/16/26. +// + +import Foundation + +struct ReportRequestDTO: Encodable { + let targetType: String + let targetId: Int +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/ReportsAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/ReportsAPI.swift index b6e70ab3..1a4f6064 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/ReportsAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/ReportsAPI.swift @@ -10,18 +10,18 @@ import Foundation import Alamofire enum ReportsAPI { - case postReport(answerID: Int) + case postReport(dto: ReportRequestDTO) } extension ReportsAPI: EndPoint { var basePath: String { - return "/api/v1/reports" + return "/api/v2/reports" } var path: String { switch self { - case .postReport(let answerID): - return "/common-quests/\(answerID)" + case .postReport: + return "" } } @@ -33,7 +33,6 @@ extension ReportsAPI: EndPoint { } var headers: HeaderType { - let keychainService = DefaultKeychainService() return .withAuth } @@ -49,8 +48,9 @@ extension ReportsAPI: EndPoint { } var bodyParameters: Alamofire.Parameters? { - nil + switch self { + case .postReport(let dto): + return try? dto.toDictionary() + } } - - } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/ReportsRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/ReportsRepository.swift index 3c1fd9ad..7b492ec2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/ReportsRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/ReportsRepository.swift @@ -15,9 +15,9 @@ struct DefaultReportsRepository: ReportsInterface { self.networkService = networkService } - func reportCommonQuest(answerID: Int) async throws { - let _ = try await networkService.request( - ReportsAPI.postReport(answerID: answerID) - ) + func reportCommonQuest(targetID: Int, targetType: CommonQuestTargetType) async throws { + let reportRequestDTO: ReportRequestDTO = .init(targetType: targetType.rawValue, targetId: targetID) + ByeBooLogger.debug("\(targetID), \(targetType)") + _ = try await networkService.request(ReportsAPI.postReport(dto: reportRequestDTO)) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ReportsInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ReportsInterface.swift index c7b35587..64677646 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ReportsInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ReportsInterface.swift @@ -6,5 +6,5 @@ // protocol ReportsInterface { - func reportCommonQuest(answerID: Int) async throws + func reportCommonQuest(targetID: Int, targetType: CommonQuestTargetType) async throws } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReportsCommonQuestAnswerUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReportsCommonQuestAnswerUseCase.swift index a2266e96..a37329fd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReportsCommonQuestAnswerUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/ReportsCommonQuestAnswerUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol ReportsCommonQuestAnswerUseCase { - func execute(answerID: Int) async throws + func execute(targetID: Int, targetType: CommonQuestTargetType) async throws } struct DefaultReportsCommonQuestAnswerUseCase: ReportsCommonQuestAnswerUseCase { @@ -21,7 +21,7 @@ struct DefaultReportsCommonQuestAnswerUseCase: ReportsCommonQuestAnswerUseCase { self.repository = repository } - func execute(answerID: Int) async throws { - let _ = try await repository.reportCommonQuest(answerID: answerID) + func execute(targetID: Int, targetType: CommonQuestTargetType) async throws { + let _ = try await repository.reportCommonQuest(targetID: targetID, targetType: targetType) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestTargetType.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestTargetType.swift new file mode 100644 index 00000000..b0d7f543 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestTargetType.swift @@ -0,0 +1,13 @@ +// +// CommonQuestTargetType.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/16/26. +// + +import Foundation + +enum CommonQuestTargetType: String { + case COMMENT + case COMMON_QUEST +} From 3408e71d2f7736a6ee13369a1738c865a5438d72 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 22 Jun 2026 02:30:22 +0900 Subject: [PATCH 177/231] =?UTF-8?q?chore:=20#456=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 6 ++++++ .../Presentation/PresentationDependencyAssembler.swift | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 4c93212f..19868c53 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -242,6 +242,12 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchCommonQuestRepliesUseCase.self) { _ in return DefaultFetchCommonQuestRepliesUseCase(repository: commentRepository) } + DIContainer.shared.register(type: DeleteCommentReplyUseCase.self) { _ in + return DefaultDeleteCommentReplyUseCase(repository: commentRepository) + } + DIContainer.shared.register(type: EditCommentReplyUseCase.self) { _ in + return DefaultEditCommentReplyUseCase(repository: commentRepository) + } DIContainer.shared.register(type: ReadAllNotificationsUseCase.self) { _ in return DefaultReadAllNotificationsUseCase(repository: notificationRepository) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index d1603f45..56e1ca35 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -319,11 +319,10 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: CommonQuestBottomSheetViewModel.self) { container in - guard let blockUserUseCase = container.resolve( - type: BlockUserUseCase.self - ), + guard let blockUserUseCase = container.resolve(type: BlockUserUseCase.self), let reportQuestAnswerUseCase = container.resolve(type: ReportsCommonQuestAnswerUseCase.self), - let deleteCommonQuestUseCase = container.resolve(type: DeleteCommonQuestUseCase.self) + let deleteCommonQuestUseCase = container.resolve(type: DeleteCommonQuestUseCase.self), + let deleteCommentReplyUseCase = container.resolve(type: DeleteCommentReplyUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -332,7 +331,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { return CommonQuestBottomSheetViewModel( blockUserUseCase: blockUserUseCase, reportCommonQuestUseCase: reportQuestAnswerUseCase, - deleteCommonQuestUseCase: deleteCommonQuestUseCase + deleteCommonQuestUseCase: deleteCommonQuestUseCase, + deleteCommentReplyUseCase: deleteCommentReplyUseCase ) } From ce11c87c05ed0613172dc8422f5b44f4258ed6d2 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 22 Jun 2026 02:34:04 +0900 Subject: [PATCH 178/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EB=8B=B5=EA=B8=80=20=EC=82=AD=EC=A0=9C=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCase/DeleteCommentReplyUseCase.swift | 25 +++++ ...CommonQuestBottomSheetViewController.swift | 53 ++++++----- .../CommonQuestBottomSheetViewModel.swift | 36 ++++++-- .../CommonQuestHistoryViewController.swift | 60 +++++++----- .../CommonQuestReplyViewController.swift | 92 ++++++++++++++++--- 5 files changed, 201 insertions(+), 65 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/DeleteCommentReplyUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/DeleteCommentReplyUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/DeleteCommentReplyUseCase.swift new file mode 100644 index 00000000..b9023553 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/DeleteCommentReplyUseCase.swift @@ -0,0 +1,25 @@ +// +// DeleteCommentReplyUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/16/26. +// + +import Foundation + +protocol DeleteCommentReplyUseCase { + func execute(commentID: Int) async throws +} + +struct DefaultDeleteCommentReplyUseCase: DeleteCommentReplyUseCase { + + private let repository: CommentInterface + + init(repository: CommentInterface) { + self.repository = repository + } + + func execute(commentID: Int) async throws { + try await repository.deleteComment(commentID: commentID) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index 59a1f192..7bd72dd8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -22,7 +22,7 @@ protocol BlockReportProtocol: AnyObject { } protocol DeleteCommonQuestDelegate: AnyObject { - func completeDeleteCommonQuest() + func completeDeleteCommonQuest(deletedID: Int) } final class CommonQuestBottomSheetViewController: BaseViewController { @@ -34,7 +34,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { private let viewModel: CommonQuestBottomSheetViewModel private var writerID: Int = 0 private var cancellables = Set() - private(set) var answerID: Int? + private(set) var targetID: Int? private(set) var answer: String? private(set) var question: String? private(set) var writtenAt: String? @@ -63,7 +63,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { super.viewDidLoad() bind() } - + override func setAddTarget() { rootView.dismissButton.addTarget(self, action: #selector(dismissButtonDidTap), for: .touchUpInside) rootView.itemList.enumerated().forEach { index, item in @@ -85,19 +85,17 @@ extension CommonQuestBottomSheetViewController { writtenAt: String? = nil ) { self.sheetType = sheeetType - self.answerID = answerID + self.targetID = answerID self.answer = answer self.question = question self.writtenAt = writtenAt } - func configure(sheeetType: CommonQuestArchiveType, targetID: Int) { - self.sheetType = sheeetType - self.writerID = targetID - if let sheetType { - rootView = CommonQuestBottomSheetView(sheetType: sheetType) - } + func configure(sheetType: CommonQuestArchiveType, targetID: Int) { + self.sheetType = sheetType + self.targetID = targetID + rootView = CommonQuestBottomSheetView(sheetType: sheetType) } @objc @@ -112,26 +110,35 @@ extension CommonQuestBottomSheetViewController { switch action { case .edit: - guard let answerID, let answer, let question, let writtenAt + guard let targetID, let answer, let question, let writtenAt else { return } dismiss(animated: false) { [weak self] in self?.bottomDelegate?.didTapEdit( - answerID: answerID, + answerID: targetID, answer: answer, question: question, writtenAt: writtenAt ) } case .delete: - guard let answerID else { return } - presentDeleteQuestModal(answerID: answerID) + guard let targetID else { return } + if sheetType == .myAnswer || sheetType == .otherAnswer { + presentDeleteQuestModal(answerID: targetID, targetType: .COMMON_QUEST) + } else { + presentDeleteQuestModal(answerID: targetID, targetType: .COMMENT) + } case .block: viewModel.action(.block(userID: writerID)) case .report: - viewModel.action(.report(answerID: answerID ?? 0)) + guard let targetID else { return } + if sheetType == .myAnswer || sheetType == .otherAnswer { + viewModel.action(.report(targetID: targetID, targetType: .COMMON_QUEST)) + } else { + viewModel.action(.report(targetID: targetID, targetType: .COMMENT)) + } default: return } @@ -139,12 +146,14 @@ extension CommonQuestBottomSheetViewController { @objc private func dismissButtonDidTap() { - presentingViewController?.dismiss(animated: true) + presentingViewController?.dismiss(animated: true) { + ByeBooLogger.debug("dismiss completion 실행") + } } - private func presentDeleteQuestModal(answerID: Int) { + private func presentDeleteQuestModal(answerID: Int, targetType: CommonQuestTargetType) { let modalView = createModalView() - let action: () -> Void = { self.viewModel.action(.delete(answerID: answerID)) } + let action: () -> Void = { self.viewModel.action(.delete(targetID: answerID, targetType: targetType)) } ModalBuilder( modalView: modalView, @@ -202,11 +211,11 @@ extension CommonQuestBottomSheetViewController { switch result { case .success(): ByeBooLogger.debug("삭제 성공") - guard let self else { return } - - self.dismiss(animated: false) { [weak self] in - self?.deleteDelegate?.completeDeleteCommonQuest() + let deleteDelegate = self.deleteDelegate + let deletedID = self.targetID ?? 0 + self.dismiss(animated: false) { + deleteDelegate?.completeDeleteCommonQuest(deletedID: deletedID) } case .failure(let error): ByeBooLogger.debug(error) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift index 9bcaaa2e..ab9f921a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift @@ -25,24 +25,27 @@ final class CommonQuestBottomSheetViewModel { private let blockUserUseCase: BlockUserUseCase private let reportCommonQuestUseCase: ReportsCommonQuestAnswerUseCase private let deleteCommonQuestUseCase: DeleteCommonQuestUseCase + private let deleteCommentReplyUseCase: DeleteCommentReplyUseCase private var cancellables = Set() init( blockUserUseCase: BlockUserUseCase, reportCommonQuestUseCase: ReportsCommonQuestAnswerUseCase, - deleteCommonQuestUseCase: DeleteCommonQuestUseCase + deleteCommonQuestUseCase: DeleteCommonQuestUseCase, + deleteCommentReplyUseCase: DeleteCommentReplyUseCase ) { self.blockUserUseCase = blockUserUseCase self.reportCommonQuestUseCase = reportCommonQuestUseCase self.deleteCommonQuestUseCase = deleteCommonQuestUseCase + self.deleteCommentReplyUseCase = deleteCommentReplyUseCase } } extension CommonQuestBottomSheetViewModel: ViewModelType { enum Input { case block(userID: Int) - case report(answerID: Int) - case delete(answerID: Int) + case report(targetID: Int, targetType: CommonQuestTargetType) + case delete(targetID: Int, targetType: CommonQuestTargetType) } struct Output { @@ -55,10 +58,14 @@ extension CommonQuestBottomSheetViewModel: ViewModelType { switch trigger { case .block(let userID): blockUser(userID: userID) - case .report(let answerID): - reportCommonQuest(answerID: answerID) - case .delete(let answerID): - deleteCommonQuest(answerID: answerID) + case .report(let targetID, let targetType): + reportCommonQuest(targetID: targetID, targetType: targetType) + case .delete(let answerID, let targetType): + if targetType == .COMMENT { + deleteCommentReply(commentID: answerID) + } else { + deleteCommonQuest(answerID: answerID) + } } } } @@ -75,10 +82,10 @@ extension CommonQuestBottomSheetViewModel { } } - private func reportCommonQuest(answerID: Int) { + private func reportCommonQuest(targetID: Int, targetType: CommonQuestTargetType) { Task { do { - try await reportCommonQuestUseCase.execute(answerID: answerID) + try await reportCommonQuestUseCase.execute(targetID: targetID, targetType: targetType) reportQuestSubject.send(.success(())) } catch(let error as ByeBooError) { reportQuestSubject.send(.failure(error)) @@ -96,4 +103,15 @@ extension CommonQuestBottomSheetViewModel { } } } + + private func deleteCommentReply(commentID: Int) { + Task { + do { + try await deleteCommentReplyUseCase.execute(commentID: commentID) + deleteQuestSubject.send(.success(())) + } catch(let error as ByeBooError){ + deleteQuestSubject.send(.failure(error)) + } + } + } } 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 d883bfd6..4e7f4350 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -165,8 +165,9 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { } extension CommonQuestHistoryViewController: DeleteCommonQuestDelegate { - func completeDeleteCommonQuest() { - self.navigationController?.popViewController(animated: false) + func completeDeleteCommonQuest(deletedID: Int) { + ByeBooLogger.debug("댓글 삭제 완료") + viewModel.action(.fetchQuestDetail(answerID: answerID)) } } @@ -200,6 +201,11 @@ extension CommonQuestHistoryViewController: CommentProtocol { else { return } cell.updateReplyCount(replyCount: newCount) } + + viewController.onDismiss = { [weak self] in + guard let self else { return } + viewModel.action(.fetchQuestDetail(answerID: answerID)) + } if let sheet = viewController.sheetPresentationController{ sheet.detents = [.large()] @@ -299,31 +305,43 @@ extension CommonQuestHistoryViewController { let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() if let commentID, let isMyComment{ - commonQuestBottomSheet.configure( - sheeetType: isMyComment ? .myComment : .otherComment , - targetID: commentID - ) + configureWhenComment(commentID: commentID, isMyComment: isMyComment, sheet: commonQuestBottomSheet) } else { - let entity = viewModel.detailEntity - guard let entity else { return } - - let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer - commonQuestBottomSheet.configure(sheeetType: sheetType, targetID: writerID) - - if entity.isMyAnswer { - commonQuestBottomSheet.configureWhenEdit( - sheeetType: sheetType, - answerID: answerID, - answer: entity.content, - question: viewModel.question, - writtenAt: entity.writtenAt - ) - } + configureWhenCommonQuest(sheet: commonQuestBottomSheet) } setDelegate(bottomSheet: commonQuestBottomSheet) presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) } + + private func configureWhenComment( + commentID: Int, + isMyComment: Bool, + sheet: CommonQuestBottomSheetViewController + ) { + sheet.configure( + sheetType: isMyComment ? .myComment : .otherComment , + targetID: commentID + ) + } + + private func configureWhenCommonQuest(sheet: CommonQuestBottomSheetViewController) { + let entity = viewModel.detailEntity + guard let entity else { return } + + let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer + sheet.configure(sheetType: sheetType, targetID: answerID) + + if entity.isMyAnswer { + sheet.configureWhenEdit( + sheeetType: sheetType, + answerID: answerID, + answer: entity.content, + question: viewModel.question, + writtenAt: entity.writtenAt + ) + } + } } extension CommonQuestHistoryViewController: KeyboardHandleProtocol { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 32fdeac8..7461f20e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -15,12 +15,13 @@ final class CommonQuestReplyViewController: BaseViewController { private var commentEntity: CommonQuestCommentEntity? = nil private var commentID: Int = 0 - + var onReplyCountChanged: ((Int, Int) -> Void)? - + private var dataSource: UITableViewDiffableDataSource! private var cancellable = Set() + var onDismiss: (() -> Void)? init(viewModel: CommonQuestReplyViewModel) { self.viewModel = viewModel @@ -42,7 +43,7 @@ final class CommonQuestReplyViewController: BaseViewController { viewModel.action(.fetchReplyList(commentID: commentID)) } - + override func viewDidLoad() { super.viewDidLoad() @@ -152,6 +153,18 @@ extension CommonQuestReplyViewController { dataSource.apply(snapshot, animatingDifferences: false) } + private func commonBottomSheetUp(commentID: Int, isMyComment: Bool) { + let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() + + commonQuestBottomSheet.configure( + sheetType: isMyComment ? .myComment : .otherComment , + targetID: commentID + ) + + setDelegate(bottomSheet: commonQuestBottomSheet) + presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) + } + @objc private func backButtonDidTap() { self.dismiss(animated: true) @@ -160,19 +173,12 @@ extension CommonQuestReplyViewController { extension CommonQuestReplyViewController: CommentProtocol { func menuButtonDidTap(commentID: Int, isMyComment: Bool) { - let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() - - commonQuestBottomSheet.configure( - sheeetType: isMyComment ? .myComment : .otherComment , - targetID: commentID - ) - - presentBottomSheet(commonQuestBottomSheet, height: 224.adjustedH) + commonBottomSheetUp(commentID: commentID, isMyComment: isMyComment) } func moreLabelDidTap(commentID: Int) { let currentSnapshot = dataSource.snapshot() - + let commentItems = currentSnapshot.itemIdentifiers(inSection: .comment).map { item -> CommentItem in guard item.entity.commentID == commentID else { return item } return CommentItem(entity: item.entity, showAllText: true) @@ -181,7 +187,7 @@ extension CommonQuestReplyViewController: CommentProtocol { guard item.entity.commentID == commentID else { return item } return CommentItem(entity: item.entity, showAllText: true) } - + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.comment, .replies]) snapshot.appendItems(commentItems, toSection: .comment) @@ -192,12 +198,72 @@ extension CommonQuestReplyViewController: CommentProtocol { } } +extension CommonQuestReplyViewController: CommonQuestBottomSheetDelegate { + + func didTapEdit( + answerID: Int, + answer: String, + question: String, + writtenAt: String + ) { + let writeCommonQuestViewController = ViewControllerFactory.shared.makeWriteQuestionTypeQuestViewController() + writeCommonQuestViewController.navigationItem.hidesBackButton = true + writeCommonQuestViewController.questScope = .common + writeCommonQuestViewController.configureToEdit( + questNumber: nil, + questType: .question, + questionTitle: question, + answerID: answerID, + answer: answer, + writtenAt: writtenAt + ) + self.navigationController?.pushViewController(writeCommonQuestViewController, animated: false) + } + + @objc + private func bottomUp() { + guard let commentEntity else { return } + commonBottomSheetUp(commentID: commentID, isMyComment: commentEntity.isMyComment) + } + + private func setDelegate(bottomSheet: CommonQuestBottomSheetViewController) { + bottomSheet.do { + $0.bottomDelegate = self + $0.deleteDelegate = self + $0.blockDelegate = self + } + } +} + extension CommonQuestReplyViewController: CommonQuestCommentProtcol { func postComment(content: String) { viewModel.action(.postReply(commentID: commentID, content: content)) } } +extension CommonQuestReplyViewController: DeleteCommonQuestDelegate { + func completeDeleteCommonQuest(deletedID: Int) { + if deletedID == self.commentID { + dismiss(animated: true) + onDismiss?() + } else { + viewModel.action(.fetchReplyList(commentID: commentID)) + } + } +} + +extension CommonQuestReplyViewController: BlockReportProtocol { + func completeBlockReport(type: CommonQuestArchiveType.Action) { + ViewControllerUtils.changeQuestTabWithIndex(index: 1) { + NotificationCenter.default.post( + name: .showToastMessage, + object: nil, + userInfo: ["type": type] + ) + } + } +} + extension CommonQuestReplyViewController: KeyboardHandleProtocol { func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { From dd22210ba0fdf0d63e19240452b84eb8dcb5fb07 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 22 Jun 2026 02:57:05 +0900 Subject: [PATCH 179/231] =?UTF-8?q?fix:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=99=84=EB=A3=8C=20=ED=9B=84=20=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=8A=A4=ED=99=80=EB=8D=94=EA=B0=80=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EC=83=9D=EA=B8=B0=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/View/CommonQuest/Common/CommentTextView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift index a2816eb3..25cfacdf 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift @@ -149,7 +149,9 @@ extension CommentTextView { private func confirmButtonDidTap() { delegate?.postComment(content: textView.text) endEditing(true) - textView.text = "" + textView.text = placeholder + isPlaceholderActive = true + textView.textColor = .grayscale600 } } From a730bdc610140cba9954f7ee2efbde4a835361d9 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 22 Jun 2026 02:57:31 +0900 Subject: [PATCH 180/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EB=8B=B5=EA=B8=80=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=B0=A8?= =?UTF-8?q?=EB=8B=A8=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestBottomSheetViewController.swift | 7 +++---- .../CommonQuestHistoryViewController.swift | 9 +++++++-- .../ViewController/CommonQuestReplyViewController.swift | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index 7bd72dd8..1b3c1064 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -92,9 +92,10 @@ extension CommonQuestBottomSheetViewController { } - func configure(sheetType: CommonQuestArchiveType, targetID: Int) { + func configure(sheetType: CommonQuestArchiveType, targetID: Int, writerID: Int) { self.sheetType = sheetType self.targetID = targetID + self.writerID = writerID rootView = CommonQuestBottomSheetView(sheetType: sheetType) } @@ -146,9 +147,7 @@ extension CommonQuestBottomSheetViewController { @objc private func dismissButtonDidTap() { - presentingViewController?.dismiss(animated: true) { - ByeBooLogger.debug("dismiss completion 실행") - } + presentingViewController?.dismiss(animated: true) } private func presentDeleteQuestModal(answerID: Int, targetType: CommonQuestTargetType) { 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 4e7f4350..9b6b9c7b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -321,7 +321,8 @@ extension CommonQuestHistoryViewController { ) { sheet.configure( sheetType: isMyComment ? .myComment : .otherComment , - targetID: commentID + targetID: commentID, + writerID: writerID ) } @@ -330,7 +331,11 @@ extension CommonQuestHistoryViewController { guard let entity else { return } let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer - sheet.configure(sheetType: sheetType, targetID: answerID) + sheet.configure( + sheetType: sheetType, + targetID: answerID, + writerID: writerID + ) if entity.isMyAnswer { sheet.configureWhenEdit( diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 7461f20e..523130c1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -158,7 +158,8 @@ extension CommonQuestReplyViewController { commonQuestBottomSheet.configure( sheetType: isMyComment ? .myComment : .otherComment , - targetID: commentID + targetID: commentID, + writerID: commentEntity?.writerID ?? 0 ) setDelegate(bottomSheet: commonQuestBottomSheet) From 210371b10489f8b2bfd87c8d96ae9ead6d7a2bca Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 00:54:14 +0900 Subject: [PATCH 181/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20uitextview=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Common/CommentTextView.swift | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift index 25cfacdf..992b4b1a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift @@ -9,6 +9,7 @@ import UIKit protocol CommonQuestCommentProtcol: AnyObject { func postComment(content: String) + func editComment(content: String) } final class CommentTextView: BaseView { @@ -17,13 +18,14 @@ final class CommentTextView: BaseView { private let topBorderLine = UIView() private let textFieldContainer = UIView() - private let textView = UITextView() + private(set) var textView = UITextView() private let countConfirmContainer = UIView() private let textCountLabel = UILabel() private let confirmButton = UIButton() private let placeholder: String = "댓글로 위로를 남겨보세요." private var isPlaceholderActive: Bool = true + private var isEditing: Bool = false private let textCountLimit: Int = 500 override init(frame: CGRect) { @@ -145,12 +147,23 @@ extension CommentTextView: UITextViewDelegate { } extension CommentTextView { + func configureWhenEdit(content: String) { + textView.text = content + isPlaceholderActive = false + isEditing = true + } + @objc private func confirmButtonDidTap() { - delegate?.postComment(content: textView.text) + if isEditing { + delegate?.editComment(content: textView.text) + } else { + delegate?.postComment(content: textView.text) + } endEditing(true) textView.text = placeholder isPlaceholderActive = true + isEditing = false textView.textColor = .grayscale600 } } From 120448015eb85e55de40a586805f8cb717e87a31 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 00:55:19 +0900 Subject: [PATCH 182/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20API=20=20Data=20Layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/EndPoint/CommentAPI.swift | 18 ++++++++++++++---- .../Data/Repository/CommentRepository.swift | 9 +++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift index 3de789dc..7b578f03 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommentAPI.swift @@ -13,6 +13,8 @@ enum CommentAPI { case postComment(dto: CommonQuestCommentRequestDTO) case postReply(commentID: Int, dto: CommonQuestReplyRequestDTO) case fetchReplies(commentID: Int) + case patchComment(commentID: Int, dto: CommonQuestReplyRequestDTO) + case deleteComment(commentID: Int) } extension CommentAPI: EndPoint { @@ -24,6 +26,8 @@ extension CommentAPI: EndPoint { switch self { case .postComment: return "" + case .patchComment(let commentID, _), .deleteComment(let commentID): + return "/\(commentID)" case .postReply(let commentID, _), .fetchReplies(let commentID): return "/\(commentID)/replies" } @@ -35,21 +39,25 @@ extension CommentAPI: EndPoint { return .post case .fetchReplies: return .get + case .patchComment: + return .patch + case .deleteComment: + return .delete } } var headers: HeaderType { switch self { - case .postComment, .postReply, .fetchReplies: + case .postComment, .postReply, .fetchReplies, .patchComment, .deleteComment: return .withAuth } } var parameterEncoding: any Alamofire.ParameterEncoding { switch self { - case .postComment, .postReply: + case .postComment, .postReply, .patchComment: return JSONEncoding.default - case .fetchReplies: + case .fetchReplies, .deleteComment: return URLEncoding.default } } @@ -64,7 +72,9 @@ extension CommentAPI: EndPoint { return try? dto.toDictionary() case .postReply(_, let dto): return try? dto.toDictionary() - case .fetchReplies: + case .patchComment(_, let dto): + return try? dto.toDictionary() + case .fetchReplies, .deleteComment: return nil } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift index a2e1d01b..fe09f89e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift @@ -38,4 +38,13 @@ struct DefaultCommentRepository: CommentInterface { ) return result.toEntity(userID: userID) } + + func patchComment(content: String, targetID: Int) async throws { + let patchCommentRequestDTO: CommonQuestReplyRequestDTO = .init(content: content) + let _ = try await network.request(CommentAPI.patchComment(commentID: targetID, dto: patchCommentRequestDTO)) + } + + func deleteComment(commentID: Int) async throws { + let _ = try await network.request(CommentAPI.deleteComment(commentID: commentID)) + } } From 53ddb8c04d632094bc697cc23386d38e3154904e Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 00:55:41 +0900 Subject: [PATCH 183/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20Domain=20Layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Interface/CommentInterface.swift | 2 ++ .../UseCase/EditCommentReplyUseCase.swift | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift index 787f5f86..4b4fd45d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift @@ -11,4 +11,6 @@ protocol CommentInterface { func postComment(content: String, targetID: Int) async throws func postReply(content: String, commentID: Int) async throws func fetchReplies(commentID: Int) async throws -> CommonQuestReplyListEntity + func patchComment(content: String, targetID: Int) async throws + func deleteComment(commentID: Int) async throws } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift new file mode 100644 index 00000000..77f4ae5c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift @@ -0,0 +1,25 @@ +// +// EditCommentReplyUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 6/16/26. +// + +import Foundation + +protocol EditCommentReplyUseCase { + func execute(content: String, targetID: Int) async throws +} + +struct DefaultEditCommentReplyUseCase: EditCommentReplyUseCase { + + private let repository: CommentInterface + + init(repository: CommentInterface) { + self.repository = repository + } + + func execute(content: String, targetID: Int) async throws { + try await repository.patchComment(content: content, targetID: targetID) + } +} From 12f491cef9a7594d976b74b90108e728ba688d2e Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:15:04 +0900 Subject: [PATCH 184/231] =?UTF-8?q?refactor:=20#456=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestBottomSheetViewController.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index 1b3c1064..bb00f656 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -8,13 +8,14 @@ import Combine import UIKit -protocol CommonQuestBottomSheetDelegate: AnyObject { - func didTapEdit( +protocol EditCommonQuestProtocol: AnyObject { + func didTapCommonQuestEdit( answerID: Int, answer: String, question: String, writtenAt: String ) + func didTapCommentEdit(commentID: Int, content: String) } protocol BlockReportProtocol: AnyObject { @@ -43,7 +44,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { var action: CommonQuestArchiveType.Action? weak var blockDelegate: BlockReportProtocol? - weak var bottomDelegate: CommonQuestBottomSheetDelegate? + weak var editDelegate: EditCommonQuestProtocol? weak var deleteDelegate: DeleteCommonQuestDelegate? init(viewModel: CommonQuestBottomSheetViewModel) { @@ -77,7 +78,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { extension CommonQuestBottomSheetViewController { - func configureWhenEdit( + func configureWhenQuestEdit( sheeetType: CommonQuestArchiveType, answerID: Int? = nil, answer: String? = nil, From 0e6aa4efc5848e06daff2196a227812900940966 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:15:41 +0900 Subject: [PATCH 185/231] =?UTF-8?q?refactor:=20#456=20=EB=B0=94=ED=85=80?= =?UTF-8?q?=EC=8B=9C=ED=8A=B8=20action=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Enum/CommonQuestArchiveType.swift | 15 ++++++++++++--- .../ParentQuestViewController.swift | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift index 06b462a6..c31d33af 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestArchiveType.swift @@ -15,7 +15,11 @@ enum CommonQuestArchiveType { case otherComment enum Action { - case edit, delete, report, block + case questEdit + case commentEdit + case delete + case report + case block } struct Item { @@ -27,9 +31,14 @@ enum CommonQuestArchiveType { var items: [Item] { switch self { - case .myAnswer, .myComment: + case .myAnswer: return [ - Item(title: "수정하기", icon: .edit, color: .white, action: .edit), + Item(title: "수정하기", icon: .edit, color: .white, action: .questEdit), + Item(title: "삭제하기", icon: .trash, color: .error300, action: .delete) + ] + case .myComment: + return [ + Item(title: "수정하기", icon: .edit, color: .white, action: .commentEdit), Item(title: "삭제하기", icon: .trash, color: .error300, action: .delete) ] case .otherAnswer: diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index 41115759..ee3a4ce6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -90,7 +90,7 @@ final class ParentQuestViewController: BaseViewController, ToastPres self.presentToastMessage(type: .block) case .report: self.presentToastMessage(type: .report) - case .edit, .delete: + case .questEdit, .commentEdit, .delete: return } } From a2172f01b6ccaf2685609fed59ca3908f5e56496 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:16:04 +0900 Subject: [PATCH 186/231] =?UTF-8?q?feat:=20#456=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommonQuestBottomSheetViewController.swift | 23 ++++++++++----- .../CommonQuestHistoryViewController.swift | 29 ++++++++++++++----- .../CommonQuestHistoryViewModel.swift | 25 ++++++++++++++-- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index bb00f656..4f99df30 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -37,6 +37,7 @@ final class CommonQuestBottomSheetViewController: BaseViewController { private var cancellables = Set() private(set) var targetID: Int? private(set) var answer: String? + private(set) var content: String? private(set) var question: String? private(set) var writtenAt: String? @@ -93,10 +94,16 @@ extension CommonQuestBottomSheetViewController { } - func configure(sheetType: CommonQuestArchiveType, targetID: Int, writerID: Int) { + func configureWhenComment( + sheetType: CommonQuestArchiveType, + targetID: Int, + writerID: Int, + content: String? = nil + ) { self.sheetType = sheetType self.targetID = targetID self.writerID = writerID + self.content = content rootView = CommonQuestBottomSheetView(sheetType: sheetType) } @@ -111,20 +118,22 @@ extension CommonQuestBottomSheetViewController { action = sheetType.items[index].action switch action { - case .edit: - guard let targetID, let answer, let question, let writtenAt - else { - return - } + case .questEdit: + guard let targetID, let answer, let question, let writtenAt else { return } dismiss(animated: false) { [weak self] in - self?.bottomDelegate?.didTapEdit( + self?.editDelegate?.didTapCommonQuestEdit( answerID: targetID, answer: answer, question: question, writtenAt: writtenAt ) } + case .commentEdit: + guard let targetID, let content else { return } + dismiss(animated: false) { [weak self] in + self?.editDelegate?.didTapCommentEdit(commentID: targetID, content: content) + } case .delete: guard let targetID else { return } if sheetType == .myAnswer || sheetType == .otherAnswer { 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 9b6b9c7b..6f3f1e57 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -29,6 +29,7 @@ final class CommonQuestHistoryViewController: BaseViewController { private var answerID: Int = 0 private var writerID: Int = 0 + private var editingCommentID: Int? override func loadView() { view = rootView @@ -128,9 +129,8 @@ extension CommonQuestHistoryViewController { } } -extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { - - func didTapEdit( +extension CommonQuestHistoryViewController: EditCommonQuestProtocol { + func didTapCommonQuestEdit( answerID: Int, answer: String, question: String, @@ -150,6 +150,12 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { self.navigationController?.pushViewController(writeCommonQuestViewController, animated: false) } + func didTapCommentEdit(commentID: Int, content: String) { + editingCommentID = commentID + rootView.commentTextView.configureWhenEdit(content: content) + rootView.commentTextView.textView.becomeFirstResponder() + } + @objc private func bottomUp() { commonBottomSheetUp() @@ -157,7 +163,7 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { private func setDelegate(bottomSheet: CommonQuestBottomSheetViewController) { bottomSheet.do { - $0.bottomDelegate = self + $0.editDelegate = self $0.deleteDelegate = self $0.blockDelegate = self } @@ -236,6 +242,11 @@ extension CommonQuestHistoryViewController: CommonQuestCommentProtcol { func postComment(content: String) { viewModel.action(.postComment(answerID: answerID, content: content)) } + + func editComment(content: String) { + guard let editingCommentID else { return } + viewModel.action(.patchComment(answerID: answerID, commentID: editingCommentID , content: content)) + } } extension CommonQuestHistoryViewController { @@ -319,10 +330,12 @@ extension CommonQuestHistoryViewController { isMyComment: Bool, sheet: CommonQuestBottomSheetViewController ) { - sheet.configure( + let content = viewModel.getComment(commentID: commentID)?.content + sheet.configureWhenComment( sheetType: isMyComment ? .myComment : .otherComment , targetID: commentID, - writerID: writerID + writerID: writerID, + content: content ) } @@ -331,14 +344,14 @@ extension CommonQuestHistoryViewController { guard let entity else { return } let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer - sheet.configure( + sheet.configureWhenComment( sheetType: sheetType, targetID: answerID, writerID: writerID ) if entity.isMyAnswer { - sheet.configureWhenEdit( + sheet.configureWhenQuestEdit( sheeetType: sheetType, answerID: answerID, answer: entity.content, 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 53686dbb..0cf7dc88 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -12,10 +12,12 @@ final class CommonQuestHistoryViewModel { private let fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase private let postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase private let postCommentUseCase: PostCommonQuestCommentUseCase + private let patchCommentUseCase: EditCommentReplyUseCase private let likeCountSubject = PassthroughSubject, Never>.init() private let fetchQuestDetailSubject: PassthroughSubject, Never> = .init() private let postCommentSubject: PassthroughSubject, Never> = .init() + private let patchCommentSubject: PassthroughSubject, Never> = .init() private var entity: CommonQuestDetailEntity? = nil private var cancellables = Set() @@ -24,7 +26,8 @@ final class CommonQuestHistoryViewModel { init( fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase, - postCommentUseCase: PostCommonQuestCommentUseCase + postCommentUseCase: PostCommonQuestCommentUseCase, + patchCommentUseCase: EditCommentReplyUseCase postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase ) { self.fetchCommonQuestCommentsUseCase = fetchCommonQuestCommentsUseCase @@ -33,7 +36,8 @@ final class CommonQuestHistoryViewModel { output = Output( fetchCommonQuestDetailPublisher: fetchQuestDetailSubject.eraseToAnyPublisher(), - postCommentPublisher: postCommentSubject.eraseToAnyPublisher() + postCommentPublisher: postCommentSubject.eraseToAnyPublisher(), + patchCommentPublisher: patchCommentSubject.eraseToAnyPublisher() commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } @@ -44,11 +48,13 @@ extension CommonQuestHistoryViewModel: ViewModelType { case fetchQuestDetail(answerID: Int) case postComment(answerID: Int, content: String) case likeButtonDidTap(answerID: Int) + case patchComment(answerID: Int, commentID: Int, content: String) } struct Output { let fetchCommonQuestDetailPublisher: AnyPublisher, Never> let postCommentPublisher: AnyPublisher,Never> + let patchCommentPublisher: AnyPublisher,Never> let commonQuestLikeCountPublisher: AnyPublisher, Never> } @@ -62,6 +68,8 @@ extension CommonQuestHistoryViewModel: ViewModelType { postComment(answerID: answerID, content: content) case .likeButtonDidTap(let answerID): postCommonQuestLike(answerID: answerID) + case .patchComment(let answerID, let commentID, let content): + patchComment(answerID: answerID, commentID: commentID , content: content) } } } @@ -130,4 +138,17 @@ extension CommonQuestHistoryViewModel { likeTasks[answerID] = nil } } + + private func patchComment(answerID: Int, commentID: Int, content: String) { + Task { + do { + _ = try await patchCommentUseCase.execute(content: content, targetID: commentID) + ByeBooLogger.debug("content: \(content)") + await fetchCommonQuestComments(answerID: answerID) + patchCommentSubject.send(.success(())) + } catch(let error as ByeBooError) { + patchCommentSubject.send(.failure(error)) + } + } + } } From c0916d844d6ae4ef92bc4b0b05eee14e0726702f Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:16:17 +0900 Subject: [PATCH 187/231] =?UTF-8?q?feat:=20#456=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestReplyViewController.swift | 26 +++++++++--- .../ViewModel/CommonQuestReplyViewModel.swift | 40 ++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 523130c1..999e83e6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -15,6 +15,7 @@ final class CommonQuestReplyViewController: BaseViewController { private var commentEntity: CommonQuestCommentEntity? = nil private var commentID: Int = 0 + private var editingCommentID: Int? var onReplyCountChanged: ((Int, Int) -> Void)? @@ -77,6 +78,7 @@ extension CommonQuestReplyViewController { ) { self.commentEntity = entity self.commentID = commentID + viewModel.setComment(entity) } private func bind() { @@ -155,11 +157,13 @@ extension CommonQuestReplyViewController { private func commonBottomSheetUp(commentID: Int, isMyComment: Bool) { let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() + let content = viewModel.getComment(commentID: commentID)?.content - commonQuestBottomSheet.configure( + commonQuestBottomSheet.configureWhenComment( sheetType: isMyComment ? .myComment : .otherComment , targetID: commentID, - writerID: commentEntity?.writerID ?? 0 + writerID: commentEntity?.writerID ?? 0, + content: content ) setDelegate(bottomSheet: commonQuestBottomSheet) @@ -199,9 +203,8 @@ extension CommonQuestReplyViewController: CommentProtocol { } } -extension CommonQuestReplyViewController: CommonQuestBottomSheetDelegate { - - func didTapEdit( +extension CommonQuestReplyViewController: EditCommonQuestProtocol { + func didTapCommonQuestEdit( answerID: Int, answer: String, question: String, @@ -221,6 +224,12 @@ extension CommonQuestReplyViewController: CommonQuestBottomSheetDelegate { self.navigationController?.pushViewController(writeCommonQuestViewController, animated: false) } + func didTapCommentEdit(commentID: Int, content: String) { + editingCommentID = commentID + rootView.commentTextView.configureWhenEdit(content: content) + rootView.commentTextView.textView.becomeFirstResponder() + } + @objc private func bottomUp() { guard let commentEntity else { return } @@ -229,7 +238,7 @@ extension CommonQuestReplyViewController: CommonQuestBottomSheetDelegate { private func setDelegate(bottomSheet: CommonQuestBottomSheetViewController) { bottomSheet.do { - $0.bottomDelegate = self + $0.editDelegate = self $0.deleteDelegate = self $0.blockDelegate = self } @@ -240,6 +249,11 @@ extension CommonQuestReplyViewController: CommonQuestCommentProtcol { func postComment(content: String) { viewModel.action(.postReply(commentID: commentID, content: content)) } + + func editComment(content: String) { + guard let editingCommentID else { return } + viewModel.action(.editReply(commentID: commentID, editingCommentID: editingCommentID, content: content)) + } } extension CommonQuestReplyViewController: DeleteCommonQuestDelegate { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift index 46250dc3..77864cb0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift @@ -13,22 +13,31 @@ final class CommonQuestReplyViewModel { private let fetchRepliesUseCase: FetchCommonQuestRepliesUseCase private let postReplyUseCase: PostCommonQuestReplyUseCase + private let patchReplyUseCase: EditCommentReplyUseCase private let fetchRepliesSubject: PassthroughSubject, Never> = .init() private let postReplySubject: PassthroughSubject, Never> = .init() + private let patchReplySubject: PassthroughSubject, Never> = .init() + private var cancellables = Set() private(set) var output: Output + + private var comment: CommonQuestCommentEntity? + private var replies: [CommonQuestCommentEntity] = [] init( fetchRepliesUseCase: FetchCommonQuestRepliesUseCase, - postReplyUseCase: PostCommonQuestReplyUseCase + postReplyUseCase: PostCommonQuestReplyUseCase, + patchReplyUseCase: EditCommentReplyUseCase ) { self.fetchRepliesUseCase = fetchRepliesUseCase self.postReplyUseCase = postReplyUseCase + self.patchReplyUseCase = patchReplyUseCase output = Output( fetchReplyListPublisher: fetchRepliesSubject.eraseToAnyPublisher(), - postReplyPublisher: postReplySubject.eraseToAnyPublisher() + postReplyPublisher: postReplySubject.eraseToAnyPublisher(), + patchReplyPublisher: patchReplySubject.eraseToAnyPublisher() ) } @@ -39,17 +48,28 @@ final class CommonQuestReplyViewModel { func getReplyList() -> [CommonQuestCommentEntity] { return CommonQuestCommentEntity.toReplyListStub() } + + func setComment(_ comment: CommonQuestCommentEntity) { + self.comment = comment + } + + func getComment(commentID: Int) -> CommonQuestCommentEntity? { + if comment?.commentID == commentID { return comment } + return replies.first { $0.commentID == commentID } + } } extension CommonQuestReplyViewModel { enum Input { case fetchReplyList(commentID: Int) case postReply(commentID: Int, content: String) + case editReply(commentID: Int, editingCommentID: Int, content: String) } struct Output { let fetchReplyListPublisher: AnyPublisher, Never> let postReplyPublisher: AnyPublisher,Never> + let patchReplyPublisher: AnyPublisher,Never> } func action(_ trigger: Input) { @@ -60,6 +80,8 @@ extension CommonQuestReplyViewModel { } case .postReply(let commentID, let content): postReply(commentID: commentID, content: content) + case .editReply(let commentID, let editingCommentID, let content): + patchReply(commentID: commentID, editingCommentID: editingCommentID, content: content) } } } @@ -68,6 +90,7 @@ extension CommonQuestReplyViewModel { private func fetchReplyList(commentID: Int) async { do { let result = try await fetchRepliesUseCase.execute(commentID: commentID) + replies = result.replies ByeBooLogger.debug("답글 목록 \(result)") fetchRepliesSubject.send(.success(result)) } catch { @@ -89,4 +112,17 @@ extension CommonQuestReplyViewModel { } } } + + private func patchReply(commentID: Int, editingCommentID: Int, content: String) { + Task { + do { + _ = try await patchReplyUseCase.execute(content: content, targetID: editingCommentID) + await fetchReplyList(commentID: commentID) + patchReplySubject.send(.success(())) + } catch { + guard let error = error as? ByeBooError else { return } + patchReplySubject.send(.failure(error)) + } + } + } } From c68ecad253fdad55df4dad8a13aa41e2389e578d Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:16:26 +0900 Subject: [PATCH 188/231] =?UTF-8?q?chore:=20#456=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PresentationDependencyAssembler.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 56e1ca35..b44ee14f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -379,7 +379,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: CommonQuestHistoryViewModel.self) { container in guard let fetchCommonQuestDetailUseCase = container.resolve(type: FetchCommonQuestDetailUseCase.self), - let postCommentUsecase = container.resolve(type: PostCommonQuestCommentUseCase.self) else { + let postCommentUseCase = container.resolve(type: PostCommonQuestCommentUseCase.self), + let patchCommentUseCase = container.resolve(type: EditCommentReplyUseCase.self) else { let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -387,21 +388,25 @@ struct PresentationDependencyAssembler: DependencyAssembler { return CommonQuestHistoryViewModel( fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase, - postCommentUseCase: postCommentUsecase + postCommentUseCase: postCommentUseCase, + patchCommentUseCase: patchCommentUseCase + postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } DIContainer.shared.register(type: CommonQuestReplyViewModel.self) { container in guard let fetchReplyUseCase = container.resolve(type: FetchCommonQuestRepliesUseCase.self), - let postReplyUseCase = container.resolve(type: PostCommonQuestReplyUseCase.self) else { + let postReplyUseCase = container.resolve(type: PostCommonQuestReplyUseCase.self), + let patchReplyUseCase = container.resolve(type: EditCommentReplyUseCase.self ) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } return CommonQuestReplyViewModel( fetchRepliesUseCase: fetchReplyUseCase, - postReplyUseCase: postReplyUseCase + postReplyUseCase: postReplyUseCase, + patchReplyUseCase: patchReplyUseCase ) } } From a867353e2ece90bc90bd445eefd41dfd9fee267a Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:25:30 +0900 Subject: [PATCH 189/231] =?UTF-8?q?fix:=20#456=20develop=20=EC=B6=A9?= =?UTF-8?q?=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 2 +- .../Quest/View/CommonQuest/Common/QuestContentView.swift | 2 +- .../ViewController/CommonQuestHistoryViewController.swift | 2 +- .../ViewController/CommonQuestMyAnswersViewController.swift | 2 +- .../Quest/ViewController/CommonQuestViewController.swift | 2 +- .../Quest/ViewModel/CommonQuestHistoryViewModel.swift | 5 +++-- .../Presentation/PresentationDependencyAssembler.swift | 5 ++--- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 19868c53..13523378 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -23,7 +23,7 @@ struct DomainDependencyAssembler: DependencyAssembler { let forbiddenWordRepository = DIContainer.shared.resolve(type: ForbiddenWordInterface.self), let commonQuestRepository = DIContainer.shared.resolve(type: CommonQuestInterface.self), let blocksRepository = DIContainer.shared.resolve(type: BlocksInterface.self), - let notificationRepository = DIContainer.shared.resolve(type: NotificationInterface.self) else { + let notificationRepository = DIContainer.shared.resolve(type: NotificationInterface.self), let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self), let commentRepository = DIContainer.shared.resolve(type: CommentInterface.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) 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 cded9918..0b2a6991 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 CommonQuestLikeProtocol: AnyObject { - func likeButtonDidTap() + func likeButtonDidTap(answerID: Int) } final class QuestContentView: BaseView { 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 6f3f1e57..23d95c19 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -371,7 +371,7 @@ extension CommonQuestHistoryViewController: KeyboardHandleProtocol { } } -extension CommonQuestHistoryViewController: CommonQuestLikeCommentProtocol { +extension CommonQuestHistoryViewController: CommonQuestLikeProtocol { func likeButtonDidTap(answerID: Int) { viewModel.action(.likeButtonDidTap(answerID: answerID)) } 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 c4abda6d..67697919 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -236,7 +236,7 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { } } -extension CommonQuestMyAnswersViewController: CommonQuestLikeCommentProtocol { +extension CommonQuestMyAnswersViewController: CommonQuestLikeProtocol { func likeButtonDidTap(answerID: Int) { viewModel.action(.likeButtonDidTap(answerID: answerID)) } 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 0f4efc65..5c322839 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -291,7 +291,7 @@ extension CommonQuestViewController: UITableViewDataSource { } } -extension CommonQuestViewController: CommonQuestLikeCommentProtocol { +extension CommonQuestViewController: CommonQuestLikeProtocol { func likeButtonDidTap(answerID: Int) { ByeBooLogger.debug("answerID: \(answerID)") 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 0cf7dc88..cd5f4a41 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -27,17 +27,18 @@ final class CommonQuestHistoryViewModel { init( fetchCommonQuestCommentsUseCase: FetchCommonQuestDetailUseCase, postCommentUseCase: PostCommonQuestCommentUseCase, - patchCommentUseCase: EditCommentReplyUseCase + patchCommentUseCase: EditCommentReplyUseCase, postCommonQuestLikeUseCase: PostCommonQuestLikeUseCase ) { self.fetchCommonQuestCommentsUseCase = fetchCommonQuestCommentsUseCase self.postCommentUseCase = postCommentUseCase self.postCommonQuestLikeUseCase = postCommonQuestLikeUseCase + self.patchCommentUseCase = patchCommentUseCase output = Output( fetchCommonQuestDetailPublisher: fetchQuestDetailSubject.eraseToAnyPublisher(), postCommentPublisher: postCommentSubject.eraseToAnyPublisher(), - patchCommentPublisher: patchCommentSubject.eraseToAnyPublisher() + patchCommentPublisher: patchCommentSubject.eraseToAnyPublisher(), commonQuestLikeCountPublisher: likeCountSubject.eraseToAnyPublisher() ) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index b44ee14f..c94b6a22 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -380,7 +380,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: CommonQuestHistoryViewModel.self) { container in guard let fetchCommonQuestDetailUseCase = container.resolve(type: FetchCommonQuestDetailUseCase.self), let postCommentUseCase = container.resolve(type: PostCommonQuestCommentUseCase.self), - let patchCommentUseCase = container.resolve(type: EditCommentReplyUseCase.self) else { + let patchCommentUseCase = container.resolve(type: EditCommentReplyUseCase.self), let postCommonQuestLikeUseCase = container.resolve(type: PostCommonQuestLikeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -389,8 +389,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { return CommonQuestHistoryViewModel( fetchCommonQuestCommentsUseCase: fetchCommonQuestDetailUseCase, postCommentUseCase: postCommentUseCase, - patchCommentUseCase: patchCommentUseCase - + patchCommentUseCase: patchCommentUseCase, postCommonQuestLikeUseCase: postCommonQuestLikeUseCase ) } From 8aaedf39b11749c3b8be6368e1d057bb04094e0c Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:31:20 +0900 Subject: [PATCH 190/231] =?UTF-8?q?fix:=20#456=20=EA=B3=B5=EA=B0=90=20?= =?UTF-8?q?=EC=95=88=EB=88=8C=EB=A6=AC=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestHistoryViewController.swift | 1 + 1 file changed, 1 insertion(+) 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 23d95c19..ea82adbb 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -77,6 +77,7 @@ final class CommonQuestHistoryViewController: BaseViewController { $0.separatorStyle = .none $0.register(CommentTableViewCell.self) } + rootView.questContentView.delegate = self rootView.commentTextView.delegate = self } } From f8d28199cffe0b8ff4ef26998c8030dfeea6f8eb Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 01:31:28 +0900 Subject: [PATCH 191/231] =?UTF-8?q?feat:=20#456=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 12 ++++++++++++ .../CommonQuestReplyViewController.swift | 12 ++++++++++++ 2 files changed, 24 insertions(+) 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 ea82adbb..2e81367a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -294,6 +294,18 @@ extension CommonQuestHistoryViewController { } } .store(in: &cancellable) + + viewModel.output.patchCommentPublisher + .receive(on: DispatchQueue.main) + .sink { result in + switch result { + case .success: + ByeBooLogger.debug("댓글 수정 성공") + case .failure(let error): + ByeBooLogger.debug(error) + } + } + .store(in: &cancellable) } private func bindData(entity: CommonQuestDetailEntity) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 999e83e6..3ea19ea4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -110,6 +110,18 @@ extension CommonQuestReplyViewController { } .store(in: &cancellable) + viewModel.output.patchReplyPublisher + .receive(on: DispatchQueue.main) + .sink { result in + switch result { + case .success: + ByeBooLogger.debug("답글 수정 성공") + case .failure(let error): + ByeBooLogger.debug(error) + } + } + .store(in: &cancellable) + } } From 153b251491a13e437463102c5b560c4ae2ea1436 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 02:30:34 +0900 Subject: [PATCH 192/231] =?UTF-8?q?refactor:=20#456=20presentation=20->=20?= =?UTF-8?q?domain=20layer=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity}/Enum/CommonQuestTargetType.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/{Presentation => Domain/Entity}/Enum/CommonQuestTargetType.swift (100%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestTargetType.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/CommonQuestTargetType.swift similarity index 100% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/CommonQuestTargetType.swift rename to ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/CommonQuestTargetType.swift From 4bf87428f7f77cde535f22bde96135699069169f Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 02:31:08 +0900 Subject: [PATCH 193/231] =?UTF-8?q?refactor:=20#456=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/Repository/CommentRepository.swift | 8 ++++---- .../ByeBoo-iOS/Domain/Interface/CommentInterface.swift | 4 ++-- .../Domain/UseCase/EditCommentReplyUseCase.swift | 2 +- .../Domain/UseCase/PostCommonQuestCommentUseCase.swift | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift index fe09f89e..cf63af1f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommentRepository.swift @@ -20,8 +20,8 @@ struct DefaultCommentRepository: CommentInterface { self.userDefaultServcie = userDefaultService } - func postComment(content: String, targetID: Int) async throws { - let postCommentRequestDTO: CommonQuestCommentRequestDTO = .init(content: content, targetId: targetID) + func postComment(content: String, answerID: Int) async throws { + let postCommentRequestDTO: CommonQuestCommentRequestDTO = .init(content: content, targetId: answerID) let _ = try await network.request(CommentAPI.postComment(dto: postCommentRequestDTO)) } @@ -39,9 +39,9 @@ struct DefaultCommentRepository: CommentInterface { return result.toEntity(userID: userID) } - func patchComment(content: String, targetID: Int) async throws { + func patchComment(content: String, commentID: Int) async throws { let patchCommentRequestDTO: CommonQuestReplyRequestDTO = .init(content: content) - let _ = try await network.request(CommentAPI.patchComment(commentID: targetID, dto: patchCommentRequestDTO)) + let _ = try await network.request(CommentAPI.patchComment(commentID: commentID, dto: patchCommentRequestDTO)) } func deleteComment(commentID: Int) async throws { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift index 4b4fd45d..2537b683 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommentInterface.swift @@ -8,9 +8,9 @@ import Foundation protocol CommentInterface { - func postComment(content: String, targetID: Int) async throws + func postComment(content: String, answerID: Int) async throws func postReply(content: String, commentID: Int) async throws func fetchReplies(commentID: Int) async throws -> CommonQuestReplyListEntity - func patchComment(content: String, targetID: Int) async throws + func patchComment(content: String, commentID: Int) async throws func deleteComment(commentID: Int) async throws } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift index 77f4ae5c..23e58e7c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/EditCommentReplyUseCase.swift @@ -20,6 +20,6 @@ struct DefaultEditCommentReplyUseCase: EditCommentReplyUseCase { } func execute(content: String, targetID: Int) async throws { - try await repository.patchComment(content: content, targetID: targetID) + try await repository.patchComment(content: content, commentID: targetID) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift index 00f09021..bc16e3c7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/PostCommonQuestCommentUseCase.swift @@ -19,6 +19,6 @@ struct DefaultPostCommonQuestCommentUseCase: PostCommonQuestCommentUseCase { } func execute(content: String, targetID: Int) async throws { - try await repository.postComment(content: content, targetID: targetID) + try await repository.postComment(content: content, answerID: targetID) } } From cffd5347449c6d6467d748fc204a77932851b40f Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 02:31:35 +0900 Subject: [PATCH 194/231] =?UTF-8?q?refactor:=20#456=20=ED=83=80=EA=B2=9F?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=EA=B0=80=200=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/CommonQuestBottomSheetViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index 4f99df30..f8b3bd73 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -222,8 +222,8 @@ extension CommonQuestBottomSheetViewController { ByeBooLogger.debug("삭제 성공") guard let self else { return } let deleteDelegate = self.deleteDelegate - let deletedID = self.targetID ?? 0 self.dismiss(animated: false) { + guard let deletedID = self.targetID else { return } deleteDelegate?.completeDeleteCommonQuest(deletedID: deletedID) } case .failure(let error): From 1773bc8390898026a87a146824f01bfde9bdab49 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 02:33:45 +0900 Subject: [PATCH 195/231] =?UTF-8?q?refactor:=20#456=20configure=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommonQuestBottomSheetViewController.swift | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index f8b3bd73..9bced0da 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -79,31 +79,23 @@ final class CommonQuestBottomSheetViewController: BaseViewController { extension CommonQuestBottomSheetViewController { - func configureWhenQuestEdit( - sheeetType: CommonQuestArchiveType, - answerID: Int? = nil, + func configure( + sheetType: CommonQuestArchiveType, + targetID: Int, + writerID: Int = 0, + content: String? = nil, answer: String? = nil, question: String? = nil, writtenAt: String? = nil - ) { - self.sheetType = sheeetType - self.targetID = answerID - self.answer = answer - self.question = question - self.writtenAt = writtenAt - } - - - func configureWhenComment( - sheetType: CommonQuestArchiveType, - targetID: Int, - writerID: Int, - content: String? = nil ) { self.sheetType = sheetType self.targetID = targetID self.writerID = writerID self.content = content + self.answer = answer + self.question = question + self.writtenAt = writtenAt + rootView = CommonQuestBottomSheetView(sheetType: sheetType) } From d3a2a8df0b4cf9d2d09b900808e0e08284c65f32 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 24 Jun 2026 02:34:01 +0900 Subject: [PATCH 196/231] =?UTF-8?q?refactor:=20#456=20viewModel=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B0=92=EC=9D=84=20=EB=93=A4=EA=B3=A0=20=EC=98=AC?= =?UTF-8?q?=20=EC=88=98=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 --- .../CommonQuestHistoryViewController.swift | 29 +++++++------------ .../CommonQuestReplyViewController.swift | 9 +++--- 2 files changed, 16 insertions(+), 22 deletions(-) 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 2e81367a..ba01b69d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -343,35 +343,28 @@ extension CommonQuestHistoryViewController { isMyComment: Bool, sheet: CommonQuestBottomSheetViewController ) { - let content = viewModel.getComment(commentID: commentID)?.content - sheet.configureWhenComment( + guard let comment = viewModel.getComment(commentID: commentID) else { return } + sheet.configure( sheetType: isMyComment ? .myComment : .otherComment , targetID: commentID, - writerID: writerID, - content: content + writerID: comment.writerID, + content: comment.content ) } - + private func configureWhenCommonQuest(sheet: CommonQuestBottomSheetViewController) { let entity = viewModel.detailEntity guard let entity else { return } - + let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer - sheet.configureWhenComment( + sheet.configure( sheetType: sheetType, targetID: answerID, - writerID: writerID + writerID: writerID, + answer: entity.isMyAnswer ? entity.content : nil, + question: entity.isMyAnswer ? viewModel.question : nil, + writtenAt: entity.isMyAnswer ? entity.writtenAt : nil ) - - if entity.isMyAnswer { - sheet.configureWhenQuestEdit( - sheeetType: sheetType, - answerID: answerID, - answer: entity.content, - question: viewModel.question, - writtenAt: entity.writtenAt - ) - } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 3ea19ea4..f5eb5a81 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -169,13 +169,13 @@ extension CommonQuestReplyViewController { private func commonBottomSheetUp(commentID: Int, isMyComment: Bool) { let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() - let content = viewModel.getComment(commentID: commentID)?.content - commonQuestBottomSheet.configureWhenComment( + guard let comment = viewModel.getComment(commentID: commentID) else { return } + commonQuestBottomSheet.configure( sheetType: isMyComment ? .myComment : .otherComment , targetID: commentID, - writerID: commentEntity?.writerID ?? 0, - content: content + writerID: comment.writerID, + content: comment.content ) setDelegate(bottomSheet: commonQuestBottomSheet) @@ -185,6 +185,7 @@ extension CommonQuestReplyViewController { @objc private func backButtonDidTap() { self.dismiss(animated: true) + onDismiss?() } } From 78d01ab8c5f937c7b4534e59954b78e08778f2d2 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 29 Jun 2026 17:28:12 +0900 Subject: [PATCH 197/231] =?UTF-8?q?refactor:=20#456=20targetType=20switch?= =?UTF-8?q?=20case=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CommonQuestBottomSheetViewController.swift | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index 9bced0da..005679b5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -127,21 +127,29 @@ extension CommonQuestBottomSheetViewController { self?.editDelegate?.didTapCommentEdit(commentID: targetID, content: content) } case .delete: - guard let targetID else { return } - if sheetType == .myAnswer || sheetType == .otherAnswer { - presentDeleteQuestModal(answerID: targetID, targetType: .COMMON_QUEST) - } else { - presentDeleteQuestModal(answerID: targetID, targetType: .COMMENT) + var targetType: CommonQuestTargetType? = nil + switch sheetType { + case .myAnswer, .otherAnswer: + targetType = .COMMON_QUEST + case .myComment, .otherComment: + targetType = .COMMENT } + + guard let targetID, let targetType else { return } + presentDeleteQuestModal(answerID: targetID, targetType: targetType) case .block: viewModel.action(.block(userID: writerID)) case .report: - guard let targetID else { return } - if sheetType == .myAnswer || sheetType == .otherAnswer { - viewModel.action(.report(targetID: targetID, targetType: .COMMON_QUEST)) - } else { - viewModel.action(.report(targetID: targetID, targetType: .COMMENT)) + var targetType: CommonQuestTargetType? = nil + switch sheetType { + case .myAnswer, .otherAnswer: + targetType = .COMMON_QUEST + case .myComment, .otherComment: + targetType = .COMMENT } + + guard let targetID, let targetType else { return } + viewModel.action(.report(targetID: targetID, targetType: targetType)) default: return } From 36f388debe4c3648aeaea8589d19af9d71c4aede Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 29 Jun 2026 17:28:35 +0900 Subject: [PATCH 198/231] =?UTF-8?q?refactor:=20#456=20bind=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) 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 ba01b69d..07840207 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -257,6 +257,30 @@ extension CommonQuestHistoryViewController { } private func bind() { + bindCommonQuestDetail() + bindCommonQuestLikeCount() + bindPostComment() + bindPatchComment() + } + + private func bindData(entity: CommonQuestDetailEntity) { + let answer = entity.answer + 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, + nickname: answer.writer, + content: answer.content, + isLiked: answer.isLiked, + likeCount: answer.likeCount, + commentCount: answer.commentCount + ) + } + + private func bindCommonQuestDetail() { viewModel.output.fetchCommonQuestDetailPublisher .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -269,7 +293,9 @@ extension CommonQuestHistoryViewController { } } .store(in: &cancellable) - + } + + private func bindCommonQuestLikeCount() { viewModel.output.commonQuestLikeCountPublisher .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -282,7 +308,9 @@ extension CommonQuestHistoryViewController { } } .store(in: &cancellable) - + } + + private func bindPostComment() { viewModel.output.postCommentPublisher .receive(on: DispatchQueue.main) .sink { result in @@ -294,7 +322,9 @@ extension CommonQuestHistoryViewController { } } .store(in: &cancellable) - + } + + private func bindPatchComment() { viewModel.output.patchCommentPublisher .receive(on: DispatchQueue.main) .sink { result in @@ -307,24 +337,9 @@ extension CommonQuestHistoryViewController { } .store(in: &cancellable) } - - private func bindData(entity: CommonQuestDetailEntity) { - let answer = entity.answer - 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, - nickname: answer.writer, - content: answer.content, - isLiked: answer.isLiked, - likeCount: answer.likeCount, - commentCount: answer.commentCount - ) - } - +} + +extension CommonQuestHistoryViewController { private func commonBottomSheetUp(commentID: Int? = nil, isMyComment: Bool? = nil) { let commonQuestBottomSheet = ViewControllerFactory.shared.makeCommonQuestBottomSheetViewController() @@ -367,7 +382,6 @@ extension CommonQuestHistoryViewController { ) } } - extension CommonQuestHistoryViewController: KeyboardHandleProtocol { func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { From 37c31308f6ec90584ef46b26ad91c85b22ef86b3 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 29 Jun 2026 17:41:56 +0900 Subject: [PATCH 199/231] =?UTF-8?q?refactor:=20#456=20=EC=B9=B4=EB=A9=9C?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Entity/Enum/CommonQuestTargetType.swift | 4 ++-- .../CommonQuestBottomSheetViewController.swift | 8 ++++---- .../CommonQuest/CommonQuestBottomSheetViewModel.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/CommonQuestTargetType.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/CommonQuestTargetType.swift index b0d7f543..240163a5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/CommonQuestTargetType.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/CommonQuestTargetType.swift @@ -8,6 +8,6 @@ import Foundation enum CommonQuestTargetType: String { - case COMMENT - case COMMON_QUEST + case comment = "COMMENT" + case commonQuest = "COMMON_QUEST" } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift index 005679b5..e89e418a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewController.swift @@ -130,9 +130,9 @@ extension CommonQuestBottomSheetViewController { var targetType: CommonQuestTargetType? = nil switch sheetType { case .myAnswer, .otherAnswer: - targetType = .COMMON_QUEST + targetType = .commonQuest case .myComment, .otherComment: - targetType = .COMMENT + targetType = .comment } guard let targetID, let targetType else { return } @@ -143,9 +143,9 @@ extension CommonQuestBottomSheetViewController { var targetType: CommonQuestTargetType? = nil switch sheetType { case .myAnswer, .otherAnswer: - targetType = .COMMON_QUEST + targetType = .commonQuest case .myComment, .otherComment: - targetType = .COMMENT + targetType = .comment } guard let targetID, let targetType else { return } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift index ab9f921a..8a6d5086 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/CommonQuest/CommonQuestBottomSheetViewModel.swift @@ -61,7 +61,7 @@ extension CommonQuestBottomSheetViewModel: ViewModelType { case .report(let targetID, let targetType): reportCommonQuest(targetID: targetID, targetType: targetType) case .delete(let answerID, let targetType): - if targetType == .COMMENT { + if targetType == .comment { deleteCommentReply(commentID: answerID) } else { deleteCommonQuest(answerID: answerID) From a1224ac1d6f6444a08edeff39c0103f4a83b6946 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 29 Jun 2026 17:44:20 +0900 Subject: [PATCH 200/231] =?UTF-8?q?chore:=20#456=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=ED=9B=84=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 13523378..67e7c71f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -239,12 +239,15 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchHasUnreadNotificationUseCase.self) { _ in return DefaultFetchHasUnreadNotificationUseCase(repository: notificationRepository) } + DIContainer.shared.register(type: FetchCommonQuestRepliesUseCase.self) { _ in return DefaultFetchCommonQuestRepliesUseCase(repository: commentRepository) } + DIContainer.shared.register(type: DeleteCommentReplyUseCase.self) { _ in return DefaultDeleteCommentReplyUseCase(repository: commentRepository) } + DIContainer.shared.register(type: EditCommentReplyUseCase.self) { _ in return DefaultEditCommentReplyUseCase(repository: commentRepository) } From e42cb39bbb571fe0bcab67ec5086c0793baca575 Mon Sep 17 00:00:00 2001 From: Lee Nayeon Date: Mon, 29 Jun 2026 18:02:46 +0900 Subject: [PATCH 201/231] =?UTF-8?q?setting:=20#456=20macos=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index e7798b88..e1edab52 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-15 steps: - name: Checkout code From f1edd45e21a3334897ee0b5a553c12fb2bb9c248 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 1 Jul 2026 00:23:52 +0900 Subject: [PATCH 202/231] =?UTF-8?q?setting:=20#426=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95=20=ED=9B=84=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index 7ab4201c..ec9db3ef 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -307,7 +307,7 @@ CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Dev.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; @@ -325,7 +325,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.2; + MARKETING_VERSION = 1.3.0; PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -351,7 +351,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; GENERATE_INFOPLIST_FILE = YES; @@ -368,7 +368,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.2; + MARKETING_VERSION = 1.3.0; PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From f5e6bdd356ab61f88ae6abfdbccb1100103d08f0 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 4 Jul 2026 01:48:21 +0900 Subject: [PATCH 203/231] =?UTF-8?q?setting:=20#470=20Firebase=20Remote=20C?= =?UTF-8?q?onfig=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index ec9db3ef..2967de7b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 183EE0E12ED1D10600154F40 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 183EE0E02ED1D10600154F40 /* FirebaseMessaging */; }; 186847F02ED5567D002827DA /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 186847EF2ED5567D002827DA /* FirebaseCore */; }; 18A8172D2E154F3D00F68F9F /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 18A8172C2E154F3D00F68F9F /* Alamofire */; }; + 514D021B2FF6ADDD0041A4E1 /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = 514D021A2FF6ADDD0041A4E1 /* FirebaseRemoteConfig */; }; 515C127F2E159D45001BA2E0 /* Then in Frameworks */ = {isa = PBXBuildFile; productRef = 515C127E2E159D45001BA2E0 /* Then */; }; 515C12822E159D77001BA2E0 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 515C12812E159D77001BA2E0 /* SnapKit */; }; 51691DED2E5CB059008CCEC6 /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 51691DEC2E5CB059008CCEC6 /* KakaoSDKAuth */; }; @@ -77,6 +78,7 @@ CBB032A72E7925FC00C0855E /* Mixpanel in Frameworks */, 51691DED2E5CB059008CCEC6 /* KakaoSDKAuth in Frameworks */, CB71A8A32F3B6F9400176B64 /* FirebaseCrashlytics in Frameworks */, + 514D021B2FF6ADDD0041A4E1 /* FirebaseRemoteConfig in Frameworks */, 183EE0E12ED1D10600154F40 /* FirebaseMessaging in Frameworks */, 183EE0DB2ED1CB0E00154F40 /* FirebaseAnalytics in Frameworks */, ); @@ -151,6 +153,7 @@ 183EE0E02ED1D10600154F40 /* FirebaseMessaging */, 186847EF2ED5567D002827DA /* FirebaseCore */, CB71A8A22F3B6F9400176B64 /* FirebaseCrashlytics */, + 514D021A2FF6ADDD0041A4E1 /* FirebaseRemoteConfig */, ); productName = "ByeBoo-iOS"; productReference = CB41808B2E0925D2001E7BCB /* ByeBoo-iOS.app */; @@ -671,6 +674,11 @@ package = 18A8172B2E154F3D00F68F9F /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + 514D021A2FF6ADDD0041A4E1 /* FirebaseRemoteConfig */ = { + isa = XCSwiftPackageProductDependency; + package = 183EE0D92ED1CB0E00154F40 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseRemoteConfig; + }; 515C127E2E159D45001BA2E0 /* Then */ = { isa = XCSwiftPackageProductDependency; package = 515C127D2E159D45001BA2E0 /* XCRemoteSwiftPackageReference "Then" */; From 858b2731014010277e5ef9d77d668fbf5bcd6741 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 4 Jul 2026 01:48:41 +0900 Subject: [PATCH 204/231] =?UTF-8?q?chore:=20#470=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 4 ++++ .../PresentationDependencyAssembler.swift | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 67e7c71f..caecb078 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -255,5 +255,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: ReadAllNotificationsUseCase.self) { _ in return DefaultReadAllNotificationsUseCase(repository: notificationRepository) } + + DIContainer.shared.register(type: CheckForceUpdateUseCase.self) { _ in + return DefaultCheckForceUpdateUsecase(repository: DefaultForceUpdateManager()) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index c94b6a22..a4f136fa 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -265,14 +265,15 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: SplashViewModel.self) { container in - guard let autoLoginUseCase = container.resolve( - type: AutoLoginUseCase.self - ) - else { + guard let autoLoginUseCase = container.resolve(type: AutoLoginUseCase.self), + let checkForceUpdateUseCase = container.resolve(type: CheckForceUpdateUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } - return SplashViewModel(autoLoginUseCase: autoLoginUseCase) + return SplashViewModel( + autoLoginUseCase: autoLoginUseCase, + checkForceUpdateUseCase: checkForceUpdateUseCase + ) } DIContainer.shared.register(type: FinishJourneyViewModel.self) { container in From fdf49aeac8b667d89281119fc78e0a4e79213cb9 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 4 Jul 2026 01:49:01 +0900 Subject: [PATCH 205/231] =?UTF-8?q?style:=20#470=20=EA=B0=95=EC=A0=9C=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=95=8C=EB=9F=BF=20?= =?UTF-8?q?=EB=B7=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Modal/ForceUpdateModalView.swift | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift new file mode 100644 index 00000000..64d5f6ea --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift @@ -0,0 +1,60 @@ +// +// ForceUpdateModalView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 7/4/26. +// + +import UIKit + +import SnapKit +import Then + +final class ForceUpdateModalView: BaseView, ModalProtocol { + let actionButton: ByeBooButton = ByeBooButton(titleText: "업데이트 하러 가기", type: .enabled) + let dismissButton: ByeBooButton? = nil + let modalType: ConfirmModalType? = nil + + private let titleLabel = UILabel() + private let descriptionLabel = UILabel() + + override func setUI() { + addSubviews(titleLabel, descriptionLabel, actionButton) + } + + override func setStyle() { + backgroundColor = .grayscale900 + layer.cornerRadius = 12 + + titleLabel.applyByeBooFont( + style: .sub3M18, + text: "새로운 업데이트가 있어요", + color: .grayscale50 + ) + + descriptionLabel.applyByeBooFont( + style: .body3R16, + text: "더 나은 바이부를 만나보세요", + color: .grayscale400 + ) + } + + override func setLayout() { + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(24.adjustedH) + $0.centerX.equalToSuperview() + } + descriptionLabel.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(16.adjustedH) + $0.centerX.equalToSuperview() + } + actionButton.snp.makeConstraints { + $0.top.equalTo(descriptionLabel.snp.bottom).offset(16.adjustedH) + $0.centerX.equalToSuperview() + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(24.adjustedH) + $0.height.equalTo(53.adjustedH) + } + } +} + From cbf93c60bbefafda56f9f2242d7bc089e0831d50 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 4 Jul 2026 01:49:14 +0900 Subject: [PATCH 206/231] =?UTF-8?q?feat:=20#470=20=EC=95=B1=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EC=96=B4=20=EA=B4=80=EB=A0=A8=20enum=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Presentation/Enum/AppConstants.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/AppConstants.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/AppConstants.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/AppConstants.swift new file mode 100644 index 00000000..1ab8cedf --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/AppConstants.swift @@ -0,0 +1,11 @@ +// +// AppConstants.swift +// ByeBoo-iOS +// +// Created by 이나연 on 7/4/26. +// + +enum AppConstants { + static let appStoreID = "6748205348" + static let appStoreURL = "itms-apps://itunes.apple.com/app/id\(appStoreID)" +} From ab077e247b9d1e0ac42a0b10781d37c2c87ff45b Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 4 Jul 2026 01:49:31 +0900 Subject: [PATCH 207/231] =?UTF-8?q?feat:=20#470=20=EC=B5=9C=EC=86=8C=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EB=B3=B4=EB=8B=A4=20=EB=82=AE=EC=9D=84=20?= =?UTF-8?q?=EC=8B=9C=20=EC=95=B1=EC=8A=A4=ED=86=A0=EC=96=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/ForceUpdateManager.swift | 44 ++++++++++++++ .../UseCase/CheckForceUpdateUseCase.swift | 25 ++++++++ .../ViewController/SplashViewController.swift | 60 +++++++++++++++++-- .../Login/ViewModel/SplashViewModel.swift | 36 +++++++++-- 4 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift new file mode 100644 index 00000000..35c31a9c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift @@ -0,0 +1,44 @@ +// +// RemoteConfigeManager.swift +// ByeBoo-iOS +// +// Created by 이나연 on 7/2/26. +// + +import FirebaseRemoteConfig + +protocol ForceUpdateManager { + func checkForUpdate() async throws-> Bool +} + +final class DefaultForceUpdateManager: ForceUpdateManager { + let remoteConfig = RemoteConfig.remoteConfig() + + init() { + let settings = RemoteConfigSettings() + settings.minimumFetchInterval = 0 + RemoteConfig.remoteConfig().configSettings = settings + } + + func checkForUpdate() async throws -> Bool { + do { + try await remoteConfig.fetch() + try await remoteConfig.activate() + + let minimumVersion = remoteConfig["min_version_code"].stringValue + + return isUpdateRequired(minimumVersion: minimumVersion) + } catch { + return false + } + } + + private var currentAppVersion: String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0" + } + + private func isUpdateRequired(minimumVersion: String) -> Bool { + ByeBooLogger.debug("현재버전: \(currentAppVersion), 최소버전: \(minimumVersion)") + return currentAppVersion.compare(minimumVersion, options: .numeric) == .orderedAscending + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift new file mode 100644 index 00000000..e7745234 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift @@ -0,0 +1,25 @@ +// +// CheckForceUpdateUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 7/4/26. +// + +import Foundation + +protocol CheckForceUpdateUseCase { + func execute() async throws -> Bool +} + +struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase { + + private let repository: ForceUpdateManager + + init(repository: ForceUpdateManager) { + self.repository = repository + } + + func execute() async throws -> Bool { + return try await repository.checkForUpdate() + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift index 36807631..44dc49db 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift @@ -9,26 +9,33 @@ import Combine import UIKit final class SplashViewController: BaseViewController { - + private let rootView = SplashView() private let viewModel: SplashViewModel private var cancellables = Set() - + private var isFirstLaunch = true + init(viewModel: SplashViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { view = rootView viewModel.action(.viewDidLoad) bind() - setAddTarget() + NotificationCenter.default.addObserver( + self, + selector: #selector(appDidBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in guard let self = self else { return } if self.cancellables.isEmpty { @@ -39,8 +46,26 @@ final class SplashViewController: BaseViewController { } extension SplashViewController { - private func bind() { + bindCheckForceUpdate() + bindAutoLogin() + } + + private func bindCheckForceUpdate() { + viewModel.output.forceUpdatePublisher + .receive(on: DispatchQueue.main) + .sink { result in + switch result { + case true: + self.presentForceUpdateModal() + case false: + self.viewModel.action(.tryAutoLogin) + } + } + .store(in: &cancellables) + } + + private func bindAutoLogin() { viewModel.output.autoLoginPublisher .receive(on: DispatchQueue.main) .sink { result in @@ -77,6 +102,29 @@ extension SplashViewController { } .store(in: &cancellables) } + + private func presentForceUpdateModal() { + let modal = ModalBuilder( + modalView: ForceUpdateModalView(), + action: openAppstore, + rootViewController: self + ) + modal.present() + } + + private func openAppstore() { + guard let url = URL(string: AppConstants.appStoreURL) else { return } + UIApplication.shared.open(url) + } + + @objc + private func appDidBecomeActive() { + guard !isFirstLaunch else { + isFirstLaunch = false + return + } + viewModel.action(.viewDidLoad) + } } extension SplashViewController { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift index 4e880c44..27d0b3c2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift @@ -9,22 +9,27 @@ import Combine import Foundation final class SplashViewModel { - + private var autoLoginSubject: PassthroughSubject, Never> = .init() - + private var forceUpdateSubject: PassthroughSubject = .init() + var output: Output { Output( - autoLoginPublisher: autoLoginSubject.eraseToAnyPublisher() + autoLoginPublisher: autoLoginSubject.eraseToAnyPublisher(), + forceUpdatePublisher: forceUpdateSubject.eraseToAnyPublisher() ) } - + private var cancellables = Set() private let autoLoginUseCase: AutoLoginUseCase - + private let checkForceUpdateUseCase: CheckForceUpdateUseCase + init( - autoLoginUseCase: AutoLoginUseCase + autoLoginUseCase: AutoLoginUseCase, + checkForceUpdateUseCase: CheckForceUpdateUseCase ) { self.autoLoginUseCase = autoLoginUseCase + self.checkForceUpdateUseCase = checkForceUpdateUseCase } } @@ -32,15 +37,19 @@ final class SplashViewModel { extension SplashViewModel { enum Input { case viewDidLoad + case tryAutoLogin } struct Output { let autoLoginPublisher: AnyPublisher, Never> + let forceUpdatePublisher: AnyPublisher } func action(_ trigger: Input) { switch trigger { case .viewDidLoad: + checkForceUpdate() + case .tryAutoLogin: autoLogin() } } @@ -48,6 +57,21 @@ extension SplashViewModel { } extension SplashViewModel { + private func checkForceUpdate() { + Task { + do { + if try await checkForceUpdateUseCase.execute() { + forceUpdateSubject.send(true) + ByeBooLogger.debug("강제 업데이트 필요") + } else { + forceUpdateSubject.send(false) + } + } catch { + forceUpdateSubject.send(false) + } + } + } + private func autoLogin() { ByeBooLogger.debug("자동로그인 실행") Task { From 032a20195e5873bc21d7327a085e1584731c268b Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 4 Jul 2026 02:10:29 +0900 Subject: [PATCH 208/231] =?UTF-8?q?chore:=20#470=20throws=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Persistence/Service/ForceUpdateManager.swift | 4 ++-- .../ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift index 35c31a9c..67030d94 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift @@ -8,7 +8,7 @@ import FirebaseRemoteConfig protocol ForceUpdateManager { - func checkForUpdate() async throws-> Bool + func checkForUpdate() async -> Bool } final class DefaultForceUpdateManager: ForceUpdateManager { @@ -20,7 +20,7 @@ final class DefaultForceUpdateManager: ForceUpdateManager { RemoteConfig.remoteConfig().configSettings = settings } - func checkForUpdate() async throws -> Bool { + func checkForUpdate() async -> Bool { do { try await remoteConfig.fetch() try await remoteConfig.activate() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift index e7745234..ad0f6d59 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol CheckForceUpdateUseCase { - func execute() async throws -> Bool + func execute() async -> Bool } struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase { @@ -19,7 +19,7 @@ struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase { self.repository = repository } - func execute() async throws -> Bool { - return try await repository.checkForUpdate() + func execute() async -> Bool { + return await repository.checkForUpdate() } } From 716c99614b58ebf509195fd2239dc4098cab435c Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 5 Jul 2026 19:35:48 +0900 Subject: [PATCH 209/231] =?UTF-8?q?refactor:=20#470=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=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 --- .../Data/Persistence/Service/ForceUpdateManager.swift | 6 +----- .../Domain/Interface/ForeceUpdateInterface.swift | 10 ++++++++++ .../Login/ViewController/SplashViewController.swift | 3 ++- .../Feature/Login/ViewModel/SplashViewModel.swift | 4 +--- 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ForeceUpdateInterface.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift index 67030d94..20f144bd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift @@ -7,11 +7,7 @@ import FirebaseRemoteConfig -protocol ForceUpdateManager { - func checkForUpdate() async -> Bool -} - -final class DefaultForceUpdateManager: ForceUpdateManager { +final class DefaultForceUpdateRepository: ForceUpdateInterface { let remoteConfig = RemoteConfig.remoteConfig() init() { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ForeceUpdateInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ForeceUpdateInterface.swift new file mode 100644 index 00000000..9e0cc6ac --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ForeceUpdateInterface.swift @@ -0,0 +1,10 @@ +// +// ForeceUpdateInterface.swift +// ByeBoo-iOS +// +// Created by 이나연 on 7/5/26. +// + +protocol ForceUpdateInterface { + func checkForUpdate() async -> Bool +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift index 44dc49db..9867b4c3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift @@ -54,7 +54,8 @@ extension SplashViewController { private func bindCheckForceUpdate() { viewModel.output.forceUpdatePublisher .receive(on: DispatchQueue.main) - .sink { result in + .sink { [weak self] result in + guard let self else { return } switch result { case true: self.presentForceUpdateModal() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift index 27d0b3c2..0b387e67 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift @@ -60,14 +60,12 @@ extension SplashViewModel { private func checkForceUpdate() { Task { do { - if try await checkForceUpdateUseCase.execute() { + if await checkForceUpdateUseCase.execute() { forceUpdateSubject.send(true) ByeBooLogger.debug("강제 업데이트 필요") } else { forceUpdateSubject.send(false) } - } catch { - forceUpdateSubject.send(false) } } } From bfe9b52da8ab3e095906183f95607a6a41fb1d40 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 6 Jul 2026 00:36:28 +0900 Subject: [PATCH 210/231] =?UTF-8?q?fix:=20#470=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 2 +- .../ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index caecb078..076b68bf 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -257,7 +257,7 @@ struct DomainDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: CheckForceUpdateUseCase.self) { _ in - return DefaultCheckForceUpdateUsecase(repository: DefaultForceUpdateManager()) + return DefaultCheckForceUpdateUsecase(repository: DefaultForceUpdateRepository()) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift index ad0f6d59..d9838484 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift @@ -13,9 +13,9 @@ protocol CheckForceUpdateUseCase { struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase { - private let repository: ForceUpdateManager + private let repository: ForceUpdateInterface - init(repository: ForceUpdateManager) { + init(repository: ForceUpdateInterface) { self.repository = repository } From 999c2eec2b232ce19f68b79fd2530d455e380885 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 6 Jul 2026 20:09:47 +0900 Subject: [PATCH 211/231] =?UTF-8?q?refactor:=20#470=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=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 --- .../Data/Persistence/Service/ForceUpdateManager.swift | 4 ++-- .../Feature/Login/ViewController/SplashViewController.swift | 2 +- .../Feature/Login/ViewModel/SplashViewModel.swift | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift index 20f144bd..cc4a0be2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift @@ -7,13 +7,13 @@ import FirebaseRemoteConfig -final class DefaultForceUpdateRepository: ForceUpdateInterface { +final class DefaultForceUpdateService: ForceUpdateInterface { let remoteConfig = RemoteConfig.remoteConfig() init() { let settings = RemoteConfigSettings() settings.minimumFetchInterval = 0 - RemoteConfig.remoteConfig().configSettings = settings + remoteConfig.configSettings = settings } func checkForUpdate() async -> Bool { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift index 9867b4c3..c4e2f421 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift @@ -60,7 +60,7 @@ extension SplashViewController { case true: self.presentForceUpdateModal() case false: - self.viewModel.action(.tryAutoLogin) + ByeBooLogger.debug("강제업데이트 필요 없음") } } .store(in: &cancellables) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift index 0b387e67..284a05e1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift @@ -37,7 +37,6 @@ final class SplashViewModel { extension SplashViewModel { enum Input { case viewDidLoad - case tryAutoLogin } struct Output { @@ -49,8 +48,6 @@ extension SplashViewModel { switch trigger { case .viewDidLoad: checkForceUpdate() - case .tryAutoLogin: - autoLogin() } } @@ -65,6 +62,7 @@ extension SplashViewModel { ByeBooLogger.debug("강제 업데이트 필요") } else { forceUpdateSubject.send(false) + autoLogin() } } } From c7a6a8bc8c4338794f6811c1b28f26d9d0ca6b35 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 6 Jul 2026 20:11:01 +0900 Subject: [PATCH 212/231] =?UTF-8?q?fix:=20#470=20=EB=B9=8C=EB=93=9C?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 2 +- .../ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 076b68bf..648e046e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -257,7 +257,7 @@ struct DomainDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: CheckForceUpdateUseCase.self) { _ in - return DefaultCheckForceUpdateUsecase(repository: DefaultForceUpdateRepository()) + return DefaultCheckForceUpdateUseCase(repository: DefaultForceUpdateService()) } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift index d9838484..e9dc4e2b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift @@ -11,7 +11,7 @@ protocol CheckForceUpdateUseCase { func execute() async -> Bool } -struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase { +struct DefaultCheckForceUpdateUseCase: CheckForceUpdateUseCase { private let repository: ForceUpdateInterface From f31ff106a2d3006dd8124bb8ec34bacd948148f2 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 12 Jul 2026 14:55:04 +0900 Subject: [PATCH 213/231] =?UTF-8?q?fix:=20#472=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=84=B0=EC=B9=98=20=EC=98=81=EC=97=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/View/CommonQuest/Common/QuestContentView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 0b2a6991..97b71977 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 @@ -64,10 +64,15 @@ final class QuestContentView: BaseView { $0.spacing = 4.adjustedW } } + likeContainerView.do { + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(likeButtonDidTap)) + $0.addGestureRecognizer(tapRecognizer) + $0.isUserInteractionEnabled = true + } likeButton.do { $0.setImage(.heartOff, for: .normal) $0.setImage(.heartOn, for: .selected) - $0.addTarget(self, action: #selector(likeButtonDidTap), for: .touchUpInside) + $0.isUserInteractionEnabled = false } commentIcon.do { $0.image = .comment From 8cfb72acad6dd1b787232ca4dca7c3d1e9778bfa Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 12 Jul 2026 15:42:13 +0900 Subject: [PATCH 214/231] =?UTF-8?q?refactor:=20#472=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Login/View/LoginView.swift | 4 +++- .../Login/ViewController/LoginViewController.swift | 4 +++- .../Feature/Login/ViewModel/LoginViewModel.swift | 11 ++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/View/LoginView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/View/LoginView.swift index 64427a21..2bdd49dd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/View/LoginView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/View/LoginView.swift @@ -35,11 +35,13 @@ final class LoginView: BaseView { appleLoginButton.do { $0.setImage(.appleLoginButton, for: .normal) $0.alpha = 0 + $0.isExclusiveTouch = true } - + kakaoLoginButton.do { $0.setImage(.kakaoLoginButton, for: .normal) $0.alpha = 0 + $0.isExclusiveTouch = true } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift index 8ad6c509..fb2b2526 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift @@ -64,12 +64,14 @@ extension LoginViewController{ @objc private func kakaoLoginButtonDidTap() { rootView.kakaoLoginButton.isUserInteractionEnabled = false + rootView.appleLoginButton.isUserInteractionEnabled = false self.platform = .KAKAO viewModel.action(.socialLoginButtonDidTap(platform: .KAKAO)) } - + @objc private func appleLoginButtonDidTap() { + rootView.kakaoLoginButton.isUserInteractionEnabled = false rootView.appleLoginButton.isUserInteractionEnabled = false self.platform = .APPLE viewModel.action(.socialLoginButtonDidTap(platform: .APPLE)) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift index 1c2b74c3..334ee70a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/LoginViewModel.swift @@ -23,7 +23,8 @@ final class LoginViewModel: NSObject { } private var cancellables = Set() - + private var isSocialLoginInProgress = false + private let socialLoginUseCase: SocialLoginUseCase private let getIsRegisteredUseCase: GetIsRegisteredUseCase private let getUserIDUseCase: GetUserIDUseCase @@ -60,7 +61,15 @@ extension LoginViewModel { extension LoginViewModel { private func socialLogin(platform: LoginPlatform) { + guard !isSocialLoginInProgress else { + ByeBooLogger.debug("이미 진행중인 요청 있음") + return + } + isSocialLoginInProgress = true + Task { + defer { isSocialLoginInProgress = false } + do { let _ = try await socialLoginUseCase.execute(platform: platform) socialLoginAuthSubject.send(.success(())) From 4084158d2d23b0579135d49050ef0af6e13caec6 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:06:52 +0900 Subject: [PATCH 215/231] =?UTF-8?q?refactor:=20#474=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=97=A4=EB=8D=94=20=EB=B7=B0=EC=99=80=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=85=80=20=EA=B0=84=20=EA=B0=84=EA=B2=A9=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Home/View/Notice/NoticeCardsView.swift | 1 + .../Home/ViewController/NotificationsViewController.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift index d97dcff6..180302c7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardsView.swift @@ -21,6 +21,7 @@ final class NoticeCardsView: BaseView { bottom: 24.adjustedH, right: 0 ) + $0.sectionHeaderTopPadding = 0 } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index d6315d04..e4281a55 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -176,6 +176,6 @@ extension NotificationsViewController: UITableViewDelegate { _ tableView: UITableView, heightForHeaderInSection section: Int ) -> CGFloat { - 0 + return section == 0 ? 0 : 24.adjustedH } } From fbffcd1b46b05d451cea87eb2b7051f0df116676 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:22:18 +0900 Subject: [PATCH 216/231] =?UTF-8?q?fix:=20#474=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=80=98=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=84=B8=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=EC=97=90=EC=84=9C=20=EB=92=A4=EB=A1=9C=20=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EC=8B=9C=20=EB=82=98=EC=9D=98=20=EC=97=AC=EC=A0=95?= =?UTF-8?q?=20=ED=83=AD=EC=9C=BC=EB=A1=9C=20=EB=96=A8=EC=96=B4=EC=A7=80?= =?UTF-8?q?=EB=8A=94=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/ParentQuestViewController.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index ee3a4ce6..918e2cb2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -35,11 +35,6 @@ final class ParentQuestViewController: BaseViewController, ToastPres tabBar.select(index: selectedIndex) } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - selectedIndex = 0 - } - override func viewDidLoad() { super.viewDidLoad() From 1a60a5427a997db7132811295e69efcfbbfc3605 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Wed, 22 Jul 2026 01:40:17 +0900 Subject: [PATCH 217/231] =?UTF-8?q?fix:=20#476=20-=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=ED=91=9C=EA=B8=B0=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift index 25f2e112..e7514006 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/Util/ServerDateFormatter.swift @@ -21,7 +21,6 @@ final class ServerDateFormatter { formatter.do { $0.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS" $0.locale = Locale(identifier: "en_US_POSIX") - $0.timeZone = TimeZone(secondsFromGMT: 0) } } From e108934b83b8d7566e36bdbd63a127f665c14316 Mon Sep 17 00:00:00 2001 From: JURI <80569323+juri123123@users.noreply.github.com> Date: Wed, 22 Jul 2026 02:03:40 +0900 Subject: [PATCH 218/231] =?UTF-8?q?[Fix]=20#476=20-=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS.xcodeproj/project.pbxproj | 8 +- .../Presentation/Extension/UITableView+.swift | 3 + .../Cells/CommentTableViewCell.swift | 80 ++++++++++++++----- 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index 2967de7b..eacd3cbd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -310,7 +310,7 @@ CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Dev.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 14; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; @@ -354,7 +354,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; GENERATE_INFOPLIST_FILE = YES; @@ -515,7 +515,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 14; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.2; MACOSX_DEPLOYMENT_TARGET = 15.2; @@ -537,7 +537,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 14; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.2; MACOSX_DEPLOYMENT_TARGET = 15.2; diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift index fc2a002b..d611132c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift @@ -7,6 +7,9 @@ import UIKit +// intrinsicContentSize가 없어 콘텐츠 크기만큼 스스로 커지지 못한다. +// isScrollEnabled = false로 쓰고 contentSize를 intrinsicContentSize로 노출시켜, +// 바깥 UIScrollView 안에 스크롤 없이 끼워넣어도 콘텐츠 높이에 맞게 자동으로 커지게 한다. final class SelfSizingTableView: UITableView { override var contentSize: CGSize { didSet { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index 9fa1dafb..362f8639 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -41,6 +41,9 @@ final class CommentTableViewCell: UITableViewCell { private var isMyComment: Bool = false + private var showAllText: Bool = false + private var lastTruncationWidth: CGFloat = -1 + override init( style: UITableViewCell.CellStyle, reuseIdentifier: String? @@ -57,6 +60,30 @@ final class CommentTableViewCell: UITableViewCell { fatalError("init(coder:) has not been implemented") } + override func layoutSubviews() { + super.layoutSubviews() + + // bounds.width가 이 기기 폭으로 확정된 뒤에만 계산해야 기기별 오차가 없다. + let width = commentTextView.bounds.width + guard width > 0, width != lastTruncationWidth else { return } + lastTruncationWidth = width + + updateTextTruncation() + } + + // exclusionPaths 변경으로 layoutSubviews가 재호출돼도 width는 안 바뀌므로 위 guard에서 걸러진다. + private func updateTextTruncation() { + commentTextView.textContainer.maximumNumberOfLines = 0 + commentTextView.textContainer.exclusionPaths = [] + + if showAllText { + commentTextView.invalidateIntrinsicContentSize() + moreLabel.isHidden = true + } else { + applyStyleWhenHideText() + } + } + private func setUI() { contentView.addSubviews(profileIcon, nicknameLabel, dateLabel, menuButton, commentTextView, moreLabel) } @@ -229,20 +256,17 @@ extension CommentTableViewCell { self.profileIcon.image = profileIcon dateLabel.text = writtenAt self.content = content + self.showAllText = showAllText commentTextView.applyTextViewStyle(style: .body6R14, text: content, color: .grayscale100) - layoutIfNeeded() - commentTextView.textContainer.maximumNumberOfLines = 0 commentTextView.textContainer.exclusionPaths = [] - - if showAllText { - commentTextView.invalidateIntrinsicContentSize() - moreLabel.isHidden = true - } else { - let numberOfLines = commentTextView.numberOfLine() - applyStyleWhenHideText(numberOfLines) - } + + // setNeedsLayout만으로는 부족하다: commentListView가 스크롤 없는 self-sizing 테이블이라 + // 일반 테이블처럼 셀이 화면에 나타날 때 자연스럽게 layoutSubviews가 재호출되지 않는다. + // layoutIfNeeded로 직접 트리거해야 truncation 계산(layoutSubviews)이 그 자리에서 실행된다. + lastTruncationWidth = -1 + layoutIfNeeded() } func updateReplyCount(replyCount: Int) { @@ -252,16 +276,37 @@ extension CommentTableViewCell { } extension CommentTableViewCell { - private func applyStyleWhenHideText(_ numberOfLines: Int) { - moreLabel.isHidden = numberOfLines > 5 ? false : true - commentTextView.textContainer.maximumNumberOfLines = 5 - + private func applyStyleWhenHideText() { + let textContainer = commentTextView.textContainer + let layoutManager = commentTextView.layoutManager + + // textContainer.size는 UITextView가 자기 layoutSubviews에서 뒤늦게 동기화하므로 직접 맞춰준다. + textContainer.size = CGSize(width: commentTextView.bounds.width, height: .greatestFiniteMagnitude) + textContainer.maximumNumberOfLines = 5 + + // glyphRange(for:)는 잘려도 전체 glyph 수를 그대로 반환해 truncation 판단에 못 쓴다. + // 마지막 줄이 실제로 잘렸는지는 truncatedGlyphRange(inLineFragmentForGlyphAt:)로 확인한다. + layoutManager.ensureLayout(for: textContainer) + let glyphRange = layoutManager.glyphRange(for: textContainer) + var isTruncated = false + if glyphRange.length > 0 { + let lastGlyphIndex = NSMaxRange(glyphRange) - 1 + let truncatedRange = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: lastGlyphIndex) + isTruncated = truncatedRange.location != NSNotFound + } + + moreLabel.isHidden = !isTruncated + + guard isTruncated else { + commentTextView.textContainer.exclusionPaths = [] + return + } + let moreLabelWidth: CGFloat = moreLabel.intrinsicContentSize.width let contentHeight = commentTextView.sizeThatFits( CGSize(width: commentTextView.bounds.width, height: .infinity) ).height - - + let lineHeight: CGFloat if let paragraphStyle = commentTextView.attributedText?.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle, @@ -270,14 +315,13 @@ extension CommentTableViewCell { } else { lineHeight = commentTextView.font?.lineHeight ?? 0 } - + let exclusionRect = CGRect( x: commentTextView.bounds.width - moreLabelWidth - 10, y: contentHeight - lineHeight, width: moreLabelWidth, height: lineHeight ) - ByeBooLogger.debug("가로 : \(exclusionRect.width), 세로: \(exclusionRect.height)" ) commentTextView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionRect)] } From f52289f27326f75d9e71ebdcdb8c2fb7ba0da896 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sat, 25 Jul 2026 02:35:21 +0900 Subject: [PATCH 219/231] =?UTF-8?q?fix:=20#478=20=EA=B3=B5=EB=B0=B1?= =?UTF-8?q?=EB=A7=8C=20=EC=9E=88=EC=9D=84=20=EB=95=8C=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EC=9D=84=20=EB=88=84=EB=A5=BC=20=EC=88=98=20?= =?UTF-8?q?=EC=97=86=EA=B2=8C=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/View/CommonQuest/Common/CommentTextView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift index 992b4b1a..0862dbca 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextView.swift @@ -123,7 +123,7 @@ extension CommentTextView: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { textCountLabel.text = "\(textView.text.count)/500" - let hasText = !textView.text.isEmpty + let hasText = !textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty confirmButton.isEnabled = hasText confirmButton.applyByeBooFont(style: .body2M16, text: "완료", color: hasText ? .primary300 : .grayscale600) From d3594b3427e90fe521e40cfe983823b9952cffa9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 27 Jul 2026 01:30:31 +0900 Subject: [PATCH 220/231] =?UTF-8?q?refactor:=20#480=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=EC=97=AC=EC=A0=95=20=EB=8B=B5=EB=B3=80=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=EC=97=90=EC=84=9C=20=EB=92=A4=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EA=B8=B0=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=97=AC=EC=A0=95=20=ED=83=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewController/ParentQuestViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index 918e2cb2..d8461d2c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -114,6 +114,7 @@ extension ParentQuestViewController { else { return } + selectedIndex = index show(controllers[index]) } } From 90d8506983660832655d210b8f3b577ec12597a2 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 27 Jul 2026 01:46:31 +0900 Subject: [PATCH 221/231] =?UTF-8?q?fix:=20#480=20=ED=80=98=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=83=AD=20=EB=B3=B5=EA=B7=80=20=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EB=90=9C=20=EC=84=9C=EB=B8=8C=ED=83=AD?= =?UTF-8?q?=EC=9D=B4=20=EC=B4=88=EA=B8=B0=ED=99=94=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/ParentQuestViewController.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index d8461d2c..bd15c188 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -35,6 +35,14 @@ final class ParentQuestViewController: BaseViewController, ToastPres tabBar.select(index: selectedIndex) } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if navigationController?.topViewController === self { + selectedIndex = 0 + } + } + override func viewDidLoad() { super.viewDidLoad() From e1c2d568e756b388c7ea10163d3f9bb12dd4e573 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:02:24 +0900 Subject: [PATCH 222/231] =?UTF-8?q?refactor:=20#480=20viewWillDisappear?= =?UTF-8?q?=EC=9D=84=20viewDidLoad=20=EB=92=A4=EB=A1=9C=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParentQuestViewController.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift index bd15c188..1b3fd2e0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -35,14 +35,6 @@ final class ParentQuestViewController: BaseViewController, ToastPres tabBar.select(index: selectedIndex) } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - if navigationController?.topViewController === self { - selectedIndex = 0 - } - } - override func viewDidLoad() { super.viewDidLoad() @@ -57,6 +49,14 @@ final class ParentQuestViewController: BaseViewController, ToastPres ) } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if navigationController?.topViewController === self { + selectedIndex = 0 + } + } + override func setView() { view.addSubviews( tabBar, From a96fce7f5e0c1bcdd21b090c35ebdfe65b06d555 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 3 Aug 2026 14:59:10 +0900 Subject: [PATCH 223/231] =?UTF-8?q?feat:=20#482=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestReplyViewController.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index f5eb5a81..e4089b8b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -100,10 +100,12 @@ extension CommonQuestReplyViewController { viewModel.output.postReplyPublisher .receive(on: DispatchQueue.main) - .sink { result in + .sink { [weak self] result in + guard let self else { return } switch result { case .success: ByeBooLogger.debug("답글 입력 성공") + self.scrollToComment() case .failure(let error): ByeBooLogger.debug(error) } @@ -187,6 +189,14 @@ extension CommonQuestReplyViewController { self.dismiss(animated: true) onDismiss?() } + + private func scrollToComment() { + let snapshot = dataSource.snapshot() + let replyCount = snapshot.numberOfItems(inSection: .replies) + guard replyCount > 0 else { return } + let lastIndex = IndexPath(row: replyCount - 1, section: 1) + rootView.commentListView.scrollToRow(at: lastIndex, at: .middle, animated: true) + } } extension CommonQuestReplyViewController: CommentProtocol { From 8c5d6be437494db47340e6ca54a8a12154a49ccd Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 3 Aug 2026 15:49:41 +0900 Subject: [PATCH 224/231] =?UTF-8?q?feat:=20#482=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) 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 07840207..aa56c93d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -184,7 +184,7 @@ extension CommonQuestHistoryViewController: CommentProtocol { guard item.entity.commentID == commentID else { return item } return CommentItem(entity: item.entity, showAllText: true) } - + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(updatedItems, toSection: .main) @@ -198,7 +198,7 @@ extension CommonQuestHistoryViewController: CommentProtocol { let entity = viewModel.getComment(commentID: commentID) guard let entity else { return } viewController.configure(entity: entity, commentID: commentID) - + viewController.onReplyCountChanged = { [weak self] commentID, newCount in guard let self else { return } let item = dataSource.snapshot().itemIdentifiers.first { $0.entity.commentID == commentID } @@ -213,7 +213,7 @@ extension CommonQuestHistoryViewController: CommentProtocol { guard let self else { return } viewModel.action(.fetchQuestDetail(answerID: answerID)) } - + if let sheet = viewController.sheetPresentationController{ sheet.detents = [.large()] sheet.prefersGrabberVisible = true @@ -313,10 +313,12 @@ extension CommonQuestHistoryViewController { private func bindPostComment() { viewModel.output.postCommentPublisher .receive(on: DispatchQueue.main) - .sink { result in + .sink { [weak self] result in + guard let self else { return } switch result { case .success: ByeBooLogger.debug("댓글 입력 성공") + self.scrollToComment() case .failure(let error): ByeBooLogger.debug(error) } @@ -366,11 +368,11 @@ extension CommonQuestHistoryViewController { content: comment.content ) } - + private func configureWhenCommonQuest(sheet: CommonQuestBottomSheetViewController) { let entity = viewModel.detailEntity guard let entity else { return } - + let sheetType: CommonQuestArchiveType = entity.isMyAnswer ? .myAnswer : .otherAnswer sheet.configure( sheetType: sheetType, @@ -381,6 +383,19 @@ extension CommonQuestHistoryViewController { writtenAt: entity.isMyAnswer ? entity.writtenAt : nil ) } + + private func scrollToComment() { + let snapshot = dataSource.snapshot() + guard snapshot.numberOfItems > 0 else { return } + rootView.layoutIfNeeded() + + let lastIndex = IndexPath(row: snapshot.numberOfItems - 1, section: 0) + let rowRect = rootView.commentListView.rectForRow(at: lastIndex) + var rectInScrollView = rootView.commentListView.convert(rowRect, to: rootView.scrollView) + rectInScrollView.size.height = rootView.scrollView.contentSize.height - rectInScrollView.origin.y + + rootView.scrollView.scrollRectToVisible(rectInScrollView, animated: true) + } } extension CommonQuestHistoryViewController: KeyboardHandleProtocol { func keyboardWillShowOrHide(height: CGFloat, duration: Double) { From 78a79e460fea015fac0f6a647c464c0af4b06e53 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 3 Aug 2026 15:49:51 +0900 Subject: [PATCH 225/231] =?UTF-8?q?setting:=20#482=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index eacd3cbd..de2d12b6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -310,7 +310,7 @@ CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Dev.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 18; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; @@ -354,7 +354,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 18; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; GENERATE_INFOPLIST_FILE = YES; @@ -515,7 +515,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 15; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.2; MACOSX_DEPLOYMENT_TARGET = 15.2; @@ -537,7 +537,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 15; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.2; MACOSX_DEPLOYMENT_TARGET = 15.2; From f566f036f3ab5c826fd0ea3b6f6c3677faf6c0ef Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 3 Aug 2026 15:54:20 +0900 Subject: [PATCH 226/231] =?UTF-8?q?chore:=20#482=20middle=20>=20bottom?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestReplyViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index e4089b8b..4ab7536c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -195,7 +195,7 @@ extension CommonQuestReplyViewController { let replyCount = snapshot.numberOfItems(inSection: .replies) guard replyCount > 0 else { return } let lastIndex = IndexPath(row: replyCount - 1, section: 1) - rootView.commentListView.scrollToRow(at: lastIndex, at: .middle, animated: true) + rootView.commentListView.scrollToRow(at: lastIndex, at: .bottom, animated: true) } } From 6cf0a21345e18b4e85530fb44f7d303aa8bf4f90 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 3 Aug 2026 16:05:24 +0900 Subject: [PATCH 227/231] =?UTF-8?q?chore:=20#482=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestReplyViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 4ab7536c..f852d994 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -194,6 +194,8 @@ extension CommonQuestReplyViewController { let snapshot = dataSource.snapshot() let replyCount = snapshot.numberOfItems(inSection: .replies) guard replyCount > 0 else { return } + rootView.layoutIfNeeded() + let lastIndex = IndexPath(row: replyCount - 1, section: 1) rootView.commentListView.scrollToRow(at: lastIndex, at: .bottom, animated: true) } From aff9475ce2a7893d65a361568c4bab1dd065219d Mon Sep 17 00:00:00 2001 From: Lee Nayeon Date: Sun, 9 Aug 2026 22:58:18 +0900 Subject: [PATCH 228/231] =?UTF-8?q?setting:=20#485=20fastfile=EC=97=90=20r?= =?UTF-8?q?elease=20=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/fastlane/Fastfile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ByeBoo-iOS/fastlane/Fastfile b/ByeBoo-iOS/fastlane/Fastfile index e9d58bc2..230852b0 100644 --- a/ByeBoo-iOS/fastlane/Fastfile +++ b/ByeBoo-iOS/fastlane/Fastfile @@ -29,4 +29,21 @@ platform :ios do ) upload_to_testflight end + + desc "Release a new version to the App Store" + lane :release_app_store do + increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") + match(type: "appstore") + build_app( + scheme: "ByeBoo-iOS", + export_method: "app-store" + ) + upload_to_app_store( + skip_screenshots: true, + skip_metadata: true, + submit_for_review: true, + force: true + ) + end +end end From 7609f93fcd87b8eefaca69a7dc880ec2d1ed2099 Mon Sep 17 00:00:00 2001 From: Lee Nayeon Date: Sun, 9 Aug 2026 23:16:16 +0900 Subject: [PATCH 229/231] =?UTF-8?q?docs:=20#485=20fastlane=20yml=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/fastlane_ci.yml | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/fastlane_ci.yml diff --git a/.github/workflows/fastlane_ci.yml b/.github/workflows/fastlane_ci.yml new file mode 100644 index 00000000..f401f7e5 --- /dev/null +++ b/.github/workflows/fastlane_ci.yml @@ -0,0 +1,66 @@ +name: Fastlane CI + +on: + pull_request: + branches: + - main + - develop + types: [closed] + +jobs: + fastlane: + name: Run Fastlane + runs-on: macos-latest + if: github.event.pull_request.merged == true + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1 + + - name: Install Bundler + run: gem install bundler + + - name: Install dependencies + run: bundle install + + - name: Output Fastlane environment + run: bundle exec fastlane env + + - name: Run upload_testflight + if: github.event.pull_request.base.ref == 'develop' + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} + FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} + APP_IDENTIFIER: ${{ secrets.APP_IDENTIFIER }} + FASTLANE_ITC_TEAM_ID: ${{ secrets.FASTLANE_ITC_TEAM_ID }} + TEAM_ID: ${{ secrets.TEAM_ID }} + PRIVATE_REPOSITORY: ${{ secrets.PRIVATE_REPOSITORY }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + DISCORD_URL: ${{ secrets.DISCORD_URL }} + run: bundle exec fastlane upload_testflight + + - name: Run release_app_store on main + if: github.event.pull_request.base.ref == 'main' + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} + FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} + APP_IDENTIFIER: ${{ secrets.APP_IDENTIFIER }} + FASTLANE_ITC_TEAM_ID: ${{ secrets.FASTLANE_ITC_TEAM_ID }} + TEAM_ID: ${{ secrets.TEAM_ID }} + PRIVATE_REPOSITORY: ${{ secrets.PRIVATE_REPOSITORY }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + DISCORD_URL: ${{ secrets.DISCORD_URL }} + run: bundle exec fastlane release_app_store From 8d705f08a4e847b6e03045946d0bce739685bdc4 Mon Sep 17 00:00:00 2001 From: Lee Nayeon Date: Sun, 9 Aug 2026 23:31:01 +0900 Subject: [PATCH 230/231] =?UTF-8?q?setting:=20#485=20=EB=94=94=EC=8A=A4?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9B=B9=ED=9B=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/fastlane/Fastfile | 69 +++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/ByeBoo-iOS/fastlane/Fastfile b/ByeBoo-iOS/fastlane/Fastfile index 230852b0..a8a88c47 100644 --- a/ByeBoo-iOS/fastlane/Fastfile +++ b/ByeBoo-iOS/fastlane/Fastfile @@ -19,31 +19,60 @@ ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10" platform :ios do + + def notify_discord_success(lane_name) + sh( + "curl -H 'Content-Type: application/json' " + + "-d '{\"content\": \"💟🍎 **#{lane_name}** 성공! 🎉\"}' " + + "#{ENV['DISCORD_URL']}" + ) + end + + def notify_discord_failure(lane_name, error_message) + sh( + "curl -H 'Content-Type: application/json' " + + "-d '{\"content\": \"🤔🌀 **#{lane_name}** 실패\\n```#{error_message.to_s.gsub('"', "'").gsub("\n", " ")}```\"}' " + + "#{ENV['DISCORD_URL']}" + ) + end + desc "Push a new beta build to TestFlight" lane :upload_testflight do - increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") - match(type: "appstore") - build_app( - scheme: "ByeBoo-iOS", - export_method: "app-store" - ) - upload_to_testflight + begin + increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") + match(type: "appstore") + build_app( + scheme: "ByeBoo-iOS", + export_method: "app-store" + ) + upload_to_testflight + notify_discord_success("TestFlight 업로드") + rescue => e + notify_discord_failure("TestFlight 업로드", e.message) + raise e + end end desc "Release a new version to the App Store" lane :release_app_store do - increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") - match(type: "appstore") - build_app( - scheme: "ByeBoo-iOS", - export_method: "app-store" - ) - upload_to_app_store( - skip_screenshots: true, - skip_metadata: true, - submit_for_review: true, - force: true - ) + begin + increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") + match(type: "appstore") + build_app( + scheme: "ByeBoo-iOS", + export_method: "app-store" + ) + upload_to_app_store( + skip_screenshots: true, + skip_metadata: true, + submit_for_review: true, + force: true + ) + notify_discord_success("App Store 배포") + rescue => e + notify_discord_failure("App Store 배포", e.message) + raise e + end end -end + end From 8e1c26c0e6112fdc22c9c4f09fabae4121456183 Mon Sep 17 00:00:00 2001 From: Lee Nayeon Date: Sun, 9 Aug 2026 23:56:38 +0900 Subject: [PATCH 231/231] =?UTF-8?q?setting:=20#485=20fastfile=20=ED=97=AC?= =?UTF-8?q?=ED=8D=BC=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/fastlane/Fastfile | 66 +++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/ByeBoo-iOS/fastlane/Fastfile b/ByeBoo-iOS/fastlane/Fastfile index a8a88c47..66dfbbdc 100644 --- a/ByeBoo-iOS/fastlane/Fastfile +++ b/ByeBoo-iOS/fastlane/Fastfile @@ -18,6 +18,9 @@ default_platform(:ios) ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10" +XCODEPROJ = "ByeBoo-iOS.xcodeproj" +SCHEME = "ByeBoo-iOS" + platform :ios do def notify_discord_success(lane_name) @@ -36,17 +39,45 @@ platform :ios do ) end + def prepare_ci_and_signing + setup_ci + match(type: "appstore") + update_project_team( + path: XCODEPROJ, + teamid: ENV['TEAM_ID'] + ) + end + + def next_build_number + latest_build = latest_testflight_build_number( + app_identifier: ENV['APP_IDENTIFIER'], + initial_build_number: 0 + ) + latest_build + 1 + end + + desc "Push a new beta build to TestFlight" lane :upload_testflight do begin - increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") - match(type: "appstore") + iprepare_ci_and_signing + + build_number = next_build_number + increment_build_number( + xcodeproj: XCODEPROJ, + build_number: build_number + ) + build_app( - scheme: "ByeBoo-iOS", + scheme: SCHEME, export_method: "app-store" ) - upload_to_testflight - notify_discord_success("TestFlight 업로드") + + upload_to_testflight( + skip_waiting_for_build_processing: true + ) + + notify_discord_success("TestFlight 업로드", build_number) rescue => e notify_discord_failure("TestFlight 업로드", e.message) raise e @@ -56,19 +87,30 @@ platform :ios do desc "Release a new version to the App Store" lane :release_app_store do begin - increment_build_number(xcodeproj: "ByeBoo-iOS.xcodeproj") - match(type: "appstore") + prepare_ci_and_signing + + build_number = next_build_number + increment_build_number( + xcodeproj: XCODEPROJ, + build_number: build_number + ) + build_app( - scheme: "ByeBoo-iOS", + scheme: SCHEME, export_method: "app-store" ) + upload_to_app_store( skip_screenshots: true, skip_metadata: true, - submit_for_review: true, - force: true - ) - notify_discord_success("App Store 배포") + force: true, + submit_for_review: true, # 자동으로 심사 제출 + automatic_release: true, # 심사 통과 후 자동 출시 + precheck_include_in_app_purchases: false, + submission_information: { + add_id_info_uses_idfa: false, # admob 추가 시 true로 변경 + export_compliance_uses_encryption: false + } rescue => e notify_discord_failure("App Store 배포", e.message) raise e