From 12d2c09434621752870f71c67b19ddfdf2a7f610 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 29 Jan 2024 15:22:45 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20-=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EA=B3=A0?= =?UTF-8?q?=EC=A0=95/=ED=95=B4=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ic_pin.imageset/Contents.json | 21 ++++ .../ic_pin.imageset/ic_pin.png | Bin 0 -> 1315 bytes .../ic_pin_cancel.imageset/Contents.json | 21 ++++ .../ic_pin_cancel.imageset/ic_pin_cancel.png | Bin 0 -> 1553 bytes .../ic_trash_can.imageset/Contents.json | 21 ++++ .../ic_trash_can.imageset/ic_trash_can.png | Bin 0 -> 787 bytes SodaLive/Sources/Content/ContentApi.swift | 15 ++- .../Sources/Content/ContentRepository.swift | 8 ++ .../Detail/ContentDetailMenuView.swift | 28 ++++- .../Content/Detail/ContentDetailView.swift | 27 +++++ .../Detail/ContentDetailViewModel.swift | 81 +++++++++++++++ .../GetAudioContentDetailResponse.swift | 2 + .../All/CreatorCommunityAllView.swift | 2 +- .../All/CreatorCommunityMenuView.swift | 97 ++++++++++++++++++ 14 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 SodaLive/Resources/Assets.xcassets/ic_pin.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_pin.imageset/ic_pin.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/ic_pin_cancel.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_trash_can.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_trash_can.imageset/ic_trash_can.png create mode 100644 SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift diff --git a/SodaLive/Resources/Assets.xcassets/ic_pin.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_pin.imageset/Contents.json new file mode 100644 index 0000000..d7e1323 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_pin.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_pin.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_pin.imageset/ic_pin.png b/SodaLive/Resources/Assets.xcassets/ic_pin.imageset/ic_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..088bb74576c91799eab63cc2bca5d4ba495c1577 GIT binary patch literal 1315 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 TuOAQsreOw8S3j3^P6 AnyPublisher { return api.requestPublisher(.getContentRanking(page: page, size: size, sortType: sortType)) } + + func pinContent(contentId: Int) -> AnyPublisher { + return api.requestPublisher(.pinContent(contentId: contentId)) + } + + func unpinContent(contentId: Int) -> AnyPublisher { + return api.requestPublisher(.unpinContent(contentId: contentId)) + } } diff --git a/SodaLive/Sources/Content/Detail/ContentDetailMenuView.swift b/SodaLive/Sources/Content/Detail/ContentDetailMenuView.swift index 6207cb4..4d3451f 100644 --- a/SodaLive/Sources/Content/Detail/ContentDetailMenuView.swift +++ b/SodaLive/Sources/Content/Detail/ContentDetailMenuView.swift @@ -11,7 +11,10 @@ struct ContentDetailMenuView: View { @Binding var isShowing: Bool + let isPin: Bool let isShowCreatorMenu: Bool + + let pinAction: () -> Void let modifyAction: () -> Void let deleteAction: () -> Void let reportAction: () -> Void @@ -28,7 +31,26 @@ struct ContentDetailMenuView: View { VStack(spacing: 13.3) { if isShowCreatorMenu { - HStack(spacing: 0) { + HStack(spacing: 13.3) { + Image(isPin ? "ic_pin_cancel" : "ic_pin") + + Text(isPin ? "내 채널에 고정 취소" : "내 채널에 고정") + .font(.custom(Font.medium.rawValue, size: 16.7)) + .foregroundColor(.white) + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 26.7) + .contentShape(Rectangle()) + .onTapGesture { + isShowing = false + pinAction() + } + + HStack(spacing: 13.3) { + Image("ic_make_message") + Text("수정") .font(.custom(Font.medium.rawValue, size: 16.7)) .foregroundColor(.white) @@ -43,7 +65,9 @@ struct ContentDetailMenuView: View { modifyAction() } - HStack(spacing: 0) { + HStack(spacing: 13.3) { + Image("ic_trash_can") + Text("삭제") .font(.custom(Font.medium.rawValue, size: 16.7)) .foregroundColor(.white) diff --git a/SodaLive/Sources/Content/Detail/ContentDetailView.swift b/SodaLive/Sources/Content/Detail/ContentDetailView.swift index 0330de2..ffceeb9 100644 --- a/SodaLive/Sources/Content/Detail/ContentDetailView.swift +++ b/SodaLive/Sources/Content/Detail/ContentDetailView.swift @@ -219,7 +219,19 @@ struct ContentDetailView: View { VStack(spacing: 0) { ContentDetailMenuView( isShowing: $viewModel.isShowReportMenu, + isPin: viewModel.audioContent!.isPin, isShowCreatorMenu: viewModel.audioContent!.creator.creatorId == UserDefaults.int(forKey: .userId), + pinAction: { + if viewModel.audioContent!.isPin { + viewModel.unpinContent(contentId: contentId) + } else { + if viewModel.audioContent!.isAvailablePin { + viewModel.pinContent(contentId: contentId) + } else { + viewModel.isShowNoticePinContentPopup = true + } + } + }, modifyAction: { if viewModel.audioContent!.creator.creatorId == UserDefaults.int(forKey: .userId) { AppState @@ -284,6 +296,21 @@ struct ContentDetailView: View { viewModel.donation(can: can, comment: comment) } } + + if viewModel.isShowNoticePinContentPopup { + SodaDialog( + title: "고정 한도 도달", + desc: "이 콘텐츠를 고정하시겠어요? " + + "채널에 콘텐츠를 최대 3개까지 고정할 수 있습니다." + + "이 콘텐츠를 고정하면 가장 오래된 콘텐츠가 대체됩니다.", + confirmButtonTitle: "확인", + confirmButtonAction: { + viewModel.pinContent(contentId: contentId) + }, + cancelButtonTitle: "취소", + cancelButtonAction: {} + ) + } } } .sheet( diff --git a/SodaLive/Sources/Content/Detail/ContentDetailViewModel.swift b/SodaLive/Sources/Content/Detail/ContentDetailViewModel.swift index 4a48764..e840677 100644 --- a/SodaLive/Sources/Content/Detail/ContentDetailViewModel.swift +++ b/SodaLive/Sources/Content/Detail/ContentDetailViewModel.swift @@ -34,6 +34,7 @@ final class ContentDetailViewModel: ObservableObject { @Published var isShowReportMenu = false @Published var isShowReportView = false @Published var isShowDeleteConfirm = false + @Published var isShowNoticePinContentPopup = false var contentId: Int = 0 { didSet { @@ -448,4 +449,84 @@ final class ContentDetailViewModel: ObservableObject { .store(in: &subscription) } } + + func pinContent(contentId: Int) { + isLoading = true + repository.pinContent(contentId: contentId) + .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 + + self.getAudioContentDetail() + } 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) + } + + func unpinContent(contentId: Int) { + isLoading = true + repository.unpinContent(contentId: contentId) + .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 + + self.getAudioContentDetail() + } 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) + } } diff --git a/SodaLive/Sources/Content/Detail/GetAudioContentDetailResponse.swift b/SodaLive/Sources/Content/Detail/GetAudioContentDetailResponse.swift index f84d032..923566a 100644 --- a/SodaLive/Sources/Content/Detail/GetAudioContentDetailResponse.swift +++ b/SodaLive/Sources/Content/Detail/GetAudioContentDetailResponse.swift @@ -32,6 +32,8 @@ struct GetAudioContentDetailResponse: Decodable { let likeCount: Int let commentList: [GetAudioContentCommentListItem] let commentCount: Int + let isPin: Bool + let isAvailablePin: Bool let creator: AudioContentCreator } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift index 057ddf4..ae2370f 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift @@ -67,7 +67,7 @@ struct CreatorCommunityAllView: View { ZStack { if viewModel.isShowReportMenu { VStack(spacing: 0) { - ContentDetailMenuView( + CreatorCommunityMenuView( isShowing: $viewModel.isShowReportMenu, isShowCreatorMenu: creatorId == UserDefaults.int(forKey: .userId), modifyAction: { diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift new file mode 100644 index 0000000..ad820cc --- /dev/null +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift @@ -0,0 +1,97 @@ +// +// CreatorCommunityMenuView.swift +// SodaLive +// +// Created by klaus on 1/29/24. +// + +import SwiftUI + +struct CreatorCommunityMenuView: View { + @Binding var isShowing: Bool + + let isShowCreatorMenu: Bool + + let modifyAction: () -> Void + let deleteAction: () -> Void + let reportAction: () -> Void + + var body: some View { + ZStack { + Color.black + .opacity(0.7) + .ignoresSafeArea() + .onTapGesture { isShowing = false } + + VStack(spacing: 0) { + Spacer() + + VStack(spacing: 13.3) { + if isShowCreatorMenu { + HStack(spacing: 13.3) { + Image("ic_make_message") + + Text("수정") + .font(.custom(Font.medium.rawValue, size: 16.7)) + .foregroundColor(.white) + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 26.7) + .contentShape(Rectangle()) + .onTapGesture { + isShowing = false + modifyAction() + } + + HStack(spacing: 13.3) { + Image("ic_trash_can") + + Text("삭제") + .font(.custom(Font.medium.rawValue, size: 16.7)) + .foregroundColor(.white) + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 26.7) + .contentShape(Rectangle()) + .onTapGesture { + isShowing = false + deleteAction() + } + } else { + HStack(spacing: 0) { + Text("신고") + .font(.custom(Font.medium.rawValue, size: 16.7)) + .foregroundColor(.white) + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 26.7) + .contentShape(Rectangle()) + .onTapGesture { + isShowing = false + reportAction() + } + } + } + .padding(24) + .background(Color(hex: "222222")) + .cornerRadius(13.3, corners: [.topLeft, .topRight]) + } + } + } +} + +#Preview { + CreatorCommunityMenuView( + isShowing: .constant(true), + isShowCreatorMenu: true, + modifyAction: {}, + deleteAction: {}, + reportAction: {} + ) +}