21
SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_card_can_gray_32.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/ic_card_can_gray_32.png
vendored
Normal file
After Width: | Height: | Size: 588 B |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_alarm.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_alarm.imageset/ic_category_alarm.png
vendored
Normal file
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_asmr.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/ic_category_asmr.png
vendored
Normal file
After Width: | Height: | Size: 4.1 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_category_audio_book.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_audio_book.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_audio_book.imageset/ic_category_audio_book.png
vendored
Normal file
After Width: | Height: | Size: 3.2 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_category_audio_toon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_audio_toon.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_audio_toon.imageset/ic_category_audio_toon.png
vendored
Normal file
After Width: | Height: | Size: 3.6 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_category_content.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_content.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_content.imageset/ic_category_content.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_free.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_free.imageset/ic_category_free.png
vendored
Normal file
After Width: | Height: | Size: 3.0 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_category_replay.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_replay.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_replay.imageset/ic_category_replay.png
vendored
Normal file
After Width: | Height: | Size: 3.2 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_category_series.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/ic_category_series.png
vendored
Normal file
After Width: | Height: | Size: 3.2 KiB |
|
@ -38,6 +38,8 @@ 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 getPopularContentByCreator(creatorId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ContentApi: TargetType {
|
extension ContentApi: TargetType {
|
||||||
|
@ -133,6 +135,12 @@ extension ContentApi: TargetType {
|
||||||
|
|
||||||
case .generateUrl(let contentId):
|
case .generateUrl(let contentId):
|
||||||
return "/audio-content/\(contentId)/generate-url"
|
return "/audio-content/\(contentId)/generate-url"
|
||||||
|
|
||||||
|
case .getContentMainHome:
|
||||||
|
return "/v2/audio-content/main/home"
|
||||||
|
|
||||||
|
case .getPopularContentByCreator:
|
||||||
|
return "/v2/audio-content/main/home/popular-content-by-creator"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +155,9 @@ extension ContentApi: TargetType {
|
||||||
case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList, .getAudioContentByTheme:
|
case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList, .getAudioContentByTheme:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
|
case .getContentMainHome, .getPopularContentByCreator:
|
||||||
|
return .get
|
||||||
|
|
||||||
case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent:
|
case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent:
|
||||||
return .put
|
return .put
|
||||||
|
|
||||||
|
@ -300,6 +311,13 @@ extension ContentApi: TargetType {
|
||||||
] as [String : Any]
|
] as [String : Any]
|
||||||
|
|
||||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
||||||
|
case .getContentMainHome:
|
||||||
|
return .requestPlain
|
||||||
|
|
||||||
|
case .getPopularContentByCreator(let creatorId):
|
||||||
|
let parameters = ["creatorId": creatorId]
|
||||||
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct GetAudioContentMainResponse: Decodable {
|
struct GetAudioContentMainResponse: Decodable {
|
||||||
let newContentUploadCreatorList: [GetNewContentUploadCreator]
|
let newContentUploadCreatorList: [ContentCreatorResponse]
|
||||||
let bannerList: [GetAudioContentBannerResponse]
|
let bannerList: [GetAudioContentBannerResponse]
|
||||||
let orderList: [GetAudioContentMainItem]
|
let orderList: [GetAudioContentMainItem]
|
||||||
let themeList: [String]
|
let themeList: [String]
|
||||||
|
@ -33,9 +33,10 @@ struct GetAudioContentRankingItem: Decodable {
|
||||||
let duration: String
|
let duration: String
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
let creatorNickname: String
|
let creatorNickname: String
|
||||||
|
let creatorProfileImageUrl: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GetNewContentUploadCreator: Decodable {
|
struct ContentCreatorResponse: Decodable {
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
let creatorNickname: String
|
let creatorNickname: String
|
||||||
let creatorProfileImageUrl: String
|
let creatorProfileImageUrl: String
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabRankContentView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentMainTabRankContentView: View {
|
||||||
|
|
||||||
|
let rows = [
|
||||||
|
GridItem(.fixed(60), alignment: .leading),
|
||||||
|
GridItem(.fixed(60), alignment: .leading),
|
||||||
|
GridItem(.fixed(60), alignment: .leading)
|
||||||
|
]
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
|
||||||
|
let isMore: Bool
|
||||||
|
let onClickMore: () -> Void
|
||||||
|
|
||||||
|
let sortList: [String]
|
||||||
|
let onClickSort: (String) -> Void
|
||||||
|
|
||||||
|
let contentList: [GetAudioContentRankingItem]
|
||||||
|
|
||||||
|
@State private var selectedSort = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if isMore {
|
||||||
|
Image("ic_forward")
|
||||||
|
.onTapGesture { onClickMore() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sortList.isEmpty {
|
||||||
|
ContentMainRankingSortView(
|
||||||
|
sorts: sortList,
|
||||||
|
selectSort: {
|
||||||
|
selectedSort = $0
|
||||||
|
onClickSort($0)
|
||||||
|
},
|
||||||
|
selectedSort: $selectedSort
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
LazyHGrid(rows: rows, spacing: 13.3) {
|
||||||
|
ForEach(0..<contentList.count, id: \.self) { index in
|
||||||
|
let content = contentList[index]
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
KFImage(URL(string: content.coverImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(
|
||||||
|
size: CGSize(
|
||||||
|
width: 60,
|
||||||
|
height: 60
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.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(content.title)
|
||||||
|
.lineLimit(2)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayd2)
|
||||||
|
|
||||||
|
Text(content.creatorNickname)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: screenSize().width * 0.66, alignment: .leading)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
AppState
|
||||||
|
.shared
|
||||||
|
.setAppStep(step: .contentDetail(contentId: content.contentId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 207)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if !sortList.isEmpty {
|
||||||
|
selectedSort = sortList[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainTabRankContentView(
|
||||||
|
title: "인기 단편",
|
||||||
|
isMore: true,
|
||||||
|
onClickMore: {},
|
||||||
|
sortList: ["매출", "댓글", "좋아요"],
|
||||||
|
onClickSort: { _ in },
|
||||||
|
contentList: [
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
contentId: 2,
|
||||||
|
title: "안녕하세요 오늘은 커버곡을 들려드릴께요....",
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
themeStr: "커버곡",
|
||||||
|
price: 0,
|
||||||
|
duration: "00:30:20",
|
||||||
|
creatorId: 2,
|
||||||
|
creatorNickname: "유저2",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
contentId: 3,
|
||||||
|
title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....",
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
themeStr: "커버곡",
|
||||||
|
price: 50,
|
||||||
|
duration: "00:30:20",
|
||||||
|
creatorId: 3,
|
||||||
|
creatorNickname: "유저3",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
//
|
||||||
|
// ContentByChannelView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentByChannelView: View {
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let creatorList: [ContentCreatorResponse]
|
||||||
|
let contentList: [GetAudioContentRankingItem]
|
||||||
|
let onClickCreator: (Int) -> Void
|
||||||
|
|
||||||
|
@State private var selectedCreatorId = 0
|
||||||
|
|
||||||
|
let columns = [GridItem(.flexible()), GridItem(.flexible())]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 20) {
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
|
||||||
|
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
|
||||||
|
selectedCreatorId = creatorId
|
||||||
|
onClickCreator(creatorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyVGrid(columns: columns, spacing: 13.3) {
|
||||||
|
ForEach(0..<contentList.count, id: \.self) { index in
|
||||||
|
let content = contentList[index]
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
ZStack(alignment:.bottom) {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
KFImage(URL(string: content.coverImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.width)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 5.3))
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
HStack(spacing: 2) {
|
||||||
|
if content.price > 0 {
|
||||||
|
Image("ic_card_can_gray_32")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(content.price > 0 ? "\(content.price)" : "무료")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
}
|
||||||
|
.padding(4)
|
||||||
|
.background(Color.gray33.opacity(0.7))
|
||||||
|
.cornerRadius(10)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(content.duration)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.padding(4)
|
||||||
|
.background(Color.gray33.opacity(0.7))
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 2.7)
|
||||||
|
.padding(.bottom, 2.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(content.title)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.grayd2)
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
HStack(spacing: 5.3) {
|
||||||
|
KFImage(URL(string: content.creatorProfileImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: 21, height: 21))
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 21, height: 21)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.clipped()
|
||||||
|
|
||||||
|
Text(content.creatorNickname)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 10))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
AppState
|
||||||
|
.shared
|
||||||
|
.setAppStep(step: .creatorDetail(userId: content.creatorId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
AppState
|
||||||
|
.shared
|
||||||
|
.setAppStep(step: .contentDetail(contentId: content.contentId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if !self.creatorList.isEmpty {
|
||||||
|
selectedCreatorId = creatorList[0].creatorId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentByChannelView(
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
contentList: [
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
contentId: 2,
|
||||||
|
title: "안녕하세요 오늘은 커버곡을 들려드릴께요....",
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
themeStr: "커버곡",
|
||||||
|
price: 0,
|
||||||
|
duration: "00:30:20",
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
contentId: 3,
|
||||||
|
title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....",
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
themeStr: "커버곡",
|
||||||
|
price: 50,
|
||||||
|
duration: "00:30:20",
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetAudioContentRankingItem(
|
||||||
|
contentId: 4,
|
||||||
|
title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....",
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
themeStr: "커버곡",
|
||||||
|
price: 50,
|
||||||
|
duration: "00:30:20",
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
) { _ in }
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// ContentCreatorView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentCreatorView: View {
|
||||||
|
|
||||||
|
let isSelected: Bool
|
||||||
|
let item: ContentCreatorResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
KFImage(URL(string: item.creatorProfileImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: 60, height: 60))
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.overlay(
|
||||||
|
Circle()
|
||||||
|
.strokeBorder(lineWidth: 3)
|
||||||
|
.foregroundColor(
|
||||||
|
.button
|
||||||
|
.opacity(isSelected ? 1 : 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(item.creatorNickname)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||||
|
.foregroundColor(
|
||||||
|
isSelected ?
|
||||||
|
Color.button :
|
||||||
|
Color.graybb
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentCreatorView(
|
||||||
|
isSelected: true,
|
||||||
|
item: ContentCreatorResponse(
|
||||||
|
creatorId: 1,
|
||||||
|
creatorNickname: "유저1",
|
||||||
|
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
//
|
||||||
|
// ContentMainBannerViewV2.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentMainBannerViewV2: View {
|
||||||
|
|
||||||
|
let bannerList: [GetAudioContentBannerResponse]
|
||||||
|
|
||||||
|
@State var currentIndex = 0
|
||||||
|
@State var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
|
@State var width: CGFloat = 0
|
||||||
|
@State var height: CGFloat = 0
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
TabView(selection: $currentIndex) {
|
||||||
|
ForEach(0..<bannerList.count, id: \.self) { index in
|
||||||
|
let item = bannerList[index]
|
||||||
|
if let url = item.thumbnailImageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
|
||||||
|
ContentMainBannerImageView(
|
||||||
|
url: url,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
item: item
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ContentMainBannerImageView(
|
||||||
|
url: item.thumbnailImageUrl,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
item: item
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||||
|
.frame(
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
)
|
||||||
|
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
ForEach(0..<bannerList.count, id: \.self) { index in
|
||||||
|
Capsule()
|
||||||
|
.foregroundColor(
|
||||||
|
index == currentIndex
|
||||||
|
? .button
|
||||||
|
: .gray90
|
||||||
|
)
|
||||||
|
.frame(
|
||||||
|
width: index == currentIndex ? 18 : 6,
|
||||||
|
height: 6
|
||||||
|
)
|
||||||
|
.tag(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.onAppear {
|
||||||
|
width = screenSize().width - 26.7
|
||||||
|
height = width * 0.53
|
||||||
|
timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
timer.upstream.connect().cancel()
|
||||||
|
}
|
||||||
|
.onReceive(timer) { _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
withAnimation {
|
||||||
|
if currentIndex == bannerList.count - 1 {
|
||||||
|
currentIndex = 0
|
||||||
|
} else {
|
||||||
|
currentIndex += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainBannerViewV2(
|
||||||
|
bannerList: [
|
||||||
|
GetAudioContentBannerResponse(
|
||||||
|
type: .CREATOR,
|
||||||
|
thumbnailImageUrl: "https://test-cf.sodalive.net/audition/role/7/audition_role-dc4174e1-10b5-4a97-8379-c7a9336ab230-6691-1735908236571",
|
||||||
|
eventItem: nil,
|
||||||
|
creatorId: 2,
|
||||||
|
seriesId: nil,
|
||||||
|
link: nil
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentMainBannerImageView: View {
|
||||||
|
let url: String
|
||||||
|
let width: CGFloat
|
||||||
|
let height: CGFloat
|
||||||
|
let item: GetAudioContentBannerResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
KFImage(URL(string: url))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: width, height: height))
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.cornerRadius(4.7)
|
||||||
|
.onTapGesture {
|
||||||
|
switch item.type {
|
||||||
|
case .EVENT:
|
||||||
|
AppState.shared.setAppStep(step: .eventDetail(event: item.eventItem!))
|
||||||
|
case .CREATOR:
|
||||||
|
AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId!))
|
||||||
|
case .SERIES:
|
||||||
|
AppState.shared.setAppStep(step: .seriesDetail(seriesId: item.seriesId!))
|
||||||
|
case .LINK:
|
||||||
|
if let link = item.link, link.trimmingCharacters(in: .whitespaces).count > 0, let url = URL(string: link), UIApplication.shared.canOpenURL(url) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabCategoryView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentMainTabCategoryView: View {
|
||||||
|
|
||||||
|
let imageName: String
|
||||||
|
let title: String
|
||||||
|
let onClick: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 5.3) {
|
||||||
|
Image(imageName)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 43, height: 43)
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(.gray77)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_series",
|
||||||
|
title: "시리즈",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabHomeNoticeView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentMainTabHomeNoticeView: View {
|
||||||
|
|
||||||
|
let notice: NoticeItem
|
||||||
|
let onClick: (NoticeItem) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text(notice.title)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("자세히 >")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.onTapGesture {
|
||||||
|
onClick(notice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainTabHomeNoticeView(
|
||||||
|
notice: NoticeItem(
|
||||||
|
title: "[업데이트] 1.28.0 버전 업데이트",
|
||||||
|
content: "test",
|
||||||
|
date: "2025-02-07"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
AppState.shared.setAppStep(step: .noticeDetail(notice: $0))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabHomeRepository.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CombineMoya
|
||||||
|
import Combine
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
class ContentMainTabHomeRepository {
|
||||||
|
private let api = MoyaProvider<ContentApi>()
|
||||||
|
|
||||||
|
func getContentMainHome() -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getContentMainHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPopularContentByCreator(creatorId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.getPopularContentByCreator(creatorId: creatorId))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabHomeView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentMainTabHomeView: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel = ContentMainTabHomeViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
BaseView(isLoading: $viewModel.isLoading) {
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
VStack(alignment: .leading, 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(.bottom, 26.7)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
if let notice = viewModel.noticeItem {
|
||||||
|
ContentMainTabHomeNoticeView(notice: notice) {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(step: .noticeDetail(notice: $0))
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.bannerList.count > 0 {
|
||||||
|
ContentMainBannerViewV2(bannerList: viewModel.bannerList)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image("ic_title_search_black")
|
||||||
|
|
||||||
|
Text("채널명을 입력해 보세요")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.gray55)
|
||||||
|
.keyboardType(.default)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 21.3)
|
||||||
|
.frame(height: 50)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 6.7)
|
||||||
|
.strokeBorder(lineWidth: 1)
|
||||||
|
.foregroundColor(Color.graybb)
|
||||||
|
)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.onTapGesture {
|
||||||
|
UserDefaults.set("", forKey: .searchChannel)
|
||||||
|
AppState.shared.setAppStep(step: .searchChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_series",
|
||||||
|
title: "시리즈",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_content",
|
||||||
|
title: "단편",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_audio_book",
|
||||||
|
title: "오디오북",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_alarm",
|
||||||
|
title: "모닝콜",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_asmr",
|
||||||
|
title: "ASMR",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_replay",
|
||||||
|
title: "다시듣기",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_audio_toon",
|
||||||
|
title: "오디오툰",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
ContentMainTabCategoryView(
|
||||||
|
imageName: "ic_category_free",
|
||||||
|
title: "무료",
|
||||||
|
onClick: {}
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
if let response = viewModel.rankCreatorResponse {
|
||||||
|
ContentMainTabHomeRankCreatorView(response: response)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viewModel.rankSeriesList.isEmpty {
|
||||||
|
ContentMainTabHomeRankSeriesView(seriesList: viewModel.rankSeriesList)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viewModel.rankSortTypeList.isEmpty {
|
||||||
|
ContentMainTabRankContentView(
|
||||||
|
title: "인기 단편",
|
||||||
|
isMore: true,
|
||||||
|
onClickMore: {
|
||||||
|
AppState.shared.setAppStep(step: .contentRankingAll)
|
||||||
|
},
|
||||||
|
sortList: !viewModel.rankSortTypeList.isEmpty ?
|
||||||
|
viewModel.rankSortTypeList :
|
||||||
|
[],
|
||||||
|
onClickSort: { viewModel.getContentRanking(sort: $0) },
|
||||||
|
contentList: viewModel.rankContentList
|
||||||
|
)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.eventBannerList.count > 0 {
|
||||||
|
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.contentRankCreatorList.isEmpty {
|
||||||
|
ContentByChannelView(
|
||||||
|
title: "채널별 인기 콘텐츠",
|
||||||
|
creatorList: viewModel.contentRankCreatorList,
|
||||||
|
contentList: viewModel.salesCountRankContentList,
|
||||||
|
onClickCreator: {
|
||||||
|
viewModel.getPopularContentByCreator(creatorId: $0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("""
|
||||||
|
- 회사명 : 주식회사 소다라이브
|
||||||
|
|
||||||
|
- 대표자 : 이재형
|
||||||
|
|
||||||
|
- 주소 : 경기도 성남시 분당구 황새울로335번길 10, 5층 563A호
|
||||||
|
|
||||||
|
- 사업자등록번호 : 870-81-03220
|
||||||
|
|
||||||
|
- 통신판매업신고 : 제2024-성남분당B-1012호
|
||||||
|
|
||||||
|
- 고객센터 : 02.2055.1477 (이용시간 10:00~19:00)
|
||||||
|
|
||||||
|
- 대표 이메일 : sodalive.official@gmail.com
|
||||||
|
""")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
|
.foregroundColor(Color.gray77)
|
||||||
|
.padding(.top, 30)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainTabHomeView()
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabHomeViewModel.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class ContentMainTabHomeViewModel: ObservableObject {
|
||||||
|
|
||||||
|
private let repository = ContentMainTabHomeRepository()
|
||||||
|
private let contentRepository = ContentRepository()
|
||||||
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@Published var errorMessage = ""
|
||||||
|
@Published var isShowPopup = false
|
||||||
|
@Published var isLoading = false
|
||||||
|
|
||||||
|
@Published var noticeItem: NoticeItem? = nil
|
||||||
|
@Published var bannerList = [GetAudioContentBannerResponse]()
|
||||||
|
@Published var rankCreatorResponse: GetExplorerSectionResponse? = nil
|
||||||
|
@Published var rankSeriesList = [SeriesListItem]()
|
||||||
|
@Published var rankSortTypeList: [String] = []
|
||||||
|
@Published var rankContentList: [GetAudioContentRankingItem] = []
|
||||||
|
@Published var eventBannerList: [EventItem] = []
|
||||||
|
@Published var contentRankCreatorList: [ContentCreatorResponse] = []
|
||||||
|
@Published var salesCountRankContentList: [GetAudioContentRankingItem] = []
|
||||||
|
|
||||||
|
func fetchData() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.getContentMainHome()
|
||||||
|
.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<GetContentMainTabHomeResponse>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.noticeItem = data.latestNotice
|
||||||
|
self.bannerList = data.bannerList
|
||||||
|
self.rankCreatorResponse = data.rankCreatorList
|
||||||
|
self.rankSortTypeList = data.rankSortTypeList
|
||||||
|
self.rankContentList = data.rankContentList
|
||||||
|
self.eventBannerList = data.eventBannerList.eventList
|
||||||
|
self.contentRankCreatorList = data.contentRankCreatorList
|
||||||
|
self.salesCountRankContentList = data.salesCountRankContentList
|
||||||
|
} 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 getContentRanking(sort: String = "매출") {
|
||||||
|
isLoading = true
|
||||||
|
contentRepository.getContentRanking(page: 1, size: 12, sortType: sort)
|
||||||
|
.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<GetAudioContentRanking>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.rankContentList = data.items
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
//
|
||||||
|
// GetContentMainTabHomeResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct GetContentMainTabHomeResponse: Decodable {
|
||||||
|
let latestNotice: NoticeItem?
|
||||||
|
let bannerList: [GetAudioContentBannerResponse]
|
||||||
|
let rankCreatorList: GetExplorerSectionResponse
|
||||||
|
let rankSeriesList: [SeriesListItem]
|
||||||
|
let rankSortTypeList: [String]
|
||||||
|
let rankContentList: [GetAudioContentRankingItem]
|
||||||
|
let eventBannerList: GetEventResponse
|
||||||
|
let contentRankCreatorList: [ContentCreatorResponse]
|
||||||
|
let salesCountRankContentList: [GetAudioContentRankingItem]
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabHomeRankCreatorView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ContentMainTabHomeRankCreatorView: View {
|
||||||
|
|
||||||
|
let response: GetExplorerSectionResponse
|
||||||
|
|
||||||
|
let rankingCrawns = ["ic_crown_1", "ic_crown_2", "ic_crown_3"]
|
||||||
|
let rankingColors = [
|
||||||
|
[Color(hex: "ffdc00"), Color(hex: "ffb600")],
|
||||||
|
[Color(hex: "ffffff"), Color(hex: "9f9f9f")],
|
||||||
|
[Color(hex: "e6a77a"), Color(hex: "c67e4a")],
|
||||||
|
[Color(hex: "ffffff").opacity(0), Color(hex: "ffffff").opacity(0)]
|
||||||
|
]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
if let desc = response.desc {
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Text("\(desc)")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
Text("※ 인기 순위는 매주 업데이트됩니다.")
|
||||||
|
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.graybb)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let coloredTitle = response.coloredTitle, let color = response.color {
|
||||||
|
let titleArray = response.title.components(separatedBy: coloredTitle)
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text(titleArray[0])
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
Text(coloredTitle)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color(hex: color))
|
||||||
|
|
||||||
|
if titleArray.count > 1 {
|
||||||
|
Text(titleArray[1])
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 30)
|
||||||
|
} else {
|
||||||
|
Text(response.title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.padding(.top, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
ForEach(0..<response.creators.count, id: \.self) { index in
|
||||||
|
let creator = response.creators[index]
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
if let _ = response.desc {
|
||||||
|
ZStack {
|
||||||
|
KFImage(URL(string: creator.profileImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: 90, height: 90))
|
||||||
|
.resizable()
|
||||||
|
.clipShape(Circle())
|
||||||
|
.frame(width: 90, height: 90)
|
||||||
|
.overlay(
|
||||||
|
Circle()
|
||||||
|
.stroke(
|
||||||
|
AngularGradient(colors: rankingColors[index < 4 ? index : 3], center: .center),
|
||||||
|
lineWidth: 3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if index < 3 {
|
||||||
|
VStack(alignment: .trailing, spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(rankingCrawns[index])
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 37, height: 37)
|
||||||
|
}
|
||||||
|
.frame(width: 93.3, height: 93.3, alignment: .trailing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: 93.3, height: 93.3)
|
||||||
|
} else {
|
||||||
|
KFImage(URL(string: creator.profileImageUrl))
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.downsampling(size: CGSize(width: 93, height: 93))
|
||||||
|
.resizable()
|
||||||
|
.clipShape(Circle())
|
||||||
|
.frame(width: 93, height: 93)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(creator.nickname)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.lineLimit(1)
|
||||||
|
.frame(width: 93.3)
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
|
Text(creator.tags)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 10))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.lineLimit(1)
|
||||||
|
.frame(width: 93.3)
|
||||||
|
.padding(.top, 3.3)
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
AppState.shared
|
||||||
|
.setAppStep(step: .creatorDetail(userId: creator.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainTabHomeRankCreatorView(
|
||||||
|
response: GetExplorerSectionResponse(
|
||||||
|
title: "인기 크리에이터",
|
||||||
|
coloredTitle: "인기",
|
||||||
|
color: "ff5c49",
|
||||||
|
desc: "2025년 02월 10일 ~ 02월 16일",
|
||||||
|
creators: [
|
||||||
|
GetExplorerSectionCreatorResponse(
|
||||||
|
id: 1,
|
||||||
|
nickname: "User1",
|
||||||
|
tags: "",
|
||||||
|
profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetExplorerSectionCreatorResponse(
|
||||||
|
id: 2,
|
||||||
|
nickname: "User2",
|
||||||
|
tags: "",
|
||||||
|
profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetExplorerSectionCreatorResponse(
|
||||||
|
id: 3,
|
||||||
|
nickname: "User3",
|
||||||
|
tags: "",
|
||||||
|
profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
),
|
||||||
|
GetExplorerSectionCreatorResponse(
|
||||||
|
id: 4,
|
||||||
|
nickname: "User4",
|
||||||
|
tags: "",
|
||||||
|
profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
//
|
||||||
|
// ContentMainTabHomeRankSeriesView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/20/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentMainTabHomeRankSeriesView: View {
|
||||||
|
|
||||||
|
let seriesList: [SeriesListItem]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
|
Text("인기 시리즈")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(alignment: .top, spacing: 13.3) {
|
||||||
|
ForEach(0..<seriesList.count, id: \.self) {
|
||||||
|
let item = seriesList[$0]
|
||||||
|
SeriesListBigItemView(item: item, isVisibleCreator: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentMainTabHomeRankSeriesView(
|
||||||
|
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: 2,
|
||||||
|
title: "제목, 관심사,프로필+방장, 참여인원(어딘가..)",
|
||||||
|
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: false,
|
||||||
|
isPopular: true
|
||||||
|
),
|
||||||
|
SeriesListItem(
|
||||||
|
seriesId: 1,
|
||||||
|
title: "제목, 관심사,프로필+방장, 참여인원(어딘가..)",
|
||||||
|
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
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ struct SectionEventBannerView: View {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.resizable()
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center)
|
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center)
|
||||||
.tag(index)
|
.tag(index)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
@ -50,6 +51,7 @@ struct SectionEventBannerView: View {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.resizable()
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center)
|
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center)
|
||||||
.tag(index)
|
.tag(index)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
|
|
@ -23,7 +23,7 @@ struct HomeView: View {
|
||||||
private let liveView = LiveView()
|
private let liveView = LiveView()
|
||||||
private let audition = AuditionView()
|
private let audition = AuditionView()
|
||||||
private let messageView = MessageView()
|
private let messageView = MessageView()
|
||||||
private let contentView = ContentMainView()
|
private let contentView = ContentMainTabHomeView()
|
||||||
|
|
||||||
@State private var isShowPlayer = false
|
@State private var isShowPlayer = false
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ struct SeriesListBigItemView: View {
|
||||||
Text(item.title)
|
Text(item.title)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color.grayee)
|
.foregroundColor(Color.grayee)
|
||||||
.lineLimit(2)
|
.lineLimit(1)
|
||||||
|
|
||||||
if isVisibleCreator {
|
if isVisibleCreator {
|
||||||
HStack(spacing: 3) {
|
HStack(spacing: 3) {
|
||||||
|
|