Skip to content

Commit 3bb4846

Browse files
authored
Merge pull request #371 from Team-Capple/feat/#370-appstore-alert
2 parents c3965d5 + 8e174ad commit 3bb4846

File tree

19 files changed

+302
-207
lines changed

19 files changed

+302
-207
lines changed

โ€ŽQapple/Qapple.xcodeproj/project.pbxprojโ€Ž

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@
402402
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
403403
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
404404
CODE_SIGN_ENTITLEMENTS = Qapple/QappleBox/Qapple.entitlements;
405+
CODE_SIGN_IDENTITY = "Apple Development";
405406
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
406407
CODE_SIGN_STYLE = Manual;
407408
CURRENT_PROJECT_VERSION = 2505142036;
@@ -425,7 +426,7 @@
425426
"$(inherited)",
426427
"@executable_path/Frameworks",
427428
);
428-
MARKETING_VERSION = 2.1.3;
429+
MARKETING_VERSION = 2.1.4;
429430
PRODUCT_BUNDLE_IDENTIFIER = "com.qapple.Apple-Developer-Academy-POSTECH";
430431
PRODUCT_NAME = "$(TARGET_NAME)";
431432
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -472,7 +473,7 @@
472473
"$(inherited)",
473474
"@executable_path/Frameworks",
474475
);
475-
MARKETING_VERSION = 2.1.3;
476+
MARKETING_VERSION = 2.1.4;
476477
PRODUCT_BUNDLE_IDENTIFIER = "com.qapple.Apple-Developer-Academy-POSTECH";
477478
PRODUCT_NAME = "$(TARGET_NAME)";
478479
PROVISIONING_PROFILE_SPECIFIER = "";

โ€ŽQapple/Qapple/SourceCode/Data/Repository/AnswerCommentRepository.swiftโ€Ž

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,24 @@ extension AnswerCommentRepository: DependencyKey {
3333
)
3434
}
3535

36-
let list = response.answerCommentInfos.map {
36+
let list = response.content.map {
3737
AnswerComment(
3838
id: $0.answerCommentId,
3939
writeId: $0.writerId,
4040
// TODO: 5/20 ๋ฌธ์˜ ํ•„์š”(writer generation, isLiked, isMine, isReport์ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€)
4141
writerGeneration: "",
4242
content: $0.content,
4343
heartCount: $0.heartCount,
44-
isLiked: false,
45-
isMine: false,
44+
isLiked: $0.isLiked,
45+
isMine: $0.isMine,
4646
isReport: false,
4747
createdAt: $0.createdAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds),
4848
anonymityId: -2
4949
)
5050
}
5151

52-
return list
52+
// TODO: ์ถ”ํ›„ ์‹ ๊ณ ๋œ ์•„์ด๋”” ๋กœ์ง ์ˆ˜์ • ํ•„์š”
53+
return list.filter { !UserDefaultsService.reportedAnswerCommentIds.contains($0.id) }
5354
},
5455
createAnswerComment: { answerId, content in
5556
let _ = try await RepositoryService.shared.request { server, accessToken in

โ€ŽQapple/Qapple/SourceCode/Data/Repository/AnswerRepository.swiftโ€Ž

Lines changed: 109 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ struct AnswerRepository {
1717
QappleAPI.TotalCount,
1818
QappleAPI.PaginationInfo
1919
)
20-
var fetchPopularAnswer: (_ question: Question?) async throws -> (Answer?, Question?, Bool)
20+
var fetchPopularAnswerOfMainQuestion: () async throws -> (Answer?, Question, Bool)
21+
var fetchPopularAnswer: (_ question: Question) async throws -> (Answer?, Question?, Bool)
2122
var postAnswer: (_ questionId: Int, _ answer: String) async throws -> Void
2223
var deleteAnswer: (_ answerId: Int) async throws -> Void
2324
var likeAnswer: (_ questionId: Int) async throws -> Void
@@ -116,115 +117,47 @@ extension AnswerRepository: DependencyKey {
116117
threshold: response.threshold,
117118
hasNext: response.hasNext
118119
)
120+
119121
return (answerList, response.total, paginationInfo)
120122
},
121-
fetchPopularAnswer: { question in
122-
let currentHour = Calendar.current.component(.hour, from: .now)
123-
if currentHour > 12 && currentHour < 19 {
124-
return (nil, nil, false)
125-
}
126-
127-
let response = try await RepositoryService.shared.request { server, accessToken in
128-
try await QuestionAPI.fetchQuestionList(
129-
threshold: nil,
130-
pageSize: 1,
131-
server: server,
132-
accessToken: accessToken
133-
)
123+
fetchPopularAnswerOfMainQuestion: {
124+
let mainQuestion = try await RepositoryService.shared.request { server, accessToken in
125+
try await QuestionAPI.fetchMainQuestion(server: server, accessToken: accessToken)
134126
}
135127

136-
guard let questionContent = response.content.first else { return (nil, nil, true) }
137-
138-
let currentQuestion = question ?? Question(
139-
id: questionContent.questionId,
140-
content: questionContent.content,
141-
publishedDate: questionContent.livedAt?.ISO8601ToDate(.yearMonthDateTime) ?? .now,
142-
isAnswered: questionContent.isAnswered,
143-
isLived: questionContent.questionStatus == ("LIVE")
128+
let question = Question(
129+
id: mainQuestion.questionId,
130+
content: mainQuestion.content,
131+
publishedDate: .now,
132+
isAnswered: mainQuestion.isAnswered,
133+
isLived: mainQuestion.questionStatus == ("LIVE")
144134
)
145135

146-
var popularAnswer: Answer = .init(
147-
id: -1,
148-
writerId: -1,
149-
content: "",
150-
authorNickname: "",
151-
authorGeneration: "",
152-
publishedDate: .init(timeIntervalSince1970: 0),
153-
isReported: false,
154-
isMine: false,
155-
isLiked: false,
156-
isResignMember: false,
157-
commentCount: 0,
158-
heartCount: 0
159-
)
136+
let currentHour = Calendar.current.component(.hour, from: .now)
137+
if currentHour > 12 && currentHour < 19 {
138+
return (nil, question, false)
139+
}
160140

161-
var hasNext = true
162-
var threshold: Int?
141+
if let popularAnswer = try await getPopularQuestion(from: question) {
142+
return (popularAnswer, question, true)
143+
} else {
144+
return (nil, question, true)
145+
}
146+
},
147+
fetchPopularAnswer: { question in
148+
let mainQuestion = try await RepositoryService.shared.request { server, accessToken in
149+
try await QuestionAPI.fetchMainQuestion(server: server, accessToken: accessToken)
150+
}
163151

164-
while hasNext {
165-
let answersOfQuestion = try await RepositoryService.shared.request { server, accessToken in
166-
try await AnswerAPI.fetchListOfQuestion(
167-
questionId: Int(currentQuestion.id),
168-
threshold: threshold,
169-
pageSize: 30,
170-
server: server,
171-
accessToken: accessToken
172-
)
173-
}
174-
175-
if answersOfQuestion.content.isEmpty {
176-
return (nil, nil, true)
177-
}
178-
179-
for answer in answersOfQuestion.content {
180-
if answer.isReported { continue }
181-
182-
let sum = answer.commentCount + answer.heartCount
183-
let popularSum = popularAnswer.heartCount + popularAnswer.commentCount
184-
185-
if sum > popularSum {
186-
popularAnswer = .init(
187-
id: answer.answerId,
188-
writerId: answer.writerId,
189-
content: answer.content,
190-
authorNickname: answer.nickname,
191-
authorGeneration: answer.writerGeneration,
192-
publishedDate: answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds),
193-
isReported: false,
194-
isMine: answer.isMine,
195-
isLiked: answer.isLiked,
196-
isResignMember: answer.nickname == "์•Œ ์ˆ˜ ์—†์Œ",
197-
commentCount: answer.commentCount,
198-
heartCount: answer.heartCount
199-
)
200-
} else if sum == popularSum {
201-
let popularAnswerDate = popularAnswer.publishedDate
202-
let answerDate = answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds)
203-
204-
if answerDate > popularAnswerDate {
205-
popularAnswer = .init(
206-
id: answer.answerId,
207-
writerId: answer.writerId,
208-
content: answer.content,
209-
authorNickname: answer.nickname,
210-
authorGeneration: answer.writerGeneration,
211-
publishedDate: answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds),
212-
isReported: false,
213-
isMine: answer.isMine,
214-
isLiked: answer.isLiked,
215-
isResignMember: answer.nickname == "์•Œ ์ˆ˜ ์—†์Œ",
216-
commentCount: answer.commentCount,
217-
heartCount: answer.heartCount
218-
)
219-
}
220-
}
221-
}
222-
223-
hasNext = answersOfQuestion.hasNext
224-
threshold = Int(answersOfQuestion.threshold)
152+
if question.id == mainQuestion.questionId {
153+
return (nil, nil, false)
225154
}
226155

227-
return (popularAnswer, currentQuestion, false)
156+
if let popularAnswer = try await getPopularQuestion(from: question) {
157+
return (popularAnswer, question, true)
158+
} else {
159+
return (nil, question, true)
160+
}
228161
},
229162
postAnswer: { questionId, answer in
230163
let response = try await RepositoryService.shared.request { server, accessToken in
@@ -282,6 +215,9 @@ extension AnswerRepository: DependencyKey {
282215
fetchAnswerListOfQuestion: { _, _ in
283216
(stubAnswerList, 25, .init(threshold: "", hasNext: false))
284217
},
218+
fetchPopularAnswerOfMainQuestion: {
219+
return (Answer.initialState, Question.initialState, true)
220+
},
285221
fetchPopularAnswer: { _ in
286222
let question = Question(id: 0, content: "", publishedDate: .now, isAnswered: false, isLived: true)
287223

@@ -302,6 +238,79 @@ extension DependencyValues {
302238
}
303239
}
304240

241+
// MARK: - Helper
242+
243+
extension AnswerRepository {
244+
245+
/// ์งˆ๋ฌธ์— ๋”ฐ๋ฅธ ์ธ๊ธฐ ๋‹ต๋ณ€์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
246+
private static func getPopularQuestion(from question: Question) async throws -> Answer? {
247+
var popularAnswer = Answer.initialState
248+
var hasNext = true
249+
var threshold: Int?
250+
251+
while hasNext {
252+
let answersOfQuestion = try await RepositoryService.shared.request { server, accessToken in
253+
try await AnswerAPI.fetchListOfQuestion(
254+
questionId: Int(question.id),
255+
threshold: threshold,
256+
pageSize: 30,
257+
server: server,
258+
accessToken: accessToken
259+
)
260+
}
261+
262+
if answersOfQuestion.content.isEmpty {
263+
return nil
264+
}
265+
266+
for (index, answer) in answersOfQuestion.content.enumerated() {
267+
guard index > 0 else {
268+
popularAnswer = toEntity(answer)
269+
continue
270+
}
271+
272+
if answer.isReported { continue }
273+
274+
let sum = answer.commentCount + answer.heartCount
275+
let popularSum = popularAnswer.heartCount + popularAnswer.commentCount
276+
277+
if sum > popularSum {
278+
popularAnswer = toEntity(answer)
279+
} else if sum == popularSum {
280+
let popularAnswerDate = popularAnswer.publishedDate
281+
let answerDate = answer.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds)
282+
283+
if answerDate < popularAnswerDate {
284+
popularAnswer = toEntity(answer)
285+
}
286+
}
287+
}
288+
289+
hasNext = answersOfQuestion.hasNext
290+
threshold = Int(answersOfQuestion.threshold)
291+
}
292+
293+
return popularAnswer
294+
}
295+
296+
private static func toEntity(_ dto: AnswerListOfQuestion.Content) -> Answer {
297+
.init(
298+
id: dto.answerId,
299+
writerId: dto.writerId,
300+
content: dto.content,
301+
authorNickname: dto.nickname,
302+
authorGeneration: dto.writerGeneration,
303+
publishedDate: dto.writeAt.ISO8601ToDate(.yearMonthDateTimeMilliseconds),
304+
isReported: false,
305+
isMine: dto.isMine,
306+
isLiked: dto.isLiked,
307+
isResignMember: dto.nickname == "์•Œ ์ˆ˜ ์—†์Œ",
308+
commentCount: dto.commentCount,
309+
heartCount: dto.heartCount
310+
)
311+
}
312+
}
313+
305314
// MARK: - Stub
306315

307316
extension AnswerRepository {

โ€ŽQapple/Qapple/SourceCode/Data/Repository/ReportRepository.swiftโ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import Foundation
1212
struct ReportRepository {
1313
var reportAnswer: (_ answerId: Int, _ reportType: ReportType) async throws -> Void
1414
var reportBoard: (_ boardId: Int, _ reportType: ReportType) async throws -> Void
15-
var reportComment: (_ commentId: Int, _ reportType: ReportType) async throws -> Void
15+
var reportBoardComment: (_ commentId: Int, _ reportType: ReportType) async throws -> Void
16+
var reportAnswerComment:(_ commentId: Int, _ reportType: ReportType) async throws -> Void
1617
}
1718

1819
// MARK: - DependencyKey
@@ -40,7 +41,7 @@ extension ReportRepository: DependencyKey {
4041
)
4142
}
4243
},
43-
reportComment: { commentId, reportType in
44+
reportBoardComment: { commentId, reportType in
4445
let _ = try await RepositoryService.shared.request { server, accessToken in
4546
try await ReportAPI.reportBoardComment(
4647
boardCommentId: commentId,
@@ -49,6 +50,9 @@ extension ReportRepository: DependencyKey {
4950
accessToken: accessToken
5051
)
5152
}
53+
}, reportAnswerComment: { commentId, reportType in
54+
// TODO: ์ถ”ํ›„ API ๊ต์ฒด ํ•„์š”
55+
UserDefaultsService.reportedAnswerCommentIds.append(commentId)
5256
}
5357
)
5458
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// UserDefaultsService.swift
3+
// Qapple
4+
//
5+
// Created by ๊น€๋ฏผ์ค€ on 5/22/25.
6+
//
7+
8+
import Foundation
9+
10+
/// UserDefaults ๊ด€๋ฆฌ ๊ฐ์ฒด
11+
struct UserDefaultsService {
12+
13+
@UserDefault(key: "reportedAnswerCommentIds", defaultValue: [])
14+
static var reportedAnswerCommentIds: [Int]
15+
}
16+
17+
// MARK: - propertyWrapper
18+
19+
@propertyWrapper
20+
struct UserDefault<T> {
21+
22+
let key: String
23+
let defaultValue: T
24+
25+
init(key: String, defaultValue: T) {
26+
self.key = key
27+
self.defaultValue = defaultValue
28+
}
29+
30+
var wrappedValue: T {
31+
get {
32+
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
33+
} set {
34+
UserDefaults.standard.set(newValue, forKey: key)
35+
}
36+
}
37+
}

โ€ŽQapple/Qapple/SourceCode/Data/Service/VersionService.swiftโ€Ž

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ struct VersionService {
1515
}
1616

1717
/// ํ˜„์žฌ ๋ฒ„์ „์ด ์ตœ์‹  ๋ฒ„์ „์ธ์ง€ ํ™•์ธ
18-
static func isRecentVersion() async -> Result<Bool, Error> {
18+
static func isRecentVersion() async throws -> Bool {
1919
guard let recentVersion = await appStoreAppVersion() else {
20-
return .failure(VersionError.networkError)
20+
throw VersionError.networkError
2121
}
2222

23-
return .success(recentVersion == deviceAppVersion)
23+
return recentVersion == deviceAppVersion
2424
}
2525

2626
/// ์•ฑ์Šคํ† ์–ด ๋‚ด ์ตœ์‹  ๋ฒ„์ „
@@ -55,7 +55,10 @@ struct VersionService {
5555

5656
/// ์•ฑ์Šคํ† ์–ด๋ฅผ Openํ•ฉ๋‹ˆ๋‹ค.
5757
static func openAppStore() {
58-
guard let url = URL(string: appStoreOpenUrlString) else { return }
58+
guard let url = URL(string: appStoreOpenUrlString) else {
59+
print("URL ์ƒ์„ฑ ์‹คํŒจ: \(appStoreOpenUrlString)")
60+
return
61+
}
5962
if UIApplication.shared.canOpenURL(url) {
6063
UIApplication.shared.open(url, options: [:], completionHandler: nil)
6164
}

0 commit comments

Comments
ย (0)