Skip to content

Commit 82ca74f

Browse files
authored
Merge pull request #32 from ApptiveDev/dev
[Release] 1.0.15 업데이트
2 parents 61b2a5e + 0cfbbef commit 82ca74f

6 files changed

Lines changed: 163 additions & 63 deletions

File tree

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 = 14;
437+
CURRENT_PROJECT_VERSION = 15;
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.14;
462+
MARKETING_VERSION = 1.0.15;
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 = 14;
482+
CURRENT_PROJECT_VERSION = 15;
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.14;
507+
MARKETING_VERSION = 1.0.15;
508508
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
509509
PRODUCT_NAME = "$(TARGET_NAME)";
510510
REGISTER_APP_GROUPS = YES;

KillingPart/Models/UserModel.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ struct UserModel: Decodable {
1010
let socialType: String
1111

1212
var profileImageURL: URL? {
13-
URL(string: profileImageUrl)
13+
let trimmed = profileImageUrl.trimmingCharacters(in: .whitespacesAndNewlines)
14+
guard !trimmed.isEmpty else { return nil }
15+
16+
if let parsed = URL(string: trimmed), parsed.scheme != nil {
17+
return parsed
18+
}
19+
20+
if trimmed.hasPrefix("//"), let parsed = URL(string: "https:\(trimmed)") {
21+
return parsed
22+
}
23+
24+
return URL(string: "https://\(trimmed)")
1425
}
1526
}
1627

KillingPart/Views/Screens/Main/My/MyCollection/Components/ProfileCard/MyCollectionProfileImageView.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ struct MyCollectionProfileImageView: View {
44
let profileImageURL: URL?
55
let size: CGFloat
66
let iconSize: CGFloat
7+
@State private var imageReloadKey = UUID()
78

89
var body: some View {
910
Circle()
@@ -18,6 +19,15 @@ struct MyCollectionProfileImageView: View {
1819
Circle()
1920
.stroke(Color.kpPrimary, lineWidth: 2)
2021
}
22+
.onAppear {
23+
imageReloadKey = UUID()
24+
}
25+
.onChange(of: profileImageURL?.absoluteString) { _ in
26+
imageReloadKey = UUID()
27+
}
28+
.onReceive(NotificationCenter.default.publisher(for: .diaryCreated)) { _ in
29+
imageReloadKey = UUID()
30+
}
2131
}
2232

2333
@ViewBuilder
@@ -35,6 +45,7 @@ struct MyCollectionProfileImageView: View {
3545
profileImagePlaceholder
3646
}
3747
}
48+
.id(imageReloadKey)
3849
} else {
3950
profileImagePlaceholder
4051
}

KillingPart/Views/Screens/Main/My/MyCollection/MyCollectionView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ struct MyCollectionView: View {
6464
diaryId: route.diaryId,
6565
displayTag: viewModel.displayTag,
6666
diary: diary
67-
) { changedDiaryId in
67+
) {
68+
collectionListRenderID = UUID()
69+
Task {
70+
await viewModel.refetchCollectionDataOnFocus()
71+
}
72+
} onDiaryDeleted: { changedDiaryId in
6873
viewModel.removeMyFeedLocally(diaryId: changedDiaryId)
6974
collectionListRenderID = UUID()
7075
Task {

KillingPart/Views/Screens/Main/My/MyCollection/[diaryId]/MyCollectionDiary.swift

Lines changed: 129 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,26 @@ struct MyCollectionDiary: View {
77

88
let diaryId: Int
99
let displayTag: String
10-
let onDiaryChanged: ((Int) -> Void)?
10+
let onDiaryUpdated: (() -> Void)?
11+
let onDiaryDeleted: ((Int) -> Void)?
1112
@StateObject private var viewModel: MyCollectionDiaryViewModel
1213
@State private var isDeleteDialogPresented = false
14+
@State private var keyboardHeight: CGFloat = 0
15+
16+
private let commentFocusAnchorID = "my-collection-diary-comment-focus-anchor"
1317

1418
init(
1519
diaryId: Int,
1620
displayTag: String,
1721
diary: DiaryFeedModel,
18-
onDiaryChanged: ((Int) -> Void)? = nil,
22+
onDiaryUpdated: (() -> Void)? = nil,
23+
onDiaryDeleted: ((Int) -> Void)? = nil,
1924
diaryService: DiaryServicing = DiaryService()
2025
) {
2126
self.diaryId = diaryId
2227
self.displayTag = displayTag
23-
self.onDiaryChanged = onDiaryChanged
28+
self.onDiaryUpdated = onDiaryUpdated
29+
self.onDiaryDeleted = onDiaryDeleted
2430
_viewModel = StateObject(
2531
wrappedValue: MyCollectionDiaryViewModel(
2632
diary: diary,
@@ -32,78 +38,101 @@ struct MyCollectionDiary: View {
3238
var body: some View {
3339
GeometryReader { proxy in
3440
let topContentInset = min(proxy.safeAreaInsets.top, AppSpacing.l) + AppSpacing.s
35-
let bottomContentInset = min(proxy.safeAreaInsets.bottom, AppSpacing.xl) + AppSpacing.l
41+
let keyboardCompensation = keyboardHeight > 0 ? max(keyboardHeight - 140, 0) : 0
42+
let extraBottomInset = keyboardHeight > 0 ? AppSpacing.s : AppSpacing.l
43+
let bottomContentInset = proxy.safeAreaInsets.bottom + keyboardCompensation + extraBottomInset
3644

3745
ZStack {
3846
Image("my_background")
3947
.resizable()
4048
.scaledToFill()
4149
.frame(maxWidth: .infinity, maxHeight: .infinity)
4250
.clipped()
43-
.ignoresSafeArea()
51+
.ignoresSafeArea(.container, edges: .all)
4452

4553
if viewModel.isDeleted {
4654
MyCollectionDiaryDeletedPlaceholder()
4755
} else {
48-
ScrollView {
49-
VStack(alignment: .leading, spacing: AppSpacing.m) {
50-
MyCollectionDiaryVideoSection(
51-
videoURL: videoURL,
52-
startSeconds: viewModel.startSeconds,
53-
endSeconds: viewModel.endSeconds
54-
)
55-
56-
MyCollectionDiaryTrackSection(
57-
artworkURL: viewModel.diary.albumImageURL,
58-
musicTitle: viewModel.diary.musicTitle,
59-
artist: viewModel.diary.artist,
60-
startMinuteSecondText: viewModel.startMinuteSecondText,
61-
endMinuteSecondText: viewModel.endMinuteSecondText,
62-
startProgress: startProgress,
63-
endProgress: endProgress
64-
)
65-
66-
MyCollectionDiaryCommentSection(
67-
isEditMode: viewModel.isEditMode,
68-
displayedContent: viewModel.displayedContent,
69-
editContentDraft: $viewModel.editContentDraft,
70-
isProcessing: viewModel.isProcessing,
71-
canSubmitEdit: viewModel.canSubmitEdit,
72-
createdDateText: createdDateText,
73-
tagText: tagText,
74-
isCommentEditorFocused: $isCommentEditorFocused,
75-
onCancelTap: {
76-
dismissKeyboard()
77-
viewModel.cancelEdit()
78-
},
79-
onSaveTap: {
80-
dismissKeyboard()
81-
Task {
82-
let isSuccess = await viewModel.submitEdit()
83-
guard isSuccess else { return }
84-
}
56+
ScrollViewReader { scrollProxy in
57+
ScrollView {
58+
VStack(alignment: .leading, spacing: AppSpacing.m) {
59+
MyCollectionDiaryVideoSection(
60+
videoURL: videoURL,
61+
startSeconds: viewModel.startSeconds,
62+
endSeconds: viewModel.endSeconds
63+
)
64+
65+
MyCollectionDiaryTrackSection(
66+
artworkURL: viewModel.diary.albumImageURL,
67+
musicTitle: viewModel.diary.musicTitle,
68+
artist: viewModel.diary.artist,
69+
startMinuteSecondText: viewModel.startMinuteSecondText,
70+
endMinuteSecondText: viewModel.endMinuteSecondText,
71+
startProgress: startProgress,
72+
endProgress: endProgress
73+
)
74+
75+
MyCollectionDiaryCommentSection(
76+
isEditMode: viewModel.isEditMode,
77+
displayedContent: viewModel.displayedContent,
78+
editContentDraft: $viewModel.editContentDraft,
79+
isProcessing: viewModel.isProcessing,
80+
canSubmitEdit: viewModel.canSubmitEdit,
81+
createdDateText: createdDateText,
82+
tagText: tagText,
83+
isCommentEditorFocused: $isCommentEditorFocused,
84+
onCancelTap: handleCancelTap,
85+
onSaveTap: handleSaveTap
86+
)
87+
88+
if let errorMessage = viewModel.errorMessage {
89+
Text(errorMessage)
90+
.font(AppFont.paperlogy4Regular(size: 13))
91+
.foregroundStyle(.red.opacity(0.95))
92+
.frame(maxWidth: .infinity, alignment: .leading)
8593
}
86-
)
8794

88-
if let errorMessage = viewModel.errorMessage {
89-
Text(errorMessage)
90-
.font(AppFont.paperlogy4Regular(size: 13))
91-
.foregroundStyle(.red.opacity(0.95))
92-
.frame(maxWidth: .infinity, alignment: .leading)
95+
Color.clear
96+
.frame(height: 1)
97+
.id(commentFocusAnchorID)
98+
}
99+
.padding(.horizontal, AppSpacing.l)
100+
.padding(.top, topContentInset)
101+
.padding(.bottom, bottomContentInset)
102+
.contentShape(Rectangle())
103+
.onTapGesture {
104+
dismissKeyboard()
93105
}
94106
}
95-
.padding(.horizontal, AppSpacing.l)
96-
.padding(.top, topContentInset)
97-
.padding(.bottom, bottomContentInset)
98-
.contentShape(Rectangle())
99-
.onTapGesture {
100-
dismissKeyboard()
107+
.scrollIndicators(.hidden)
108+
.scrollDismissesKeyboard(.interactively)
109+
.onChange(of: isCommentEditorFocused) { isFocused in
110+
guard isFocused else { return }
111+
DispatchQueue.main.async {
112+
withAnimation(.easeOut(duration: 0.22)) {
113+
scrollProxy.scrollTo(commentFocusAnchorID, anchor: .bottom)
114+
}
115+
}
116+
}
117+
.onChange(of: keyboardHeight) { height in
118+
guard isCommentEditorFocused else { return }
119+
guard height > 0 else { return }
120+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
121+
withAnimation(.easeOut(duration: 0.18)) {
122+
scrollProxy.scrollTo(commentFocusAnchorID, anchor: .bottom)
123+
}
124+
}
101125
}
102126
}
103-
.scrollIndicators(.hidden)
104127
}
105128
}
106129
}
130+
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
131+
updateKeyboardHeight(from: notification)
132+
}
133+
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { notification in
134+
updateKeyboardHeight(from: notification)
135+
}
107136
.navigationTitle("")
108137
.navigationBarTitleDisplayMode(.inline)
109138
.toolbar(.visible, for: .navigationBar)
@@ -137,7 +166,7 @@ struct MyCollectionDiary: View {
137166
Task {
138167
let isSuccess = await viewModel.deleteDiary()
139168
if isSuccess {
140-
onDiaryChanged?(diaryId)
169+
onDiaryDeleted?(diaryId)
141170
dismiss()
142171
}
143172
}
@@ -188,6 +217,50 @@ struct MyCollectionDiary: View {
188217
return raw.hasPrefix("@") ? raw : "@\(raw)"
189218
}
190219

220+
private func updateKeyboardHeight(from notification: Notification) {
221+
guard
222+
let userInfo = notification.userInfo,
223+
let endFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
224+
else {
225+
return
226+
}
227+
228+
let keyWindow = UIApplication.shared.connectedScenes
229+
.compactMap { $0 as? UIWindowScene }
230+
.flatMap(\.windows)
231+
.first(where: \.isKeyWindow)
232+
233+
let overlapHeight: CGFloat
234+
if let keyWindow {
235+
let endFrameInWindow = keyWindow.convert(endFrame, from: nil)
236+
overlapHeight = max(
237+
0,
238+
keyWindow.bounds.maxY - endFrameInWindow.minY - keyWindow.safeAreaInsets.bottom
239+
)
240+
} else {
241+
overlapHeight = max(0, UIScreen.main.bounds.maxY - endFrame.minY)
242+
}
243+
244+
let duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double) ?? 0.25
245+
withAnimation(.easeOut(duration: duration)) {
246+
keyboardHeight = overlapHeight
247+
}
248+
}
249+
250+
private func handleCancelTap() {
251+
dismissKeyboard()
252+
viewModel.cancelEdit()
253+
}
254+
255+
private func handleSaveTap() {
256+
dismissKeyboard()
257+
Task {
258+
let isSuccess = await viewModel.submitEdit()
259+
guard isSuccess else { return }
260+
onDiaryUpdated?()
261+
}
262+
}
263+
191264
private func dismissKeyboard() {
192265
isCommentEditorFocused = false
193266
UIApplication.shared.sendAction(

KillingPart/Views/Screens/Main/My/MyCollection/[diaryId]/components/MyCollectionDiaryCommentSection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct MyCollectionDiaryCommentSection: View {
1313
let onSaveTap: () -> Void
1414

1515
var body: some View {
16-
VStack(alignment: .leading, spacing: AppSpacing.xs) {
16+
VStack(alignment: .leading, spacing: AppSpacing.m) {
1717
if isEditMode {
1818
editCommentContainer
1919
editActionSection

0 commit comments

Comments
 (0)