커뮤니티 게시물 전체보기, 좋아요, 댓글 추가 API 적용

This commit is contained in:
Yu Sung 2023-12-20 14:28:20 +09:00
parent 28b64ba8a8
commit 02764f5fe8
11 changed files with 313 additions and 43 deletions

View File

@ -9,8 +9,7 @@ import SwiftUI
struct IconAndTitleToggleButton: View {
@Binding var isChecked: Bool
let isChecked: Bool
let title: String
let normalIconName: String
let checkedIconName: String
@ -36,7 +35,7 @@ struct IconAndTitleToggleButton: View {
struct IconAndTitleToggleButton_Previews: PreviewProvider {
static var previews: some View {
IconAndTitleToggleButton(
isChecked: .constant(true),
isChecked: true,
title: "100",
normalIconName: "ic_audio_content_heart_normal",
checkedIconName: "ic_audio_content_heart_pressed"

View File

@ -0,0 +1,12 @@
//
// CreateCommunityPostCommentRequest.swift
// SodaLive
//
// Created by klaus on 2023/12/20.
//
struct CreateCommunityPostCommentRequest: Encodable {
let comment: String
let postId: Int
let parentId: Int?
}

View File

@ -6,12 +6,13 @@
//
import SwiftUI
import Kingfisher
struct CreatorCommunityCommentView: View {
let commentCount: Int
let commentList: [String]
let commentItem: GetCommunityPostCommentListItem?
let registerComment: (String) -> Void
let onClickWriteComment: (String) -> Void
@State private var comment = ""
@ -30,18 +31,23 @@ struct CreatorCommunityCommentView: View {
}
HStack(spacing: 8) {
Image("ic_place_holder")
.resizable()
.frame(width: 33.3, height: 33.3)
.clipShape(Circle())
if let comment = commentItem {
KFImage(URL(string: comment.profileUrl))
.resizable()
.frame(width: 33.3, height: 33.3)
.clipShape(Circle())
if commentCount > 0 {
Text(commentList[0])
Text(comment.comment)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "bbbbbb"))
.lineLimit(1)
.padding(.leading, 3)
} else {
KFImage(URL(string: UserDefaults.string(forKey: .profileImage)))
.resizable()
.frame(width: 33.3, height: 33.3)
.clipShape(Circle())
HStack(spacing: 0) {
TextField("댓글을 입력해 보세요.", text: $comment)
.autocapitalization(.none)
@ -60,7 +66,8 @@ struct CreatorCommunityCommentView: View {
.padding(6.7)
.onTapGesture {
hideKeyboard()
registerComment(comment)
onClickWriteComment(comment)
comment = ""
}
}
.background(Color(hex: "232323"))
@ -83,11 +90,16 @@ struct CreatorCommunityCommentView_Previews: PreviewProvider {
static var previews: some View {
CreatorCommunityCommentView(
commentCount: 0,
commentList: [
"내용 읽어보니까 결혼해도 될것 같은데",
"너무 조하유 앞으로도 좋은 라이브 많이 들려주세요"
],
registerComment: { _ in }
commentItem: GetCommunityPostCommentListItem(
id: 1,
writerId: 1,
nickname: "닉네임",
profileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
comment: "댓글 테스트",
date: "1시간전",
replyCount: 0
),
onClickWriteComment: { _ in }
)
}
}

View File

@ -6,25 +6,45 @@
//
import SwiftUI
import Kingfisher
struct CreatorCommunityAllItemView: View {
@State var isExpandContent = true
let item: GetCommunityPostListResponse
let onClickLike: () -> Void
let onClickWriteComment: (String) -> Void
@State var isExpandContent = false
@State var isLike = false
@State var likeCount = 0
init(
item: GetCommunityPostListResponse,
onClickLike: @escaping () -> Void,
onClickWriteComment: @escaping (String) -> Void
) {
self.item = item
self.onClickLike = onClickLike
self.onClickWriteComment = onClickWriteComment
self._isLike = State(initialValue: item.isLike)
self._likeCount = State(initialValue: item.likeCount)
}
var body: some View {
VStack(spacing: 13.3) {
HStack(spacing: 0) {
Image("ic_place_holder")
KFImage(URL(string: item.creatorProfileUrl))
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 3) {
Text("민하나")
Text(item.creatorNickname)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
Text("1개월 전(수정됨)")
Text(item.date)
.font(.custom(Font.light.rawValue, size: 13.3))
.foregroundColor(Color(hex: "777777"))
}
@ -37,7 +57,7 @@ struct CreatorCommunityAllItemView: View {
.onTapGesture {}
}
Text("너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!")
Text(item.content)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "bbbbbb"))
.fixedSize(horizontal: false, vertical: true)
@ -45,36 +65,61 @@ struct CreatorCommunityAllItemView: View {
.lineLimit(isExpandContent ? Int.max : 3)
.onTapGesture { isExpandContent.toggle() }
Image("img_sample")
.resizable()
.frame(maxWidth: .infinity)
.scaledToFit()
if let imageUrl = item.imageUrl {
KFImage(URL(string: imageUrl))
.resizable()
.frame(maxWidth: .infinity)
.scaledToFit()
}
HStack(spacing: 8) {
IconAndTitleToggleButton(
isChecked: .constant(true),
title: "252",
isChecked: isLike,
title: "\(likeCount)",
normalIconName: "ic_audio_content_heart_normal",
checkedIconName: "ic_audio_content_heart_pressed"
) {}
IconAndTitleButton(iconName: "ic_audio_content_share", title: "공유") {}
) {
if isLike {
isLike = false
likeCount -= 1
} else {
isLike = true
likeCount += 1
}
onClickLike()
}
}
.frame(maxWidth: .infinity, alignment: .leading)
CreatorCommunityCommentView(
commentCount: 2,
commentList: [
"내용 읽어보니까 결혼해도 될것 같은데",
"너무 조하유 앞으로도 좋은 라이브 많이 들려주세요"
],
registerComment: { _ in }
commentCount: item.commentCount,
commentItem: item.firstComment,
onClickWriteComment: onClickWriteComment
)
}
.padding(6.7)
.background(Color(hex: "222222"))
.cornerRadius(5.3)
}
}
struct CreatorCommunityAllItemView_Previews: PreviewProvider {
static var previews: some View {
CreatorCommunityAllItemView()
CreatorCommunityAllItemView(
item: GetCommunityPostListResponse(
postId: 1,
creatorNickname: "민하나",
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
date: "3일전",
isLike: true,
likeCount: 10,
commentCount: 0,
firstComment: nil
),
onClickLike: {},
onClickWriteComment: { _ in }
)
}
}

View File

@ -12,17 +12,35 @@ struct CreatorCommunityAllView: View {
let creatorId: Int
@State var isShowingReportView = false
@StateObject var viewModel = CreatorCommunityAllViewModel()
var body: some View {
BaseView {
BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) {
DetailNavigationBar(title: "커뮤니티")
ScrollView(.vertical, showsIndicators: false) {
LazyVStack(spacing: 26.7) {
CreatorCommunityAllItemView()
CreatorCommunityAllItemView()
CreatorCommunityAllItemView()
ForEach(0..<viewModel.communityPostList.count, id: \.self) { index in
let item = viewModel.communityPostList[index]
CreatorCommunityAllItemView(
item: item,
onClickLike: {
viewModel.communityPostLike(postId: item.postId)
},
onClickWriteComment: { comment in
viewModel.createCommunityPostComment(
comment: comment,
postId: item.postId
)
}
)
.onAppear {
if index == viewModel.communityPostList.count - 1 {
viewModel.getCommunityPostList()
}
}
}
}
.padding(.horizontal, 13.3)
.padding(.vertical, 5.3)
@ -34,6 +52,10 @@ struct CreatorCommunityAllView: View {
}
}
}
.onAppear {
viewModel.creatorId = creatorId
viewModel.getCommunityPostList()
}
}
}

View File

@ -7,6 +7,133 @@
import Foundation
import Moya
import Combine
class CreatorCommunityAllViewModel: ObservableObject {
private let repository = CreatorCommunityRepository()
private var subscription = Set<AnyCancellable>()
@Published var isLoading = false
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published private(set) var communityPostList = [GetCommunityPostListResponse]()
var creatorId = 0
var page = 1
var isLast = false
private var pageSize = 10
func getCommunityPostList() {
if !isLoading && !isLast {
isLoading = true
repository
.getCommunityPostList(creatorId: creatorId, page: page, size: pageSize)
.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(ApiResponse<[GetCommunityPostListResponse]>.self, from: responseData)
if let data = decoded.data, decoded.success {
if data.count > 0 {
if page == 1 {
self.communityPostList.removeAll()
}
self.communityPostList.append(contentsOf: data)
self.page += 1
self.pageSize = 10
} else {
isLast = true
}
} 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 communityPostLike(postId: Int) {
repository.communityPostLike(postId: postId)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { _ in
}
.store(in: &subscription)
}
func createCommunityPostComment(comment: String, postId: Int, parentId: Int? = nil) {
if !isLoading && !isLast {
isLoading = true
repository.createCommunityPostComment(comment: comment, postId: postId, parentId: parentId)
.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 {
DispatchQueue.main.async {
self.pageSize *= self.page
self.page = 1
self.isLast = false
self.getCommunityPostList()
}
} 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)
}
}
}

View File

@ -10,6 +10,9 @@ import Moya
enum CreatorCommunityApi {
case createCommunityPost(parameters: [MultipartFormData])
case getCommunityPostList(creatorId: Int, page: Int, size: Int)
case communityPostLike(postId: Int)
case createCommunityPostComment(comment: String, postId: Int, parentId: Int?)
}
extension CreatorCommunityApi: TargetType {
@ -19,15 +22,24 @@ extension CreatorCommunityApi: TargetType {
var path: String {
switch self {
case .createCommunityPost:
case .createCommunityPost, .getCommunityPostList:
return "/creator-community"
case .communityPostLike:
return "/creator-community/like"
case .createCommunityPostComment:
return "/creator-community/comment"
}
}
var method: Moya.Method {
switch self {
case .createCommunityPost:
case .createCommunityPost, .communityPostLike, .createCommunityPostComment:
return .post
case .getCommunityPostList:
return .get
}
}
@ -35,6 +47,23 @@ extension CreatorCommunityApi: TargetType {
switch self {
case .createCommunityPost(let parameters):
return .uploadMultipart(parameters)
case .getCommunityPostList(let creatorId, let page, let size):
let parameters = [
"creatorId": creatorId,
"page": page - 1,
"size": size,
"timezone": TimeZone.current.identifier
] as [String: Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .communityPostLike(let postId):
let request = PostCommunityPostLikeRequest(postId: postId)
return .requestJSONEncodable(request)
case .createCommunityPostComment(let comment, let postId, let parentId):
let request = CreateCommunityPostCommentRequest(comment: comment, postId: postId, parentId: parentId)
return .requestJSONEncodable(request)
}
}

View File

@ -85,6 +85,7 @@ struct CreatorCommunityItemView_Previews: PreviewProvider {
static var previews: some View {
CreatorCommunityItemView(
item: GetCommunityPostListResponse(
postId: 1,
creatorNickname: "민하나",
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",

View File

@ -16,4 +16,16 @@ class CreatorCommunityRepository {
func createCommunityPost(parameters: [MultipartFormData]) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.createCommunityPost(parameters: parameters))
}
func getCommunityPostList(creatorId: Int, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getCommunityPostList(creatorId: creatorId, page: page, size: size))
}
func communityPostLike(postId: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.communityPostLike(postId: postId))
}
func createCommunityPostComment(comment: String, postId: Int, parentId: Int?) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.createCommunityPostComment(comment: comment, postId: postId, parentId: parentId))
}
}

View File

@ -6,6 +6,7 @@
//
struct GetCommunityPostListResponse: Decodable {
let postId: Int
let creatorNickname: String
let creatorProfileUrl: String
let imageUrl: String?

View File

@ -0,0 +1,10 @@
//
// PostCommunityPostLikeRequest.swift
// SodaLive
//
// Created by klaus on 2023/12/20.
//
struct PostCommunityPostLikeRequest: Encodable {
let postId: Int
}