커뮤니티 수정 UI, API 추가
This commit is contained in:
parent
f40642f90f
commit
7bd32f8486
|
@ -115,4 +115,6 @@ enum AppStep {
|
|||
case creatorCommunityAll(creatorId: Int)
|
||||
|
||||
case creatorCommunityWrite(onSuccess: () -> Void)
|
||||
|
||||
case creatorCommunityModify(postId: Int, onSuccess: () -> Void)
|
||||
}
|
||||
|
|
|
@ -169,6 +169,9 @@ struct ContentView: View {
|
|||
case .creatorCommunityWrite(let onSuccess):
|
||||
CreatorCommunityWriteView(onSuccess: onSuccess)
|
||||
|
||||
case .creatorCommunityModify(let postId, let onSuccess):
|
||||
CreatorCommunityModifyView(postId: postId, onSuccess: onSuccess)
|
||||
|
||||
default:
|
||||
EmptyView()
|
||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||
|
|
|
@ -125,6 +125,8 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
|
|||
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
|
||||
date: "3일전",
|
||||
isCommentAvailable: false,
|
||||
isAdult: false,
|
||||
isLike: true,
|
||||
likeCount: 10,
|
||||
commentCount: 0,
|
||||
|
|
|
@ -71,6 +71,15 @@ struct CreatorCommunityAllView: View {
|
|||
isShowing: $viewModel.isShowReportMenu,
|
||||
isShowCreatorMenu: creatorId == UserDefaults.int(forKey: .userId),
|
||||
modifyAction: {
|
||||
let postId = viewModel.postId
|
||||
AppState.shared
|
||||
.setAppStep(
|
||||
step: .creatorCommunityModify(
|
||||
postId: postId,
|
||||
onSuccess: creatorCommunityModifySuccess
|
||||
)
|
||||
)
|
||||
viewModel.postId = 0
|
||||
},
|
||||
deleteAction: {
|
||||
if creatorId == UserDefaults.int(forKey: .userId) {
|
||||
|
@ -137,6 +146,10 @@ struct CreatorCommunityAllView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func creatorCommunityModifySuccess() {
|
||||
viewModel.getCommunityPostList()
|
||||
}
|
||||
}
|
||||
|
||||
struct CreatorCommunityAllView_Previews: PreviewProvider {
|
||||
|
|
|
@ -12,6 +12,7 @@ enum CreatorCommunityApi {
|
|||
case createCommunityPost(parameters: [MultipartFormData])
|
||||
case modifyCommunityPost(parameters: [MultipartFormData])
|
||||
case getCommunityPostList(creatorId: Int, page: Int, size: Int)
|
||||
case getCommunityPostDetail(postId: Int)
|
||||
case communityPostLike(postId: Int)
|
||||
case createCommunityPostComment(comment: String, postId: Int, parentId: Int?)
|
||||
case getCommunityPostCommentList(postId: Int, page: Int, size: Int)
|
||||
|
@ -40,6 +41,9 @@ extension CreatorCommunityApi: TargetType {
|
|||
|
||||
case .getCommentReplyList(let commentId, _, _):
|
||||
return "/creator-community/comment/\(commentId)"
|
||||
|
||||
case .getCommunityPostDetail(let postId):
|
||||
return "/creator-community/\(postId)"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +52,7 @@ extension CreatorCommunityApi: TargetType {
|
|||
case .createCommunityPost, .communityPostLike, .createCommunityPostComment:
|
||||
return .post
|
||||
|
||||
case .getCommunityPostList, .getCommunityPostCommentList, .getCommentReplyList:
|
||||
case .getCommunityPostList, .getCommunityPostCommentList, .getCommentReplyList, .getCommunityPostDetail:
|
||||
return .get
|
||||
|
||||
case .modifyComment, .modifyCommunityPost:
|
||||
|
@ -99,7 +103,10 @@ extension CreatorCommunityApi: TargetType {
|
|||
|
||||
case .modifyComment(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
|
||||
case .getCommunityPostDetail:
|
||||
let parameters = ["timezone": TimeZone.current.identifier] as [String: Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ struct CreatorCommunityItemView_Previews: PreviewProvider {
|
|||
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
content: "안녕하세요",
|
||||
date: "3일전",
|
||||
isCommentAvailable: false,
|
||||
isAdult: false,
|
||||
isLike: false,
|
||||
likeCount: 10,
|
||||
commentCount: 0,
|
||||
|
|
|
@ -25,6 +25,10 @@ class CreatorCommunityRepository {
|
|||
return api.requestPublisher(.getCommunityPostList(creatorId: creatorId, page: page, size: size))
|
||||
}
|
||||
|
||||
func getCommunityPostDetail(postId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getCommunityPostDetail(postId: postId))
|
||||
}
|
||||
|
||||
func communityPostLike(postId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.communityPostLike(postId: postId))
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ struct GetCommunityPostListResponse: Decodable {
|
|||
let imageUrl: String?
|
||||
let content: String
|
||||
let date: String
|
||||
let isCommentAvailable: Bool
|
||||
let isAdult: Bool
|
||||
let isLike: Bool
|
||||
let likeCount: Int
|
||||
let commentCount: Int
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
//
|
||||
// CreatorCommunityModifyView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/12/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct CreatorCommunityModifyView: View {
|
||||
|
||||
let postId: Int
|
||||
@StateObject var keyboardHandler = KeyboardHandler()
|
||||
@StateObject private var viewModel = CreatorCommunityModifyViewModel()
|
||||
|
||||
@State private var isShowPhotoPicker = false
|
||||
let onSuccess: () -> Void
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
GeometryReader { proxy in
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "게시글 수정")
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
Text("이미지")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
ZStack {
|
||||
if let selectedImage = viewModel.postImage {
|
||||
Image(uiImage: selectedImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 107, height: 107)
|
||||
.background(Color(hex: "3e3358"))
|
||||
.cornerRadius(8)
|
||||
.clipped()
|
||||
} else if let postImageUrl = viewModel.postImageUrl {
|
||||
KFImage(URL(string: postImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 107, height: 107)
|
||||
.background(Color(hex: "3e3358"))
|
||||
.cornerRadius(8)
|
||||
.clipped()
|
||||
} else {
|
||||
Image("ic_logo2")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.padding(13.3)
|
||||
.frame(width: 107, height: 107)
|
||||
.background(Color(hex: "13181B"))
|
||||
.cornerRadius(8)
|
||||
.clipped()
|
||||
}
|
||||
|
||||
Image("ic_camera")
|
||||
.padding(10)
|
||||
.background(Color(hex: "3BB9F1"))
|
||||
.cornerRadius(30)
|
||||
.offset(x: 50, y: 36)
|
||||
}
|
||||
.frame(alignment: .bottomTrailing)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { isShowPhotoPicker = true }
|
||||
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("※ ")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
|
||||
Text("등록할 이미지가 없으면 이미지 없이 게시글만 등록 하셔도 됩니다.")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 24)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("내용")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.content.count)자")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "ff5c49")) +
|
||||
Text(" / 최대 500자")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
}
|
||||
.padding(.top, 26.7)
|
||||
|
||||
TextViewWrapper(
|
||||
text: $viewModel.content,
|
||||
placeholder: viewModel.placeholder,
|
||||
textColorHex: "eeeeee",
|
||||
backgroundColorHex: "222222"
|
||||
)
|
||||
.frame(height: 184)
|
||||
.cornerRadius(6.7)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("댓글 가능 여부")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(
|
||||
title: "댓글 가능",
|
||||
isChecked: viewModel.isAvailableComment
|
||||
) {
|
||||
if !viewModel.isAvailableComment {
|
||||
viewModel.isAvailableComment = true
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(
|
||||
title: "댓글 불가",
|
||||
isChecked: !viewModel.isAvailableComment
|
||||
) {
|
||||
if viewModel.isAvailableComment {
|
||||
viewModel.isAvailableComment = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 26.7)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("연령 제한")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(
|
||||
title: "전체 연령",
|
||||
isChecked: !viewModel.isAdult
|
||||
) {
|
||||
if viewModel.isAdult {
|
||||
viewModel.isAdult = false
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(
|
||||
title: "19세 이상",
|
||||
isChecked: viewModel.isAdult
|
||||
) {
|
||||
if !viewModel.isAdult {
|
||||
viewModel.isAdult = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 26.7)
|
||||
}
|
||||
.padding(13.3)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 13.3) {
|
||||
Text("닫기")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "3BB9F1"))
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.background(Color(hex: "13181B"))
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color(hex: "3BB9F1"), lineWidth: 1)
|
||||
)
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
AppState.shared.back()
|
||||
}
|
||||
|
||||
Text("수정")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.background(Color(hex: "3BB9F1"))
|
||||
.cornerRadius(10)
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
viewModel.modifyCommunityPost {
|
||||
AppState.shared.back()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(13.3)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(Color(hex: "222222"))
|
||||
.frame(height: keyboardHandler.keyboardHeight)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
if proxy.safeAreaInsets.bottom > 0 {
|
||||
Rectangle()
|
||||
.foregroundColor(Color(hex: "222222"))
|
||||
.frame(height: 15.3)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.padding(.top, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isShowPhotoPicker {
|
||||
ImagePicker(
|
||||
isShowing: $isShowPhotoPicker,
|
||||
selectedImage: $viewModel.postImage,
|
||||
sourceType: .photoLibrary
|
||||
)
|
||||
}
|
||||
}
|
||||
.onTapGesture { hideKeyboard() }
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||
GeometryReader { geo in
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.background(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.cornerRadius(20)
|
||||
.padding(.top, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.postId = postId
|
||||
viewModel.getCommunityPostDetail {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
AppState.shared.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CreatorCommunityModifyView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CreatorCommunityModifyView(postId: 0) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// CreatorCommunityModifyViewModel.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/12/21.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Moya
|
||||
import Combine
|
||||
|
||||
final class CreatorCommunityModifyViewModel: ObservableObject {
|
||||
private let repository = CreatorCommunityRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
|
||||
@Published var content = ""
|
||||
@Published var isAdult = false
|
||||
@Published var isAvailableComment = true
|
||||
@Published var postImage: UIImage? = nil
|
||||
@Published var postImageUrl: String? = nil
|
||||
@Published private(set) var communityPost: GetCommunityPostListResponse?
|
||||
|
||||
var placeholder = "내용을 입력하세요"
|
||||
var postId = 0
|
||||
|
||||
func getCommunityPostDetail(onFailure: (() -> Void)? = nil) {
|
||||
communityPost = nil
|
||||
isLoading = true
|
||||
|
||||
repository.getCommunityPostDetail(postId: postId)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
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 {
|
||||
self.communityPost = data
|
||||
|
||||
self.content = data.content
|
||||
self.isAdult = data.isAdult
|
||||
self.isAvailableComment = data.isCommentAvailable
|
||||
self.postImageUrl = data.imageUrl
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
if let onFailure = onFailure {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
if let onFailure = onFailure {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
|
||||
}
|
||||
|
||||
func modifyCommunityPost(onSuccess: @escaping () -> Void) {
|
||||
if !isLoading && validateData() {
|
||||
isLoading = true
|
||||
|
||||
let request = ModifyCommunityPostRequest(
|
||||
creatorCommunityId: postId,
|
||||
content: communityPost!.content != content ? content : nil,
|
||||
isCommentAvailable: communityPost!.isCommentAvailable != isAvailableComment ? isAvailableComment : nil,
|
||||
isAdult: communityPost!.isAdult != isAdult ? isAdult : nil
|
||||
)
|
||||
var multipartData = [MultipartFormData]()
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .withoutEscapingSlashes
|
||||
let jsonData = try? encoder.encode(request)
|
||||
|
||||
if let jsonData = jsonData {
|
||||
if let postImage = postImage, let imageData = postImage.jpegData(compressionQuality: 0.8) {
|
||||
multipartData.append(
|
||||
MultipartFormData(
|
||||
provider: .data(imageData),
|
||||
name: "postImage",
|
||||
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg",
|
||||
mimeType: "image/*")
|
||||
)
|
||||
}
|
||||
|
||||
multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request"))
|
||||
|
||||
repository
|
||||
.modifyCommunityPost(parameters: multipartData)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.errorMessage = "게시물이 수정되었습니다."
|
||||
self.isShowPopup = true
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
onSuccess()
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func validateData() -> Bool {
|
||||
if content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || content.count < 5 {
|
||||
errorMessage = "내용을 5자 이상 입력해 주세요."
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -93,7 +93,6 @@ final class CreatorCommunityWriteViewModel: ObservableObject {
|
|||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue