Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Qapple/Qapple.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 = "";
Expand Down Expand Up @@ -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 = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,24 @@ extension AnswerCommentRepository: DependencyKey {
)
}

let list = response.answerCommentInfos.map {
let list = response.content.map {
AnswerComment(
id: $0.answerCommentId,
writeId: $0.writerId,
// TODO: 5/20 문의 필요(writer generation, isLiked, isMine, isReport이 있는지 여부)
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
Expand Down
209 changes: 109 additions & 100 deletions Qapple/Qapple/SourceCode/Data/Repository/AnswerRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -49,6 +50,9 @@ extension ReportRepository: DependencyKey {
accessToken: accessToken
)
}
}, reportAnswerComment: { commentId, reportType in
// TODO: 추후 API 교체 필요
UserDefaultsService.reportedAnswerCommentIds.append(commentId)
}
)
}
Expand Down
37 changes: 37 additions & 0 deletions Qapple/Qapple/SourceCode/Data/Service/UserDefaultsService.swift
Original file line number Diff line number Diff line change
@@ -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<T> {

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)
}
}
}
11 changes: 7 additions & 4 deletions Qapple/Qapple/SourceCode/Data/Service/VersionService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ struct VersionService {
}

/// 현재 버전이 최신 버전인지 확인
static func isRecentVersion() async -> Result<Bool, Error> {
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
}

/// 앱스토어 내 최신 버전
Expand Down Expand Up @@ -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)
}
Expand Down
Loading