콘텐츠 상세 - 콘텐츠 고정/해제 기능 추가
This commit is contained in:
		
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin.imageset/ic_pin.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin.imageset/ic_pin.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "ic_pin_cancel.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/ic_pin_cancel.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/ic_pin_cancel.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_trash_can.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_trash_can.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "ic_trash_can.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_trash_can.imageset/ic_trash_can.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_trash_can.imageset/ic_trash_can.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 787 B | 
| @@ -34,6 +34,8 @@ enum ContentApi { | ||||
|     case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) | ||||
|     case getContentRanking(page: Int, size: Int, sortType: String) | ||||
|     case getContentRankingSortType | ||||
|     case pinContent(contentId: Int) | ||||
|     case unpinContent(contentId: Int) | ||||
| } | ||||
|  | ||||
| extension ContentApi: TargetType { | ||||
| @@ -117,6 +119,12 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .getContentRankingSortType: | ||||
|             return  "/audio-content/ranking-sort-type" | ||||
|              | ||||
|         case .pinContent(let contentId): | ||||
|             return  "/audio-content/pin-to-the-top/\(contentId)" | ||||
|              | ||||
|         case .unpinContent(let contentId): | ||||
|             return  "/audio-content/unpin-at-the-top/\(contentId)" | ||||
|         } | ||||
|     } | ||||
|      | ||||
| @@ -131,10 +139,10 @@ extension ContentApi: TargetType { | ||||
|         case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList: | ||||
|             return .get | ||||
|              | ||||
|         case .likeContent, .modifyAudioContent, .modifyComment: | ||||
|         case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: | ||||
|             return .put | ||||
|              | ||||
|         case .registerComment, .orderAudioContent, .addAllPlaybackTracking, .uploadAudioContent, .donation: | ||||
|         case .registerComment, .orderAudioContent, .addAllPlaybackTracking, .uploadAudioContent, .donation, .pinContent: | ||||
|             return .post | ||||
|              | ||||
|         case .deleteAudioContent: | ||||
| @@ -259,6 +267,9 @@ extension ContentApi: TargetType { | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .pinContent, .unpinContent: | ||||
|             return .requestPlain | ||||
|         } | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -112,4 +112,12 @@ final class ContentRepository { | ||||
|     func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getContentRanking(page: page, size: size, sortType: sortType)) | ||||
|     } | ||||
|      | ||||
|     func pinContent(contentId: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.pinContent(contentId: contentId)) | ||||
|     } | ||||
|      | ||||
|     func unpinContent(contentId: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.unpinContent(contentId: contentId)) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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: { | ||||
|   | ||||
| @@ -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: {} | ||||
|     ) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung