콘텐츠 메인 시리즈 탭

- 완결 시리즈 전체보기 추가
This commit is contained in:
Yu Sung 2025-02-22 08:29:02 +09:00
parent fac5fe9a84
commit 58258078c3
7 changed files with 184 additions and 2 deletions

View File

@ -143,4 +143,6 @@ enum AppStep {
case searchChannel case searchChannel
case contentMain(startTab: ContentMainTab) case contentMain(startTab: ContentMainTab)
case completedSeriesAll
} }

View File

@ -45,6 +45,7 @@ enum ContentApi {
case getContentMainSeries case getContentMainSeries
case getRecommendSeriesListByGenre(genreId: Int) case getRecommendSeriesListByGenre(genreId: Int)
case getRecommendSeriesByCreator(creatorId: Int) case getRecommendSeriesByCreator(creatorId: Int)
case getCompletedSeries(page: Int, size: Int)
case getContentMainContent case getContentMainContent
case getContentMainNewContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType) case getContentMainNewContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType)
@ -175,6 +176,9 @@ extension ContentApi: TargetType {
case .getRecommendSeriesByCreator: case .getRecommendSeriesByCreator:
return "/v2/audio-content/main/series/recommend-series-by-creator" return "/v2/audio-content/main/series/recommend-series-by-creator"
case .getCompletedSeries:
return "/v2/audio-content/main/series/completed-rank"
case .getContentMainContent: case .getContentMainContent:
return "/v2/audio-content/main/content" return "/v2/audio-content/main/content"
@ -233,7 +237,7 @@ extension ContentApi: TargetType {
case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator, .getContentMainContent, case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator, .getContentMainContent,
.getContentMainNewContentOfTheme, .getDailyContentRanking, .getRecommendContentByTag, .getContentMainAlarm, .getContentMainAlarmAll, .getContentMainNewContentOfTheme, .getDailyContentRanking, .getRecommendContentByTag, .getContentMainAlarm, .getContentMainAlarmAll,
.getContentMainAsmr, .getPopularAsmrContentByCreator, .getContentMainReplay, .getPopularReplayContentByCreator, .getContentMainAsmr, .getPopularAsmrContentByCreator, .getContentMainReplay, .getPopularReplayContentByCreator,
.getContentMainFree, .getIntroduceCreatorList, .getNewFreeContentOfTheme, .getPopularFreeContentByCreator: .getContentMainFree, .getIntroduceCreatorList, .getNewFreeContentOfTheme, .getPopularFreeContentByCreator, .getCompletedSeries:
return .get return .get
case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent:
@ -437,6 +441,13 @@ extension ContentApi: TargetType {
case .getPopularAsmrContentByCreator(let creatorId), .getPopularReplayContentByCreator(let creatorId), .getPopularFreeContentByCreator(let creatorId): case .getPopularAsmrContentByCreator(let creatorId), .getPopularReplayContentByCreator(let creatorId), .getPopularFreeContentByCreator(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 .getCompletedSeries(let page, let size):
let parameters = [
"page": page - 1,
"size": size
]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
} }
} }

View File

@ -0,0 +1,86 @@
//
// CompletedSeriesView.swift
// SodaLive
//
// Created by klaus on 2/22/25.
//
import SwiftUI
struct CompletedSeriesView: View {
@StateObject var viewModel = CompletedSeriesViewModel()
private let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 13.3) {
DetailNavigationBar(title: "완결 시리즈")
HStack(alignment: .center, spacing: 0) {
Text("전체")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.graye2)
Text("\(viewModel.totalCount)")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.mainRed)
.padding(.leading, 6.7)
Text("")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.graye2)
Spacer()
}
.padding(.horizontal, 13.3)
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 13.3) {
ForEach(0..<viewModel.rankCompleteSeriesList.count, id: \.self) { index in
let item = viewModel.rankCompleteSeriesList[index]
SeriesListItemView(
itemWidth: (screenSize().width - (13.3 * 4)) / 3,
item: item
)
.onAppear {
if index == viewModel.rankCompleteSeriesList.count - 1 {
viewModel.getCompletedSeries()
}
}
}
}
.padding(.horizontal, 13.3)
}
}
}
.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()
}
}
.onAppear {
viewModel.getCompletedSeries()
}
}
}
#Preview {
CompletedSeriesView()
}

View File

@ -0,0 +1,73 @@
//
// CompletedSeriesViewModel.swift
// SodaLive
//
// Created by klaus on 2/22/25.
//
import Foundation
import Combine
final class CompletedSeriesViewModel: ObservableObject {
private let repository = ContentMainTabSeriesRepository()
private var subscription = Set<AnyCancellable>()
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published var isLoading = false
@Published var rankCompleteSeriesList: [SeriesListItem] = []
@Published var totalCount = 0
var isLast = false
var page = 1
private let size = 20
func getCompletedSeries() {
if !isLast && !isLoading {
isLoading = true
repository.getCompletedSeries(page: page, size: size)
.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<GetSeriesListResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
page += 1
if (data.items.count > 0) {
self.totalCount = data.totalCount
self.rankCompleteSeriesList = data.items
} 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

@ -24,4 +24,8 @@ final class ContentMainTabSeriesRepository {
func getRecommendSeriesByCreator(creatorId: Int) -> AnyPublisher<Response, MoyaError> { func getRecommendSeriesByCreator(creatorId: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getRecommendSeriesByCreator(creatorId: creatorId)) return api.requestPublisher(.getRecommendSeriesByCreator(creatorId: creatorId))
} }
func getCompletedSeries(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getCompletedSeries(page: page, size: size))
}
} }

View File

@ -52,7 +52,10 @@ struct ContentMainTabSeriesView: View {
if !viewModel.rankCompleteSeriesList.isEmpty { if !viewModel.rankCompleteSeriesList.isEmpty {
ContentMainCompletedSeriesView( ContentMainCompletedSeriesView(
itemList: viewModel.rankCompleteSeriesList, itemList: viewModel.rankCompleteSeriesList,
onClickMore: {} onClickMore: {
AppState.shared
.setAppStep(step: .completedSeriesAll)
}
) )
.padding(.top, 30) .padding(.top, 30)
} }

View File

@ -218,6 +218,9 @@ struct ContentView: View {
case .contentMain(let startTab): case .contentMain(let startTab):
ContentMainViewV2(selectedTab: startTab) ContentMainViewV2(selectedTab: startTab)
case .completedSeriesAll:
CompletedSeriesView()
default: default:
EmptyView() EmptyView()
.frame(width: 0, height: 0, alignment: .topLeading) .frame(width: 0, height: 0, alignment: .topLeading)