콘텐츠 메인
- 무료 탭 UI 페이지 생성
This commit is contained in:
		| @@ -59,6 +59,11 @@ enum ContentApi { | ||||
|      | ||||
|     case getContentMainReplay | ||||
|     case getPopularReplayContentByCreator(creatorId: Int) | ||||
|      | ||||
|     case getContentMainFree | ||||
|     case getIntroduceCreatorList(page: Int, size: Int) | ||||
|     case getNewFreeContentOfTheme(theme: String, page: Int, size: Int) | ||||
|     case getPopularFreeContentByCreator(creatorId: Int) | ||||
| } | ||||
|  | ||||
| extension ContentApi: TargetType { | ||||
| @@ -199,6 +204,18 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .getPopularReplayContentByCreator: | ||||
|             return "/v2/audio-content/main/replay/popular-content-by-creator" | ||||
|              | ||||
|         case .getContentMainFree: | ||||
|             return "/v2/audio-content/main/free" | ||||
|              | ||||
|         case .getIntroduceCreatorList: | ||||
|             return "/v2/audio-content/main/free/introduce-creator" | ||||
|              | ||||
|         case .getNewFreeContentOfTheme: | ||||
|             return "/v2/audio-content/main/free/new-content-by-theme" | ||||
|              | ||||
|         case .getPopularFreeContentByCreator: | ||||
|             return "/v2/audio-content/main/free/popular-content-by-creator" | ||||
|         } | ||||
|     } | ||||
|      | ||||
| @@ -215,7 +232,8 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator, .getContentMainContent, | ||||
|                 .getContentMainNewContentOfTheme, .getDailyContentRanking, .getRecommendContentByTag, .getContentMainAlarm, .getContentMainAlarmAll, | ||||
|                 .getContentMainAsmr, .getPopularAsmrContentByCreator, .getContentMainReplay, .getPopularReplayContentByCreator: | ||||
|                 .getContentMainAsmr, .getPopularAsmrContentByCreator, .getContentMainReplay, .getPopularReplayContentByCreator, | ||||
|                 .getContentMainFree, .getIntroduceCreatorList, .getNewFreeContentOfTheme, .getPopularFreeContentByCreator: | ||||
|             return .get | ||||
|              | ||||
|         case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: | ||||
| @@ -372,7 +390,8 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getContentMainHome, .getContentMainSeries, .getContentMainContent, .getContentMainAlarm, .getContentMainAsmr, .getContentMainReplay: | ||||
|         case .getContentMainHome, .getContentMainSeries, .getContentMainContent,  | ||||
|                 .getContentMainAlarm, .getContentMainAsmr, .getContentMainReplay, .getContentMainFree: | ||||
|             return .requestPlain | ||||
|              | ||||
|         case .getRecommendSeriesListByGenre(let genreId): | ||||
| @@ -403,16 +422,19 @@ extension ContentApi: TargetType { | ||||
|             let parameters = ["tag": tag] | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getContentMainAlarmAll(let theme, let page, let size): | ||||
|         case .getContentMainAlarmAll(let theme, let page, let size), .getNewFreeContentOfTheme(let theme, let page, let size): | ||||
|             let parameters = [ | ||||
|                 "theme": theme, | ||||
|                 "page": page - 1, | ||||
|                 "size": size | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getPopularAsmrContentByCreator(let creatorId), .getPopularReplayContentByCreator(let creatorId): | ||||
|         case .getIntroduceCreatorList(let page, let size): | ||||
|             let parameters = ["page": page - 1, "size": size] | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getPopularAsmrContentByCreator(let creatorId), .getPopularReplayContentByCreator(let creatorId), .getPopularFreeContentByCreator(let creatorId): | ||||
|             let parameters = ["creatorId": creatorId] | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|         } | ||||
|   | ||||
| @@ -0,0 +1,32 @@ | ||||
| // | ||||
| //  ContentMainTabFreeRepository.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/22/25. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import CombineMoya | ||||
| import Combine | ||||
| import Moya | ||||
|  | ||||
| final class ContentMainTabFreeRepository { | ||||
|      | ||||
|     private let api = MoyaProvider<ContentApi>() | ||||
|      | ||||
|     func getContentMainFree() -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getContentMainFree) | ||||
|     } | ||||
|      | ||||
|     func getIntroduceCreatorList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getIntroduceCreatorList(page: page, size: size)) | ||||
|     } | ||||
|      | ||||
|     func getNewContentOfTheme(theme: String, page: Int = 1, size: Int = 20) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getNewFreeContentOfTheme(theme: theme, page: page, size: size)) | ||||
|     } | ||||
|      | ||||
|     func getPopularContentByCreator(creatorId: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getPopularFreeContentByCreator(creatorId: creatorId)) | ||||
|     } | ||||
| } | ||||
| @@ -15,7 +15,76 @@ struct ContentMainTabFreeView: View { | ||||
|         BaseView(isLoading: $viewModel.isLoading) { | ||||
|             ScrollView(.vertical, showsIndicators: false) { | ||||
|                 VStack(spacing: 0) { | ||||
|                     if !viewModel.bannerList.isEmpty { | ||||
|                         ContentMainBannerViewV2(bannerList: viewModel.bannerList) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                     } | ||||
|                      | ||||
|                     if let introduceCreator = viewModel.introduceCreator { | ||||
|                         ContentMainNewContentViewV2( | ||||
|                             title: introduceCreator.title, | ||||
|                             onClickMore: {}, | ||||
|                             themeList: [], | ||||
|                             contentList: introduceCreator.items | ||||
|                         ) { _ in } | ||||
|                         .padding(.top, 30) | ||||
|                     } | ||||
|                      | ||||
|                     if !viewModel.recommendSeriesList.isEmpty { | ||||
|                         ContentMainNewOrRecommendSeriesView( | ||||
|                             title: "추천 무료 시리즈", | ||||
|                             recommendSeriesList: viewModel.recommendSeriesList | ||||
|                         ) | ||||
|                         .padding(.top, 30) | ||||
|                     } | ||||
|                      | ||||
|                     if !viewModel.themeList.isEmpty { | ||||
|                         ContentMainNewContentViewV2( | ||||
|                             title: "새로운 무료 콘텐츠", | ||||
|                             onClickMore: {}, | ||||
|                             themeList: viewModel.themeList, | ||||
|                             contentList: viewModel.newFreeContentList | ||||
|                         ) { | ||||
|                             viewModel.getNewContentOfTheme(theme: $0) | ||||
|                         } | ||||
|                         .padding(.top, 30) | ||||
|                     } | ||||
|                      | ||||
|                     if !viewModel.creatorList.isEmpty { | ||||
|                         ContentByChannelView( | ||||
|                             title: "채널별 추천 무료 콘텐츠", | ||||
|                             creatorList: viewModel.creatorList, | ||||
|                             contentList: viewModel.playCountRankContentList, | ||||
|                             onClickCreator: { | ||||
|                                 viewModel.getPopularContentByCreator(creatorId: $0) | ||||
|                             } | ||||
|                         ) | ||||
|                         .padding(.top, 30) | ||||
|                     } | ||||
|                      | ||||
|                     if !viewModel.curationList.isEmpty { | ||||
|                         ContentMainCurationViewV2(curationList: viewModel.curationList) | ||||
|                         .padding(.top, 30) | ||||
|                     } | ||||
|                 } | ||||
|                 .onAppear { | ||||
|                     viewModel.fetchData() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) { | ||||
|             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.button) | ||||
|                     .foregroundColor(Color.white) | ||||
|                     .multilineTextAlignment(.leading) | ||||
|                     .cornerRadius(20) | ||||
|                     .padding(.bottom, 66.7) | ||||
|                 Spacer() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -6,9 +6,143 @@ | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainTabFreeViewModel: ObservableObject { | ||||
|     private let repository = ContentMainTabFreeRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var bannerList: [GetAudioContentBannerResponse] = [] | ||||
|     @Published var introduceCreator: GetContentCurationResponse? = nil | ||||
|     @Published var recommendSeriesList: [GetRecommendSeriesListResponse] = [] | ||||
|     @Published var themeList: [String] = [] | ||||
|     @Published var newFreeContentList: [GetAudioContentMainItem] = [] | ||||
|     @Published var creatorList: [ContentCreatorResponse] = [] | ||||
|     @Published var playCountRankContentList: [GetAudioContentRankingItem] = [] | ||||
|     @Published var curationList: [GetContentCurationResponse] = [] | ||||
|      | ||||
|     func fetchData() { | ||||
|         isLoading = true | ||||
|         repository.getContentMainFree() | ||||
|             .sink { result in | ||||
|                 switch result { | ||||
|                 case .finished: | ||||
|                     DEBUG_LOG("finish") | ||||
|                 case .failure(let error): | ||||
|                     ERROR_LOG(error.localizedDescription) | ||||
|                 } | ||||
|             } receiveValue: { [unowned self] response in | ||||
|                 let responseData = response.data | ||||
|                  | ||||
|                 do { | ||||
|                     let jsonDecoder = JSONDecoder() | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponse<GetContentMainTabFreeResponse>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.bannerList = data.contentBannerList | ||||
|                         self.introduceCreator = data.introduceCreator | ||||
|                         self.recommendSeriesList = data.recommendSeriesList | ||||
|                         self.themeList = data.themeList | ||||
|                         self.newFreeContentList = data.newFreeContentList | ||||
|                         self.creatorList = data.creatorList | ||||
|                         self.playCountRankContentList = data.playCountRankContentList | ||||
|                         self.curationList = data.curationList | ||||
|                     } 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) | ||||
|     } | ||||
|      | ||||
|     func getNewContentOfTheme(theme: String) { | ||||
|         isLoading = true | ||||
|         repository.getNewContentOfTheme(theme: theme) | ||||
|             .sink { result in | ||||
|                 switch result { | ||||
|                 case .finished: | ||||
|                     DEBUG_LOG("finish") | ||||
|                 case .failure(let error): | ||||
|                     ERROR_LOG(error.localizedDescription) | ||||
|                 } | ||||
|             } receiveValue: { [unowned self] response in | ||||
|                 let responseData = response.data | ||||
|                  | ||||
|                 do { | ||||
|                     let jsonDecoder = JSONDecoder() | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponse<[GetAudioContentMainItem]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.newFreeContentList = data | ||||
|                     } 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) | ||||
|     } | ||||
|      | ||||
|     func getPopularContentByCreator(creatorId: Int) { | ||||
|         isLoading = true | ||||
|         repository.getPopularContentByCreator(creatorId: creatorId) | ||||
|             .sink { result in | ||||
|                 switch result { | ||||
|                 case .finished: | ||||
|                     DEBUG_LOG("finish") | ||||
|                 case .failure(let error): | ||||
|                     ERROR_LOG(error.localizedDescription) | ||||
|                 } | ||||
|             } receiveValue: { [unowned self] response in | ||||
|                 let responseData = response.data | ||||
|                  | ||||
|                 do { | ||||
|                     let jsonDecoder = JSONDecoder() | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponse<[GetAudioContentRankingItem]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.playCountRankContentList = data | ||||
|                     } 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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,17 @@ | ||||
| // | ||||
| //  GetContentMainTabFreeResponse.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/22/25. | ||||
| // | ||||
|  | ||||
| struct GetContentMainTabFreeResponse: Decodable { | ||||
|     let contentBannerList: [GetAudioContentBannerResponse] | ||||
|     let introduceCreator: GetContentCurationResponse? | ||||
|     let recommendSeriesList: [GetRecommendSeriesListResponse] | ||||
|     let themeList: [String] | ||||
|     let newFreeContentList: [GetAudioContentMainItem] | ||||
|     let creatorList: [ContentCreatorResponse] | ||||
|     let playCountRankContentList: [GetAudioContentRankingItem] | ||||
|     let curationList: [GetContentCurationResponse] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung