오디션 메인

- 페이지 추가
This commit is contained in:
Yu Sung 2025-01-06 14:34:42 +09:00
parent 5c3bf18d27
commit 3d8817275c
6 changed files with 303 additions and 1 deletions

View File

@ -0,0 +1,52 @@
//
// AuditionApi.swift
// SodaLive
//
// Created by klaus on 1/5/25.
//
import Foundation
import Moya
enum AuditionApi {
case getAuditionList(page: Int, size: Int)
}
extension AuditionApi: TargetType {
var baseURL: URL {
return URL(string: BASE_URL)!
}
var path: String {
switch self {
case .getAuditionList:
return "/audition"
}
}
var method: Moya.Method {
switch self {
case .getAuditionList:
return .get
}
}
var task: Moya.Task {
switch self {
case .getAuditionList(let page, let size):
let parameters = [
"page": page - 1,
"size": size
] 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,49 @@
//
// AuditionItemView.swift
// SodaLive
//
// Created by klaus on 1/6/25.
//
import SwiftUI
import Kingfisher
struct AuditionItemView: View {
let item: GetAuditionListItem
var body: some View {
VStack(alignment: .leading, spacing: 10) {
ZStack {
KFImage(URL(string: item.imageUrl))
.cancelOnDisappear(true)
.downsampling(size: CGSize(width: 1000, height: 530))
.resizable()
.aspectRatio(1000/530, contentMode: .fit)
.frame(maxWidth: .infinity)
.overlay(
Color.black
.opacity(item.isOff ? 0.7 : 0.0)
)
}
.frame(maxWidth: .infinity)
Text(item.title)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color.grayee)
.lineLimit(1)
.truncationMode(.tail)
}
}
}
#Preview {
AuditionItemView(
item: GetAuditionListItem(
id: 1,
title: "[원작] 성인식",
imageUrl: "https://test-cf.sodalive.net/audition/production/3/audition-aa934579-c01a-4da2-89fd-cce70d51c612-4267-1735908116928",
isOff: false
)
)
}

View File

@ -0,0 +1,19 @@
//
// AuditionRepository.swift
// SodaLive
//
// Created by klaus on 1/5/25.
//
import Foundation
import CombineMoya
import Combine
import Moya
final class AuditionRepository {
private let api = MoyaProvider<AuditionApi>()
func getAuditionList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getAuditionList(page: page, size: size))
}
}

View File

@ -8,8 +8,86 @@
import SwiftUI
struct AuditionView: View {
@StateObject var viewModel = AuditionViewModel()
var body: some View {
Text("오디션")
BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) {
HomeNavigationBar(title: "오디션") {}
ScrollView(.vertical, showsIndicators: false) {
LazyVStack(alignment: .leading, spacing: 16.7) {
ForEach(0..<viewModel.auditionList.count, id: \.self) {
let item = viewModel.auditionList[$0]
if $0 == 0 && !item.isOff {
VStack(alignment: .leading, spacing: 16.7) {
HStack(spacing: 0) {
Text("오디션")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color.grayee)
Text(" ON")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color.mainRed)
Spacer()
Text("\(viewModel.inProgressCount)")
.font(.custom(Font.medium.rawValue, size: 11.3))
.foregroundColor(Color.graybb)
}
AuditionItemView(item: item)
}
} else if $0 == viewModel.firstIsOffIndex {
VStack(alignment: .leading, spacing: 0) {
Color.gray23
.frame(maxWidth: .infinity)
.frame(height: 6.7)
.padding(.top, 13.3)
HStack(spacing: 0) {
Text("오디션")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color.grayee)
Text(" OFF")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color.graybb)
Spacer()
Text("\(viewModel.completedCount)")
.font(.custom(Font.medium.rawValue, size: 11.3))
.foregroundColor(Color.graybb)
}
.padding(.top, 30)
AuditionItemView(item: item)
.padding(.top, 16.7)
}
} else {
AuditionItemView(item: item)
}
if $0 == viewModel.auditionList.count - 1 {
Color.clear
.frame(height: 0)
.onAppear {
viewModel.getAuditionList()
}
}
}
}
.padding(.horizontal, 13.3)
}
}
.onAppear {
viewModel.getAuditionList()
}
}
}
}

View File

@ -0,0 +1,85 @@
//
// AuditionViewModel.swift
// SodaLive
//
// Created by klaus on 1/5/25.
//
import Foundation
import Combine
final class AuditionViewModel: ObservableObject {
private let repository = AuditionRepository()
private var subscription = Set<AnyCancellable>()
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published var isLoading = false
@Published var auditionList = [GetAuditionListItem]()
@Published var inProgressCount = 0
@Published var completedCount = 0
@Published var firstIsOffIndex = -1
var page = 1
var isLast = false
private let pageSize = 10
func getAuditionList() {
if !isLast && !isLoading {
isLoading = true
DEBUG_LOG("호출됨")
repository.getAuditionList(page: page, size: pageSize)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] response in
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<GetAuditionListResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
if page == 1 {
self.auditionList.removeAll()
}
self.inProgressCount = data.inProgressCount
self.completedCount = data.completedCount
if !data.items.isEmpty {
page += 1
self.auditionList.append(contentsOf: data.items)
self.firstIsOffIndex = self.auditionList.firstIndex(where: { $0.isOff }) ?? -1
} else {
isLast = true
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
self.isLoading = false
}
.store(in: &subscription)
}
}
}

View File

@ -0,0 +1,19 @@
//
// GetAuditionListResponse.swift
// SodaLive
//
// Created by klaus on 1/5/25.
//
struct GetAuditionListResponse: Decodable {
let inProgressCount: Int
let completedCount: Int
let items: [GetAuditionListItem]
}
struct GetAuditionListItem: Decodable {
let id: Int
let title: String
let imageUrl: String
let isOff: Bool
}