feat: 신규 홈 추가

This commit is contained in:
Yu Sung
2025-07-11 12:18:37 +09:00
parent fca5425e81
commit e121ec1ee4
12 changed files with 492 additions and 3 deletions

View File

@@ -0,0 +1,15 @@
//
// AudioContentMainItem.swift
// SodaLive
//
// Created by klaus on 7/10/25.
//
struct AudioContentMainItem: Decodable {
let contentId: Int
let creatorId: Int
let title: String
let coverImageUrl: String
let creatorNickname: String
let isPointAvailable: Bool
}

View File

@@ -0,0 +1,21 @@
//
// GetHomeResponse.swift
// SodaLive
//
// Created by klaus on 7/11/25.
//
struct GetHomeResponse: Decodable {
let liveList: [GetRoomListResponse]
let creatorRanking: [GetExplorerSectionCreatorResponse]
let latestContentThemeList: [String]
let latestContentList: [AudioContentMainItem]
let eventBannerList: GetEventResponse
let originalAudioDramaList: [SeriesListItem]
let auditionList: [GetAuditionListItem]
let dayOfWeekSeriesList: [SeriesListItem]
let contentRanking: [GetAudioContentRankingItem]
let recommendChannelList: [RecommendChannelResponse]
let freeContentList: [AudioContentMainItem]
let curationList: [GetContentCurationResponse]
}

View File

@@ -0,0 +1,73 @@
//
// HomeApi.swift
// SodaLive
//
// Created by klaus on 7/10/25.
//
import Foundation
import Moya
enum HomeApi {
case getHomeData(isAdultContentVisible: Bool, contentType: ContentType)
case getLatestContentByTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType)
case getDayOfWeekSeriesList(dayOfWeek: SeriesPublishedDaysOfWeek, isAdultContentVisible: Bool, contentType: ContentType)
}
extension HomeApi: TargetType {
var baseURL: URL {
return URL(string: BASE_URL)!
}
var path: String {
switch self {
case .getHomeData:
return "/api/home"
case .getLatestContentByTheme:
return "/api/home/latest-content"
case .getDayOfWeekSeriesList:
return "/api/home/day-of-week-series"
}
}
var method: Moya.Method {
return .get
}
var task: Moya.Task {
switch self {
case .getHomeData(let isAdultContentVisible, let contentType):
let parameters = [
"timezone": TimeZone.current.identifier,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String: Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getLatestContentByTheme(let theme, let isAdultContentVisible, let contentType):
let parameters = [
"theme": theme,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String: Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getDayOfWeekSeriesList(let dayOfWeek, let isAdultContentVisible, let contentType):
let parameters = [
"dayOfWeek": dayOfWeek,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String: Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
}
}
var headers: [String : String]? {
return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"]
}
}

View File

@@ -0,0 +1,18 @@
//
// HomeCurationView.swift
// SodaLive
//
// Created by klaus on 7/10/25.
//
import SwiftUI
struct HomeCurationView: View {
var body: some View {
VStack(spacing: 30) {}
}
}
#Preview {
HomeCurationView()
}

View File

@@ -0,0 +1,15 @@
//
// HomeTabRepository.swift
// SodaLive
//
// Created by klaus on 7/10/25.
//
import Foundation
import CombineMoya
import Combine
import Moya
class HomeTabRepository {
private let api = MoyaProvider<HomeApi>()
}

View File

@@ -0,0 +1,204 @@
//
// HomeTabView.swift
// SodaLive
//
// Created by klaus on 7/10/25.
//
import SwiftUI
struct HomeTabView: View {
@StateObject var viewModel = ContentMainTabHomeViewModel()
@AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token)
@AppStorage("role") private var role: String = UserDefaults.string(forKey: UserDefaultsKey.role)
var body: some View {
BaseView(isLoading: $viewModel.isLoading) {
ZStack(alignment: .bottomTrailing) {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) {
Image("img_text_logo")
Spacer()
if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Image("ic_can")
.onTapGesture {
AppState
.shared
.setAppStep(step: .canCharge(refresh: {}))
}
}
}
.padding(.horizontal, 24)
.padding(.vertical, 20)
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 48) {
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("지금")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 라이브중")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("인기")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 크리에이터")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("최신")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 콘텐츠")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("오직")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 보이스온에서만")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("요일별")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 시리즈")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("보온")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 주간 차트")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("추천")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 채널")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) {
Text("무료")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.button)
Text(" 콘텐츠")
.font(.custom(Font.bold.rawValue, size: 26))
.foregroundColor(.white)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
}
}
}
}
.padding(24)
}
}
}
}
}
}
#Preview {
HomeTabView()
}

View File

@@ -0,0 +1,35 @@
//
// HomeTabViewModel.swift
// SodaLive
//
// Created by klaus on 7/10/25.
//
import Foundation
import Combine
enum SeriesPublishedDaysOfWeek: String, Encodable {
case SUN, MON, TUE, WED, THU, FRI, SAT, RANDOM
}
final class HomeTabViewModel: ObservableObject {
private let repository = HomeTabRepository()
private var subscription = Set<AnyCancellable>()
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published var isLoading = false
@Published var liveList: [GetRoomListResponse] = []
@Published var creatorRanking: [GetExplorerSectionCreatorResponse] = []
@Published var latestContentThemeList: [String] = []
@Published var latestContentList: [AudioContentMainItem] = []
@Published var eventBannerList: GetEventResponse? = nil
@Published var originalAudioDramaList: [SeriesListItem] = []
@Published var auditionList: [GetAuditionListItem] = []
@Published var dayOfWeekSeriesList: [SeriesListItem] = []
@Published var contentRanking: [GetAudioContentRankingItem] = []
@Published var recommendChannelList: [RecommendChannelResponse] = []
@Published var freeContentList: [AudioContentMainItem] = []
@Published var curationList: [GetContentCurationResponse] = []
}

View File

@@ -0,0 +1,22 @@
//
// RecommendChannelResponse.swift
// SodaLive
//
// Created by klaus on 7/11/25.
//
struct RecommendChannelResponse: Decodable {
let channelId: Int
let creatorNickname: String
let creatorProfileImageUrl: String
let contentCount: Int
let contentList: [RecommendChannelContentItem]
}
struct RecommendChannelContentItem: Decodable {
let contentId: Int
let title: String
let thumbnailImageUrl: String
let likeCount: Int
let commentCount: Int
}