From 6bd27c5301b4c68e496a3ade2da61bf74acb0cf4 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Fri, 21 Feb 2025 21:10:16 +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=EC=8B=9C=EB=A6=AC=EC=A6=88=20=ED=83=AD=20UI=20?= =?UTF-8?q?=ED=8E=98=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/App/AppStep.swift | 2 + SodaLive/Sources/Content/ContentApi.swift | 26 ++- .../Main/V2/ContentMainNoItemView.swift | 34 ++++ .../Content/Main/V2/ContentMainViewV2.swift | 123 +++++++++++++- .../Main/V2/Home/ContentMainTabHomeView.swift | 64 ++++++- .../ContentMainCompletedSeriesView.swift | 83 +++++++++ .../ContentMainNewOrRecommendSeriesView.swift | 115 +++++++++++++ .../Series/ContentMainSeriesByGenreView.swift | 102 ++++++++++- .../ContentMainSeriesCurationView.swift | 158 ++++++++++++++++++ .../Series/ContentMainSeriesGenreView.swift | 44 ++++- .../Series/ContentMainSeriesRankingView.swift | 141 ++++++++++++++++ .../ContentMainTabSeriesRepository.swift | 19 +++ .../V2/Series/ContentMainTabSeriesView.swift | 80 ++++++++- .../ContentMainTabSeriesViewModel.swift | 149 +++++++++++++++++ .../GetContentMainTabSeriesResponse.swift | 33 +++- ...ontentMainOriginalAudioDramaItemView.swift | 94 ++++++++++- .../ContentMainOriginalAudioDramaView.swift | 68 +++++++- .../Main/V2/Series/SeriesByChannelView.swift | 123 ++++++++++++++ SodaLive/Sources/ContentView.swift | 3 + 19 files changed, 1439 insertions(+), 22 deletions(-) create mode 100644 SodaLive/Sources/Content/Main/V2/ContentMainNoItemView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Series/ContentMainCompletedSeriesView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Series/ContentMainNewOrRecommendSeriesView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Series/ContentMainSeriesCurationView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Series/ContentMainSeriesRankingView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Series/SeriesByChannelView.swift diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index eea785d..2633128 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -141,4 +141,6 @@ enum AppStep { case auditionRoleDetail(roleId: Int, auditionTitle: String) case searchChannel + + case contentMain(startTab: ContentMainTab) } diff --git a/SodaLive/Sources/Content/ContentApi.swift b/SodaLive/Sources/Content/ContentApi.swift index 3d8c534..911cfd3 100644 --- a/SodaLive/Sources/Content/ContentApi.swift +++ b/SodaLive/Sources/Content/ContentApi.swift @@ -38,8 +38,13 @@ enum ContentApi { case unpinContent(contentId: Int) case getAudioContentByTheme(themeId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort) case generateUrl(contentId: Int) + case getContentMainHome case getPopularContentByCreator(creatorId: Int) + + case getContentMainSeries + case getRecommendSeriesListByGenre(genreId: Int) + case getRecommendSeriesByCreator(creatorId: Int) } extension ContentApi: TargetType { @@ -141,6 +146,15 @@ extension ContentApi: TargetType { case .getPopularContentByCreator: return "/v2/audio-content/main/home/popular-content-by-creator" + + case .getContentMainSeries: + return "/v2/audio-content/main/series" + + case .getRecommendSeriesListByGenre: + return "/v2/audio-content/main/series/recommend-by-genre" + + case .getRecommendSeriesByCreator: + return "/v2/audio-content/main/series/recommend-series-by-creator" } } @@ -155,7 +169,7 @@ extension ContentApi: TargetType { case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList, .getAudioContentByTheme: return .get - case .getContentMainHome, .getPopularContentByCreator: + case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator: return .get case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: @@ -312,12 +326,20 @@ extension ContentApi: TargetType { return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) - case .getContentMainHome: + case .getContentMainHome, .getContentMainSeries: return .requestPlain + case .getRecommendSeriesListByGenre(let genreId): + let parameters = ["genreId": genreId] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + case .getPopularContentByCreator(let creatorId): let parameters = ["creatorId": creatorId] return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + + case .getRecommendSeriesByCreator(let creatorId): + let parameters = ["creatorId": creatorId] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/Content/Main/V2/ContentMainNoItemView.swift b/SodaLive/Sources/Content/Main/V2/ContentMainNoItemView.swift new file mode 100644 index 0000000..c5615a4 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/ContentMainNoItemView.swift @@ -0,0 +1,34 @@ +// +// ContentMainNoItemView.swift +// SodaLive +// +// Created by klaus on 2/21/25. +// + +import SwiftUI + +struct ContentMainNoItemView: View { + var body: some View { + VStack(spacing: 0) { + Image("ic_no_item") + .resizable() + .frame(width: 60, height: 60) + + Text("마이페이지에서 본인인증을 해주세요") + .font(.custom(Font.medium.rawValue, size: 13)) + .foregroundColor(.graybb) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.center) + .lineSpacing(8) + .padding(.vertical, 8) + } + .padding(.vertical, 16.7) + .frame(maxWidth: .infinity) + .background(Color.bg) + .cornerRadius(4.7) + } +} + +#Preview { + ContentMainNoItemView() +} diff --git a/SodaLive/Sources/Content/Main/V2/ContentMainViewV2.swift b/SodaLive/Sources/Content/Main/V2/ContentMainViewV2.swift index 5612969..60285c6 100644 --- a/SodaLive/Sources/Content/Main/V2/ContentMainViewV2.swift +++ b/SodaLive/Sources/Content/Main/V2/ContentMainViewV2.swift @@ -7,12 +7,131 @@ import SwiftUI +enum ContentMainTab { + case HOME + case SERIES + case CONTENT + case ALARM + case ASMR + case REPLAY + case FREE +} + +struct TabItem { + let title: String + let tab: ContentMainTab +} + struct ContentMainViewV2: View { + + @State private var selectedTab: ContentMainTab = .SERIES + + let tabItemList = [ + TabItem(title: "홈", tab: .HOME), + TabItem(title: "시리즈", tab: .SERIES), + TabItem(title: "단편", tab: .CONTENT), + TabItem(title: "모닝콜", tab: .ALARM), + TabItem(title: "ASMR", tab: .ASMR), + TabItem(title: "다시듣기", tab: .REPLAY), + TabItem(title: "무료", tab: .FREE) + ] + + init(selectedTab: ContentMainTab = .SERIES) { + self._selectedTab = State(initialValue: selectedTab) + } + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + ZStack { + + Color.black.ignoresSafeArea() + + VStack(spacing: 0) { + HStack(spacing: 0) { + Text("콘텐츠 마켓") + .font(.custom(Font.bold.rawValue, size: 21.3)) + .foregroundColor(Color.button) + + Spacer() + + Image("ic_content_keep") + .onTapGesture { + AppState.shared.setAppStep(step: .myBox(currentTab: .orderlist)) + } + } + .padding(.horizontal, 13.3) + + ScrollViewReader { proxy in + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(0.. Void + + var body: some View { + VStack(spacing: 13.3) { + HStack(spacing: 0) { + Text("완결 시리즈") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(.grayee) + + Spacer() + + Image("ic_forward") + .onTapGesture { + onClickMore() + } + } + .padding(.horizontal, 13.3) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 13.3) { + ForEach(0.. Void + + @State private var selectedGenreId = 0 + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack(alignment: .leading, spacing: 13.3) { + Text("장르별 추천 시리즈") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color.grayee) + .padding(.horizontal, 13.3) + + ContentMainSeriesGenreView( + genreList: genreList, + selectGenre: { + selectedGenreId = $0 + onClickGenre($0) + }, + selectedGenreId: $selectedGenreId + ) + + if !itemList.isEmpty { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 13.3) { + ForEach(0.. Void + + @Binding var selectedGenreId: Int + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: 8) { + ForEach(0..() + + func getContentMainSeries() -> AnyPublisher { + return api.requestPublisher(.getContentMainSeries) + } + + func getRecommendSeriesListByGenre(genreId: Int) -> AnyPublisher { + return api.requestPublisher(.getRecommendSeriesListByGenre(genreId: genreId)) + } + + func getRecommendSeriesByCreator(creatorId: Int) -> AnyPublisher { + return api.requestPublisher(.getRecommendSeriesByCreator(creatorId: creatorId)) + } +} diff --git a/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesView.swift b/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesView.swift index bf69020..1df8604 100644 --- a/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesView.swift +++ b/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesView.swift @@ -8,8 +8,86 @@ import SwiftUI struct ContentMainTabSeriesView: View { + + @StateObject var viewModel = ContentMainTabSeriesViewModel() + 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.originalAudioDramaList.isEmpty { + ContentMainOriginalAudioDramaView(itemList: viewModel.originalAudioDramaList) { + } + .padding(.top, 30) + } + + if !viewModel.rankSeriesList.isEmpty { + ContentMainSeriesRankingView(seriesList: viewModel.rankSeriesList) + .padding(.top, 30) + } + + if !viewModel.genreList.isEmpty { + ContentMainSeriesByGenreView( + genreList: viewModel.genreList, + itemList: viewModel.recommendSeriesList + ) { + viewModel.getRecommendSeriesListByGenre(genreId: $0) + } + .padding(.top, 30) + } + + if !viewModel.newSeriesList.isEmpty { + ContentMainNewOrRecommendSeriesView( + title: "새로운 시리즈", + recommendSeriesList: viewModel.newSeriesList + ) + .padding(.top, 30) + } + + if !viewModel.rankCompleteSeriesList.isEmpty { + ContentMainCompletedSeriesView( + itemList: viewModel.rankCompleteSeriesList, + onClickMore: {} + ) + .padding(.top, 30) + } + + if !viewModel.seriesRankCreatorList.isEmpty { + SeriesByChannelView( + title: "채널별 추천 시리즈", + creatorList: viewModel.seriesRankCreatorList, + seriesList: viewModel.recommendSeriesByChannel + ) { + viewModel.getRecommendSeriesByCreator(creatorId: $0) + } + .padding(.top, 30) + } + + if !viewModel.eventBannerList.isEmpty { + SectionEventBannerView(items: viewModel.eventBannerList) + .frame( + width: viewModel.eventBannerList.count > 0 ? screenSize().width : 0, + height: viewModel.eventBannerList.count > 0 ? screenSize().width * 300 / 1000 : 0, + alignment: .center + ) + .padding(.top, 30) + } + + if !viewModel.curationList.isEmpty { + ContentMainSeriesCurationView(curationList: viewModel.curationList) + .padding(.top, 30) + } + } + .onAppear { + viewModel.fetchData() + } + } + } } } diff --git a/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesViewModel.swift b/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesViewModel.swift index 4c0fce4..b91ab7e 100644 --- a/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesViewModel.swift +++ b/SodaLive/Sources/Content/Main/V2/Series/ContentMainTabSeriesViewModel.swift @@ -6,3 +6,152 @@ // import Foundation +import Combine + +final class ContentMainTabSeriesViewModel: ObservableObject { + private let repository = ContentMainTabSeriesRepository() + private let contentRepository = ContentRepository() + private var subscription = Set() + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + + @Published var bannerList: [GetAudioContentBannerResponse] = [] + @Published var originalAudioDramaList: [SeriesListItem] = [] + @Published var rankSeriesList: [SeriesListItem] = [] + @Published var genreList: [GetSeriesGenreListResponse] = [] + @Published var recommendSeriesList: [SeriesListItem] = [] + @Published var newSeriesList: [GetRecommendSeriesListResponse] = [] + @Published var rankCompleteSeriesList: [SeriesListItem] = [] + @Published var seriesRankCreatorList: [ContentCreatorResponse] = [] + @Published var recommendSeriesByChannel: [SeriesListItem] = [] + @Published var eventBannerList: [EventItem] = [] + @Published var curationList: [GetSeriesCurationResponse] = [] + + func fetchData() { + isLoading = true + + repository.getContentMainSeries() + .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.originalAudioDramaList = data.originalAudioDrama + self.rankSeriesList = data.rankSeriesList + self.genreList = data.genreList + self.recommendSeriesList = data.recommendSeriesList + self.newSeriesList = data.newSeriesList + self.rankCompleteSeriesList = data.rankCompleteSeriesList + self.seriesRankCreatorList = data.seriesRankCreatorList + self.recommendSeriesByChannel = data.recommendSeriesByChannel + 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 getRecommendSeriesListByGenre(genreId: Int) { + isLoading = true + repository.getRecommendSeriesListByGenre(genreId: genreId) + .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<[SeriesListItem]>.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.recommendSeriesList = 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 getRecommendSeriesByCreator(creatorId: Int) { + seriesRankCreatorList = [] + isLoading = true + repository.getRecommendSeriesByCreator(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<[SeriesListItem]>.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.recommendSeriesByChannel = 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/Series/GetContentMainTabSeriesResponse.swift b/SodaLive/Sources/Content/Main/V2/Series/GetContentMainTabSeriesResponse.swift index fc8129a..85e3874 100644 --- a/SodaLive/Sources/Content/Main/V2/Series/GetContentMainTabSeriesResponse.swift +++ b/SodaLive/Sources/Content/Main/V2/Series/GetContentMainTabSeriesResponse.swift @@ -5,4 +5,35 @@ // Created by klaus on 2/20/25. // -import Foundation +struct GetContentMainTabSeriesResponse: Decodable { + let contentBannerList: [GetAudioContentBannerResponse] + let originalAudioDrama: [SeriesListItem] + let rankSeriesList: [SeriesListItem] + let genreList: [GetSeriesGenreListResponse] + let recommendSeriesList: [SeriesListItem] + let newSeriesList: [GetRecommendSeriesListResponse] + let rankCompleteSeriesList: [SeriesListItem] + let seriesRankCreatorList: [ContentCreatorResponse] + let recommendSeriesByChannel: [SeriesListItem] + let eventBannerList: GetEventResponse + let curationList: [GetSeriesCurationResponse] +} + +struct GetSeriesGenreListResponse: Decodable { + let id: Int + let genre: String +} + +struct GetRecommendSeriesListResponse: Decodable { + let seriesId: Int + let title: String + let imageUrl: String + let creatorId: Int + let creatorNickname: String + let creatorProfileImageUrl: String +} + +struct GetSeriesCurationResponse: Decodable { + let title: String + let items: [SeriesListItem] +} diff --git a/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaItemView.swift b/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaItemView.swift index cc31892..02aace6 100644 --- a/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaItemView.swift +++ b/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaItemView.swift @@ -6,13 +6,103 @@ // import SwiftUI +import Kingfisher struct ContentMainOriginalAudioDramaItemView: View { + + let itemWidth: CGFloat + let item: SeriesListItem + let isAll: Bool + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack(alignment: .leading, spacing: 8) { + ZStack { + KFImage(URL(string: item.coverImage)) + .cancelOnDisappear(true) + .downsampling( + size: CGSize( + width: itemWidth, + height: (itemWidth * 636) / 450 + ) + ) + .resizable() + .scaledToFill() + .frame(width: itemWidth, height: (itemWidth * 636) / 450, alignment: .center) + .cornerRadius(5) + .clipped() + .onTapGesture { + AppState.shared + .setAppStep(step: .seriesDetail(seriesId: item.seriesId)) + } + + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 3.3) { + if !item.isComplete && item.isNew { + SeriesItemBadgeView(title: "신작", backgroundColor: .button) + } + + if item.isComplete { + SeriesItemBadgeView(title: "완결", backgroundColor: Color(hex: "002abd")) + } + + if item.isPopular { + SeriesItemBadgeView(title: "인기", backgroundColor: Color(hex: "ec6033")) + } + + Spacer() + + if !isAll { + SeriesItemBadgeView(title: "총 \(item.numberOfContent)화", backgroundColor: Color.gray33.opacity(0.7)) + } + } + + Spacer() + + HStack { + Spacer() + + if isAll { + SeriesItemBadgeView(title: "총 \(item.numberOfContent)화", backgroundColor: Color.gray33.opacity(0.7)) + } + } + } + .padding(3.3) + } + .frame(width: itemWidth, height: (itemWidth * 636) / 450, alignment: .center) + + Text(item.title) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.grayee) + .lineLimit(1) + + if isAll { + Text(item.publishedDaysOfWeek) + .font(.custom(Font.medium.rawValue, size: 11)) + .foregroundColor(Color.gray77) + } + } + .frame(width: itemWidth) } } #Preview { - ContentMainOriginalAudioDramaItemView() + ContentMainOriginalAudioDramaItemView( + itemWidth: 150, + item: SeriesListItem( + seriesId: 1, + title: "제목, 관심사,프로필+방장, 참여인원(어딘가..)", + coverImage: "https://test-cf.sodalive.net/profile/default-profile.png", + publishedDaysOfWeek: "매주 수, 토요일", + isComplete: true, + creator: SeriesListItemCreator( + creatorId: 1, + nickname: "creator", + profileImage: "https://test-cf.sodalive.net/profile/default-profile.png" + ), + numberOfContent: 10, + isNew: false, + isPopular: true + ), + isAll: false + ) } diff --git a/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaView.swift b/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaView.swift index 7a08376..f0e744c 100644 --- a/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaView.swift +++ b/SodaLive/Sources/Content/Main/V2/Series/OriginalAudioDrama/ContentMainOriginalAudioDramaView.swift @@ -8,11 +8,75 @@ import SwiftUI struct ContentMainOriginalAudioDramaView: View { + + let itemList: [SeriesListItem] + let onClickMore: () -> Void + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack(spacing: 13.3) { + HStack(spacing: 0) { + Text("오리지널 오디오 드라마") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(.grayee) + + Spacer() + + Image("ic_forward") + .onTapGesture { onClickMore() } + } + .padding(.horizontal, 13.3) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 13.3) { + ForEach(0.. Void + + @State private var selectedCreatorId = 0 + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + Text(title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + .padding(.horizontal, 13.3) + + ScrollView(.horizontal) { + HStack(spacing: 22) { + ForEach(0..