import SwiftUI import Kingfisher struct CommunityPostCard: View { let postId: Int let creatorNickname: String let creatorProfileImageUrl: String? let content: String let imageUrl: String? let audioUrl: String? let price: Int let existOrdered: Bool let createdAt: String let isLike: Bool let likeCount: Int let commentCount: Int let onTapPost: () -> Void let onTapLike: () -> Void let onTapComment: () -> Void let onTapPurchase: () -> Void @State private var localIsLike: Bool @State private var localLikeCount: Int @StateObject private var playManager = CreatorCommunityMediaPlayerManager.shared @StateObject private var contentPlayManager = ContentPlayManager.shared init( postId: Int, creatorNickname: String, creatorProfileImageUrl: String?, content: String, imageUrl: String?, audioUrl: String?, price: Int, existOrdered: Bool, createdAt: String, isLike: Bool, likeCount: Int, commentCount: Int, onTapPost: @escaping () -> Void = {}, onTapLike: @escaping () -> Void = {}, onTapComment: @escaping () -> Void = {}, onTapPurchase: @escaping () -> Void = {} ) { self.postId = postId self.creatorNickname = creatorNickname self.creatorProfileImageUrl = creatorProfileImageUrl self.content = content self.imageUrl = imageUrl self.audioUrl = audioUrl self.price = price self.existOrdered = existOrdered self.createdAt = createdAt self.isLike = isLike self.likeCount = likeCount self.commentCount = commentCount self.onTapPost = onTapPost self.onTapLike = onTapLike self.onTapComment = onTapComment self.onTapPurchase = onTapPurchase _localIsLike = State(initialValue: isLike) _localLikeCount = State(initialValue: likeCount) } var body: some View { VStack(alignment: .leading, spacing: SodaSpacing.s16) { header contentText if hasImage { imageContent } if isAccessiblePost { reactionBar } } .frame(maxWidth: .infinity, alignment: .leading) .padding(SodaSpacing.s14) .background(Color.gray900) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) .contentShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) .onTapGesture(perform: onTapPost) .onChange(of: isLike) { newValue in localIsLike = newValue } .onChange(of: likeCount) { newValue in localLikeCount = newValue } } private var header: some View { HStack(spacing: SodaSpacing.s8) { DownsampledKFImage(url: URL(string: creatorProfileImageUrl ?? ""), size: CGSize(width: 42, height: 42)) .background(Color.gray800) .clipShape(Circle()) .frame(width: 42, height: 42) VStack(alignment: .leading, spacing: 2) { Text(creatorNickname) .appFont(.body4) .foregroundColor(.white) .lineLimit(1) Text(createdAt) .appFont(.body5) .foregroundColor(Color.gray500) .lineLimit(1) } Spacer(minLength: 0) } } private var contentText: some View { Text(content) .appFont(.body2) .foregroundColor(.white) .lineLimit(5) .truncationMode(.tail) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) } private var imageContent: some View { ZStack { KFImage(URL(string: imageUrl ?? "")) .cancelOnDisappear(true) .resizable() .scaledToFit() .frame(maxWidth: .infinity) .background(Color.gray800) .blur(radius: isLocked ? 12 : 0) if isLocked { paidLockOverlay } else if hasAudio { audioPlayButton } } .frame(maxWidth: .infinity) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) } private var paidLockOverlay: some View { Button(action: onTapPurchase) { ZStack { Color.gray700.opacity(0.35) VStack(spacing: SodaSpacing.s4) { Image("ic_new_community_lock") .resizable() .renderingMode(.template) .foregroundColor(.white) .frame(width: 24, height: 24) HStack(spacing: SodaSpacing.s6) { Image("ic_bar_cash") .resizable() .frame(width: 20, height: 20) Text("\(price)") .appFont(.body2) .foregroundColor(.black) } .padding(SodaSpacing.s8) .background(Color.white) .clipShape(Capsule()) } } } .buttonStyle(.plain) } private var audioPlayButton: some View { Button { guard let audioUrl, !audioUrl.isEmpty else { return } contentPlayManager.pauseAudio() playManager.toggleContent(item: CreatorCommunityContentItem(contentId: postId, url: audioUrl)) } label: { Image(playManager.isPlaying && playManager.currentPlayingContentId == postId ? "btn_audio_content_pause" : "btn_audio_content_play") .resizable() .frame(width: 48, height: 48) } .buttonStyle(.plain) } private var reactionBar: some View { HStack(spacing: 15) { Button { toggleLike() } label: { HStack(spacing: SodaSpacing.s4) { Image(localIsLike ? "ic_feed_community_heart_fill" : "ic_feed_community_heart") .resizable() .frame(width: 18, height: 18) Text("\(localLikeCount)") .appFont(.body2) .foregroundColor(Color.gray400) } } .buttonStyle(.plain) Button(action: onTapComment) { HStack(spacing: SodaSpacing.s4) { Image("ic_feed_community_reply") .resizable() .frame(width: 18, height: 18) Text("\(commentCount)") .appFont(.body2) .foregroundColor(Color.gray400) } } .buttonStyle(.plain) } .frame(height: 24) } private var hasImage: Bool { !(imageUrl ?? "").isEmpty } private var hasAudio: Bool { !(audioUrl ?? "").isEmpty } private var isLocked: Bool { price > 0 && !existOrdered } private var isAccessiblePost: Bool { price == 0 || existOrdered } private func toggleLike() { localIsLike.toggle() localLikeCount += localIsLike ? 1 : -1 localLikeCount = max(0, localLikeCount) onTapLike() } } struct CommunityPostCard_Previews: PreviewProvider { private static let previewImageUrl = "https://picsum.photos/500/500" static var previews: some View { ScrollView(.vertical, showsIndicators: false) { VStack(spacing: SodaSpacing.s8) { CommunityPostCard( postId: 1, creatorNickname: "크리에이터 이름", creatorProfileImageUrl: previewImageUrl, content: "크리에이터가 커뮤니티에 올린 글이 보이는 부분 크리에이터가 커뮤니티에 올린 글이 보이는 부분", imageUrl: nil, audioUrl: nil, price: 0, existOrdered: false, createdAt: "2분 전", isLike: false, likeCount: 5, commentCount: 6 ) CommunityPostCard( postId: 2, creatorNickname: "크리에이터 이름", creatorProfileImageUrl: previewImageUrl, content: "이미지가 있는 커뮤니티 게시글입니다.", imageUrl: previewImageUrl, audioUrl: "https://example.com/audio.mp3", price: 30, existOrdered: false, createdAt: "2분 전", isLike: true, likeCount: 5, commentCount: 6 ) CommunityPostCard( postId: 3, creatorNickname: "크리에이터 이름", creatorProfileImageUrl: previewImageUrl, content: "이미지가 있는 커뮤니티 게시글입니다.", imageUrl: previewImageUrl, audioUrl: "https://example.com/audio.mp3", price: 0, existOrdered: false, createdAt: "2분 전", isLike: true, likeCount: 5, commentCount: 6 ) } .padding(SodaSpacing.s14) .background(Color.black) .previewLayout(.sizeThatFits) } } }