diff --git a/Qapple/Qapple.xcodeproj/project.pbxproj b/Qapple/Qapple.xcodeproj/project.pbxproj index a9d72c93..a04c4e7a 100644 --- a/Qapple/Qapple.xcodeproj/project.pbxproj +++ b/Qapple/Qapple.xcodeproj/project.pbxproj @@ -402,6 +402,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Qapple/QappleBox/Qapple.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 2505142036; @@ -425,7 +426,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.3; + MARKETING_VERSION = 2.1.4; PRODUCT_BUNDLE_IDENTIFIER = "com.qapple.Apple-Developer-Academy-POSTECH"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -472,7 +473,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.3; + MARKETING_VERSION = 2.1.4; PRODUCT_BUNDLE_IDENTIFIER = "com.qapple.Apple-Developer-Academy-POSTECH"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Qapple/Qapple/SourceCode/Data/Repository/AnswerCommentRepository.swift b/Qapple/Qapple/SourceCode/Data/Repository/AnswerCommentRepository.swift index 32380987..ae180984 100644 --- a/Qapple/Qapple/SourceCode/Data/Repository/AnswerCommentRepository.swift +++ b/Qapple/Qapple/SourceCode/Data/Repository/AnswerCommentRepository.swift @@ -33,7 +33,7 @@ extension AnswerCommentRepository: DependencyKey { ) } - let list = response.answerCommentInfos.map { + let list = response.content.map { AnswerComment( id: $0.answerCommentId, writeId: $0.writerId, @@ -41,15 +41,16 @@ extension AnswerCommentRepository: DependencyKey { writerGeneration: "", content: $0.content, heartCount: $0.heartCount, - isLiked: false, - isMine: false, + isLiked: $0.isLiked, + isMine: $0.isMine, isReport: false, createdAt: $0.createdAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds), anonymityId: -2 ) } - return list + // TODO: 추후 신고된 아이디 로직 수정 필요 + return list.filter { !UserDefaultsService.reportedAnswerCommentIds.contains($0.id) } }, createAnswerComment: { answerId, content in let _ = try await RepositoryService.shared.request { server, accessToken in diff --git a/Qapple/Qapple/SourceCode/Data/Repository/AnswerRepository.swift b/Qapple/Qapple/SourceCode/Data/Repository/AnswerRepository.swift index 1ae9b409..0d23980f 100644 --- a/Qapple/Qapple/SourceCode/Data/Repository/AnswerRepository.swift +++ b/Qapple/Qapple/SourceCode/Data/Repository/AnswerRepository.swift @@ -17,7 +17,8 @@ struct AnswerRepository { QappleAPI.TotalCount, QappleAPI.PaginationInfo ) - var fetchPopularAnswer: (_ question: Question?) async throws -> (Answer?, Question?, Bool) + var fetchPopularAnswerOfMainQuestion: () async throws -> (Answer?, Question, Bool) + var fetchPopularAnswer: (_ question: Question) async throws -> (Answer?, Question?, Bool) var postAnswer: (_ questionId: Int, _ answer: String) async throws -> Void var deleteAnswer: (_ answerId: Int) async throws -> Void var likeAnswer: (_ questionId: Int) async throws -> Void @@ -116,115 +117,47 @@ extension AnswerRepository: DependencyKey { threshold: response.threshold, hasNext: response.hasNext ) + return (answerList, response.total, paginationInfo) }, - fetchPopularAnswer: { question in - let currentHour = Calendar.current.component(.hour, from: .now) - if currentHour > 12 && currentHour < 19 { - return (nil, nil, false) - } - - let response = try await RepositoryService.shared.request { server, accessToken in - try await QuestionAPI.fetchQuestionList( - threshold: nil, - pageSize: 1, - server: server, - accessToken: accessToken - ) + fetchPopularAnswerOfMainQuestion: { + let mainQuestion = try await RepositoryService.shared.request { server, accessToken in + try await QuestionAPI.fetchMainQuestion(server: server, accessToken: accessToken) } - guard let questionContent = response.content.first else { return (nil, nil, true) } - - let currentQuestion = question ?? Question( - id: questionContent.questionId, - content: questionContent.content, - publishedDate: questionContent.livedAt?.ISO8601ToDate(.yearMonthDateTime) ?? .now, - isAnswered: questionContent.isAnswered, - isLived: questionContent.questionStatus == ("LIVE") + let question = Question( + id: mainQuestion.questionId, + content: mainQuestion.content, + publishedDate: .now, + isAnswered: mainQuestion.isAnswered, + isLived: mainQuestion.questionStatus == ("LIVE") ) - var popularAnswer: Answer = .init( - id: -1, - writerId: -1, - content: "", - authorNickname: "", - authorGeneration: "", - publishedDate: .init(timeIntervalSince1970: 0), - isReported: false, - isMine: false, - isLiked: false, - isResignMember: false, - commentCount: 0, - heartCount: 0 - ) + let currentHour = Calendar.current.component(.hour, from: .now) + if currentHour > 12 && currentHour < 19 { + return (nil, question, false) + } - var hasNext = true - var threshold: Int? + if let popularAnswer = try await getPopularQuestion(from: question) { + return (popularAnswer, question, true) + } else { + return (nil, question, true) + } + }, + fetchPopularAnswer: { question in + let mainQuestion = try await RepositoryService.shared.request { server, accessToken in + try await QuestionAPI.fetchMainQuestion(server: server, accessToken: accessToken) + } - while hasNext { - let answersOfQuestion = try await RepositoryService.shared.request { server, accessToken in - try await AnswerAPI.fetchListOfQuestion( - questionId: Int(currentQuestion.id), - threshold: threshold, - pageSize: 30, - server: server, - accessToken: accessToken - ) - } - - if answersOfQuestion.content.isEmpty { - return (nil, nil, true) - } - - for answer in answersOfQuestion.content { - if answer.isReported { continue } - - let sum = answer.commentCount + answer.heartCount - let popularSum = popularAnswer.heartCount + popularAnswer.commentCount - - if sum > popularSum { - popularAnswer = .init( - id: answer.answerId, - writerId: answer.writerId, - content: answer.content, - authorNickname: answer.nickname, - authorGeneration: answer.writerGeneration, - publishedDate: answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds), - isReported: false, - isMine: answer.isMine, - isLiked: answer.isLiked, - isResignMember: answer.nickname == "알 수 없음", - commentCount: answer.commentCount, - heartCount: answer.heartCount - ) - } else if sum == popularSum { - let popularAnswerDate = popularAnswer.publishedDate - let answerDate = answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds) - - if answerDate > popularAnswerDate { - popularAnswer = .init( - id: answer.answerId, - writerId: answer.writerId, - content: answer.content, - authorNickname: answer.nickname, - authorGeneration: answer.writerGeneration, - publishedDate: answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds), - isReported: false, - isMine: answer.isMine, - isLiked: answer.isLiked, - isResignMember: answer.nickname == "알 수 없음", - commentCount: answer.commentCount, - heartCount: answer.heartCount - ) - } - } - } - - hasNext = answersOfQuestion.hasNext - threshold = Int(answersOfQuestion.threshold) + if question.id == mainQuestion.questionId { + return (nil, nil, false) } - return (popularAnswer, currentQuestion, false) + if let popularAnswer = try await getPopularQuestion(from: question) { + return (popularAnswer, question, true) + } else { + return (nil, question, true) + } }, postAnswer: { questionId, answer in let response = try await RepositoryService.shared.request { server, accessToken in @@ -282,6 +215,9 @@ extension AnswerRepository: DependencyKey { fetchAnswerListOfQuestion: { _, _ in (stubAnswerList, 25, .init(threshold: "", hasNext: false)) }, + fetchPopularAnswerOfMainQuestion: { + return (Answer.initialState, Question.initialState, true) + }, fetchPopularAnswer: { _ in let question = Question(id: 0, content: "", publishedDate: .now, isAnswered: false, isLived: true) @@ -302,6 +238,79 @@ extension DependencyValues { } } +// MARK: - Helper + +extension AnswerRepository { + + /// 질문에 따른 인기 답변을 계산합니다. + private static func getPopularQuestion(from question: Question) async throws -> Answer? { + var popularAnswer = Answer.initialState + var hasNext = true + var threshold: Int? + + while hasNext { + let answersOfQuestion = try await RepositoryService.shared.request { server, accessToken in + try await AnswerAPI.fetchListOfQuestion( + questionId: Int(question.id), + threshold: threshold, + pageSize: 30, + server: server, + accessToken: accessToken + ) + } + + if answersOfQuestion.content.isEmpty { + return nil + } + + for (index, answer) in answersOfQuestion.content.enumerated() { + guard index > 0 else { + popularAnswer = toEntity(answer) + continue + } + + if answer.isReported { continue } + + let sum = answer.commentCount + answer.heartCount + let popularSum = popularAnswer.heartCount + popularAnswer.commentCount + + if sum > popularSum { + popularAnswer = toEntity(answer) + } else if sum == popularSum { + let popularAnswerDate = popularAnswer.publishedDate + let answerDate = answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds) + + if answerDate < popularAnswerDate { + popularAnswer = toEntity(answer) + } + } + } + + hasNext = answersOfQuestion.hasNext + threshold = Int(answersOfQuestion.threshold) + } + + return popularAnswer + } + + private static func toEntity(_ dto: AnswerListOfQuestion.Content) -> Answer { + .init( + id: dto.answerId, + writerId: dto.writerId, + content: dto.content, + authorNickname: dto.nickname, + authorGeneration: dto.writerGeneration, + publishedDate: dto.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds), + isReported: false, + isMine: dto.isMine, + isLiked: dto.isLiked, + isResignMember: dto.nickname == "알 수 없음", + commentCount: dto.commentCount, + heartCount: dto.heartCount + ) + } +} + // MARK: - Stub extension AnswerRepository { diff --git a/Qapple/Qapple/SourceCode/Data/Repository/ReportRepository.swift b/Qapple/Qapple/SourceCode/Data/Repository/ReportRepository.swift index 70edbd6c..e5718d12 100644 --- a/Qapple/Qapple/SourceCode/Data/Repository/ReportRepository.swift +++ b/Qapple/Qapple/SourceCode/Data/Repository/ReportRepository.swift @@ -12,7 +12,8 @@ import Foundation struct ReportRepository { var reportAnswer: (_ answerId: Int, _ reportType: ReportType) async throws -> Void var reportBoard: (_ boardId: Int, _ reportType: ReportType) async throws -> Void - var reportComment: (_ commentId: Int, _ reportType: ReportType) async throws -> Void + var reportBoardComment: (_ commentId: Int, _ reportType: ReportType) async throws -> Void + var reportAnswerComment:(_ commentId: Int, _ reportType: ReportType) async throws -> Void } // MARK: - DependencyKey @@ -40,7 +41,7 @@ extension ReportRepository: DependencyKey { ) } }, - reportComment: { commentId, reportType in + reportBoardComment: { commentId, reportType in let _ = try await RepositoryService.shared.request { server, accessToken in try await ReportAPI.reportBoardComment( boardCommentId: commentId, @@ -49,6 +50,9 @@ extension ReportRepository: DependencyKey { accessToken: accessToken ) } + }, reportAnswerComment: { commentId, reportType in + // TODO: 추후 API 교체 필요 + UserDefaultsService.reportedAnswerCommentIds.append(commentId) } ) } diff --git a/Qapple/Qapple/SourceCode/Data/Service/UserDefaultsService.swift b/Qapple/Qapple/SourceCode/Data/Service/UserDefaultsService.swift new file mode 100644 index 00000000..737dd255 --- /dev/null +++ b/Qapple/Qapple/SourceCode/Data/Service/UserDefaultsService.swift @@ -0,0 +1,37 @@ +// +// UserDefaultsService.swift +// Qapple +// +// Created by 김민준 on 5/22/25. +// + +import Foundation + +/// UserDefaults 관리 객체 +struct UserDefaultsService { + + @UserDefault(key: "reportedAnswerCommentIds", defaultValue: []) + static var reportedAnswerCommentIds: [Int] +} + +// MARK: - propertyWrapper + +@propertyWrapper +struct UserDefault { + + let key: String + let defaultValue: T + + init(key: String, defaultValue: T) { + self.key = key + self.defaultValue = defaultValue + } + + var wrappedValue: T { + get { + UserDefaults.standard.object(forKey: key) as? T ?? defaultValue + } set { + UserDefaults.standard.set(newValue, forKey: key) + } + } +} diff --git a/Qapple/Qapple/SourceCode/Data/Service/VersionService.swift b/Qapple/Qapple/SourceCode/Data/Service/VersionService.swift index 47909768..6f78ac17 100644 --- a/Qapple/Qapple/SourceCode/Data/Service/VersionService.swift +++ b/Qapple/Qapple/SourceCode/Data/Service/VersionService.swift @@ -15,12 +15,12 @@ struct VersionService { } /// 현재 버전이 최신 버전인지 확인 - static func isRecentVersion() async -> Result { + static func isRecentVersion() async throws -> Bool { guard let recentVersion = await appStoreAppVersion() else { - return .failure(VersionError.networkError) + throw VersionError.networkError } - return .success(recentVersion == deviceAppVersion) + return recentVersion == deviceAppVersion } /// 앱스토어 내 최신 버전 @@ -55,7 +55,10 @@ struct VersionService { /// 앱스토어를 Open합니다. static func openAppStore() { - guard let url = URL(string: appStoreOpenUrlString) else { return } + guard let url = URL(string: appStoreOpenUrlString) else { + print("URL 생성 실패: \(appStoreOpenUrlString)") + return + } if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:], completionHandler: nil) } diff --git a/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpFlowFeature.swift b/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpFlowFeature.swift index e15e2547..d93cfe46 100644 --- a/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpFlowFeature.swift +++ b/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpFlowFeature.swift @@ -21,13 +21,16 @@ struct SignUpFlowFeature { enum Action { case onAppear + case updateVersion case autoLoginResponse case socialLogin(SocialLoginFeature.Action) case networkingFailed(Error) case path(StackActionOf) case alert(PresentationAction) - enum Alert: Equatable {} + enum Alert: Equatable { + case navigateToAppStore + } } @Dependency(\.appleLoginService) var appleLoginService @@ -43,12 +46,23 @@ struct SignUpFlowFeature { return .run { send in do { try await appleLoginService.autoLogin() - await send(.autoLoginResponse) + await send(.autoLoginResponse) + let isRecentVersion = try await VersionService.isRecentVersion() + if isRecentVersion { + try await appleLoginService.autoLogin() + await send(.autoLoginResponse) + } else { + await send(.updateVersion) + } } catch { - await send(.networkingFailed(error)) + print(error) } } + case .updateVersion: + state.alert = .requiredUpdate + return .none + case .autoLoginResponse: state.isFirstLaunch = false state.$isSignIn.withLock { $0 = true } @@ -97,6 +111,10 @@ struct SignUpFlowFeature { return .none } + case .alert(.presented(.navigateToAppStore)): + VersionService.openAppStore() + return .none + case .alert: return .none @@ -122,3 +140,15 @@ extension SignUpFlowFeature { case signUpComplete(SignUpCompleteFeature) } } + +// MARK: - Alert + +extension AlertState where Action == SignUpFlowFeature.Action.Alert { + static let requiredUpdate = AlertState { + TextState("원활한 서비스 이용을 위해 캐플 앱 업데이트가 필요해요!") + } actions: { + ButtonState(role: .none, action: .navigateToAppStore) { + TextState("앱스토어 이동") + } + } +} diff --git a/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpView.swift b/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpView.swift index 5dd23985..cfc7a268 100644 --- a/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpView.swift +++ b/Qapple/Qapple/SourceCode/Feature/0.SignUpFlow/0-1.SignUpFlow/SignUpView.swift @@ -27,6 +27,7 @@ struct SignUpFlowView: View { .onAppear { store.send(.onAppear) } + .alert($store.scope(state: \.alert, action: \.alert)) } } diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift index e1e0662e..7cf11819 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift @@ -300,20 +300,24 @@ private struct AnswerPreviewList: View { VStack(spacing: 0) { ForEach(enumerated(store.answerPreviewList), id: \.element.id) { index, answer in - QPAnswerCell( - answer: answer, - index: index, - state: .normal, - seeMoreAction: { - store.send(.seeMoreAnswerButtonTapped(answer)) - }, - likeAction: { - store.send(.likeAnswerButtonTapped(answer)) - }, - commentAction: { - store.send(.answerCommentButtonTapped(answer)) - } - ) + Button { + store.send(.answerCommentButtonTapped(answer)) + } label: { + QPAnswerCell( + answer: answer, + index: index, + state: .normal, + seeMoreAction: { + store.send(.seeMoreAnswerButtonTapped(answer)) + }, + likeAction: { + store.send(.likeAnswerButtonTapped(answer)) + }, + commentAction: { + store.send(.answerCommentButtonTapped(answer)) + } + ) + } } } } diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListFeature.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListFeature.swift index 0f809c60..642b9f01 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListFeature.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListFeature.swift @@ -60,20 +60,10 @@ struct AnswerListFeature { let response = try await answerRepository.fetchAnswerListOfQuestion( question.id, nil ) - let currentHour = Calendar.current.component(.hour, from: .now) - if question.isLived, !(currentHour > 12 && currentHour < 19){ - if !(currentHour > 12 && currentHour < 19) { - let response = try await answerRepository.fetchPopularAnswer(question) - if let answer = response.0 { - await send(.fetchPopularAnswer(answer)) - } - } - } else { - let response = try await answerRepository.fetchPopularAnswer(question) - if let answer = response.0 { - await send(.fetchPopularAnswer(answer)) - } + let (answer, _, isEmpty) = try await answerRepository.fetchPopularAnswer(question) + if let popularAnswer = answer { + await send(.fetchPopularAnswer(popularAnswer)) } await send( diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift index 84040b00..68d0b4c7 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift @@ -138,20 +138,24 @@ private struct AnswerList: View { ForEach(enumerated(store.answerList), id: \.element.id) { index, answer in - QPAnswerCell( - answer: answer, - index: index, - state: .normal, - seeMoreAction: { - store.send(.seeMoreAction(answer)) - }, - likeAction: { - store.send(.likeAnswerButtonTapped(answer)) - }, - commentAction: { - store.send(.answerCommentButtonTapped(answer)) - } - ) + Button { + store.send(.answerCommentButtonTapped(answer)) + } label: { + QPAnswerCell( + answer: answer, + index: index, + state: .normal, + seeMoreAction: { + store.send(.seeMoreAction(answer)) + }, + likeAction: { + store.send(.likeAnswerButtonTapped(answer)) + }, + commentAction: { + store.send(.answerCommentButtonTapped(answer)) + } + ) + } .configurePagination( store.answerList, currentIndex: index, diff --git a/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardCell.swift b/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardCell.swift index 8fac5022..3bd87217 100644 --- a/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardCell.swift +++ b/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardCell.swift @@ -181,9 +181,11 @@ private struct RemoteView: View { HStack(spacing: 4) { Image(board.isLiked ? .heartActive : .heart) - Text("\(board.heartCount)") - .pretendard(.regular, 13) - .foregroundStyle(TextLabel.sub3) + if board.heartCount > 0 { + Text("\(board.heartCount)") + .pretendard(.regular, 13) + .foregroundStyle(TextLabel.sub3) + } } } @@ -193,9 +195,11 @@ private struct RemoteView: View { .frame(width: 15, height: 14) .foregroundStyle(TextLabel.sub3) - Text("\(board.commentCount)") - .pretendard(.regular, 13) - .foregroundStyle(TextLabel.sub3) + if board.commentCount > 0 { + Text("\(board.commentCount)") + .pretendard(.regular, 13) + .foregroundStyle(TextLabel.sub3) + } } } } diff --git a/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardFeature.swift b/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardFeature.swift index 80d4f179..4b141481 100644 --- a/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardFeature.swift +++ b/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardFeature.swift @@ -47,7 +47,7 @@ struct BulletinBoardFeature { case questionNotiTapped(Question) case popularAnswerTapped(Question) - case fetchPopularAnswer((Answer?, Question?, Bool)) + case fetchPopularAnswer((Answer?, Question, Bool)) case sheet(PresentationAction) case alert(PresentationAction) @@ -64,7 +64,7 @@ struct BulletinBoardFeature { @Dependency(\.questionRepository.fetchMainQuestion) var fetchMainQuestion @Dependency(\.bulletinBoardRepository) var bulletinBoardRepository - @Dependency(\.answerRepository.fetchPopularAnswer) var fetchPopularAnswer + @Dependency(\.answerRepository) var answerRepository var body: some ReducerOf { Reduce { state, action in @@ -78,7 +78,7 @@ struct BulletinBoardFeature { do { let mainQuestion = try await fetchMainQuestion() let response = try await bulletinBoardRepository.fetchBulletinBoardList(nil) - let popularAnswer = try await fetchPopularAnswer(nil) + let popularAnswer = try await answerRepository.fetchPopularAnswerOfMainQuestion() await send(.fetchPopularAnswer(popularAnswer)) await send(.bulletinBoardListResponse(mainQuestion, response.0, response.1)) } catch { @@ -152,13 +152,12 @@ struct BulletinBoardFeature { } return .none - case let .fetchPopularAnswer(result): - if let answer = result.0, let question = result.1 { - state.popularAnswerStatus = .popularAnswer(answer, question) + case let .fetchPopularAnswer((answer, question, isEmpty)): + if let popularAnswer = answer { + state.popularAnswerStatus = .popularAnswer(popularAnswer, question) } else { - state.popularAnswerStatus = result.2 ? .none : .todayQuestion + state.popularAnswerStatus = question.isAnswered || isEmpty ? .none : .todayQuestion } - return .none case .searchButtonTapped: diff --git a/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardView.swift b/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardView.swift index 7b7f100f..fd95629b 100644 --- a/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardView.swift +++ b/Qapple/Qapple/SourceCode/Feature/3.BulletinBoard/3-1.BulletinBoard/BulletinBoardView.swift @@ -21,7 +21,7 @@ struct BulletinBoardView: View { BulletinBoardContentView(store: store) NewBoardPostButton(store: store) - .padding(.bottom, 20) + .padding(.bottom, 16) } .background(.first) .onChange(of: scenePhase) { _, newPhase in @@ -70,17 +70,8 @@ private struct BulletinBoardContentView: View { } ) - Button { - store.send(.academyDayCounterTapped) - } label: { - QPAcademyDayCounter(event: store.event) - .padding(.top, 8) - .padding(.horizontal, 16) - } - .buttonStyle(ScalableButtonStyle()) - BulletionBoardListView(store: store) - .padding(.top, 20) + .padding(.top, 8) Spacer() .frame(height: 2) @@ -96,6 +87,14 @@ private struct BulletionBoardListView: View { var body: some View { ScrollView { + Button { + store.send(.academyDayCounterTapped) + } label: { + QPAcademyDayCounter(event: store.event) + .padding(.horizontal, 16) + } + .buttonStyle(ScalableButtonStyle()) + Group { switch store.state.popularAnswerStatus { case .todayQuestion: @@ -113,7 +112,7 @@ private struct BulletionBoardListView: View { } } .padding(.horizontal) - .padding(.top, 2) + .padding(.top, 12) LazyVStack(spacing: 0) { ForEach(enumerated(store.bulletinBoardList), id: \.offset) { index, board in diff --git a/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift b/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift index ee498522..90882a20 100644 --- a/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift +++ b/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift @@ -64,20 +64,24 @@ private struct MyAnswerList: View { LazyVStack(spacing: 0) { ForEach(enumerated(store.myAnswerList), id: \.element.id) { index, answer in - QPAnswerCell( - answer: answer, - index: index, - state: .written, - seeMoreAction:{ - store.send(.seeMoreAction(answer)) - }, - likeAction: { - store.send(.likeAnswerButtonTapped(answer)) - }, - commentAction: { - store.send(.commentButtonTapped(answer)) - } - ) + Button { + store.send(.commentButtonTapped(answer)) + } label: { + QPAnswerCell( + answer: answer, + index: index, + state: .written, + seeMoreAction:{ + store.send(.seeMoreAction(answer)) + }, + likeAction: { + store.send(.likeAnswerButtonTapped(answer)) + }, + commentAction: { + store.send(.commentButtonTapped(answer)) + } + ) + } .configurePagination( store.myAnswerList, currentIndex: index, diff --git a/Qapple/Qapple/SourceCode/Feature/8.Report/ReportFeature.swift b/Qapple/Qapple/SourceCode/Feature/8.Report/ReportFeature.swift index a8496161..4d2917be 100644 --- a/Qapple/Qapple/SourceCode/Feature/8.Report/ReportFeature.swift +++ b/Qapple/Qapple/SourceCode/Feature/8.Report/ReportFeature.swift @@ -58,11 +58,10 @@ struct ReportFeature { try await reportRepository.reportBoard(board.id, reportType) case let .comment(comment): - try await reportRepository.reportComment(comment.id, reportType) - case let .answerComment(comment): - // TODO: 답변 댓글 신고 구현 - break + try await reportRepository.reportBoardComment(comment.id, reportType) + case let .answerComment(comment): + try await reportRepository.reportAnswerComment(comment.id, reportType) } await send(.completionReport) } catch { diff --git a/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift b/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift index 0e875155..f56d8701 100644 --- a/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift +++ b/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift @@ -140,6 +140,7 @@ private struct NormalCell: View { Text(answer.content) .pretendard(.medium, 16) + .multilineTextAlignment(.leading) .foregroundStyle(.main) .padding(.top, 2) } @@ -159,9 +160,11 @@ private struct NormalCell: View { .resizable() .frame(width: 18, height: 18) - Text("\(answer.heartCount)") - .pretendard(.regular, 13) - .foregroundStyle(.sub3) + if answer.heartCount > 0 { + Text("\(answer.heartCount)") + .pretendard(.regular, 13) + .foregroundStyle(.sub3) + } } } .padding(.leading, 8) @@ -174,8 +177,10 @@ private struct NormalCell: View { .resizable() .frame(width: 15, height: 14) - Text("\(answer.commentCount)") - .pretendard(.regular, 13) + if answer.commentCount > 0 { + Text("\(answer.commentCount)") + .pretendard(.regular, 13) + } } .foregroundStyle(.sub3) } diff --git a/Qapple/Qapple/SourceCode/Utility/NetworkErrorAlert+.swift b/Qapple/Qapple/SourceCode/Utility/NetworkErrorAlert+.swift index 755de3a4..d0738f0f 100644 --- a/Qapple/Qapple/SourceCode/Utility/NetworkErrorAlert+.swift +++ b/Qapple/Qapple/SourceCode/Utility/NetworkErrorAlert+.swift @@ -11,7 +11,8 @@ extension AlertState { /// 네트워킹 실패 기본 Alert static func failedNetworking(with error: Error) -> Self { - Self { + print(error) + return Self { TextState("네트워크 상태가 불안정해요") } actions: { ButtonState(role: .cancel) { diff --git a/Qapple/Qapple/SourceCode/Utility/Pagination+.swift b/Qapple/Qapple/SourceCode/Utility/Pagination+.swift index d22954b9..bd6fcd5a 100644 --- a/Qapple/Qapple/SourceCode/Utility/Pagination+.swift +++ b/Qapple/Qapple/SourceCode/Utility/Pagination+.swift @@ -16,7 +16,7 @@ struct ConfigurePagination: ViewModifier { func body(content: Content) -> some View { content.onAppear { - if currentIndex == list.endIndex - 5 && hasNext { + if currentIndex == list.endIndex - 1 && hasNext { pagination() } }