diff --git a/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift b/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift index 3eb0abd6..70136271 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Core/ByeBooError.swift @@ -27,6 +27,7 @@ enum ByeBooError: Error, LocalizedError, Equatable { case configError case fileNotFound case nicknameViolation + case questViolation var errorDescription: String? { switch self { @@ -68,6 +69,8 @@ enum ByeBooError: Error, LocalizedError, Equatable { return "파일을 찾을 수 없음" case .nicknameViolation: return "비속어나 부적절한 단어가 포함된 닉네임" + case .questViolation: + return "비속어나 부적절한 단어가 포함된 답변" } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift index c81a75e4..4569a410 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift @@ -47,6 +47,10 @@ struct DataDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: ForbiddenWordInterface.self) { _ in return DefaultForbiddenWordRepository() } + + DIContainer.shared.register(type: CommonQuestInterface.self) { _ in + return DefaultCommonQuestRepository(network: networkService) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/SaveCommonQuestRequestDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/SaveCommonQuestRequestDTO.swift new file mode 100644 index 00000000..8f205b65 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/SaveCommonQuestRequestDTO.swift @@ -0,0 +1,12 @@ +// +// SaveCommonQuest.swift +// ByeBoo-iOS +// +// Created by 이나연 on 3/1/26. +// + +import Foundation + +struct SaveCommonQuestRequestDTO: Encodable { + let answer: String +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift new file mode 100644 index 00000000..0fb844de --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/CommonQuestAPI.swift @@ -0,0 +1,61 @@ +// +// CommonQuestAPI.swift +// ByeBoo-iOS +// +// Created by 이나연 on 3/1/26. +// + +import Foundation + +import Alamofire + +enum CommonQuestAPI { + case postCommonQuest(questID: Int, dto: SaveCommonQuestRequestDTO) +} + +extension CommonQuestAPI: EndPoint { + + var basePath: String { + return "/api/v1/common-quests" + } + + var path: String { + switch self { + case .postCommonQuest(let questID, _): + return "/\(questID)" + } + } + + var method: HTTPMethod { + switch self { + case .postCommonQuest: + return .post + } + } + + var headers: HeaderType { + switch self { + case .postCommonQuest: + let keychainService = DefaultKeychainService() + return .withAuth(acessToken: keychainService.load(key: .accessToken)) + } + } + + var parameterEncoding: any ParameterEncoding { + switch self { + case .postCommonQuest: + return JSONEncoding.default + } + } + + var queryParameters: [String : String]? { + nil + } + + var bodyParameters: Parameters? { + switch self { + case .postCommonQuest(_, 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 new file mode 100644 index 00000000..2afdeacd --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/CommonQuestRepository.swift @@ -0,0 +1,26 @@ +// +// CommonQuestRepository.swift +// ByeBoo-iOS +// +// Created by 이나연 on 3/1/26. +// + +import Foundation + +struct DefaultCommonQuestRepository: CommonQuestInterface { + private let network: NetworkService + + init( + network: NetworkService + ) { + self.network = network + } + + func saveCommonQuest(questID: Int, answer: String) async throws { + let saveCommonQuestRequestDTO: SaveCommonQuestRequestDTO = .init(answer: answer) + + let _ = try await network.request( + CommonQuestAPI.postCommonQuest(questID: questID, dto: saveCommonQuestRequestDTO) + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index ba2900d8..58377627 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -20,11 +20,11 @@ struct DomainDependencyAssembler: DependencyAssembler { guard let userRepository = DIContainer.shared.resolve(type: UsersInterface.self), let questRepository = DIContainer.shared.resolve(type: QuestsInterface.self), let authRepository = DIContainer.shared.resolve(type: AuthInterface.self), - let forbiddenWordRepository = DIContainer.shared.resolve(type: ForbiddenWordInterface.self) - else { - ByeBooLogger.error(ByeBooError.DIFailedError) - return - } + let forbiddenWordRepository = DIContainer.shared.resolve(type: ForbiddenWordInterface.self), + let commonQuestRepository = DIContainer.shared.resolve(type: CommonQuestInterface.self) else { + ByeBooLogger.error(ByeBooError.DIFailedError) + return + } DIContainer.shared.register(type: FetchUserJourneyUseCase.self) { _ in return DefaultFetchUserJourneyUseCase(repository: userRepository) @@ -141,7 +141,7 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchCommonQuestByDateUseCase.self) { _ in return DefaultFetchCommonQuestByDateUseCase(repository: questRepository) } - + DIContainer.shared.register(type: AutoLoginUseCase.self) { _ in return DefaultAutoLoginUseCase(repository: authRepository) } @@ -169,5 +169,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: IsForbiddenWordUseCase.self) { _ in return DefaultIsForbiddenWordUseCase(repository: forbiddenWordRepository) } + + DIContainer.shared.register(type: SaveCommonQuestUseCase.self) { _ in + return DefaultSaveCommonQuestUseCase(repository: commonQuestRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift index 4ddb291c..d36a98a9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift @@ -9,6 +9,7 @@ import Foundation struct CommonQuestAnswersEntity { let question: String + let questID: Int let answerCount: Int let isAnswered: Bool let answers: [CommonQuestAnswerEntity] @@ -26,6 +27,7 @@ extension CommonQuestAnswersEntity { static func stub() -> Self { .init( question: "연애에서 반복된 문제 패턴 3가지를 생각해보아요", + questID: 1, answerCount: 5, isAnswered: false, answers: [.stub(), .stub(), .stub(), .stub(), .stub()] diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift new file mode 100644 index 00000000..cb7a0256 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/CommonQuestInterface.swift @@ -0,0 +1,12 @@ +// +// CommonQuestInterface.swift +// ByeBoo-iOS +// +// Created by 이나연 on 3/1/26. +// + +import Foundation + +protocol CommonQuestInterface { + func saveCommonQuest(questID: Int, answer: String) async throws +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/SaveCommonQuestUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/SaveCommonQuestUseCase.swift new file mode 100644 index 00000000..235a5bba --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/SaveCommonQuestUseCase.swift @@ -0,0 +1,24 @@ +// +// SaveCommonQuestUseCase.swift +// ByeBoo-iOS +// +// Created by 이나연 on 3/3/26. +// + +import Foundation + +protocol SaveCommonQuestUseCase { + func execute(questID: Int, answer: String) async throws +} + +struct DefaultSaveCommonQuestUseCase: SaveCommonQuestUseCase { + private let repository: CommonQuestInterface + + init(repository: CommonQuestInterface) { + self.repository = repository + } + + func execute(questID: Int, answer: String) async throws { + try await repository.saveCommonQuest(questID: questID, answer: answer) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/EmotionBottomSheet/EmotionBottomSheetViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/EmotionBottomSheet/EmotionBottomSheetViewController.swift index f3bac6c2..4d525071 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/EmotionBottomSheet/EmotionBottomSheetViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/BottomSheet/EmotionBottomSheet/EmotionBottomSheetViewController.swift @@ -78,7 +78,7 @@ final class EmotionBottomSheetViewController: BaseViewController { } self.delegate?.saveEmotionState(emotionState: selectedEmotion) - self.delegate?.saveQuest() + self.delegate?.saveQuest(isEdit: false, isCommonQuest: false) rootView.confirmButton.isEnabled = false let property = QuestEvents.QuestWriteFinishWithEmotionProperty( diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ModalBuilder.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ModalBuilder.swift index 6cca959c..3868109b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ModalBuilder.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ModalBuilder.swift @@ -34,6 +34,6 @@ struct ModalBuilder { } func dismiss() { - modalViewController.dismiss(animated: false) + modalViewController.dismiss(animated: true) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/TopTabBar/TopTabBar.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/TopTabBar/TopTabBar.swift index c5f696a2..604d6e53 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/TopTabBar/TopTabBar.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/TopTabBar/TopTabBar.swift @@ -54,7 +54,11 @@ final class TopTabBar: UIStackView { } extension TopTabBar { - + func select(index: Int) { + guard itemViews.indices.contains(index) else { return } + updateTabBar(tag: index) + } + @objc private func barDidTap(_ sender: UITapGestureRecognizer) { guard let tag = sender.view?.tag else { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/Coordinator/QuestCheckCoordinator.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/Coordinator/QuestCheckCoordinator.swift index 0b6b68fa..ca3dc1a1 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/Coordinator/QuestCheckCoordinator.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/Coordinator/QuestCheckCoordinator.swift @@ -77,14 +77,14 @@ final class QuestCheckCoordinator: QuestCheckCoordinating { private func moveToWriteQuestion(questID: Int, questNumber: Int, questType: QuestType) { let questionQuestViewController = ViewControllerFactory.shared.makeWriteQuestionTypeQuestViewController() - questionQuestViewController.configure(questID, questNumber, questType) + questionQuestViewController.configure(questID, questNumber, questType, nil) rootViewController?.tabBarController?.tabBar.isHidden = true rootViewController?.navigationController?.pushViewController(questionQuestViewController, animated: false) } private func moveToWriteActivity(questID: Int, questNumber: Int, questType: QuestType) { let activationQuestViewController = ViewControllerFactory.shared.makeWriteActiveTypeQuestViewController() - activationQuestViewController.configure(questID, questNumber, questType) + activationQuestViewController.configure(questID, questNumber, questType, nil) rootViewController?.tabBarController?.tabBar.isHidden = true rootViewController?.navigationController?.pushViewController(activationQuestViewController, animated: false) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestHeaderView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestHeaderView.swift index bd7ae307..312b96ea 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestHeaderView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestHeaderView.swift @@ -75,7 +75,7 @@ final class ArchiveQuestHeaderView: BaseView { color: .grayscale100, numberOfLines: 0 ) - $0.lineBreakMode = .byWordWrapping + $0.lineBreakStrategy = .hangulWordPriority } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestView.swift index af4b44c1..968c0244 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Archive/ArchiveQuestView.swift @@ -88,8 +88,8 @@ final class ArchiveQuestView: BaseView { } contentView.snp.makeConstraints { - $0.edges.equalToSuperview() - $0.width.equalToSuperview() + $0.edges.equalTo(scrollView.contentLayoutGuide) + $0.width.equalTo(scrollView.frameLayoutGuide) $0.height.greaterThanOrEqualTo(scrollView.frameLayoutGuide) } @@ -108,15 +108,13 @@ final class ArchiveQuestView: BaseView { textBoxView.snp.makeConstraints { if let photoBoxView { - if !descriptionText.isEmpty { - $0.top.equalTo(photoBoxView.snp.bottom).offset(20.adjustedH) - } + $0.top.equalTo(photoBoxView.snp.bottom).offset(20.adjustedH) } else { $0.top.equalTo(headerView.snp.bottom).offset(20.adjustedH) } $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) } - + feelView.snp.makeConstraints { $0.top.equalTo(textBoxView.snp.bottom).offset(20.adjustedH) $0.horizontalEdges.equalToSuperview() @@ -150,9 +148,9 @@ extension ArchiveQuestView { case .activation: guard let photoBoxView else { return } - if let url = URL(string: entity.imageUrl!) { - photoBoxView.kf.setImage(with: url) - } + guard let imageUrl = entity.imageUrl, + let url = URL(string: imageUrl) else { return } + photoBoxView.kf.setImage(with: url) if entity.answer.isEmpty { textBoxView.removeFromSuperview() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestContentCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestContentCell.swift index a36db95b..59a02d97 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestContentCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestContentCell.swift @@ -72,7 +72,7 @@ final class CommonQuestContentCell: UITableViewCell { } private func setUI() { - addSubviews( + contentView.addSubviews( questionView, guideTimeLabel, moveWriteAnswerButton, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/ActivationType/WriteActiveTypeQuestView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/ActivationType/WriteActiveTypeQuestView.swift index 1e75b510..2b87da9f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/ActivationType/WriteActiveTypeQuestView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/ActivationType/WriteActiveTypeQuestView.swift @@ -180,7 +180,7 @@ extension WriteActiveTypeQuestView: WriteQuestBaseProtocol { questTextField.textCountLabel } var tipTagView: UIView { - headerView.tipTag + headerView.tipTag ?? UIView() } } @@ -188,7 +188,6 @@ extension WriteActiveTypeQuestView { func updateQuestTitle( questScope: QuestScope? = nil, questNumber: Int, - questStyle: String, question: String ) { headerView.bind( diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/QuestionType/WriteQuestionTypeQuestView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/QuestionType/WriteQuestionTypeQuestView.swift index 1ebba3f3..c3dff053 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/QuestionType/WriteQuestionTypeQuestView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/View/QuestionType/WriteQuestionTypeQuestView.swift @@ -14,7 +14,7 @@ final class WriteQuestionTypeQuestView: BaseView { private let questScope: QuestScope private(set) var scrollView = UIScrollView() private let contentView = UIView() - private(set) var headerView = WriteQuestTitleView(questScope: .personal, questNum: 0, title: "") + private(set) var headerView = WriteQuestTitleView(questNum: 0, title: "") private let divider = UIView() private(set) var questTextField = QuestTextField(type: .question) @@ -100,7 +100,7 @@ extension WriteQuestionTypeQuestView: WriteQuestBaseProtocol { questTextField.textCountLabel } var tipTagView: UIView { - headerView.tipTag + headerView.tipTag ?? UIView() } } @@ -108,7 +108,6 @@ extension WriteQuestionTypeQuestView { func updateQuestTitle( questScope: QuestScope, questNumber: Int, - questStyle: String, question: String ) { headerView.bind( diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/WriteQuestTitleView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/WriteQuestTitleView.swift index 52f903d1..c89429ef 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/WriteQuestTitleView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/WriteQuestTitleView.swift @@ -15,7 +15,7 @@ final class WriteQuestTitleView: BaseView { private var questNum: Int private let questNumLabel = UILabel() private let titleLabel = UILabel() - let tipTag = ByeBooTipTag(text: "작성 TIP") + private(set) var tipTag = ByeBooTipTag(text: "작성 TIP") init( questScope: QuestScope? = nil, @@ -27,14 +27,14 @@ final class WriteQuestTitleView: BaseView { self.titleLabel.text = title if let questScope { - self.questNumLabel.text = { - switch questScope { - case .common: - "공통퀘스트" - case .personal: - "\(questNum)번째 퀘스트" - } - }() + switch questScope { + case .common: + questNumLabel.text = "공통퀘스트" + case .personal: + questNumLabel.text = "\(questNum)번째 퀘스트" + } + } else { + questNumLabel.text = "\(questNum)번째 퀘스트" } super.init(frame: .zero) @@ -43,7 +43,7 @@ final class WriteQuestTitleView: BaseView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func setUI() { addSubviews(questNumLabel, titleLabel, tipTag) } @@ -61,9 +61,16 @@ final class WriteQuestTitleView: BaseView { $0.lineBreakMode = .byWordWrapping } + tipTag.do { $0.isUserInteractionEnabled = true + if questScope == .common { + $0.isHidden = true + } else { + $0.isHidden = false + } } + } override func setLayout() { @@ -76,6 +83,9 @@ final class WriteQuestTitleView: BaseView { $0.top.equalTo(questNumLabel.snp.bottom).offset(12.adjustedH) $0.width.equalTo(327.adjustedW) $0.centerX.equalToSuperview() + if questScope == .common { + $0.bottom.equalToSuperview().inset(16.adjustedH) + } } tipTag.snp.makeConstraints { @@ -85,6 +95,7 @@ final class WriteQuestTitleView: BaseView { $0.centerX.equalToSuperview() $0.bottom.equalToSuperview().inset(16.adjustedH) } + } } @@ -94,16 +105,21 @@ extension WriteQuestTitleView { self.titleLabel.text = title if let questScope { - self.questNumLabel.text = { - switch questScope { - case .common: - "공통퀘스트" - case .personal: - "\(questNum)번째 퀘스트" + switch questScope { + case .common: + questNumLabel.text = "공통퀘스트" + tipTag.isHidden = true + + titleLabel.snp.makeConstraints { + $0.top.equalTo(questNumLabel.snp.bottom).offset(12.adjustedH) + $0.width.equalTo(327.adjustedW) + $0.centerX.equalToSuperview() + $0.bottom.equalToSuperview().inset(16.adjustedH) } - }() - } else { - self.questNumLabel.text = "\(questNum)번째 퀘스트" + case .personal: + questNumLabel.text = "\(questNum)번째 퀘스트" + tipTag.isHidden = false + } } - } + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ArchiveQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ArchiveQuestViewController.swift index f6248c99..b8b7418b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ArchiveQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ArchiveQuestViewController.swift @@ -17,7 +17,7 @@ enum ArchiveViewControllerEntryPoint { final class ArchiveQuestViewController: BaseViewController { - private var rootView = ArchiveQuestView(type: .activation) + private var rootView = ArchiveQuestView(type: .question) private let viewModel: ArchiveQuestViewModel private var cancellables = Set() 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 5fbc1616..054a49df 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -79,9 +79,14 @@ extension CommonQuestViewController: DateNavigatorDelegate { @objc private func moveWriteAnswerButtonDidTap() { - + let questID = viewModel.questID + let writeCommonQuestViewController = ViewControllerFactory.shared.makeWriteQuestionTypeQuestViewController() + writeCommonQuestViewController.navigationItem.hidesBackButton = true + writeCommonQuestViewController.questScope = .common + writeCommonQuestViewController.configure(questID, nil, QuestType.question, viewModel.question) + self.navigationController?.pushViewController(writeCommonQuestViewController, animated: false) } - + func dateDidChanged(to date: String) { let _ = viewModel.action( .moveDateButtonDidTap(selectedDate: date) @@ -91,7 +96,7 @@ extension CommonQuestViewController: DateNavigatorDelegate { } extension CommonQuestViewController: UITableViewDelegate { - + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0 || !viewModel.isExistAnswer { return 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 cdf32179..231c8d1d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/ParentQuestViewController.swift @@ -41,10 +41,16 @@ final class ParentQuestViewController: BaseViewController { containerView ) } + + func selectTab(index: Int) { + guard controllers.indices.contains(index) else { return } + show(controllers[index]) + tabBar.select(index: index) + } } extension ParentQuestViewController { - + private func setLayout() { tabBar.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20.adjustedH) 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 7de090ff..85a8ac6f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteActiveTypeQuestViewController.swift @@ -85,7 +85,7 @@ final class WriteActiveTypeQuestViewController: WriteQuestBaseViewController else { return } + + commonQuestTab.loadViewIfNeeded() + commonQuestTab.selectTab(index: 1) + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) { + + ViewControllerUtils.setRootViewController( + window: window, + viewController: viewController, + withAnimation: true + ) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { + let modal = ModalBuilder( + modalView: QuestCompleteModal(), + action: nil, + rootViewController: viewController + ) + modal.present() + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + modal.dismiss() + } + } } } 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 ad6b8b5e..0990582c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -58,6 +58,10 @@ extension CommonQuestViewModel { commonQuest?.question ?? "" } + var questID: Int { + commonQuest?.questID ?? 1 + } + var answersCount: Int { commonQuest?.answerCount ?? 0 } 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 aaa5bd33..8c89bac7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/WriteQuestTypeViewModel.swift @@ -18,31 +18,38 @@ struct WriteQuestionTypeViewModel: ViewModelType { private let getQuestInfoUseCase: GetQuestInfoUseCase private let editQuestTypeUseCase: EditQuestTypeUseCase private let isValidQuestAnswerUseCase: IsValidQuestAnswerUseCase + private let saveCommonQuestUseCase: SaveCommonQuestUseCase + private let isForbiddenWordUseCase: IsForbiddenWordUseCase private let questInfoResultSubject: PassthroughSubject, Never> = .init() private let questInfoWhenEditModeSubject: PassthroughSubject, Never> = .init() private let didSuccessPostSubject: PassthroughSubject, Never> = .init() private let didSuccessEditSubject: PassthroughSubject, Never> = .init() private let isValidTextSubject: PassthroughSubject = .init() + private let isForbiddenWordSubject: PassthroughSubject, Never> = .init() init( saveQuestTypeUseCase: SaveQuestTypeUseCase, getQuestInfoUseCase: GetQuestInfoUseCase, editQuestTypeUseCase: EditQuestTypeUseCase, - isValidQuestAnswerUseCase: IsValidQuestAnswerUseCase - + isValidQuestAnswerUseCase: IsValidQuestAnswerUseCase, + saveCommonQuestUseCase: SaveCommonQuestUseCase, + isForbiddenWordUseCase: IsForbiddenWordUseCase ){ self.saveQuestTypeUseCase = saveQuestTypeUseCase self.getQuestInfoUseCase = getQuestInfoUseCase self.editQuestTypeUseCase = editQuestTypeUseCase self.isValidQuestAnswerUseCase = isValidQuestAnswerUseCase + self.saveCommonQuestUseCase = saveCommonQuestUseCase + self.isForbiddenWordUseCase = isForbiddenWordUseCase output = Output( questInfoResultPublisher: questInfoResultSubject.eraseToAnyPublisher(), didSuccessPostPublisher: didSuccessPostSubject.eraseToAnyPublisher(), questInfoWhenEditModeResultPublisher: questInfoWhenEditModeSubject.eraseToAnyPublisher(), didSuccessEditPublisher: didSuccessEditSubject.eraseToAnyPublisher(), - isValidTextPublisher: isValidTextSubject.eraseToAnyPublisher() + isValidTextPublisher: isValidTextSubject.eraseToAnyPublisher(), + isForbiddenWordPublisher: isForbiddenWordSubject.eraseToAnyPublisher() ) } } @@ -50,7 +57,7 @@ struct WriteQuestionTypeViewModel: ViewModelType { extension WriteQuestionTypeViewModel { enum Input { case viewDidLoad(quesetID: Int) - case presentCompleteView(questID: Int, answer: String, emotionState: String?, isEdit: Bool) + case saveQuest(questID: Int, answer: String, emotionState: String?, isEdit: Bool, isCommonQuest: Bool) case viewDidLoadWhenEditMode(questID: Int) case textFieldEditing(answerText: String, text: String) } @@ -61,24 +68,43 @@ extension WriteQuestionTypeViewModel { let questInfoWhenEditModeResultPublisher: AnyPublisher, Never> let didSuccessEditPublisher: AnyPublisher, Never> let isValidTextPublisher: AnyPublisher + let isForbiddenWordPublisher: AnyPublisher, Never> } func action(_ trigger: Input) { switch trigger { case .viewDidLoad(let questID), .viewDidLoadWhenEditMode(let questID): getQuestInfo(questID: questID) - case let .presentCompleteView( + case let .saveQuest( questID, answer, emotionState, - isEdit + isEdit, + isCommonQuest ): + if isCommonQuest && isForbiddenWordUseCase.execute(word: answer) { + isForbiddenWordSubject.send(.failure(.questViolation)) + return + } + + if isCommonQuest { + if isEdit { + // 수정 api 연결 + } else { + saveCommonQuest(questID: questID, answer: answer) + } + return + } + if isEdit { editQuestType(questID: questID, answer: answer) - } else { - guard let emotionState = emotionState else { return } - postQuestType(questID: questID, answer: answer, emotionState: emotionState) + return } + + guard let emotionState else { return } + postQuestType(questID: questID, answer: answer, emotionState: emotionState) + + case .textFieldEditing(let answerText, let text): isValidText(previousText: answerText, changingText: text) } @@ -130,6 +156,21 @@ extension WriteQuestionTypeViewModel { } } + private func saveCommonQuest(questID: Int, answer: String) { + Task { + do { + try await saveCommonQuestUseCase.execute(questID: questID, answer: answer) + didSuccessPostSubject.send(.success(())) + } catch { + guard let error = error as? ByeBooError else { + return + } + didSuccessPostSubject.send(.failure(error as ByeBooError)) + ByeBooLogger.error(error) + } + } + } + private func isValidText(previousText: String, changingText: String) { let isValidText: Bool = isValidQuestAnswerUseCase.executeWhenQuestionType(previousText: previousText, changingText: changingText) isValidTextSubject.send(isValidText) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 8b0522c3..5f679800 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -36,7 +36,10 @@ struct PresentationDependencyAssembler: DependencyAssembler { guard let getQuestInfoUseCase = container.resolve(type: GetQuestInfoUseCase.self), let saveQuestTypeUseCase = container.resolve(type: SaveQuestTypeUseCase.self), let editQuestTypeUseCase = container.resolve(type: EditQuestTypeUseCase.self), - let isValidQuestAnswerUseCase = container.resolve(type: IsValidQuestAnswerUseCase.self) else { + let isValidQuestAnswerUseCase = container.resolve(type: IsValidQuestAnswerUseCase.self), + let saveCommonQuestUseCase = container.resolve(type: SaveCommonQuestUseCase.self), + let isForbbidenWordUseCoase = container.resolve(type: IsForbiddenWordUseCase.self) + else { ByeBooLogger.error(ByeBooError.DIFailedError) return } @@ -44,7 +47,10 @@ struct PresentationDependencyAssembler: DependencyAssembler { saveQuestTypeUseCase: saveQuestTypeUseCase, getQuestInfoUseCase: getQuestInfoUseCase, editQuestTypeUseCase: editQuestTypeUseCase, - isValidQuestAnswerUseCase: isValidQuestAnswerUseCase + isValidQuestAnswerUseCase: isValidQuestAnswerUseCase, + saveCommonQuestUseCase: saveCommonQuestUseCase, + isForbiddenWordUseCase: isForbiddenWordUseCase + ) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/BottomSheetProtocol.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/BottomSheetProtocol.swift index 73c60949..b37f4a9e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/BottomSheetProtocol.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/BottomSheetProtocol.swift @@ -9,5 +9,5 @@ import Foundation protocol BottomSheetProtocol: AnyObject { func saveEmotionState(emotionState: ByeBooEmotion) - func saveQuest() + func saveQuest(isEdit: Bool, isCommonQuest: Bool?) }