인기콘텐츠 전체 보기 페이지 추가
This commit is contained in:
		| @@ -111,4 +111,6 @@ enum AppStep { | ||||
|     case newContentAll | ||||
|      | ||||
|     case curationAll(title: String, curationId: Int) | ||||
|      | ||||
|     case contentRankingAll | ||||
| } | ||||
|   | ||||
							
								
								
									
										153
									
								
								SodaLive/Sources/Content/All/ContentRankingAllView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								SodaLive/Sources/Content/All/ContentRankingAllView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| // | ||||
| //  ContentRankingAllView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/10/15. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import Kingfisher | ||||
|  | ||||
| struct ContentRankingAllView: View { | ||||
|      | ||||
|     @StateObject var viewModel = ContentRankingAllViewModel() | ||||
|      | ||||
|     var body: some View { | ||||
|         BaseView(isLoading: $viewModel.isLoading) { | ||||
|             VStack(spacing: 0) { | ||||
|                 DetailNavigationBar(title: "인기 콘텐츠") | ||||
|                  | ||||
|                 VStack(spacing: 8) { | ||||
|                     Text("\(viewModel.dateString)") | ||||
|                         .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                         .foregroundColor(Color(hex: "eeeeee")) | ||||
|                      | ||||
|                     Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.") | ||||
|                         .font(.custom(Font.light.rawValue, size: 13.3)) | ||||
|                         .foregroundColor(Color(hex: "bbbbbb")) | ||||
|                 } | ||||
|                 .padding(.vertical, 8) | ||||
|                 .frame(width: screenSize().width - 26.7) | ||||
|                 .background(Color(hex: "222222")) | ||||
|                 .padding(.top, 13.3) | ||||
|                  | ||||
|                 ScrollView(.vertical, showsIndicators: false) { | ||||
|                     VStack(spacing: 20) { | ||||
|                         ForEach(0..<viewModel.contentRankingItemList.count, id: \.self) { index in | ||||
|                             let item = viewModel.contentRankingItemList[index] | ||||
|                             HStack(spacing: 0) { | ||||
|                                 KFImage(URL(string: item.coverImageUrl)) | ||||
|                                     .resizable() | ||||
|                                     .scaledToFill() | ||||
|                                     .frame(width: 66.7, height: 66.7, alignment: .top) | ||||
|                                     .clipped() | ||||
|                                     .cornerRadius(5.3) | ||||
|                                  | ||||
|                                 Text("\(index + 1)") | ||||
|                                     .font(.custom(Font.bold.rawValue, size: 16.7)) | ||||
|                                     .foregroundColor(Color(hex: "3bb9f1")) | ||||
|                                     .padding(.horizontal, 12) | ||||
|                                  | ||||
|                                 VStack(alignment: .leading, spacing: 0) { | ||||
|                                     HStack(spacing: 8) { | ||||
|                                         Text(item.themeStr) | ||||
|                                             .font(.custom(Font.medium.rawValue, size: 8)) | ||||
|                                             .foregroundColor(Color(hex: "3bac6a")) | ||||
|                                             .padding(2.6) | ||||
|                                             .background(Color(hex: "28312b")) | ||||
|                                             .cornerRadius(2.6) | ||||
|                                          | ||||
|                                         Text(item.duration) | ||||
|                                             .font(.custom(Font.medium.rawValue, size: 8)) | ||||
|                                             .foregroundColor(Color(hex: "777777")) | ||||
|                                             .padding(2.6) | ||||
|                                             .background(Color(hex: "222222")) | ||||
|                                             .cornerRadius(2.6) | ||||
|                                     } | ||||
|                                      | ||||
|                                     Text(item.creatorNickname) | ||||
|                                         .font(.custom(Font.medium.rawValue, size: 10.7)) | ||||
|                                         .foregroundColor(Color(hex: "777777")) | ||||
|                                         .padding(.vertical, 8) | ||||
|                                      | ||||
|                                     Text(item.title) | ||||
|                                         .font(.custom(Font.medium.rawValue, size: 12)) | ||||
|                                         .foregroundColor(Color(hex: "d2d2d2")) | ||||
|                                         .lineLimit(2) | ||||
|                                         .padding(.top, 2.7) | ||||
|                                 } | ||||
|                                  | ||||
|                                 Spacer() | ||||
|                                  | ||||
|                                 if item.price > 0 { | ||||
|                                     HStack(spacing: 8) { | ||||
|                                         Image("ic_can") | ||||
|                                             .resizable() | ||||
|                                             .frame(width: 17, height: 17) | ||||
|                                          | ||||
|                                         Text("\(item.price)") | ||||
|                                             .font(.custom(Font.medium.rawValue, size: 12)) | ||||
|                                             .foregroundColor(Color(hex: "909090")) | ||||
|                                     } | ||||
|                                 } else { | ||||
|                                     Text("무료") | ||||
|                                         .font(.custom(Font.medium.rawValue, size: 12)) | ||||
|                                         .foregroundColor(Color(hex: "ffffff")) | ||||
|                                         .padding(.horizontal, 5.3) | ||||
|                                         .padding(.vertical, 2.7) | ||||
|                                         .background(Color(hex: "cf5c37")) | ||||
|                                         .cornerRadius(2.6) | ||||
|                                 } | ||||
|                             } | ||||
|                             .frame(height: 66.7) | ||||
|                             .contentShape(Rectangle()) | ||||
|                             .onTapGesture { | ||||
|                                 AppState | ||||
|                                     .shared | ||||
|                                     .setAppStep(step: .contentDetail(contentId: item.contentId)) | ||||
|                             } | ||||
|                             .onAppear { | ||||
|                                 if index == viewModel.contentRankingItemList.count - 1 { | ||||
|                                     viewModel.getContentRanking() | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 .padding(13.3) | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|                 LoadingView() | ||||
|             } | ||||
|         } | ||||
|         .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { | ||||
|             GeometryReader { geo in | ||||
|                 HStack { | ||||
|                     Spacer() | ||||
|                     Text(viewModel.errorMessage) | ||||
|                         .padding(.vertical, 13.3) | ||||
|                         .padding(.horizontal, 6.7) | ||||
|                         .frame(width: geo.size.width - 66.7, alignment: .center) | ||||
|                         .font(.custom(Font.medium.rawValue, size: 12)) | ||||
|                         .background(Color(hex: "9970ff")) | ||||
|                         .foregroundColor(Color.white) | ||||
|                         .multilineTextAlignment(.leading) | ||||
|                         .fixedSize(horizontal: false, vertical: true) | ||||
|                         .cornerRadius(20) | ||||
|                         .padding(.top, 66.7) | ||||
|                     Spacer() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         .onAppear { | ||||
|             viewModel.getContentRanking() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentRankingAllView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentRankingAllView() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,79 @@ | ||||
| // | ||||
| //  ContentRankingAllViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/10/15. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentRankingAllViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var dateString = "" | ||||
|     @Published var contentRankingItemList = [GetAudioContentRankingItem]() | ||||
|      | ||||
|     var page = 1 | ||||
|     var isLast = false | ||||
|     private let pageSize = 10 | ||||
|      | ||||
|     func getContentRanking() { | ||||
|         if (!isLast && !isLoading && page <= 5) { | ||||
|             isLoading = true | ||||
|              | ||||
|             repository.getContentRanking(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 | ||||
|                     self.isLoading = false | ||||
|                     let responseData = response.data | ||||
|                      | ||||
|                     do { | ||||
|                         let jsonDecoder = JSONDecoder() | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<GetAudioContentRanking>.self, from: responseData) | ||||
|                         self.isLoading = false | ||||
|                          | ||||
|                         if let data = decoded.data, decoded.success { | ||||
|                             if page == 1 { | ||||
|                                 contentRankingItemList.removeAll() | ||||
|                             } | ||||
|                              | ||||
|                             dateString = "\(data.startDate)~\(data.endDate)" | ||||
|                              | ||||
|                             if !data.items.isEmpty { | ||||
|                                 page += 1 | ||||
|                                 self.contentRankingItemList.append(contentsOf: data.items) | ||||
|                             } 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -29,6 +29,7 @@ enum ContentApi { | ||||
|     case getNewContentThemeList | ||||
|     case getNewContentAllOfTheme(theme: String, page: Int, size: Int) | ||||
|     case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) | ||||
|     case getContentRanking(page: Int, size: Int) | ||||
| } | ||||
|  | ||||
| extension ContentApi: TargetType { | ||||
| @@ -97,12 +98,17 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .getAudioContentListByCurationId(let curationId, _, _, _): | ||||
|             return "/audio-content/curation/\(curationId)" | ||||
|              | ||||
|         case .getContentRanking: | ||||
|             return  "/audio-content/ranking" | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var method: Moya.Method { | ||||
|         switch self { | ||||
|         case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme, .getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId: | ||||
|         case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, | ||||
|                 .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme, | ||||
|                 .getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId, .getContentRanking: | ||||
|             return .get | ||||
|              | ||||
|         case .likeContent, .modifyAudioContent, .modifyComment: | ||||
| @@ -212,6 +218,14 @@ extension ContentApi: TargetType { | ||||
|                 "sort-type": sort | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getContentRanking(let page, let size): | ||||
|             let parameters = [ | ||||
|                 "page": page - 1, | ||||
|                 "size": size, | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -92,4 +92,8 @@ final class ContentRepository { | ||||
|     func getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getAudioContentListByCurationId(curationId: curationId, page: page, size: size, sort: sort)) | ||||
|     } | ||||
|      | ||||
|     func getContentRanking(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getContentRanking(page: page, size: size)) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,9 @@ struct ContentMainRankingView: View { | ||||
|                 Spacer() | ||||
|                  | ||||
|                 Image("ic_forward") | ||||
|                     .onTapGesture {} | ||||
|                     .onTapGesture { | ||||
|                         AppState.shared.setAppStep(step: .contentRankingAll) | ||||
|                     } | ||||
|             } | ||||
|              | ||||
|             VStack(spacing: 8) { | ||||
| @@ -72,6 +74,7 @@ struct ContentMainRankingView: View { | ||||
|                             } | ||||
|                         } | ||||
|                         .frame(maxWidth: screenSize().width * 0.66, alignment: .leading) | ||||
|                         .contentShape(Rectangle()) | ||||
|                         .onTapGesture { | ||||
|                             AppState | ||||
|                                 .shared | ||||
|   | ||||
| @@ -163,6 +163,9 @@ struct ContentView: View { | ||||
|             case .curationAll(let title, let curationId): | ||||
|                 ContentCurationView(title: title, curationId: curationId) | ||||
|                  | ||||
|             case .contentRankingAll: | ||||
|                 ContentRankingAllView() | ||||
|                  | ||||
|             default: | ||||
|                 EmptyView() | ||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung