diff --git a/SodaLive/Sources/Explorer/ExplorerApi.swift b/SodaLive/Sources/Explorer/ExplorerApi.swift index 3ce990f..6c92a21 100644 --- a/SodaLive/Sources/Explorer/ExplorerApi.swift +++ b/SodaLive/Sources/Explorer/ExplorerApi.swift @@ -15,7 +15,7 @@ enum ExplorerApi { case getFollowerList(userId: Int, page: Int, size: Int) case getCreatorProfileCheers(userId: Int, page: Int, size: Int) case writeCheers(parentCheersId: Int?, creatorId: Int, content: String) - case modifyCheers(cheersId: Int, content: String) + case modifyCheers(request: PutModifyCheersRequest) case writeCreatorNotice(request: PostCreatorNoticeRequest) case getCreatorProfileDonationRanking(userId: Int, page: Int, size: Int) } @@ -102,8 +102,7 @@ extension ExplorerApi: TargetType { let request = PostWriteCheersRequest(parentId: parentCheersId, creatorId: creatorId, content: content) return .requestJSONEncodable(request) - case .modifyCheers(let cheersId, let content): - let request = PutModifyCheersRequest(cheersId: cheersId, content: content) + case .modifyCheers(let request): return .requestJSONEncodable(request) case .writeCreatorNotice(let request): diff --git a/SodaLive/Sources/Explorer/ExplorerRepository.swift b/SodaLive/Sources/Explorer/ExplorerRepository.swift index 5af42bf..bbb6c39 100644 --- a/SodaLive/Sources/Explorer/ExplorerRepository.swift +++ b/SodaLive/Sources/Explorer/ExplorerRepository.swift @@ -37,8 +37,9 @@ final class ExplorerRepository { return api.requestPublisher(.writeCheers(parentCheersId: parentCheersId, creatorId: creatorId, content: content)) } - func modifyCheers(cheersId: Int, content: String) -> AnyPublisher { - return api.requestPublisher(.modifyCheers(cheersId: cheersId, content: content)) + func modifyCheers(cheersId: Int, content: String?, isActive: Bool?) -> AnyPublisher { + let request = PutModifyCheersRequest(cheersId: cheersId, content: content, isActive: isActive) + return api.requestPublisher(.modifyCheers(request: request)) } func writeCreatorNotice(notice: String) -> AnyPublisher { diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/PostWriteCheersRequest.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/PostWriteCheersRequest.swift index ad95fb6..8be80e5 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/PostWriteCheersRequest.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/PostWriteCheersRequest.swift @@ -15,5 +15,6 @@ struct PostWriteCheersRequest: Encodable { struct PutModifyCheersRequest: Encodable { let cheersId: Int - let content: String + let content: String? + let isActive: Bool? } diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift index 5b4a9d5..1465b82 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift @@ -81,17 +81,18 @@ struct UserProfileFanTalkAllView: View { let cheer = viewModel.cheersList[index] UserProfileFanTalkCheersItemView( userId: userId, - cheer: cheer, + cheersItem: cheer, writeCheerReply: { cheersReplyContent in viewModel.writeCheersReply(parentCheersId: cheer.cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent) }, modifyCheer: { cheersId, cheersReplyContent in - viewModel.modifyCheersReply(cheersId: cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent) + viewModel.modifyCheers(cheersId: cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent) }, reportPopup: { cheersId in viewModel.reportCheersId = cheersId viewModel.isShowCheersReportMenu = true - } + }, + onClickDelete: { _ in } ) .onAppear { if index == viewModel.cheersList.count - 1 { diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift index 3c5454c..b3c4f38 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift @@ -11,127 +11,176 @@ import Kingfisher struct UserProfileFanTalkCheersItemView: View { let userId: Int - let cheer: GetCheersResponseItem + let cheersItem: GetCheersResponseItem let writeCheerReply: (String) -> Void let modifyCheer: (Int, String) -> Void let reportPopup: (Int) -> Void + let onClickDelete: (Int) -> Void @State var replyContent: String = "" @State var isShowInputReply = false + @State var isShowPopupMenu: Bool = false + @State var isModeModify: Bool = false + @State var cheers: String = "" + var body: some View { - VStack(alignment: .leading, spacing: 0) { - HStack(alignment: .top, spacing: 6.7) { - KFImage(URL(string: cheer.profileUrl)) - .cancelOnDisappear(true) - .downsampling(size: CGSize(width: 33.3, height: 33.3)) - .resizable() - .frame(width: 33.3, height: 33.3) - .clipShape(Circle()) - - VStack(alignment: .leading, spacing: 0) { - Text("\(cheer.nickname)") - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) + ZStack(alignment: .topTrailing) { + VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .top, spacing: 6.7) { + KFImage(URL(string: cheersItem.profileUrl)) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 33.3, height: 33.3)) + .resizable() + .frame(width: 33.3, height: 33.3) + .clipShape(Circle()) - Text("\(cheer.date)") - .font(.custom(Font.medium.rawValue, size: 10.7)) - .foregroundColor(Color(hex: "525252")) - .padding(.top, 8.3) - - Text("\(cheer.content)") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "777777")) - .padding(.top, 13.3) - - if isShowInputReply { - HStack(spacing: 10) { - TextField("응원댓글에 답글을 남겨보세요!", text: $replyContent) - .autocapitalization(.none) - .disableAutocorrection(true) - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .padding(13.3) - .background(Color(hex: "232323")) - .accentColor(Color(hex: "9970ff")) - .keyboardType(.default) - .cornerRadius(10) - .overlay( - RoundedRectangle(cornerRadius: 10) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "9970ff")) - ) - - Text("등록") - .font(.custom(Font.bold.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "ffffff")) - .padding(13.3) - .background(Color(hex: "9970ff")) - .cornerRadius(6.7) - .onTapGesture { - if cheer.replyList.count > 0 { - modifyCheer(cheer.replyList[0].cheersId, replyContent) - } else { - writeCheerReply(replyContent) - } - } - } - .padding(.top, 10) - } else { - if cheer.replyList.count <= 0 { - if userId == UserDefaults.int(forKey: .userId) { - Text("답글쓰기") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "9970ff")) - .padding(.top, 18.3) + VStack(alignment: .leading, spacing: 0) { + Text("\(cheersItem.nickname)") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + + Text("\(cheersItem.date)") + .font(.custom(Font.medium.rawValue, size: 10.7)) + .foregroundColor(Color(hex: "525252")) + .padding(.top, 8.3) + + Text("\(cheersItem.content)") + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "777777")) + .padding(.top, 13.3) + + if isShowInputReply { + HStack(spacing: 10) { + TextField("응원댓글에 답글을 남겨보세요!", text: $replyContent) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .padding(13.3) + .background(Color(hex: "232323")) + .accentColor(Color(hex: "9970ff")) + .keyboardType(.default) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color(hex: "9970ff")) + ) + + Text("등록") + .font(.custom(Font.bold.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "ffffff")) + .padding(13.3) + .background(Color(hex: "9970ff")) + .cornerRadius(6.7) .onTapGesture { - isShowInputReply = true + if cheersItem.replyList.count > 0 { + modifyCheer(cheersItem.replyList[0].cheersId, replyContent) + } else { + writeCheerReply(replyContent) + } } } + .padding(.top, 10) } else { - let reply = cheer.replyList[0] - VStack(alignment: .leading, spacing: 8.3) { - Text(reply.content) - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "ffffff")) - .frame(minWidth: 100) - .padding(.horizontal, 6.7) - .padding(.vertical, 6.7) - .background(Color(hex: "9970ff").opacity(0.3)) - .cornerRadius(16.7) - .padding(.top, 18.3) - - HStack(spacing: 6.7) { - Text(reply.date) - .font(.custom(Font.medium.rawValue, size: 10.7)) - .foregroundColor(Color(hex: "525252")) + if cheersItem.replyList.count <= 0 { + if userId == UserDefaults.int(forKey: .userId) { + Text("답글쓰기") + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "9970ff")) + .padding(.top, 18.3) + .onTapGesture { + isShowInputReply = true + } + } + } else { + let reply = cheersItem.replyList[0] + VStack(alignment: .leading, spacing: 8.3) { + Text(reply.content) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "ffffff")) + .frame(minWidth: 100) + .padding(.horizontal, 6.7) + .padding(.vertical, 6.7) + .background(Color(hex: "9970ff").opacity(0.3)) + .cornerRadius(16.7) + .padding(.top, 18.3) - if userId == UserDefaults.int(forKey: .userId) { - Text("답글 수정") + HStack(spacing: 6.7) { + Text(reply.date) .font(.custom(Font.medium.rawValue, size: 10.7)) - .foregroundColor(Color(hex: "9970ff")) - .onTapGesture { - self.replyContent = reply.content - isShowInputReply = true - } + .foregroundColor(Color(hex: "525252")) + + if userId == UserDefaults.int(forKey: .userId) { + Text("답글 수정") + .font(.custom(Font.medium.rawValue, size: 10.7)) + .foregroundColor(Color(hex: "9970ff")) + .onTapGesture { + self.replyContent = reply.content + isShowInputReply = true + } + } } } } } } + + Spacer() + + Image("ic_seemore_vertical") + .onTapGesture { isShowPopupMenu = true } } - Spacer() - - Image("ic_seemore_vertical") - .onTapGesture { reportPopup(cheer.cheersId) } + Rectangle() + .frame(height: 1) + .foregroundColor(Color(hex: "909090").opacity(0.5)) + .padding(.top, 13.3) } + .frame(width: screenSize().width - 26.7) - Rectangle() - .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.5)) - .padding(.top, 13.3) + if isShowPopupMenu { + VStack(spacing: 10) { + if cheersItem.memberId != UserDefaults.int(forKey: .userId) { + Text("신고하기") + .font(.custom(Font.medium.rawValue, size: 14)) + .foregroundColor(Color(hex: "777777")) + .onTapGesture { + reportPopup(cheersItem.cheersId) + isShowPopupMenu = false + } + } + + if cheersItem.memberId == UserDefaults.int(forKey: .userId) { + Text("수정") + .font(.custom(Font.medium.rawValue, size: 14)) + .foregroundColor(Color(hex: "777777")) + .onTapGesture { + isModeModify = true + isShowPopupMenu = false + } + } + + if userId == UserDefaults.int(forKey: .userId) || + cheersItem.memberId == UserDefaults.int(forKey: .userId) + { + Text("삭제") + .font(.custom(Font.medium.rawValue, size: 14)) + .foregroundColor(Color(hex: "777777")) + .onTapGesture { + onClickDelete(cheersItem.cheersId) + isShowPopupMenu = false + } + } + } + .padding(10) + .background(Color(hex: "222222")) + } + } + .contentShape(Rectangle()) + .onTapGesture { + isShowPopupMenu = false } - .frame(width: screenSize().width - 26.7) } } diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift index 6a4d170..48ccf64 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift @@ -16,6 +16,7 @@ struct UserProfileFanTalkView: View { let cheers: GetCheersResponse let errorPopup: (String) -> Void let reportPopup: (Int) -> Void + let deletePopup: (Int) -> Void @Binding var isLoading: Bool @State private var cheersContent: String = "" @@ -97,15 +98,18 @@ struct UserProfileFanTalkView: View { let cheer = viewModel.cheersList[$0] UserProfileFanTalkCheersItemView( userId: userId, - cheer: cheer, + cheersItem: cheer, writeCheerReply: { cheersReplyContent in viewModel.writeCheersReply(parentCheersId: cheer.cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent) }, modifyCheer: { cheersId, cheersReplyContent in - viewModel.modifyCheersReply(cheersId: cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent) + viewModel.modifyCheers(cheersId: cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent) }, reportPopup: { cheersId in reportPopup(cheersId) + }, + onClickDelete: { cheersId in + deletePopup(cheersId) } ) .onTapGesture { diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift index 4c0eb93..90bfc65 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift @@ -224,7 +224,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { .store(in: &subscription) } - func modifyCheersReply(cheersId: Int, creatorId: Int, cheersReplyContent: String) { + func modifyCheers(cheersId: Int, creatorId: Int, cheersReplyContent: String) { if cheersReplyContent.trimmingCharacters(in: .whitespaces).isEmpty { if let errorPopup = errorPopup { errorPopup("내용을 입력하세요") @@ -241,7 +241,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { } isLoading = true - repository.modifyCheers(cheersId: cheersId, content: cheersReplyContent) + repository.modifyCheers(cheersId: cheersId, content: cheersReplyContent, isActive: nil) .sink { result in switch result { case .finished: diff --git a/SodaLive/Sources/Explorer/Profile/GetCheersResponse.swift b/SodaLive/Sources/Explorer/Profile/GetCheersResponse.swift index 29edc9b..c71c144 100644 --- a/SodaLive/Sources/Explorer/Profile/GetCheersResponse.swift +++ b/SodaLive/Sources/Explorer/Profile/GetCheersResponse.swift @@ -14,6 +14,7 @@ struct GetCheersResponse: Decodable { struct GetCheersResponseItem: Decodable { let cheersId: Int + let memberId: Int let nickname: String let profileUrl: String let content: String diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift index 920cb30..912ee4d 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift @@ -174,8 +174,12 @@ struct UserProfileView: View { viewModel.isShowPopup = true }, reportPopup: { cheerId in - viewModel.reportCheersId = cheerId - viewModel.isShowCheersReportMenu = true + viewModel.cheersId = cheerId + viewModel.isShowCheersReportView = true + }, + deletePopup: { cheerId in + viewModel.cheersId = cheerId + viewModel.isShowCheersDeleteView = true }, isLoading: $viewModel.isLoading ) @@ -220,20 +224,18 @@ struct UserProfileView: View { ) } - if viewModel.isShowCheersReportMenu { - VStack(spacing: 0) { - CheersReportMenuView( - isShowing: $viewModel.isShowCheersReportMenu, - onClickReport: { viewModel.isShowCheersReportView = true } - ) - - if proxy.safeAreaInsets.bottom > 0 { - Rectangle() - .foregroundColor(Color(hex: "222222")) - .frame(width: proxy.size.width, height: 15.3) - } - } - .ignoresSafeArea() + if viewModel.isShowCheersDeleteView { + SodaDialog( + title: "응원글 삭제", + desc: "삭제하시겠습니까?", + confirmButtonTitle: "삭제", + confirmButtonAction: { + viewModel.deleteCheers(creatorId: userId) + viewModel.isShowCheersDeleteView = false + }, + cancelButtonTitle: "취소", + cancelButtonAction: { viewModel.isShowCheersDeleteView = false } + ) } if viewModel.isShowCheersReportView { diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift b/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift index e5b83de..8e52eae 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift @@ -47,10 +47,9 @@ final class UserProfileViewModel: ObservableObject { @Published var isShowUesrReportView = false @Published var isShowProfileReportConfirm = false - @Published var reportCheersId = 0 - @Published var isShowCheersReportMenu = false + @Published var cheersId = 0 @Published var isShowCheersReportView = false - + @Published var isShowCheersDeleteView = false let paymentDialogCancelTitle = "취소" @@ -488,7 +487,7 @@ final class UserProfileViewModel: ObservableObject { func report(type: ReportType, userId: Int? = nil, reason: String = "프로필 신고") { isLoading = true - let request = ReportRequest(type: type, reason: reason, reportedMemberId: userId, cheersId: reportCheersId > 0 && type == .CHEERS ? reportCheersId : nil, audioContentId: nil) + let request = ReportRequest(type: type, reason: reason, reportedMemberId: userId, cheersId: cheersId > 0 && type == .CHEERS ? cheersId : nil, audioContentId: nil) reportRepository.report(request: request) .sink { result in switch result { @@ -501,7 +500,7 @@ final class UserProfileViewModel: ObservableObject { self.isLoading = false let responseData = response.data - self.reportCheersId = 0 + self.cheersId = 0 do { let jsonDecoder = JSONDecoder() @@ -521,4 +520,43 @@ final class UserProfileViewModel: ObservableObject { } .store(in: &subscription) } + + func deleteCheers(creatorId: Int) { + isLoading = true + + repository.modifyCheers(cheersId: cheersId, content: nil, isActive: false) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { [unowned self] response in + self.cheersId = 0 + self.isLoading = false + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) + + if decoded.success { + self.getCreatorProfile(userId: creatorId) + } 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) + } }