From cb1b26c548f487f3d5dd0a1cda5cdf5102e7dc1f Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Sat, 15 Nov 2025 05:41:14 +0900 Subject: [PATCH] =?UTF-8?q?feat(series-all-by-genre):=20=EC=8B=9C=EB=A6=AC?= =?UTF-8?q?=EC=A6=88=20=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20=EC=9E=A5?= =?UTF-8?q?=EB=A5=B4=EB=B3=84=20=ED=83=AD=20-=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/ByGenre/SeriesMainByGenreView.swift | 30 ++++- .../ByGenre/SeriesMainByGenreViewModel.swift | 116 ++++++++++++++++++ .../Content/Series/Main/SeriesMainApi.swift | 29 ++++- .../Series/Main/SeriesMainRepository.swift | 21 ++++ 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreView.swift b/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreView.swift index 50c2cdf..4ade3c5 100644 --- a/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreView.swift +++ b/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreView.swift @@ -8,8 +8,36 @@ import SwiftUI struct SeriesMainByGenreView: View { + + @StateObject var viewModel = SeriesMainByGenreViewModel() + var body: some View { - Text("시리즈 전체보기 장르별") + ZStack { + VStack(spacing: 16) { + } + .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() + } + } + .onAppear { + viewModel.getGenreList() + } + + if viewModel.isLoading { + LoadingView() + } + } } } diff --git a/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift b/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift index 844fdac..98ee0ed 100644 --- a/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift +++ b/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift @@ -6,3 +6,119 @@ // import Foundation +import Combine + +final class SeriesMainByGenreViewModel: ObservableObject { + private let repository = SeriesMainRepository() + private var subscription = Set() + + @Published var isLoading = false + @Published var errorMessage = "" + @Published var isShowPopup = false + + @Published var genreList: [GetSeriesGenreListResponse] = [] + @Published var seriesList: [SeriesListItem] = [] + + private var page = 1 + private var isLast = false + private let pageSize = 20 + + func onTapGenre(genreId: Int) { + page = 1 + isLast = false + getSeriesListByGenre(genreId: genreId) + } + + func getGenreList() { + if !genreList.isEmpty { + return + } + + isLoading = true + repository.getGenreList() + .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<[GetSeriesGenreListResponse]>.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.genreList = data + self.getSeriesListByGenre(genreId: genreList[0].id) + } 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 getSeriesListByGenre(genreId: Int) { + if !isLast && !isLoading { + isLoading = true + + repository.getSeriesListByGenre(genreId: genreId, 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 + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if let data = decoded.data, decoded.success { + if page == 1 { + self.seriesList.removeAll() + } + + if !data.items.isEmpty { + page += 1 + self.seriesList.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) + } + } +} diff --git a/SodaLive/Sources/Content/Series/Main/SeriesMainApi.swift b/SodaLive/Sources/Content/Series/Main/SeriesMainApi.swift index c23cf34..e911fb8 100644 --- a/SodaLive/Sources/Content/Series/Main/SeriesMainApi.swift +++ b/SodaLive/Sources/Content/Series/Main/SeriesMainApi.swift @@ -12,6 +12,8 @@ enum SeriesMainApi { case fetchHome(isAdultContentVisible: Bool, contentType: ContentType) case getRecommendSeriesList(isAdultContentVisible: Bool, contentType: ContentType) case getDayOfWeekSeriesList(dayOfWeek: SeriesPublishedDaysOfWeek, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) + case getGenreList(isAdultContentVisible: Bool, contentType: ContentType) + case getSeriesListByGenre(genreId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) } extension SeriesMainApi: TargetType { @@ -26,6 +28,12 @@ extension SeriesMainApi: TargetType { case .getDayOfWeekSeriesList: return "/audio-content/series/main/day-of-week" + + case .getGenreList: + return "/audio-content/series/main/genre-list" + + case .getSeriesListByGenre: + return "/audio-content/series/main/list-by-genre" } } @@ -48,7 +56,7 @@ extension SeriesMainApi: TargetType { return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) case .getDayOfWeekSeriesList(let dayOfWeek, let isAdultContentVisible, let contentType, let page, let size): - var parameters = [ + let parameters = [ "dayOfWeek": dayOfWeek, "isAdultContentVisible": isAdultContentVisible, "contentType": contentType, @@ -56,6 +64,25 @@ extension SeriesMainApi: TargetType { "size": size ] as [String : Any] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + + case .getGenreList(let isAdultContentVisible, let contentType): + let parameters = [ + "isAdultContentVisible": isAdultContentVisible, + "contentType": contentType, + ] as [String : Any] + + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + + case .getSeriesListByGenre(let genreId, let isAdultContentVisible, let contentType, let page, let size): + let parameters = [ + "genreId": genreId, + "isAdultContentVisible": isAdultContentVisible, + "contentType": contentType, + "page": page - 1, + "size": size + ] as [String : Any] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/Content/Series/Main/SeriesMainRepository.swift b/SodaLive/Sources/Content/Series/Main/SeriesMainRepository.swift index 4b87985..e541bbb 100644 --- a/SodaLive/Sources/Content/Series/Main/SeriesMainRepository.swift +++ b/SodaLive/Sources/Content/Series/Main/SeriesMainRepository.swift @@ -42,4 +42,25 @@ class SeriesMainRepository { ) ) } + + func getGenreList() -> AnyPublisher { + return api.requestPublisher( + .getGenreList( + isAdultContentVisible: UserDefaults.isAdultContentVisible(), + contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL, + ) + ) + } + + func getSeriesListByGenre(genreId: Int, page: Int, size: Int) -> AnyPublisher { + return api.requestPublisher( + .getSeriesListByGenre( + genreId: genreId, + isAdultContentVisible: UserDefaults.isAdultContentVisible(), + contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL, + page: page, + size: size + ) + ) + } }