parent
cf5d0dc19e
commit
6bd27c5301
|
@ -141,4 +141,6 @@ enum AppStep {
|
||||||
case auditionRoleDetail(roleId: Int, auditionTitle: String)
|
case auditionRoleDetail(roleId: Int, auditionTitle: String)
|
||||||
|
|
||||||
case searchChannel
|
case searchChannel
|
||||||
|
|
||||||
|
case contentMain(startTab: ContentMainTab)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,13 @@ enum ContentApi {
|
||||||
case unpinContent(contentId: Int)
|
case unpinContent(contentId: Int)
|
||||||
case getAudioContentByTheme(themeId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort)
|
case getAudioContentByTheme(themeId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort)
|
||||||
case generateUrl(contentId: Int)
|
case generateUrl(contentId: Int)
|
||||||
|
|
||||||
case getContentMainHome
|
case getContentMainHome
|
||||||
case getPopularContentByCreator(creatorId: Int)
|
case getPopularContentByCreator(creatorId: Int)
|
||||||
|
|
||||||
|
case getContentMainSeries
|
||||||
|
case getRecommendSeriesListByGenre(genreId: Int)
|
||||||
|
case getRecommendSeriesByCreator(creatorId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ContentApi: TargetType {
|
extension ContentApi: TargetType {
|
||||||
|
@ -141,6 +146,15 @@ extension ContentApi: TargetType {
|
||||||
|
|
||||||
case .getPopularContentByCreator:
|
case .getPopularContentByCreator:
|
||||||
return "/v2/audio-content/main/home/popular-content-by-creator"
|
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:
|
case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList, .getAudioContentByTheme:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .getContentMainHome, .getPopularContentByCreator:
|
case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent:
|
case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent:
|
||||||
|
@ -312,12 +326,20 @@ extension ContentApi: TargetType {
|
||||||
|
|
||||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
||||||
case .getContentMainHome:
|
case .getContentMainHome, .getContentMainSeries:
|
||||||
return .requestPlain
|
return .requestPlain
|
||||||
|
|
||||||
|
case .getRecommendSeriesListByGenre(let genreId):
|
||||||
|
let parameters = ["genreId": genreId]
|
||||||
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
||||||
case .getPopularContentByCreator(let creatorId):
|
case .getPopularContentByCreator(let creatorId):
|
||||||
let parameters = ["creatorId": creatorId]
|
let parameters = ["creatorId": creatorId]
|
||||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
||||||
|
case .getRecommendSeriesByCreator(let creatorId):
|
||||||
|
let parameters = ["creatorId": creatorId]
|
||||||
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -7,12 +7,131 @@
|
||||||
|
|
||||||
import SwiftUI
|
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 {
|
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 {
|
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..<tabItemList.count, id: \.self) { index in
|
||||||
|
let tabItem = tabItemList[index]
|
||||||
|
|
||||||
|
Text(tabItem.title)
|
||||||
|
.font(
|
||||||
|
.custom(
|
||||||
|
selectedTab == tabItem.tab ?
|
||||||
|
Font.bold.rawValue :
|
||||||
|
Font.medium.rawValue,
|
||||||
|
size: 16
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.foregroundColor(
|
||||||
|
selectedTab == tabItem.tab ?
|
||||||
|
.button :
|
||||||
|
.graybb
|
||||||
|
)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.onTapGesture {
|
||||||
|
if selectedTab != tabItem.tab {
|
||||||
|
selectedTab = tabItem.tab
|
||||||
|
proxy.scrollTo(tabItem.tab, anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.id(tabItem.tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 15)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
withAnimation {
|
||||||
|
proxy.scrollTo(selectedTab, anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: selectedTab) { newTab in
|
||||||
|
withAnimation {
|
||||||
|
if newTab == .HOME {
|
||||||
|
AppState.shared.back()
|
||||||
|
} else {
|
||||||
|
proxy.scrollTo(newTab, anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ZStack {
|
||||||
|
switch selectedTab {
|
||||||
|
case .HOME:
|
||||||
|
EmptyView()
|
||||||
|
case .SERIES:
|
||||||
|
ContentMainTabSeriesView()
|
||||||
|
case .CONTENT:
|
||||||
|
EmptyView()
|
||||||
|
case .ALARM:
|
||||||
|
EmptyView()
|
||||||
|
case .ASMR:
|
||||||
|
EmptyView()
|
||||||
|
case .REPLAY:
|
||||||
|
EmptyView()
|
||||||
|
case .FREE:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentMainViewV2()
|
ContentMainViewV2(selectedTab: .SERIES)
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,28 +76,52 @@ struct ContentMainTabHomeView: View {
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_series",
|
imageName: "ic_category_series",
|
||||||
title: "시리즈",
|
title: "시리즈",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(
|
||||||
|
step: .contentMain(
|
||||||
|
startTab: .SERIES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_content",
|
imageName: "ic_category_content",
|
||||||
title: "단편",
|
title: "단편",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(
|
||||||
|
step: .contentMain(
|
||||||
|
startTab: .CONTENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_audio_book",
|
imageName: "ic_category_audio_book",
|
||||||
title: "오디오북",
|
title: "오디오북",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
viewModel.errorMessage = "준비중입니다."
|
||||||
|
viewModel.isShowPopup = true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_alarm",
|
imageName: "ic_category_alarm",
|
||||||
title: "모닝콜",
|
title: "모닝콜",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(
|
||||||
|
step: .contentMain(
|
||||||
|
startTab: .ALARM
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
@ -106,28 +130,52 @@ struct ContentMainTabHomeView: View {
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_asmr",
|
imageName: "ic_category_asmr",
|
||||||
title: "ASMR",
|
title: "ASMR",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(
|
||||||
|
step: .contentMain(
|
||||||
|
startTab: .ASMR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_replay",
|
imageName: "ic_category_replay",
|
||||||
title: "다시듣기",
|
title: "다시듣기",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(
|
||||||
|
step: .contentMain(
|
||||||
|
startTab: .REPLAY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_audio_toon",
|
imageName: "ic_category_audio_toon",
|
||||||
title: "오디오툰",
|
title: "오디오툰",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
viewModel.errorMessage = "준비중입니다."
|
||||||
|
viewModel.isShowPopup = true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
ContentMainTabCategoryView(
|
ContentMainTabCategoryView(
|
||||||
imageName: "ic_category_free",
|
imageName: "ic_category_free",
|
||||||
title: "무료",
|
title: "무료",
|
||||||
onClick: {}
|
onClick: {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(
|
||||||
|
step: .contentMain(
|
||||||
|
startTab: .FREE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
//
|
||||||
|
// ContentMainCompletedSeriesView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/21/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentMainCompletedSeriesView: View {
|
||||||
|
|
||||||
|
let itemList: [SeriesListItem]
|
||||||
|
let onClickMore: () -> 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..<itemList.count, id: \.self) { index in
|
||||||
|
let item = itemList[index]
|
||||||
|
SeriesListBigItemView(
|
||||||
|
item: item,
|
||||||
|
isVisibleCreator: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainCompletedSeriesView(
|
||||||
|
itemList: [
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
)
|
||||||
|
],
|
||||||
|
onClickMore: {}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
//
|
||||||
|
// ContentMainNewOrRecommendSeriesView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/21/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentMainNewOrRecommendSeriesView: View {
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let recommendSeriesList: [GetRecommendSeriesListResponse]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
ForEach(0..<recommendSeriesList.count, id: \.self) { index in
|
||||||
|
let item = recommendSeriesList[index]
|
||||||
|
ContentMainNewOrRecommendSeriesItemView(item: item)
|
||||||
|
.onTapGesture {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(step: .seriesDetail(seriesId: item.seriesId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentMainNewOrRecommendSeriesItemView: View {
|
||||||
|
|
||||||
|
let item: GetRecommendSeriesListResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 9) {
|
||||||
|
KFImage(URL(string: item.imageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: 267, height: 141.3))
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: 267, height: 141.3)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||||
|
|
||||||
|
Text(item.title)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
|
||||||
|
HStack(spacing: 5.3) {
|
||||||
|
KFImage(URL(string: item.creatorProfileImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: 30, height: 30))
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.clipShape(Circle())
|
||||||
|
|
||||||
|
Text(item.creatorNickname)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 10))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(step: .creatorDetail(userId: item.creatorId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainNewOrRecommendSeriesView(
|
||||||
|
title: "추천 무료 시리즈",
|
||||||
|
recommendSeriesList: [
|
||||||
|
GetRecommendSeriesListResponse(
|
||||||
|
seriesId: 1,
|
||||||
|
title: "시리즈 1",
|
||||||
|
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetRecommendSeriesListResponse(
|
||||||
|
seriesId: 1,
|
||||||
|
title: "시리즈 1",
|
||||||
|
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
contentId: 1,
|
||||||
|
title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....",
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
themeStr: "커버곡",
|
||||||
|
price: 100,
|
||||||
|
duration: "00:30:20",
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
|
@ -8,11 +8,109 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentMainSeriesByGenreView: View {
|
struct ContentMainSeriesByGenreView: View {
|
||||||
|
|
||||||
|
let genreList: [GetSeriesGenreListResponse]
|
||||||
|
let itemList: [SeriesListItem]
|
||||||
|
let onClickGenre: (Int) -> Void
|
||||||
|
|
||||||
|
@State private var selectedGenreId = 0
|
||||||
|
|
||||||
var body: some View {
|
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..<itemList.count, id: \.self) { index in
|
||||||
|
let item = itemList[index]
|
||||||
|
|
||||||
|
SeriesListBigItemView(
|
||||||
|
item: item,
|
||||||
|
isVisibleCreator: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ContentMainNoItemView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if !genreList.isEmpty {
|
||||||
|
selectedGenreId = genreList[0].id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentMainSeriesByGenreView()
|
ContentMainSeriesByGenreView(
|
||||||
|
genreList: [
|
||||||
|
GetSeriesGenreListResponse(id: 1, genre: "test"),
|
||||||
|
GetSeriesGenreListResponse(id: 2, genre: "test2"),
|
||||||
|
GetSeriesGenreListResponse(id: 3, genre: "test3")
|
||||||
|
],
|
||||||
|
itemList: [
|
||||||
|
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
|
||||||
|
),
|
||||||
|
SeriesListItem(
|
||||||
|
seriesId: 2,
|
||||||
|
title: "제목2",
|
||||||
|
coverImage: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
publishedDaysOfWeek: "랜덤",
|
||||||
|
isComplete: false,
|
||||||
|
creator: SeriesListItemCreator(
|
||||||
|
creatorId: 1,
|
||||||
|
nickname: "creator",
|
||||||
|
profileImage: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
numberOfContent: 10,
|
||||||
|
isNew: true,
|
||||||
|
isPopular: false
|
||||||
|
),
|
||||||
|
SeriesListItem(
|
||||||
|
seriesId: 2,
|
||||||
|
title: "제목2",
|
||||||
|
coverImage: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
publishedDaysOfWeek: "랜덤",
|
||||||
|
isComplete: false,
|
||||||
|
creator: SeriesListItemCreator(
|
||||||
|
creatorId: 1,
|
||||||
|
nickname: "creator",
|
||||||
|
profileImage: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
numberOfContent: 10,
|
||||||
|
isNew: true,
|
||||||
|
isPopular: false
|
||||||
|
)
|
||||||
|
],
|
||||||
|
onClickGenre: { _ in }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
//
|
||||||
|
// ContentMainSeriesCurationView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/21/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentMainSeriesCurationView: View {
|
||||||
|
|
||||||
|
let curationList: [GetSeriesCurationResponse]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
LazyVStack(spacing: 30) {
|
||||||
|
ForEach(0..<curationList.count, id: \.self) { index in
|
||||||
|
let curation = curationList[index]
|
||||||
|
ContentMainSeriesCurationItemView(curation: curation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentMainSeriesCurationItemView: View {
|
||||||
|
|
||||||
|
let curation: GetSeriesCurationResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
|
Text(curation.title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
ForEach(0..<curation.items.count, id: \.self) { index in
|
||||||
|
let item = curation.items[index]
|
||||||
|
SeriesListBigItemView(item: item, isVisibleCreator: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainSeriesCurationView(
|
||||||
|
curationList: [
|
||||||
|
GetSeriesCurationResponse(
|
||||||
|
title: "test",
|
||||||
|
items: [
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
GetSeriesCurationResponse(
|
||||||
|
title: "test2",
|
||||||
|
items: [
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
GetSeriesCurationResponse(
|
||||||
|
title: "test3",
|
||||||
|
items: [
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
|
@ -8,11 +8,51 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentMainSeriesGenreView: View {
|
struct ContentMainSeriesGenreView: View {
|
||||||
|
let genreList: [GetSeriesGenreListResponse]
|
||||||
|
let selectGenre: (Int) -> Void
|
||||||
|
|
||||||
|
@Binding var selectedGenreId: Int
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(alignment: .top, spacing: 8) {
|
||||||
|
ForEach(0..<genreList.count, id: \.self) { index in
|
||||||
|
let genre = genreList[index]
|
||||||
|
Text(genre.genre)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
|
.foregroundColor(selectedGenreId == genre.id ? Color.button : Color.gray77)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.padding(.vertical, 9.3)
|
||||||
|
.border(
|
||||||
|
selectedGenreId == genre.id ? Color.button : Color.grayee,
|
||||||
|
width: 1
|
||||||
|
)
|
||||||
|
.cornerRadius(16.7)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: CGFloat(16.7))
|
||||||
|
.stroke(lineWidth: 1)
|
||||||
|
.foregroundColor(selectedGenreId == genre.id ? Color.button : Color.grayee)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if selectedGenreId != genre.id {
|
||||||
|
selectGenre(genre.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentMainSeriesGenreView()
|
ContentMainSeriesGenreView(
|
||||||
|
genreList: [
|
||||||
|
GetSeriesGenreListResponse(id: 1, genre: "test"),
|
||||||
|
GetSeriesGenreListResponse(id: 2, genre: "test2"),
|
||||||
|
GetSeriesGenreListResponse(id: 3, genre: "test3")
|
||||||
|
],
|
||||||
|
selectGenre: { _ in },
|
||||||
|
selectedGenreId: .constant(2)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
//
|
||||||
|
// ContentMainSeriesRankingView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/21/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentMainSeriesRankingView: View {
|
||||||
|
|
||||||
|
let seriesList: [SeriesListItem]
|
||||||
|
|
||||||
|
let rows = [
|
||||||
|
GridItem(.flexible(), alignment: .leading),
|
||||||
|
GridItem(.flexible(), alignment: .leading),
|
||||||
|
GridItem(.flexible(), alignment: .leading)
|
||||||
|
]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
|
Text("일간 랭킹")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(.grayee)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
LazyHGrid(rows: rows, spacing: 13.3) {
|
||||||
|
ForEach(0..<seriesList.count, id: \.self) { index in
|
||||||
|
let series = seriesList[index]
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
KFImage(URL(string: series.coverImage))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(
|
||||||
|
size: CGSize(
|
||||||
|
width: 60,
|
||||||
|
height: 85
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 60, height: 85)
|
||||||
|
.cornerRadius(2.7)
|
||||||
|
|
||||||
|
Text("\(index + 1)")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(.button)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text(series.title)
|
||||||
|
.lineLimit(2)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayd2)
|
||||||
|
|
||||||
|
Text(series.creator.nickname)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: screenSize().width * 0.66, alignment: .leading)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
AppState
|
||||||
|
.shared
|
||||||
|
.setAppStep(step: .seriesDetail(seriesId: series.seriesId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainSeriesRankingView(
|
||||||
|
seriesList: [
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
SeriesListItem(
|
||||||
|
seriesId: 3,
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
SeriesListItem(
|
||||||
|
seriesId: 4,
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
|
@ -6,3 +6,22 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import CombineMoya
|
||||||
|
import Combine
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
final class ContentMainTabSeriesRepository {
|
||||||
|
private let api = MoyaProvider<ContentApi>()
|
||||||
|
|
||||||
|
func getContentMainSeries() -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getContentMainSeries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecommendSeriesListByGenre(genreId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getRecommendSeriesListByGenre(genreId: genreId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecommendSeriesByCreator(creatorId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getRecommendSeriesByCreator(creatorId: creatorId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,86 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentMainTabSeriesView: View {
|
struct ContentMainTabSeriesView: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel = ContentMainTabSeriesViewModel()
|
||||||
|
|
||||||
var body: some View {
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,152 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class ContentMainTabSeriesViewModel: ObservableObject {
|
||||||
|
private let repository = ContentMainTabSeriesRepository()
|
||||||
|
private let contentRepository = ContentRepository()
|
||||||
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@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<GetContentMainTabSeriesResponse>.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,4 +5,35 @@
|
||||||
// Created by klaus on 2/20/25.
|
// 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]
|
||||||
|
}
|
||||||
|
|
|
@ -6,13 +6,103 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
struct ContentMainOriginalAudioDramaItemView: View {
|
struct ContentMainOriginalAudioDramaItemView: View {
|
||||||
|
|
||||||
|
let itemWidth: CGFloat
|
||||||
|
let item: SeriesListItem
|
||||||
|
let isAll: Bool
|
||||||
|
|
||||||
var body: some View {
|
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 {
|
#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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,75 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentMainOriginalAudioDramaView: View {
|
struct ContentMainOriginalAudioDramaView: View {
|
||||||
|
|
||||||
|
let itemList: [SeriesListItem]
|
||||||
|
let onClickMore: () -> Void
|
||||||
|
|
||||||
var body: some View {
|
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..<itemList.count, id: \.self) { index in
|
||||||
|
let item = itemList[index]
|
||||||
|
|
||||||
|
ContentMainOriginalAudioDramaItemView(
|
||||||
|
itemWidth: 150,
|
||||||
|
item: item,
|
||||||
|
isAll: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentMainOriginalAudioDramaView()
|
ContentMainOriginalAudioDramaView(
|
||||||
|
itemList: [
|
||||||
|
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
|
||||||
|
),
|
||||||
|
SeriesListItem(
|
||||||
|
seriesId: 2,
|
||||||
|
title: "제목2",
|
||||||
|
coverImage: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
publishedDaysOfWeek: "랜덤",
|
||||||
|
isComplete: false,
|
||||||
|
creator: SeriesListItemCreator(
|
||||||
|
creatorId: 1,
|
||||||
|
nickname: "creator",
|
||||||
|
profileImage: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
numberOfContent: 10,
|
||||||
|
isNew: true,
|
||||||
|
isPopular: false
|
||||||
|
)
|
||||||
|
]
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
//
|
||||||
|
// SeriesByChannelView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/21/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SeriesByChannelView: View {
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let creatorList: [ContentCreatorResponse]
|
||||||
|
let seriesList: [SeriesListItem]
|
||||||
|
let onClickCreator: (Int) -> 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..<creatorList.count, id: \.self) { index in
|
||||||
|
let item = creatorList[index]
|
||||||
|
|
||||||
|
ContentCreatorView(
|
||||||
|
isSelected: item.creatorId == selectedCreatorId,
|
||||||
|
item: item
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
let creatorId = item.creatorId
|
||||||
|
|
||||||
|
if creatorId != selectedCreatorId {
|
||||||
|
selectedCreatorId = creatorId
|
||||||
|
onClickCreator(creatorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if seriesList.isEmpty {
|
||||||
|
ContentMainNoItemView()
|
||||||
|
} else {
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
HStack(spacing: 22) {
|
||||||
|
ForEach(0..<seriesList.count, id: \.self) { index in
|
||||||
|
let item = seriesList[index]
|
||||||
|
|
||||||
|
SeriesListBigItemView(
|
||||||
|
item: item,
|
||||||
|
isVisibleCreator: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if !self.creatorList.isEmpty {
|
||||||
|
selectedCreatorId = creatorList[0].creatorId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SeriesByChannelView(
|
||||||
|
title: "채널별 추천 시리즈",
|
||||||
|
creatorList: [
|
||||||
|
ContentCreatorResponse(
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
ContentCreatorResponse(
|
||||||
|
creatorId: 2,
|
||||||
|
creatorNickname: "유저2",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
seriesList: [
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
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: true,
|
||||||
|
isPopular: true
|
||||||
|
)
|
||||||
|
],
|
||||||
|
onClickCreator: { _ in }
|
||||||
|
)
|
||||||
|
}
|
|
@ -215,6 +215,9 @@ struct ContentView: View {
|
||||||
case .searchChannel:
|
case .searchChannel:
|
||||||
SearchChannelView()
|
SearchChannelView()
|
||||||
|
|
||||||
|
case .contentMain(let startTab):
|
||||||
|
ContentMainViewV2(selectedTab: startTab)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||||
|
|
Loading…
Reference in New Issue