Skip to content

Commit f24795d

Browse files
authored
feat - 소셜 탭 1차 업데이트 (#62)
* feat(Social): 소셜 탭 UI 추가 * feat(Subscribe): 나의 픽, 나의 팬덤 목록 API 연동 * feat(Social): 목록 개수 추가 및 데이터 refetch 추가 * feat(Social): 친구 검색 및 상세 조회 페이지 추가 및 API연동 * feat: 프로필이미지 안뜨는 이슈 해결 * refactor: 컴포넌트 위치 변경 * refactor: 컴포넌트 위치 변경 * feat(SocialMyCollectionDiary): 소셜 내 컬렉션 페이지 다이어리 보기 기능 추가 * feat(isMyPick): 내 픽인지 아닌지 분기처리 추가 * feat(SocialView): isMyPick DTO 업데이트 반영 * feat(SocialMyCollectionVeiw): 구독 추가/취소 API 연동 * fix(SocialView): 데이터 refetch 이슈 수정 * refactor(SocialView): 소셜페이지 리팩토링 * feat(FeedSection): 피드 섹션 추가 및 폴더구조 변경 * Refactor(SocialMyCollection): 폴더 위치 변경 * fix(SocialFeedSection): UI 재구성 및 업데이트 * feat(SocialFeed): 영상 반복재생 추가 * feat(SocialFeed): 실시간 위치 UI 업데이트 * fix(SocialFeed): 다음 음악 재생 이슈 해결 * fix(FeedSection): 재생구간 하단에 고정 배치 * fix(DiaryService): 다이어리 api 잘못 동기화 된 부분 해결 * feat(FeedView): like, store api 세팅 및 연동 * feat(FeedView): 좋아요 및 킬링파트 일기 여백 조정 등 UI 업데이트 * fix(SocialFeed): 음악 피드 재진입 시 조건 추가 * fix(FeedSectoion): 다이어리 재생 부분 이슈 개선 * feat(1.1.0): 버전 업데이트
1 parent 167bc6a commit f24795d

37 files changed

Lines changed: 3068 additions & 30 deletions

KillingPart.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 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 = 24;
437+
CURRENT_PROJECT_VERSION = 25;
438438
DEAD_CODE_STRIPPING = YES;
439439
DEVELOPMENT_TEAM = GQ89YG5G9R;
440440
ENABLE_APP_SANDBOX = 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.24;
462+
MARKETING_VERSION = 1.0.25;
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 = 24;
482+
CURRENT_PROJECT_VERSION = 25;
483483
DEAD_CODE_STRIPPING = YES;
484484
DEVELOPMENT_TEAM = GQ89YG5G9R;
485485
ENABLE_APP_SANDBOX = 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.24;
507+
MARKETING_VERSION = 1.0.25;
508508
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
509509
PRODUCT_NAME = "$(TARGET_NAME)";
510510
REGISTER_APP_GROUPS = YES;

KillingPart/Models/DiaryModel.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,70 @@ struct DiaryFeedPageModel: Decodable {
6666
let totalPages: Int
6767
}
6868

69+
struct DiaryLikeToggleResponse: Decodable {
70+
let isLiked: Bool
71+
}
72+
73+
struct DiaryStoreToggleResponse: Decodable {
74+
let isStored: Bool
75+
}
76+
6977
struct MyDiaryFeedsResponse: Decodable {
7078
let content: [DiaryFeedModel]
7179
let page: DiaryFeedPageModel
7280
}
7381

82+
struct UserDiaryFeedModel: Decodable, Identifiable {
83+
let diaryId: Int
84+
let artist: String
85+
let musicTitle: String
86+
let albumImageUrl: String
87+
let content: String
88+
let videoUrl: String
89+
let scope: DiaryScope
90+
let duration: String
91+
let totalDuration: String
92+
let start: String
93+
let end: String
94+
let createDate: String
95+
let updateDate: String
96+
let isLiked: Bool
97+
let isStored: Bool
98+
let likeCount: Int
99+
100+
var id: Int { diaryId }
101+
102+
func toDiaryFeedModel(user: UserModel) -> DiaryFeedModel {
103+
DiaryFeedModel(
104+
diaryId: diaryId,
105+
artist: artist,
106+
musicTitle: musicTitle,
107+
albumImageUrl: albumImageUrl,
108+
content: content,
109+
videoUrl: videoUrl,
110+
scope: scope,
111+
duration: duration,
112+
totalDuration: totalDuration,
113+
start: start,
114+
end: end,
115+
createDate: createDate,
116+
updateDate: updateDate,
117+
isLiked: isLiked,
118+
isStored: isStored,
119+
likeCount: likeCount,
120+
userId: user.userId,
121+
username: user.username,
122+
tag: user.tag,
123+
profileImageUrl: user.profileImageUrl
124+
)
125+
}
126+
}
127+
128+
struct UserDiaryFeedsResponse: Decodable {
129+
let content: [UserDiaryFeedModel]
130+
let page: DiaryFeedPageModel
131+
}
132+
74133
struct DiaryCreateRequest: Encodable {
75134
let artist: String
76135
let musicTitle: String
@@ -394,4 +453,33 @@ extension DiaryFeedModel {
394453
guard !trimmed.isEmpty else { return "" }
395454
return trimmed.split(separator: "T").first.map(String.init) ?? trimmed
396455
}
456+
457+
func replacingInteraction(
458+
isLiked: Bool? = nil,
459+
isStored: Bool? = nil,
460+
likeCount: Int? = nil
461+
) -> DiaryFeedModel {
462+
DiaryFeedModel(
463+
diaryId: diaryId,
464+
artist: artist,
465+
musicTitle: musicTitle,
466+
albumImageUrl: albumImageUrl,
467+
content: content,
468+
videoUrl: videoUrl,
469+
scope: scope,
470+
duration: duration,
471+
totalDuration: totalDuration,
472+
start: start,
473+
end: end,
474+
createDate: createDate,
475+
updateDate: updateDate,
476+
isLiked: isLiked ?? self.isLiked,
477+
isStored: isStored ?? self.isStored,
478+
likeCount: likeCount ?? self.likeCount,
479+
userId: userId,
480+
username: username,
481+
tag: tag,
482+
profileImageUrl: profileImageUrl
483+
)
484+
}
397485
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
func resolvedProfileImageURL(from rawValue: String) -> URL? {
4+
let trimmed = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
5+
guard !trimmed.isEmpty else { return nil }
6+
7+
let normalized: String
8+
if trimmed.hasPrefix("//") {
9+
normalized = "https:\(trimmed)"
10+
} else if trimmed.lowercased().hasPrefix("http://") {
11+
normalized = "https://\(trimmed.dropFirst("http://".count))"
12+
} else if trimmed.lowercased().hasPrefix("https://") {
13+
normalized = trimmed
14+
} else {
15+
normalized = "https://\(trimmed)"
16+
}
17+
18+
if let parsed = URL(string: normalized), parsed.scheme != nil {
19+
return parsed
20+
}
21+
22+
let allowed = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~:/?#[]@!$&'()*+,;=%")
23+
guard let encoded = normalized.addingPercentEncoding(withAllowedCharacters: allowed) else {
24+
return nil
25+
}
26+
27+
return URL(string: encoded)
28+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import Foundation
2+
3+
struct SubscribeUserModel: Decodable, Identifiable {
4+
let userId: Int
5+
let username: String
6+
let tag: String
7+
let identifier: String
8+
let profileImageUrl: String
9+
let userRoleType: String
10+
let socialType: String
11+
let isMyPick: Bool?
12+
13+
private enum CodingKeys: String, CodingKey {
14+
case userId
15+
case username
16+
case tag
17+
case identifier
18+
case profileImageUrl
19+
case userRoleType
20+
case socialType
21+
case isMyPick
22+
}
23+
24+
init(from decoder: Decoder) throws {
25+
let container = try decoder.container(keyedBy: CodingKeys.self)
26+
userId = try container.decode(Int.self, forKey: .userId)
27+
username = try container.decode(String.self, forKey: .username)
28+
tag = try container.decode(String.self, forKey: .tag)
29+
identifier = try container.decode(String.self, forKey: .identifier)
30+
profileImageUrl = try container.decode(String.self, forKey: .profileImageUrl)
31+
userRoleType = try container.decode(String.self, forKey: .userRoleType)
32+
socialType = try container.decode(String.self, forKey: .socialType)
33+
isMyPick = container.decodeFlexibleBoolIfPresent(forKey: .isMyPick)
34+
}
35+
36+
var id: Int { userId }
37+
38+
var displayName: String {
39+
let trimmed = username.trimmingCharacters(in: .whitespacesAndNewlines)
40+
return trimmed.isEmpty ? "킬링파트 사용자" : trimmed
41+
}
42+
43+
var displayTag: String {
44+
let trimmed = tag.trimmingCharacters(in: .whitespacesAndNewlines)
45+
guard !trimmed.isEmpty else { return "@killingpart_user" }
46+
return trimmed.hasPrefix("@") ? trimmed : "@\(trimmed)"
47+
}
48+
49+
var profileImageURL: URL? {
50+
resolvedProfileImageURL(from: profileImageUrl)
51+
}
52+
}
53+
54+
struct SubscribePageModel: Decodable {
55+
let size: Int
56+
let number: Int
57+
let totalElements: Int
58+
let totalPages: Int
59+
}
60+
61+
struct SubscribeListResponse: Decodable {
62+
let content: [SubscribeUserModel]
63+
let page: SubscribePageModel
64+
}
65+
66+
private extension KeyedDecodingContainer {
67+
func decodeFlexibleBoolIfPresent(forKey key: K) -> Bool? {
68+
if let boolValue = try? decodeIfPresent(Bool.self, forKey: key) {
69+
return boolValue
70+
}
71+
72+
if let stringValue = try? decodeIfPresent(String.self, forKey: key) {
73+
let normalized = stringValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
74+
if normalized == "true" { return true }
75+
if normalized == "false" { return false }
76+
}
77+
78+
if let intValue = try? decodeIfPresent(Int.self, forKey: key) {
79+
return intValue != 0
80+
}
81+
82+
return nil
83+
}
84+
}

0 commit comments

Comments
 (0)