feat(community): 크리에이터 커뮤니티 게시물 고정 기능을 추가한다

This commit is contained in:
Yu Sung
2026-03-17 10:41:28 +09:00
parent de627e1700
commit 5e08711b29
12 changed files with 241 additions and 48 deletions

View File

@@ -257,6 +257,7 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
isCommentAvailable: false,
isAdult: false,
isLike: true,
isFixed: false,
existOrdered: false,
likeCount: 10,
commentCount: 0,

View File

@@ -74,6 +74,12 @@ struct CreatorCommunityAllView: View {
CreatorCommunityMenuView(
isShowing: $viewModel.isShowReportMenu,
isShowCreatorMenu: creatorId == UserDefaults.int(forKey: .userId),
isFixed: viewModel.isPostFixed,
fixedAction: {
if creatorId == UserDefaults.int(forKey: .userId) {
viewModel.updateCommunityPostFixed()
}
},
modifyAction: {
let postId = viewModel.postId
AppState.shared
@@ -165,6 +171,7 @@ struct CreatorCommunityAllView: View {
let item = viewModel.communityPostList[index]
CreatorCommunityAllGridItemView(item: item)
.id(index)
.aspectRatio(1, contentMode: .fit)
.onTapGesture {
selectedListIndex = index
listAnchorIndex = index
@@ -173,6 +180,9 @@ struct CreatorCommunityAllView: View {
isGridMode = false
}
}
.onLongPressGesture(minimumDuration: 0.5) {
viewModel.openReportMenu(item: item)
}
.onAppear {
if index == viewModel.communityPostList.count - 1 {
viewModel.getCommunityPostList()
@@ -243,8 +253,7 @@ struct CreatorCommunityAllView: View {
)
},
onClickShowReportMenu: {
viewModel.postId = item.postId
viewModel.isShowReportMenu = true
viewModel.openReportMenu(item: item)
},
onClickPurchaseContent: {
viewModel.postId = item.postId
@@ -328,20 +337,27 @@ private struct CreatorCommunityAllGridItemView: View {
.scaledToFit()
.frame(width: 24, height: 24)
} else if let imageUrl = item.imageUrl, !imageUrl.isEmpty {
AsyncImage(url: URL(string: imageUrl)) { phase in
switch phase {
case .empty:
Color.gray33
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure:
Color(hex: "263238")
@unknown default:
Color.gray33
Rectangle()
.fill(Color.clear)
.aspectRatio(1, contentMode: .fit)
.overlay {
AsyncImage(url: URL(string: imageUrl)) { phase in
switch phase {
case .empty:
Color.gray33
case .success(let image):
image
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: .infinity)
case .failure:
Color(hex: "263238")
@unknown default:
Color.gray33
}
}
}
}
.clipped()
} else {
Color(hex: "263238")
@@ -355,6 +371,17 @@ private struct CreatorCommunityAllGridItemView: View {
}
.aspectRatio(1, contentMode: .fit)
.clipped()
.overlay(alignment: .topTrailing) {
if item.isFixed == true {
Image("ic_pin")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.padding(8)
.shadow(color: .black.opacity(0.45), radius: 2, x: 0, y: 1)
.allowsHitTesting(false)
}
}
}
private var isPaidLocked: Bool {

View File

@@ -21,6 +21,7 @@ class CreatorCommunityAllViewModel: ObservableObject {
@Published private(set) var communityPostList = [GetCommunityPostListResponse]()
@Published var postId = 0
@Published var isPostFixed = false
@Published var postPrice = 0
@Published var postIndex = -1
@Published var isShowSecret = false
@@ -124,6 +125,12 @@ class CreatorCommunityAllViewModel: ObservableObject {
isShowCommentListView = true
}
func openReportMenu(item: GetCommunityPostListResponse) {
postId = item.postId
isPostFixed = item.isFixed == true
isShowReportMenu = true
}
func openCommentListForDeepLink(postId: Int) {
guard postId > 0 else {
return
@@ -289,6 +296,56 @@ class CreatorCommunityAllViewModel: ObservableObject {
self.isLoading = false
}
}
func updateCommunityPostFixed() {
let postId = postId
let nextIsFixed = !isPostFixed
guard postId > 0 else {
return
}
isLoading = true
let request = UpdateCommunityPostFixedRequest(postId: postId, isFixed: nextIsFixed)
repository.updateCommunityPostFixed(request: request)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] response in
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success {
self.isPostFixed = nextIsFixed
DispatchQueue.main.async {
self.refreshCommunityPostListKeepingLoadedCount()
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
self.isLoading = false
}
.store(in: &subscription)
}
func purchaseCommunityPost() {
let postId = postId
@@ -337,6 +394,13 @@ class CreatorCommunityAllViewModel: ObservableObject {
}
}
private func refreshCommunityPostListKeepingLoadedCount() {
pageSize = max(communityPostList.count, 10)
page = 1
isLast = false
getCommunityPostList()
}
private func shouldShowSecretCommentOption(item: GetCommunityPostListResponse) -> Bool {
item.price > 0 && item.existOrdered && item.creatorId != UserDefaults.int(forKey: .userId)
}

View File

@@ -11,7 +11,9 @@ struct CreatorCommunityMenuView: View {
@Binding var isShowing: Bool
let isShowCreatorMenu: Bool
let isFixed: Bool
let fixedAction: () -> Void
let modifyAction: () -> Void
let deleteAction: () -> Void
let reportAction: () -> Void
@@ -28,6 +30,23 @@ struct CreatorCommunityMenuView: View {
VStack(spacing: 13.3) {
if isShowCreatorMenu {
HStack(spacing: 13.3) {
Image(isFixed ? "ic_pin_cancel" : "ic_pin")
Text(isFixed ? "고정 해제" : "최상단에 고정")
.appFont(size: 16.7, weight: .medium)
.foregroundColor(.white)
Spacer()
}
.padding(.vertical, 8)
.padding(.horizontal, 26.7)
.contentShape(Rectangle())
.onTapGesture {
isShowing = false
fixedAction()
}
HStack(spacing: 13.3) {
Image("ic_make_message")
@@ -90,6 +109,8 @@ struct CreatorCommunityMenuView: View {
CreatorCommunityMenuView(
isShowing: .constant(true),
isShowCreatorMenu: true,
isFixed: false,
fixedAction: {},
modifyAction: {},
deleteAction: {},
reportAction: {}