diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index 46bc904..2e63d8f 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -111,4 +111,6 @@ enum AppStep { case newContentAll case curationAll(title: String, curationId: Int) + + case contentRankingAll } diff --git a/SodaLive/Sources/Content/All/ContentRankingAllView.swift b/SodaLive/Sources/Content/All/ContentRankingAllView.swift new file mode 100644 index 0000000..5b103f2 --- /dev/null +++ b/SodaLive/Sources/Content/All/ContentRankingAllView.swift @@ -0,0 +1,153 @@ +// +// ContentRankingAllView.swift +// SodaLive +// +// Created by klaus on 2023/10/15. +// + +import SwiftUI +import Kingfisher + +struct ContentRankingAllView: View { + + @StateObject var viewModel = ContentRankingAllViewModel() + + var body: some View { + BaseView(isLoading: $viewModel.isLoading) { + VStack(spacing: 0) { + DetailNavigationBar(title: "인기 콘텐츠") + + VStack(spacing: 8) { + Text("\(viewModel.dateString)") + .font(.custom(Font.bold.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "eeeeee")) + + Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.") + .font(.custom(Font.light.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "bbbbbb")) + } + .padding(.vertical, 8) + .frame(width: screenSize().width - 26.7) + .background(Color(hex: "222222")) + .padding(.top, 13.3) + + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 20) { + ForEach(0.. 0 { + HStack(spacing: 8) { + Image("ic_can") + .resizable() + .frame(width: 17, height: 17) + + Text("\(item.price)") + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "909090")) + } + } else { + Text("무료") + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "ffffff")) + .padding(.horizontal, 5.3) + .padding(.vertical, 2.7) + .background(Color(hex: "cf5c37")) + .cornerRadius(2.6) + } + } + .frame(height: 66.7) + .contentShape(Rectangle()) + .onTapGesture { + AppState + .shared + .setAppStep(step: .contentDetail(contentId: item.contentId)) + } + .onAppear { + if index == viewModel.contentRankingItemList.count - 1 { + viewModel.getContentRanking() + } + } + } + } + } + .padding(13.3) + } + + if viewModel.isLoading { + LoadingView() + } + } + .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .padding(.horizontal, 6.7) + .frame(width: geo.size.width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color(hex: "9970ff")) + .foregroundColor(Color.white) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + } + } + .onAppear { + viewModel.getContentRanking() + } + } +} + +struct ContentRankingAllView_Previews: PreviewProvider { + static var previews: some View { + ContentRankingAllView() + } +} diff --git a/SodaLive/Sources/Content/All/ContentRankingAllViewModel.swift b/SodaLive/Sources/Content/All/ContentRankingAllViewModel.swift new file mode 100644 index 0000000..a3f0e58 --- /dev/null +++ b/SodaLive/Sources/Content/All/ContentRankingAllViewModel.swift @@ -0,0 +1,79 @@ +// +// ContentRankingAllViewModel.swift +// SodaLive +// +// Created by klaus on 2023/10/15. +// + +import Foundation +import Combine + +final class ContentRankingAllViewModel: ObservableObject { + + private let repository = ContentRepository() + private var subscription = Set() + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + + @Published var dateString = "" + @Published var contentRankingItemList = [GetAudioContentRankingItem]() + + var page = 1 + var isLast = false + private let pageSize = 10 + + func getContentRanking() { + if (!isLast && !isLoading && page <= 5) { + isLoading = true + + repository.getContentRanking(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 + self.isLoading = false + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + self.isLoading = false + + if let data = decoded.data, decoded.success { + if page == 1 { + contentRankingItemList.removeAll() + } + + dateString = "\(data.startDate)~\(data.endDate)" + + if !data.items.isEmpty { + page += 1 + self.contentRankingItemList.append(contentsOf: 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) + } + } +} diff --git a/SodaLive/Sources/Content/ContentApi.swift b/SodaLive/Sources/Content/ContentApi.swift index d7b14de..94f2843 100644 --- a/SodaLive/Sources/Content/ContentApi.swift +++ b/SodaLive/Sources/Content/ContentApi.swift @@ -29,6 +29,7 @@ enum ContentApi { case getNewContentThemeList case getNewContentAllOfTheme(theme: String, page: Int, size: Int) case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) + case getContentRanking(page: Int, size: Int) } extension ContentApi: TargetType { @@ -97,12 +98,17 @@ extension ContentApi: TargetType { case .getAudioContentListByCurationId(let curationId, _, _, _): return "/audio-content/curation/\(curationId)" + + case .getContentRanking: + return "/audio-content/ranking" } } var method: Moya.Method { switch self { - case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme, .getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId: + case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, + .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme, + .getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId, .getContentRanking: return .get case .likeContent, .modifyAudioContent, .modifyComment: @@ -212,6 +218,14 @@ extension ContentApi: TargetType { "sort-type": sort ] as [String : Any] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + + case .getContentRanking(let page, let size): + let parameters = [ + "page": page - 1, + "size": size, + ] as [String : Any] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/Content/ContentRepository.swift b/SodaLive/Sources/Content/ContentRepository.swift index 45989b9..74104f9 100644 --- a/SodaLive/Sources/Content/ContentRepository.swift +++ b/SodaLive/Sources/Content/ContentRepository.swift @@ -92,4 +92,8 @@ final class ContentRepository { func getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) -> AnyPublisher { return api.requestPublisher(.getAudioContentListByCurationId(curationId: curationId, page: page, size: size, sort: sort)) } + + func getContentRanking(page: Int, size: Int) -> AnyPublisher { + return api.requestPublisher(.getContentRanking(page: page, size: size)) + } } diff --git a/SodaLive/Sources/Content/Main/ContentMainRankingView.swift b/SodaLive/Sources/Content/Main/ContentMainRankingView.swift index 254be0d..e79381d 100644 --- a/SodaLive/Sources/Content/Main/ContentMainRankingView.swift +++ b/SodaLive/Sources/Content/Main/ContentMainRankingView.swift @@ -28,7 +28,9 @@ struct ContentMainRankingView: View { Spacer() Image("ic_forward") - .onTapGesture {} + .onTapGesture { + AppState.shared.setAppStep(step: .contentRankingAll) + } } VStack(spacing: 8) { @@ -72,6 +74,7 @@ struct ContentMainRankingView: View { } } .frame(maxWidth: screenSize().width * 0.66, alignment: .leading) + .contentShape(Rectangle()) .onTapGesture { AppState .shared diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index ad49a92..19242b5 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -163,6 +163,9 @@ struct ContentView: View { case .curationAll(let title, let curationId): ContentCurationView(title: title, curationId: curationId) + case .contentRankingAll: + ContentRankingAllView() + default: EmptyView() .frame(width: 0, height: 0, alignment: .topLeading)