From 021fbd5294951829bb79a549e6d1edbae1577c8a Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Sat, 22 Feb 2025 05:05:07 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20ASMR=20=ED=83=AD=20UI=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/Content/ContentApi.swift | 18 +++++- .../Asmr/ContentMainTabAsmrRepository.swift | 24 +++++++ .../Main/V2/Asmr/ContentMainTabAsmrView.swift | 64 ++++++++++++++++++- .../V2/Asmr/ContentMainTabAsmrViewModel.swift | 58 +++++++++++++++++ .../Asmr/GetContentMainTabAsmrResponse.swift | 15 +++++ .../Main/V2/ContentMainNewContentViewV2.swift | 21 +++--- 6 files changed, 188 insertions(+), 12 deletions(-) create mode 100644 SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Asmr/GetContentMainTabAsmrResponse.swift diff --git a/SodaLive/Sources/Content/ContentApi.swift b/SodaLive/Sources/Content/ContentApi.swift index d8f7d74..714306d 100644 --- a/SodaLive/Sources/Content/ContentApi.swift +++ b/SodaLive/Sources/Content/ContentApi.swift @@ -53,6 +53,9 @@ enum ContentApi { case getContentMainAlarm case getContentMainAlarmAll(theme: String, page: Int, size: Int) + + case getContentMainAsmr + case getPopularAsmrContentByCreator(creatorId: Int) } extension ContentApi: TargetType { @@ -181,6 +184,12 @@ extension ContentApi: TargetType { case .getContentMainAlarmAll: return "/v2/audio-content/main/alarm/all" + + case .getContentMainAsmr: + return "/v2/audio-content/main/asmr" + + case .getPopularAsmrContentByCreator: + return "/v2/audio-content/main/asmr/popular-content-by-creator" } } @@ -196,7 +205,8 @@ extension ContentApi: TargetType { return .get case .getContentMainHome, .getPopularContentByCreator, .getContentMainSeries, .getRecommendSeriesListByGenre, .getRecommendSeriesByCreator, .getContentMainContent, - .getContentMainNewContentOfTheme, .getDailyContentRanking, .getRecommendContentByTag, .getContentMainAlarm, .getContentMainAlarmAll: + .getContentMainNewContentOfTheme, .getDailyContentRanking, .getRecommendContentByTag, .getContentMainAlarm, .getContentMainAlarmAll, + .getContentMainAsmr, .getPopularAsmrContentByCreator: return .get case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: @@ -353,7 +363,7 @@ extension ContentApi: TargetType { return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) - case .getContentMainHome, .getContentMainSeries, .getContentMainContent, .getContentMainAlarm: + case .getContentMainHome, .getContentMainSeries, .getContentMainContent, .getContentMainAlarm, .getContentMainAsmr: return .requestPlain case .getRecommendSeriesListByGenre(let genreId): @@ -392,6 +402,10 @@ extension ContentApi: TargetType { ] as [String : Any] return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + + case .getPopularAsmrContentByCreator(let creatorId): + let parameters = ["creatorId": creatorId] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift new file mode 100644 index 0000000..3a131e8 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrRepository.swift @@ -0,0 +1,24 @@ +// +// ContentMainTabAsmrRepository.swift +// SodaLive +// +// Created by klaus on 2/22/25. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +final class ContentMainTabAsmrRepository { + + private let api = MoyaProvider() + + func getContentMainAsmr() -> AnyPublisher { + return api.requestPublisher(.getContentMainAsmr) + } + + func getPopularContentByCreator(creatorId: Int) -> AnyPublisher { + return api.requestPublisher(.getPopularContentByCreator(creatorId: creatorId)) + } +} diff --git a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrView.swift b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrView.swift index ecbca0d..5a92c1a 100644 --- a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrView.swift +++ b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrView.swift @@ -8,8 +8,70 @@ import SwiftUI struct ContentMainTabAsmrView: View { + + @StateObject var viewModel = ContentMainTabAsmrViewModel() + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + BaseView(isLoading: $viewModel.isLoading) { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 0) { + if !viewModel.bannerList.isEmpty { + ContentMainBannerViewV2(bannerList: viewModel.bannerList) + .padding(.horizontal, 13.3) + } + + if !viewModel.newAsmrContentList.isEmpty { + ContentMainNewContentViewV2( + title: "새로운 ASMR", + onClickMore: {}, + themeList: [], + contentList: viewModel.newAsmrContentList + ) { _ in } + .padding(.top, 30) + } + + if !viewModel.creatorList.isEmpty { + ContentByChannelView( + title: "채널별 추천 ASMR", + creatorList: viewModel.creatorList, + contentList: viewModel.salesCountRankContentList, + onClickCreator: { + viewModel.getPopularContentByCreator(creatorId: $0) + } + ) + .padding(.top, 30) + } + + if !viewModel.eventBannerList.isEmpty { + SectionEventBannerView(items: viewModel.eventBannerList) + .padding(.top, 30) + } + + if !viewModel.curationList.isEmpty { + ContentMainCurationViewV2(curationList: viewModel.curationList) + .padding(.top, 30) + } + } + .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() + } + } } } diff --git a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift index 3088df5..7fe58c9 100644 --- a/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift +++ b/SodaLive/Sources/Content/Main/V2/Asmr/ContentMainTabAsmrViewModel.swift @@ -6,9 +6,67 @@ // import Foundation +import Combine final class ContentMainTabAsmrViewModel: ObservableObject { + private let repository = ContentMainTabAsmrRepository() + private var subscription = Set() + @Published var errorMessage = "" @Published var isShowPopup = false @Published var isLoading = false + + @Published var bannerList: [GetAudioContentBannerResponse] = [] + @Published var newAsmrContentList: [GetAudioContentMainItem] = [] + @Published var creatorList: [ContentCreatorResponse] = [] + @Published var salesCountRankContentList: [GetAudioContentRankingItem] = [] + @Published var eventBannerList: [EventItem] = [] + @Published var curationList: [GetContentCurationResponse] = [] + + func fetchData() { + isLoading = true + repository.getContentMainAsmr() + .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.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.bannerList = data.contentBannerList + self.newAsmrContentList = data.newAsmrContentList + self.creatorList = data.creatorList + self.salesCountRankContentList = data.salesCountRankContentList + self.eventBannerList = data.eventBannerList.eventList + self.curationList = data.curationList + } 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) { + + } } diff --git a/SodaLive/Sources/Content/Main/V2/Asmr/GetContentMainTabAsmrResponse.swift b/SodaLive/Sources/Content/Main/V2/Asmr/GetContentMainTabAsmrResponse.swift new file mode 100644 index 0000000..5b54bc7 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Asmr/GetContentMainTabAsmrResponse.swift @@ -0,0 +1,15 @@ +// +// GetContentMainTabAsmrResponse.swift +// SodaLive +// +// Created by klaus on 2/22/25. +// + +struct GetContentMainTabAsmrResponse: Decodable { + let contentBannerList: [GetAudioContentBannerResponse] + let newAsmrContentList: [GetAudioContentMainItem] + let creatorList: [ContentCreatorResponse] + let salesCountRankContentList: [GetAudioContentRankingItem] + let eventBannerList: GetEventResponse + let curationList: [GetContentCurationResponse] +} diff --git a/SodaLive/Sources/Content/Main/V2/ContentMainNewContentViewV2.swift b/SodaLive/Sources/Content/Main/V2/ContentMainNewContentViewV2.swift index 1d4e3eb..fcb63c0 100644 --- a/SodaLive/Sources/Content/Main/V2/ContentMainNewContentViewV2.swift +++ b/SodaLive/Sources/Content/Main/V2/ContentMainNewContentViewV2.swift @@ -22,23 +22,24 @@ struct ContentMainNewContentViewV2: View { HStack(spacing: 0) { Text(title) .font(.custom(Font.bold.rawValue, size: 18.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(.grayee) Spacer() Image("ic_forward") .resizable() .frame(width: 20, height: 20) - .onTapGesture { - } + .onTapGesture { onClickMore() } } .padding(.horizontal, 13.3) - ContentMainContentThemeView( - themeList: themeList, - selectTheme: selectTheme, - selectedTheme: $selectedTheme - ) + if !themeList.isEmpty { + ContentMainContentThemeView( + themeList: themeList, + selectTheme: selectTheme, + selectedTheme: $selectedTheme + ) + } ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 13.3) { @@ -50,7 +51,9 @@ struct ContentMainNewContentViewV2: View { } } .onAppear { - selectedTheme = themeList[0] + if !themeList.isEmpty { + selectedTheme = themeList[0] + } } } }