Skip to content

Commit fa57530

Browse files
authored
feat - 소셜 탭 마무리 (#65) (#66)
* fix: 저전력 모드 영상 재생 대응 * fix: 저전력 모드 시 유튜브 재생 안됨 이슈 개선 * feat(FeedSection): 피드색션의 프로필카드 라우팅 추가 * feat: 팬덤,PICKS 바텀시트 추가 * feat(SocialMyCollectionDiary): 좋아요, 스크랩 기능 추가 * feat(FeedSection): 좋아요, 스크랩 refetch 추가 * feat(SocialMyCollectionDiary): 다이어리 페이지 좋아요,스크랩 refetch 추가 * feat: 좋아요목록 api연동 * feat(FriendSection): 나의 픽/나의 팬덤 UI 업데이트 * feat(FeedSection): 신고하기 API 연동 및 UI 구성 * feat(SocialFeedPageCardView): 신고하기 UI 업데이트 * fix: 신고하기 아이콘 변경 * feat(MyCollection): 팬덤/PICKS/좋아요 목록 바텀시트 추가 * feat(FeedSection): 좋아요, 스크랩 피드백 UI 추가 * feat(SocialFeed): 프로필 가장자리 색상 추가 스크롤 부분 킬링파트 일기 부분만 적용 * feat(1.1.1): 버전 업데이트
2 parents b6b0647 + 60b1833 commit fa57530

29 files changed

Lines changed: 2814 additions & 146 deletions

KillingPart.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@
434434
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
435435
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
436436
CODE_SIGN_STYLE = Automatic;
437-
CURRENT_PROJECT_VERSION = 25;
437+
CURRENT_PROJECT_VERSION = 27;
438438
DEAD_CODE_STRIPPING = YES;
439439
DEVELOPMENT_TEAM = GQ89YG5G9R;
440440
ENABLE_APP_SANDBOX = YES;
@@ -444,7 +444,7 @@
444444
GENERATE_INFOPLIST_FILE = YES;
445445
INFOPLIST_FILE = KillingPart/Info.plist;
446446
INFOPLIST_KEY_CFBundleDisplayName = "킬링파트";
447-
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
447+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
448448
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
449449
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
450450
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -459,7 +459,7 @@
459459
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
460460
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
461461
MACOSX_DEPLOYMENT_TARGET = 14.0;
462-
MARKETING_VERSION = 1.0.25;
462+
MARKETING_VERSION = 1.1.1;
463463
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
464464
PRODUCT_NAME = "$(TARGET_NAME)";
465465
REGISTER_APP_GROUPS = YES;
@@ -479,7 +479,7 @@
479479
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
480480
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
481481
CODE_SIGN_STYLE = Automatic;
482-
CURRENT_PROJECT_VERSION = 25;
482+
CURRENT_PROJECT_VERSION = 27;
483483
DEAD_CODE_STRIPPING = YES;
484484
DEVELOPMENT_TEAM = GQ89YG5G9R;
485485
ENABLE_APP_SANDBOX = YES;
@@ -489,7 +489,7 @@
489489
GENERATE_INFOPLIST_FILE = YES;
490490
INFOPLIST_FILE = KillingPart/Info.plist;
491491
INFOPLIST_KEY_CFBundleDisplayName = "킬링파트";
492-
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
492+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
493493
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
494494
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
495495
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -504,7 +504,7 @@
504504
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
505505
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
506506
MACOSX_DEPLOYMENT_TARGET = 14.0;
507-
MARKETING_VERSION = 1.0.25;
507+
MARKETING_VERSION = 1.1.1;
508508
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
509509
PRODUCT_NAME = "$(TARGET_NAME)";
510510
REGISTER_APP_GROUPS = YES;

KillingPart/Models/DiaryModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ struct DiaryCreateRequest: Encodable {
143143
let end: String
144144
}
145145

146+
struct DiaryReportRequest: Encodable {
147+
let content: String
148+
}
149+
146150
struct DiaryCreateResult {
147151
let diaryId: Int?
148152
let location: String?

KillingPart/Services/DiaryService.swift

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ protocol DiaryServicing {
44
func fetchMyDiaries(page: Int, size: Int) async throws -> MyDiaryFeedsResponse
55
func fetchMyFeeds(page: Int, size: Int) async throws -> MyDiaryFeedsResponse
66
func fetchUserFeeds(userId: Int, page: Int, size: Int) async throws -> UserDiaryFeedsResponse
7+
func fetchDiaryLikeUsers(diaryId: Int, searchCond: String?, page: Int, size: Int) async throws -> UserSearchResponse
78
func createDiary(request: DiaryCreateRequest) async throws -> DiaryCreateResult
89
func updateDiary(diaryId: Int, request: DiaryUpdateRequest) async throws
910
func deleteDiary(diaryId: Int) async throws
1011
func updateMyDiaryOrder(request: DiaryOrderUpdateRequest) async throws
1112
func toggleDiaryLike(diaryId: Int) async throws -> DiaryLikeToggleResponse
1213
func toggleDiaryStore(diaryId: Int) async throws -> DiaryStoreToggleResponse
14+
func reportDiary(diaryId: Int, content: String) async throws
1315
}
1416

1517
enum DiaryServiceError: LocalizedError {
@@ -124,6 +126,40 @@ struct DiaryService: DiaryServicing {
124126
}
125127
}
126128

129+
func fetchDiaryLikeUsers(
130+
diaryId: Int,
131+
searchCond: String? = nil,
132+
page: Int = Self.defaultPage,
133+
size: Int = Self.defaultSize
134+
) async throws -> UserSearchResponse {
135+
let resolvedPage = max(page, Self.defaultPage)
136+
let resolvedSize = size > 0 ? size : Self.defaultSize
137+
let trimmedSearchCond = searchCond?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
138+
139+
var queryItems = [
140+
URLQueryItem(name: "page", value: String(resolvedPage)),
141+
URLQueryItem(name: "size", value: String(resolvedSize))
142+
]
143+
144+
if !trimmedSearchCond.isEmpty {
145+
queryItems.insert(URLQueryItem(name: "searchCond", value: trimmedSearchCond), at: 0)
146+
}
147+
148+
do {
149+
let request = APIRequest(
150+
path: "/diaries/\(diaryId)/like",
151+
method: .get,
152+
queryItems: queryItems,
153+
requiresAuthorization: true
154+
)
155+
let response = try await apiClient.request(request, responseType: UserSearchResponseDTO.self)
156+
return response.toModel()
157+
} catch {
158+
if isRequestCancelled(error) { throw error }
159+
throw mapError(error)
160+
}
161+
}
162+
127163
func createDiary(request: DiaryCreateRequest) async throws -> DiaryCreateResult {
128164
let requestBody: Data
129165
do {
@@ -244,6 +280,32 @@ struct DiaryService: DiaryServicing {
244280
}
245281
}
246282

283+
func reportDiary(diaryId: Int, content: String) async throws {
284+
let requestBody: Data
285+
do {
286+
requestBody = try JSONEncoder().encode(
287+
DiaryReportRequest(content: content.trimmingCharacters(in: .whitespacesAndNewlines))
288+
)
289+
} catch {
290+
throw DiaryServiceError.requestEncodingFailed
291+
}
292+
293+
do {
294+
var request = APIRequest(
295+
path: "/diaries/\(diaryId)/reports",
296+
method: .post,
297+
requiresAuthorization: true,
298+
body: requestBody
299+
)
300+
request.headers["Accept"] = "application/json"
301+
request.headers["Content-Type"] = "application/json"
302+
try await apiClient.request(request)
303+
} catch {
304+
if isRequestCancelled(error) { throw error }
305+
throw mapError(error)
306+
}
307+
}
308+
247309
private func mapError(_ error: Error) -> DiaryServiceError {
248310
if let diaryServiceError = error as? DiaryServiceError {
249311
return diaryServiceError
@@ -256,7 +318,10 @@ struct DiaryService: DiaryServicing {
256318
case .missingAccessToken, .missingRefreshToken, .unauthorized:
257319
return .sessionExpired
258320
case .serverError(let statusCode, let message):
259-
return .serverError(statusCode: statusCode, message: message)
321+
return .serverError(
322+
statusCode: statusCode,
323+
message: normalizeServerErrorMessage(message)
324+
)
260325
case .decodingFailed:
261326
return .decodingFailed
262327
}
@@ -265,6 +330,41 @@ struct DiaryService: DiaryServicing {
265330
return .networkFailure(message: "네트워크 요청 중 오류가 발생했어요.")
266331
}
267332

333+
private func normalizeServerErrorMessage(_ rawMessage: String?) -> String? {
334+
guard
335+
let rawMessage = rawMessage?.trimmingCharacters(in: .whitespacesAndNewlines),
336+
!rawMessage.isEmpty
337+
else {
338+
return nil
339+
}
340+
341+
guard
342+
rawMessage.first == "{",
343+
let data = rawMessage.data(using: .utf8),
344+
let parsed = try? JSONDecoder().decode(DiaryServiceErrorResponse.self, from: data)
345+
else {
346+
return rawMessage
347+
}
348+
349+
if let message = parsed.message?.trimmingCharacters(in: .whitespacesAndNewlines),
350+
!message.isEmpty {
351+
return message
352+
}
353+
354+
let fieldMessages = (parsed.fieldErrors ?? [])
355+
.flatMap(\.values)
356+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
357+
.filter { !$0.isEmpty }
358+
359+
let globalMessages = (parsed.globalErrors ?? [])
360+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
361+
.filter { !$0.isEmpty }
362+
363+
let merged = fieldMessages + globalMessages
364+
guard !merged.isEmpty else { return rawMessage }
365+
return merged.joined(separator: "\n")
366+
}
367+
268368
private func extractDiaryID(from location: String?) -> Int? {
269369
guard let location else { return nil }
270370
let trimmed = location.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -284,3 +384,9 @@ struct DiaryService: DiaryServicing {
284384
return nsError.domain == NSURLErrorDomain && nsError.code == NSURLErrorCancelled
285385
}
286386
}
387+
388+
private struct DiaryServiceErrorResponse: Decodable {
389+
let message: String?
390+
let fieldErrors: [[String: String]]?
391+
let globalErrors: [String]?
392+
}

0 commit comments

Comments
 (0)