From 4df34b7fb6cd0d9b1a70e2d7f1f24ea53cad4ea1 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Sat, 22 Feb 2025 05:20:55 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=EB=93=A3=EA=B8=B0=20=ED=83=AD=20UI=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/Content/ContentApi.swift | 15 ++- .../Asmr/ContentMainTabAsmrRepository.swift | 2 +- .../V2/Asmr/ContentMainTabAsmrViewModel.swift | 36 +++++++- .../ContentMainTabReplayRepository.swift | 24 +++++ .../V2/Replay/ContentMainTabReplayView.swift | 64 ++++++++++++- .../ContentMainTabReplayViewModel.swift | 92 +++++++++++++++++++ .../GetContentMainTabReplayResponse.swift | 15 +++ 7 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayRepository.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Replay/GetContentMainTabReplayResponse.swift diff --git a/SodaLive/Sources/Content/ContentApi.swift b/SodaLive/Sources/Content/ContentApi.swift index 714306d..8de859f 100644 --- a/SodaLive/Sources/Content/ContentApi.swift +++ b/SodaLive/Sources/Content/ContentApi.swift @@ -56,6 +56,9 @@ enum ContentApi { case getContentMainAsmr case getPopularAsmrContentByCreator(creatorId: Int) + + case getContentMainReplay + case getPopularReplayContentByCreator(creatorId: Int) } extension ContentApi: TargetType { @@ -190,6 +193,12 @@ extension ContentApi: TargetType { case .getPopularAsmrContentByCreator: return "/v2/audio-content/main/asmr/popular-content-by-creator" + + case .getContentMainReplay: + return "/v2/audio-content/main/replay" + + case .getPopularReplayContentByCreator: + return "/v2/audio-content/main/replay/popular-content-by-creator" } } @@ -206,7 +215,7 @@ extension ContentApi: TargetType { case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator, .getContentMainContent, .getContentMainNewContentOfTheme, .getDailyContentRanking, .getRecommendContentByTag, .getContentMainAlarm, .getContentMainAlarmAll, - .getContentMainAsmr, .getPopularAsmrContentByCreator: + .getContentMainAsmr, .getPopularAsmrContentByCreator, .getContentMainReplay, .getPopularReplayContentByCreator: return .get case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: @@ -363,7 +372,7 @@ extension ContentApi: TargetType { return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) - case .getContentMainHome, .getContentMainSeries, .getContentMainContent, .getContentMainAlarm, .getContentMainAsmr: + case .getContentMainHome, .getContentMainSeries, .getContentMainContent, .getContentMainAlarm, .getContentMainAsmr, .getContentMainReplay: return .requestPlain case .getRecommendSeriesListByGenre(let genreId): @@ -403,7 +412,7 @@ extension ContentApi: TargetType { return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) - case .getPopularAsmrContentByCreator(let creatorId): + case .getPopularAsmrContentByCreator(let creatorId), .getPopularReplayContentByCreator(let creatorId): let parameters = ["creatorId": creatorId] return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } diff --git a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift index 3a131e8..c99beec 100644 --- a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift +++ b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift @@ -19,6 +19,6 @@ final class ContentMainTabAsmrRepository { } func getPopularContentByCreator(creatorId: Int) -> AnyPublisher { - return api.requestPublisher(.getPopularContentByCreator(creatorId: creatorId)) + return api.requestPublisher(.getPopularAsmrContentByCreator(creatorId: creatorId)) } } diff --git a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift index 7fe58c9..d5ec319 100644 --- a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift +++ b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift @@ -67,6 +67,40 @@ final class ContentMainTabAsmrViewModel: ObservableObject { } 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.salesCountRankContentList = 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) } } diff --git a/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayRepository.swift b/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayRepository.swift new file mode 100644 index 0000000..0f5f147 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayRepository.swift @@ -0,0 +1,24 @@ +// +// ContentMainTabReplayRepository.swift +// SodaLive +// +// Created by klaus on 2/22/25. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +final class ContentMainTabReplayRepository { + + private let api = MoyaProvider() + + func getContentMainReplay() -> AnyPublisher { + return api.requestPublisher(.getContentMainReplay) + } + + func getPopularContentByCreator(creatorId: Int) -> AnyPublisher { + return api.requestPublisher(.getPopularReplayContentByCreator(creatorId: creatorId)) + } +} diff --git a/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayView.swift b/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayView.swift index c990b9b..1020583 100644 --- a/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayView.swift +++ b/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayView.swift @@ -8,8 +8,70 @@ import SwiftUI struct ContentMainTabReplayView: View { + + @StateObject var viewModel = ContentMainTabReplayViewModel() + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + BaseView(isLoading: $viewModel.isLoading) { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 0) { + if !viewModel.bannerList.isEmpty { + ContentMainBannerViewV2(bannerList: viewModel.bannerList) + .padding(.horizontal, 13.3) + } + + if !viewModel.newReplayContentList.isEmpty { + ContentMainNewContentViewV2( + title: "새로운 라이브 다시듣기", + onClickMore: {}, + themeList: [], + contentList: viewModel.newReplayContentList + ) { _ in } + .padding(.top, 30) + } + + if !viewModel.creatorList.isEmpty { + ContentByChannelView( + title: "채널별 라이브 다시듣기", + creatorList: viewModel.creatorList, + contentList: viewModel.salesCountRankContentList, + onClickCreator: { + viewModel.getPopularContentByCreator(creatorId: $0) + } + ) + .padding(.top, 30) + } + + if !viewModel.eventBannerList.isEmpty { + SectionEventBannerView(items: viewModel.eventBannerList) + .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() + } + } } } diff --git a/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayViewModel.swift b/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayViewModel.swift index 8efd16d..df78626 100644 --- a/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayViewModel.swift +++ b/SodaLive/Sources/Content/Main/V2/Replay/ContentMainTabReplayViewModel.swift @@ -6,9 +6,101 @@ // import Foundation +import Combine final class ContentMainTabReplayViewModel: ObservableObject { + private let repository = ContentMainTabReplayRepository() + private var subscription = Set() + @Published var errorMessage = "" @Published var isShowPopup = false @Published var isLoading = false + + @Published var bannerList: [GetAudioContentBannerResponse] = [] + @Published var newReplayContentList: [GetAudioContentMainItem] = [] + @Published var creatorList: [ContentCreatorResponse] = [] + @Published var salesCountRankContentList: [GetAudioContentRankingItem] = [] + @Published var eventBannerList: [EventItem] = [] + @Published var curationList: [GetContentCurationResponse] = [] + + func fetchData() { + isLoading = true + repository.getContentMainReplay() + .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.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.bannerList = data.contentBannerList + self.newReplayContentList = data.newLiveReplayContentList + self.creatorList = data.creatorList + self.salesCountRankContentList = data.salesCountRankContentList + self.eventBannerList = data.eventBannerList.eventList + 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 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.salesCountRankContentList = 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) + } } diff --git a/SodaLive/Sources/Content/Main/V2/Replay/GetContentMainTabReplayResponse.swift b/SodaLive/Sources/Content/Main/V2/Replay/GetContentMainTabReplayResponse.swift new file mode 100644 index 0000000..3e61883 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Replay/GetContentMainTabReplayResponse.swift @@ -0,0 +1,15 @@ +// +// GetContentMainTabReplayResponse.swift +// SodaLive +// +// Created by klaus on 2/22/25. +// + +struct GetContentMainTabReplayResponse: Decodable { + let contentBannerList: [GetAudioContentBannerResponse] + let newLiveReplayContentList: [GetAudioContentMainItem] + let creatorList: [ContentCreatorResponse] + let salesCountRankContentList: [GetAudioContentRankingItem] + let eventBannerList: GetEventResponse + let curationList: [GetContentCurationResponse] +}