콘텐츠 메인 - Api 분리
This commit is contained in:
		| @@ -22,8 +22,11 @@ enum ContentApi { | ||||
|     case getAudioContentCommentReplyList(commentId: Int, page: Int, size: Int) | ||||
|     case deleteAudioContent(audioContentId: Int) | ||||
|     case modifyAudioContent(parameters: [MultipartFormData]) | ||||
|     case getMain | ||||
|     case getNewContentUploadCreatorList | ||||
|     case getMainBannerList | ||||
|     case getMainOrderList | ||||
|     case getNewContentOfTheme(theme: String) | ||||
|     case getCurationList(page: Int, size: Int) | ||||
|     case donation(request: AudioContentDonationRequest) | ||||
|     case modifyComment(request: ModifyCommentRequest) | ||||
|     case getNewContentThemeList | ||||
| @@ -79,12 +82,21 @@ extension ContentApi: TargetType { | ||||
|         case .modifyAudioContent: | ||||
|             return "/audio-content" | ||||
|              | ||||
|         case .getMain: | ||||
|             return "/audio-content/main" | ||||
|         case .getNewContentUploadCreatorList: | ||||
|             return "/audio-content/main/new-content-upload-creator" | ||||
|              | ||||
|         case .getMainBannerList: | ||||
|             return "/audio-content/main/banner-list" | ||||
|              | ||||
|         case .getMainOrderList: | ||||
|             return "/audio-content/main/order-list" | ||||
|              | ||||
|         case .getNewContentOfTheme: | ||||
|             return "/audio-content/main/new" | ||||
|              | ||||
|         case .getCurationList: | ||||
|             return "/audio-content/main/curation-list" | ||||
|              | ||||
|         case .donation: | ||||
|             return "/audio-content/donation" | ||||
|              | ||||
| @@ -111,11 +123,14 @@ extension ContentApi: TargetType { | ||||
|     var method: Moya.Method { | ||||
|         switch self { | ||||
|         case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, | ||||
|                 .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme, | ||||
|                 .getAudioContentCommentList, .getAudioContentCommentReplyList, .getNewContentOfTheme, | ||||
|                 .getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId, .getContentRanking, | ||||
|                 .getContentRankingSortType: | ||||
|             return .get | ||||
|              | ||||
|         case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList: | ||||
|             return .get | ||||
|              | ||||
|         case .likeContent, .modifyAudioContent, .modifyComment: | ||||
|             return .put | ||||
|              | ||||
| @@ -163,7 +178,7 @@ extension ContentApi: TargetType { | ||||
|         case .addAllPlaybackTracking(let request): | ||||
|             return .requestJSONEncodable(request) | ||||
|              | ||||
|         case .getAudioContentThemeList, .getMain: | ||||
|         case .getAudioContentThemeList, .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList: | ||||
|             return .requestPlain | ||||
|              | ||||
|         case .uploadAudioContent(let parameters): | ||||
| @@ -236,6 +251,14 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .getContentRankingSortType: | ||||
|             return .requestPlain | ||||
|              | ||||
|         case .getCurationList(let page, let size): | ||||
|             let parameters = [ | ||||
|                 "page": page - 1, | ||||
|                 "size": size | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -65,14 +65,26 @@ final class ContentRepository { | ||||
|         return api.requestPublisher(.modifyAudioContent(parameters: parameters)) | ||||
|     } | ||||
|      | ||||
|     func getMain() -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getMain) | ||||
|     func getNewContentUploadCreatorList() -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getNewContentUploadCreatorList) | ||||
|     } | ||||
|      | ||||
|     func getMainBannerList() -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getMainBannerList) | ||||
|     } | ||||
|      | ||||
|     func getMainOrderList() -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getMainOrderList) | ||||
|     } | ||||
|      | ||||
|     func getNewContentOfTheme(theme: String) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getNewContentOfTheme(theme: theme)) | ||||
|     } | ||||
|      | ||||
|     func getCurationList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getCurationList(page: page, size: size)) | ||||
|     } | ||||
|      | ||||
|     func donation(contentId: Int, can: Int, comment: String) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.donation(request: AudioContentDonationRequest(contentId: contentId, donationCan: can, comment: comment))) | ||||
|     } | ||||
|   | ||||
							
								
								
									
										122
									
								
								SodaLive/Sources/Content/Main/Banner/ContentMainBannerView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								SodaLive/Sources/Content/Main/Banner/ContentMainBannerView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| // | ||||
| //  ContentMainBannerView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import Kingfisher | ||||
|  | ||||
| struct ContentMainBannerView: View { | ||||
|      | ||||
|     @StateObject private var viewModel = ContentMainBannerViewModel() | ||||
|      | ||||
|     var body: some View { | ||||
|         ZStack { | ||||
|             if !viewModel.bannerList.isEmpty { | ||||
|                 VStack(spacing: 0) { | ||||
|                     TabView(selection: $viewModel.currentIndex) { | ||||
|                         ForEach(0..<viewModel.bannerList.count, id: \.self) { index in | ||||
|                             let item = viewModel.bannerList[index] | ||||
|                             if let url = item.thumbnailImageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { | ||||
|                                 KFImage(URL(string: url)) | ||||
|                                     .resizable() | ||||
|                                     .scaledToFill() | ||||
|                                     .frame( | ||||
|                                         width: screenSize().width - 26.7, | ||||
|                                         height: (screenSize().width - 26.7) * 0.53 | ||||
|                                     ) | ||||
|                                     .onTapGesture { | ||||
|                                         switch item.type { | ||||
|                                         case .EVENT: | ||||
|                                             AppState.shared.setAppStep(step: .eventDetail(event: item.eventItem!)) | ||||
|                                         case .CREATOR: | ||||
|                                             AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId!)) | ||||
|                                         case .LINK: | ||||
|                                             if let link = item.link, link.trimmingCharacters(in: .whitespaces).count > 0, let url = URL(string: link), UIApplication.shared.canOpenURL(url) { | ||||
|                                                 UIApplication.shared.open(url) | ||||
|                                             } | ||||
|                                         } | ||||
|                                     } | ||||
|                                     .cornerRadius(4.7) | ||||
|                             } else { | ||||
|                                 KFImage(URL(string: item.thumbnailImageUrl)) | ||||
|                                     .resizable() | ||||
|                                     .scaledToFill() | ||||
|                                     .frame( | ||||
|                                         width: screenSize().width - 26.7, | ||||
|                                         height: (screenSize().width - 26.7) * 0.53 | ||||
|                                     ) | ||||
|                                     .onTapGesture { | ||||
|                                         switch item.type { | ||||
|                                         case .EVENT: | ||||
|                                             AppState.shared.setAppStep(step: .eventDetail(event: item.eventItem!)) | ||||
|                                         case .CREATOR: | ||||
|                                             AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId!)) | ||||
|                                         case .LINK: | ||||
|                                             if let link = item.link, link.trimmingCharacters(in: .whitespaces).count > 0, let url = URL(string: link), UIApplication.shared.canOpenURL(url) { | ||||
|                                                 UIApplication.shared.open(url) | ||||
|                                             } | ||||
|                                         } | ||||
|                                     } | ||||
|                                     .cornerRadius(4.7) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) | ||||
|                     .frame( | ||||
|                         width: screenSize().width - 26.7, | ||||
|                         height: (screenSize().width - 26.7) * 0.53 | ||||
|                     ) | ||||
|                      | ||||
|                     HStack(spacing: 4) { | ||||
|                         ForEach(0..<viewModel.bannerList.count, id: \.self) { index in | ||||
|                             Capsule() | ||||
|                                 .foregroundColor(index == viewModel.currentIndex ? Color(hex: "9970ff") : Color(hex: "909090")) | ||||
|                                 .frame( | ||||
|                                     width: index == viewModel.currentIndex ? 18 : 6, | ||||
|                                     height: 6 | ||||
|                                 ) | ||||
|                                 .tag(index) | ||||
|                         } | ||||
|                     } | ||||
|                     .padding(.top, 13.3) | ||||
|                 } | ||||
|                 .frame(maxWidth: .infinity) | ||||
|                 .onAppear { | ||||
|                     viewModel.timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() | ||||
|                 } | ||||
|                 .onDisappear { | ||||
|                     viewModel.timer.upstream.connect().cancel() | ||||
|                 } | ||||
|                 .onReceive(viewModel.timer) { _ in | ||||
|                     DispatchQueue.main.async { | ||||
|                         withAnimation { | ||||
|                             if viewModel.currentIndex == viewModel.bannerList.count - 1 { | ||||
|                                 viewModel.currentIndex = 0 | ||||
|                             } else { | ||||
|                                 viewModel.currentIndex += 1 | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|                 ActivityIndicatorView() | ||||
|                     .frame(width: 100, height: 100) | ||||
|             } | ||||
|         } | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .onAppear { | ||||
|             viewModel.getBannerList() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainBannerView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainBannerView() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,65 @@ | ||||
| // | ||||
| //  ContentMainBannerViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/12/11. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainBannerViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var currentIndex = 0 | ||||
|     @Published var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() | ||||
|      | ||||
|     @Published var bannerList = [GetAudioContentBannerResponse]() | ||||
|      | ||||
|     func getBannerList() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getMainBannerList() | ||||
|             .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<[GetAudioContentBannerResponse]>.self, from: responseData) | ||||
|                     self.isLoading = false | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.bannerList.removeAll() | ||||
|                         self.bannerList.append(contentsOf: data) | ||||
|                     } 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) | ||||
|     } | ||||
| } | ||||
| @@ -1,114 +0,0 @@ | ||||
| // | ||||
| //  ContentMainBannerView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import Kingfisher | ||||
|  | ||||
| struct ContentMainBannerView: View { | ||||
|      | ||||
|     let items: [GetAudioContentBannerResponse] | ||||
|     @State private var currentIndex = 0 | ||||
|     @State private var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(spacing: 0) { | ||||
|             TabView(selection: $currentIndex) { | ||||
|                 ForEach(0..<items.count, id: \.self) { index in | ||||
|                     let item = items[index] | ||||
|                     if let url = item.thumbnailImageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { | ||||
|                         KFImage(URL(string: url)) | ||||
|                             .resizable() | ||||
|                             .scaledToFill() | ||||
|                             .frame( | ||||
|                                 width: screenSize().width - 26.7, | ||||
|                                 height: (screenSize().width - 26.7) * 0.53 | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 switch item.type { | ||||
|                                 case .EVENT: | ||||
|                                     AppState.shared.setAppStep(step: .eventDetail(event: item.eventItem!)) | ||||
|                                 case .CREATOR: | ||||
|                                     AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId!)) | ||||
|                                 case .LINK: | ||||
|                                     if let link = item.link, link.trimmingCharacters(in: .whitespaces).count > 0, let url = URL(string: link), UIApplication.shared.canOpenURL(url) { | ||||
|                                         UIApplication.shared.open(url) | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             .cornerRadius(4.7) | ||||
|                     } else { | ||||
|                         KFImage(URL(string: item.thumbnailImageUrl)) | ||||
|                             .resizable() | ||||
|                             .scaledToFill() | ||||
|                             .frame( | ||||
|                                 width: screenSize().width - 26.7, | ||||
|                                 height: (screenSize().width - 26.7) * 0.53 | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 switch item.type { | ||||
|                                 case .EVENT: | ||||
|                                     AppState.shared.setAppStep(step: .eventDetail(event: item.eventItem!)) | ||||
|                                 case .CREATOR: | ||||
|                                     AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId!)) | ||||
|                                 case .LINK: | ||||
|                                     if let link = item.link, link.trimmingCharacters(in: .whitespaces).count > 0, let url = URL(string: link), UIApplication.shared.canOpenURL(url) { | ||||
|                                         UIApplication.shared.open(url) | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             .cornerRadius(4.7) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) | ||||
|             .frame( | ||||
|                 width: screenSize().width - 26.7, | ||||
|                 height: (screenSize().width - 26.7) * 0.53 | ||||
|             ) | ||||
|              | ||||
|             HStack(spacing: 4) { | ||||
|                 ForEach(0..<items.count, id: \.self) { index in | ||||
|                     Capsule() | ||||
|                         .foregroundColor(index == currentIndex ? Color(hex: "9970ff") : Color(hex: "909090")) | ||||
|                         .frame( | ||||
|                             width: index == currentIndex ? 18 : 6, | ||||
|                             height: 6 | ||||
|                         ) | ||||
|                         .tag(index) | ||||
|                 } | ||||
|             } | ||||
|             .padding(.top, 13.3) | ||||
|         } | ||||
|         .frame(width: screenSize().width - 26.7) | ||||
|         .onAppear { | ||||
|             timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() | ||||
|         } | ||||
|         .onDisappear { | ||||
|             timer.upstream.connect().cancel() | ||||
|         } | ||||
|         .onReceive(timer) { _ in | ||||
|             DispatchQueue.main.async { | ||||
|                 withAnimation { | ||||
|                     if currentIndex == items.count - 1 { | ||||
|                         currentIndex = 0 | ||||
|                     } else { | ||||
|                         currentIndex += 1 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainBannerView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainBannerView( | ||||
|             items: [ | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| // | ||||
| //  ContentMainCurationView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainCurationView: View { | ||||
|      | ||||
|     let items: [GetAudioContentCurationResponse] | ||||
|      | ||||
|     var body: some View { | ||||
|         LazyVStack(spacing: 40) { | ||||
|             ForEach(0..<items.count, id: \.self) { | ||||
|                 ContentMainCurationItemView(item: items[$0]) | ||||
|                     .padding(.horizontal, 13.3) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| // | ||||
| //  ContentMainMyStashView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainMyStashView: View { | ||||
|      | ||||
|     let items: [GetAudioContentMainItem] | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(alignment: .leading, spacing: 13.3) { | ||||
|             HStack(spacing: 0) { | ||||
|                 Text("내 보관함") | ||||
|                     .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                     .foregroundColor(Color(hex: "eeeeee")) | ||||
|                  | ||||
|                 Spacer() | ||||
|                  | ||||
|                 Text("전체보기") | ||||
|                     .font(.custom(Font.light.rawValue, size: 11.3)) | ||||
|                     .foregroundColor(Color(hex: "bbbbbb")) | ||||
|                     .onTapGesture { | ||||
|                         AppState.shared.setAppStep(step: .orderListAll) | ||||
|                     } | ||||
|             } | ||||
|              | ||||
|             ScrollView(.horizontal, showsIndicators: false) { | ||||
|                 LazyHStack(alignment: .top, spacing: 13.3) { | ||||
|                     ForEach(0..<items.count, id: \.self) { index in | ||||
|                         let item = items[index] | ||||
|                         ContentMainItemView(item: item) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainMyStashView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainMyStashView( | ||||
|             items: [ | ||||
|                 GetAudioContentMainItem( | ||||
|                     contentId: 1, | ||||
|                     coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", | ||||
|                     title: "테스트", | ||||
|                     creatorId: 7, | ||||
|                     creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", | ||||
|                     creatorNickname: "유저1" | ||||
|                 ) | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| // | ||||
| //  ContentMainNewContentCreatorView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainNewContentCreatorView: View { | ||||
|      | ||||
|     let items: [GetNewContentUploadCreator] | ||||
|      | ||||
|     var body: some View { | ||||
|         ScrollView(.horizontal, showsIndicators: false) { | ||||
|             LazyHStack(spacing: 21.3) { | ||||
|                 ForEach(0..<items.count, id: \.self) { index in | ||||
|                     let item = items[index] | ||||
|                     ContentMainNewContentCreatorItemView(item: item) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainNewContentCreatorView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainNewContentCreatorView( | ||||
|             items: [ | ||||
|                 GetNewContentUploadCreator( | ||||
|                     creatorId: 1, | ||||
|                     creatorNickname: "수다친구1", | ||||
|                     creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" | ||||
|                 ), | ||||
|                 GetNewContentUploadCreator( | ||||
|                     creatorId: 2, | ||||
|                     creatorNickname: "수다친구2", | ||||
|                     creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" | ||||
|                 ), | ||||
|                 GetNewContentUploadCreator( | ||||
|                     creatorId: 3, | ||||
|                     creatorNickname: "수다친구3", | ||||
|                     creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" | ||||
|                 ) | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| // | ||||
| //  ContentMainNewContentView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainNewContentView: View { | ||||
|      | ||||
|     let themes: [String] | ||||
|     let items: [GetAudioContentMainItem] | ||||
|      | ||||
|     let selectTheme: (String) -> Void | ||||
|     @Binding var selectedTheme: String | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(alignment: .leading, spacing: 0) { | ||||
|             HStack(spacing: 0) { | ||||
|                 Text("새로운 콘텐츠") | ||||
|                     .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                     .foregroundColor(Color(hex: "eeeeee")) | ||||
|                  | ||||
|                 Spacer() | ||||
|                  | ||||
|                 Image("ic_forward") | ||||
|                     .resizable() | ||||
|                     .frame(width: 20, height: 20) | ||||
|                     .onTapGesture { | ||||
|                         AppState.shared.setAppStep(step: .newContentAll) | ||||
|                     } | ||||
|             } | ||||
|              | ||||
|             ContentMainNewContentThemeView(themes: themes, selectTheme: selectTheme, selectedTheme: $selectedTheme) | ||||
|                 .padding(.vertical, 16.7) | ||||
|              | ||||
|             ScrollView(.horizontal, showsIndicators: false) { | ||||
|                 HStack(alignment: .top, spacing: 13.3) { | ||||
|                     ForEach(0..<items.count, id: \.self) { | ||||
|                         ContentMainItemView(item: items[$0]) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainNewContentView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainNewContentView( | ||||
|             themes: ["전체", "테마1", "테마2"], | ||||
|             items: [ | ||||
|                 GetAudioContentMainItem( | ||||
|                     contentId: 1, | ||||
|                     coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", | ||||
|                     title: "테스트", | ||||
|                     creatorId: 7, | ||||
|                     creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", | ||||
|                     creatorNickname: "유저1" | ||||
|                 ) | ||||
|             ], | ||||
|             selectTheme: { _ in }, | ||||
|             selectedTheme: .constant("전체") | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -19,7 +19,7 @@ struct ContentMainView: View { | ||||
|             RefreshableScrollView( | ||||
|                 refreshing: $viewModel.isLoading, | ||||
|                 action: { | ||||
|                     viewModel.getMain() | ||||
|                     viewModel.refresh() | ||||
|                 } | ||||
|             ) { | ||||
|                 LazyVStack(alignment: .leading, spacing: 0) { | ||||
| @@ -29,46 +29,28 @@ struct ContentMainView: View { | ||||
|                         .padding(.bottom, 26.7) | ||||
|                         .padding(.horizontal, 13.3) | ||||
|                      | ||||
|                     if viewModel.newContentUploadCreatorList.count > 0 { | ||||
|                         ContentMainNewContentCreatorView(items: viewModel.newContentUploadCreatorList) | ||||
|                     if !viewModel.isLoading { | ||||
|                         ContentMainNewContentCreatorView() | ||||
|                             .padding(.bottom, 26.7) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                     } | ||||
|                          | ||||
|                     if viewModel.bannerList.count > 0 { | ||||
|                         ContentMainBannerView(items: viewModel.bannerList) | ||||
|                         ContentMainBannerView() | ||||
|                             .padding(.bottom, 40) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                     } | ||||
|                          | ||||
|                     if viewModel.orderList.count > 0 { | ||||
|                         ContentMainMyStashView(items: viewModel.orderList) | ||||
|                         ContentMainMyStashView() | ||||
|                             .padding(.bottom, 40) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                     } | ||||
|                          | ||||
|                     ContentMainNewContentView( | ||||
|                         themes: viewModel.themeList, | ||||
|                         items: viewModel.newContentList, | ||||
|                         selectTheme: { viewModel.selectedTheme = $0 }, | ||||
|                         selectedTheme: $viewModel.selectedTheme | ||||
|                     ) | ||||
|                     .padding(.horizontal, 13.3) | ||||
|                         ContentMainNewContentView() | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                     if let contentRanking = viewModel.contentRanking { | ||||
|                         ContentMainRankingView( | ||||
|                             sorts: viewModel.contentRankingSortList, | ||||
|                             item: contentRanking, | ||||
|                             selectSort: { viewModel.selectedContentRankingSort = $0 }, | ||||
|                             selectedSort: $viewModel.selectedContentRankingSort | ||||
|                         ) | ||||
|                         ContentMainRankingView() | ||||
|                             .padding(.top, 40) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                             .animation(nil) | ||||
|                     } | ||||
|                          | ||||
|                     if viewModel.curationList.count > 0 { | ||||
|                         ContentMainCurationView(items: viewModel.curationList) | ||||
|                         ContentMainCurationView() | ||||
|                             .padding(.top, 40) | ||||
|                             .padding(.bottom, 20) | ||||
|                     } | ||||
| @@ -100,28 +82,6 @@ struct ContentMainView: View { | ||||
|                 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.getMain() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -10,165 +10,13 @@ import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var newContentUploadCreatorList = [GetNewContentUploadCreator]() | ||||
|     @Published var newContentList = [GetAudioContentMainItem]() | ||||
|     @Published var bannerList = [GetAudioContentBannerResponse]() | ||||
|     @Published var orderList = [GetAudioContentMainItem]() | ||||
|     @Published var themeList = [String]() | ||||
|     @Published var curationList = [GetAudioContentCurationResponse]() | ||||
|     @Published var contentRankingSortList = [String]() | ||||
|     @Published var contentRanking: GetAudioContentRanking? = nil | ||||
|      | ||||
|     @Published var selectedTheme = "전체" { | ||||
|         didSet { | ||||
|             getNewContentOfTheme() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @Published var selectedContentRankingSort = "매출" { | ||||
|         didSet { | ||||
|             getContentRanking() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func getMain() { | ||||
|     func refresh() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getMain() | ||||
|             .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<GetAudioContentMainResponse>.self, from: responseData) | ||||
|                     self.isLoading = false | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.newContentUploadCreatorList.removeAll() | ||||
|                         self.newContentList.removeAll() | ||||
|                         self.bannerList.removeAll() | ||||
|                         self.orderList.removeAll() | ||||
|                         self.curationList.removeAll() | ||||
|                         self.themeList.removeAll() | ||||
|                         self.contentRankingSortList.removeAll() | ||||
|                          | ||||
|                         self.newContentUploadCreatorList.append(contentsOf: data.newContentUploadCreatorList) | ||||
|                         self.newContentList.append(contentsOf: data.newContentList) | ||||
|                         self.bannerList.append(contentsOf: data.bannerList) | ||||
|                         self.orderList.append(contentsOf: data.orderList) | ||||
|                         self.curationList.append(contentsOf: data.curationList) | ||||
|                         self.contentRanking = data.contentRanking | ||||
|                         self.contentRankingSortList.append(contentsOf: data.contentRankingSortTypeList) | ||||
|                          | ||||
|                         self.themeList.append("전체") | ||||
|                         self.themeList.append(contentsOf: data.themeList) | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     print(error) | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                     self.isLoading = false | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
|      | ||||
|     func getNewContentOfTheme() { | ||||
|         repository.getNewContentOfTheme(theme: selectedTheme == "전체" ? "" : selectedTheme) | ||||
|             .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<[GetAudioContentMainItem]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.newContentList.removeAll() | ||||
|                         self.newContentList.append(contentsOf: data) | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
|      | ||||
|     func getContentRanking() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getContentRanking(page: 1, size: 12, sortType: selectedContentRankingSort) | ||||
|             .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<GetAudioContentRanking>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.contentRanking = data | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|         DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [unowned self] in | ||||
|             self.isLoading = false | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,42 @@ | ||||
| // | ||||
| //  ContentMainCurationView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainCurationView: View { | ||||
|      | ||||
|     @StateObject private var viewModel = ContentMainCurationViewModel() | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack { | ||||
|             if !viewModel.curationList.isEmpty { | ||||
|                 LazyVStack(spacing: 40) { | ||||
|                     ForEach(0..<viewModel.curationList.count, id: \.self) { index in | ||||
|                         ContentMainCurationItemView(item: viewModel.curationList[index]) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                             .onAppear { | ||||
|                                 if index == viewModel.curationList.count - 1 { | ||||
|                                     viewModel.getCurationList() | ||||
|                                 } | ||||
|                             } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|                 ActivityIndicatorView() | ||||
|                     .frame(width: 100, height: 100) | ||||
|             } | ||||
|         } | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .onAppear { | ||||
|             viewModel.page = 1 | ||||
|             viewModel.isLast = false | ||||
|             viewModel.getCurationList() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,76 @@ | ||||
| // | ||||
| //  ContentMainCurationViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/12/11. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainCurationViewModel: ObservableObject { | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var curationList = [GetAudioContentCurationResponse]() | ||||
|      | ||||
|     var isLast = false | ||||
|     var page = 1 | ||||
|     private let size = 10 | ||||
|      | ||||
|     func getCurationList() { | ||||
|         if !isLoading && !isLast { | ||||
|             isLoading = true | ||||
|              | ||||
|             repository.getCurationList(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 | ||||
|                     self.isLoading = false | ||||
|                     let responseData = response.data | ||||
|                      | ||||
|                     do { | ||||
|                         let jsonDecoder = JSONDecoder() | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<[GetAudioContentCurationResponse]>.self, from: responseData) | ||||
|                         self.isLoading = false | ||||
|                          | ||||
|                         if let data = decoded.data, decoded.success { | ||||
|                             if page == 1 { | ||||
|                                 self.curationList.removeAll() | ||||
|                             } | ||||
|                              | ||||
|                             if !data.isEmpty { | ||||
|                                 page += 1 | ||||
|                                 self.curationList.append(contentsOf: data) | ||||
|                             } 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,68 @@ | ||||
| // | ||||
| //  ContentMainNewContentView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainNewContentView: View { | ||||
|      | ||||
|     @StateObject private var viewModel = ContentMainNewContentViewModel() | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(spacing: 16.7) { | ||||
|             HStack(spacing: 0) { | ||||
|                 Text("새로운 콘텐츠") | ||||
|                     .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                     .foregroundColor(Color(hex: "eeeeee")) | ||||
|                  | ||||
|                 Spacer() | ||||
|                  | ||||
|                 Image("ic_forward") | ||||
|                     .resizable() | ||||
|                     .frame(width: 20, height: 20) | ||||
|                     .onTapGesture { | ||||
|                         AppState.shared.setAppStep(step: .newContentAll) | ||||
|                     } | ||||
|             } | ||||
|              | ||||
|             if !viewModel.themeList.isEmpty { | ||||
|                 ContentMainNewContentThemeView( | ||||
|                     themes: viewModel.themeList, | ||||
|                     selectTheme: { theme in | ||||
|                         viewModel.selectedTheme = theme | ||||
|                     }, | ||||
|                     selectedTheme: $viewModel.selectedTheme | ||||
|                 ) | ||||
|             } | ||||
|              | ||||
|             if !viewModel.newContentList.isEmpty { | ||||
|                 ScrollView(.horizontal, showsIndicators: false) { | ||||
|                     HStack(alignment: .top, spacing: 13.3) { | ||||
|                         ForEach(0..<viewModel.newContentList.count, id: \.self) { | ||||
|                             ContentMainItemView(item: viewModel.newContentList[$0]) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|                 ActivityIndicatorView() | ||||
|                     .frame(width: 100, height: 100) | ||||
|             } | ||||
|         } | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .onAppear { | ||||
|             viewModel.getThemeList() | ||||
|             viewModel.getNewContentOfTheme() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainNewContentView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainNewContentView() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,104 @@ | ||||
| // | ||||
| //  ContentMainNewContentViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/12/11. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainNewContentViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var themeList = [String]() | ||||
|     @Published var newContentList = [GetAudioContentMainItem]() | ||||
|      | ||||
|     @Published var selectedTheme = "전체" { | ||||
|         didSet { | ||||
|             newContentList.removeAll() | ||||
|             getNewContentOfTheme() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func getThemeList() { | ||||
|         repository.getNewContentThemeList() | ||||
|             .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<[String]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.themeList.append("전체") | ||||
|                         self.themeList.append(contentsOf: data) | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
|      | ||||
|     func getNewContentOfTheme() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getNewContentOfTheme(theme: selectedTheme == "전체" ? "" : selectedTheme) | ||||
|             .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<[GetAudioContentMainItem]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.newContentList.removeAll() | ||||
|                         self.newContentList.append(contentsOf: data) | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,43 @@ | ||||
| // | ||||
| //  ContentMainNewContentCreatorView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainNewContentCreatorView: View { | ||||
|      | ||||
|     @StateObject private var viewModel = ContentMainNewContentCreatorViewModel() | ||||
|      | ||||
|     var body: some View { | ||||
|         ZStack { | ||||
|             if !viewModel.newContentUploadCreatorList.isEmpty { | ||||
|                 ScrollView(.horizontal, showsIndicators: false) { | ||||
|                     LazyHStack(spacing: 21.3) { | ||||
|                         ForEach(0..<viewModel.newContentUploadCreatorList.count, id: \.self) { index in | ||||
|                             let item = viewModel.newContentUploadCreatorList[index] | ||||
|                             ContentMainNewContentCreatorItemView(item: item) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|                 ActivityIndicatorView() | ||||
|                     .frame(width: 100, height: 100) | ||||
|             } | ||||
|         } | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .onAppear { | ||||
|             viewModel.getNewContentUploadCreatorList() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainNewContentCreatorView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainNewContentCreatorView() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,62 @@ | ||||
| // | ||||
| //  ContentMainNewContentCreatorViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/12/11. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainNewContentCreatorViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var newContentUploadCreatorList = [GetNewContentUploadCreator]() | ||||
|      | ||||
|     func getNewContentUploadCreatorList() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getNewContentUploadCreatorList() | ||||
|             .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<[GetNewContentUploadCreator]>.self, from: responseData) | ||||
|                     self.isLoading = false | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.newContentUploadCreatorList.removeAll() | ||||
|                         self.newContentUploadCreatorList.append(contentsOf: data) | ||||
|                     } 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) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,60 @@ | ||||
| // | ||||
| //  ContentMainMyStashView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/11. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentMainMyStashView: View { | ||||
|      | ||||
|     @StateObject private var viewModel = ContentMainMyStashViewModel() | ||||
|      | ||||
|     var body: some View { | ||||
|         ZStack { | ||||
|             if !viewModel.orderList.isEmpty { | ||||
|                 VStack(alignment: .leading, spacing: 13.3) { | ||||
|                     HStack(spacing: 0) { | ||||
|                         Text("내 보관함") | ||||
|                             .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                             .foregroundColor(Color(hex: "eeeeee")) | ||||
|                          | ||||
|                         Spacer() | ||||
|                          | ||||
|                         Text("전체보기") | ||||
|                             .font(.custom(Font.light.rawValue, size: 11.3)) | ||||
|                             .foregroundColor(Color(hex: "bbbbbb")) | ||||
|                             .onTapGesture { | ||||
|                                 AppState.shared.setAppStep(step: .orderListAll) | ||||
|                             } | ||||
|                     } | ||||
|                      | ||||
|                     ScrollView(.horizontal, showsIndicators: false) { | ||||
|                         LazyHStack(alignment: .top, spacing: 13.3) { | ||||
|                             ForEach(0..<viewModel.orderList.count, id: \.self) { index in | ||||
|                                 let item = viewModel.orderList[index] | ||||
|                                 ContentMainItemView(item: item) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|                 ActivityIndicatorView() | ||||
|                     .frame(width: 100, height: 100) | ||||
|             } | ||||
|         } | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .onAppear { | ||||
|             viewModel.getOrderList() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ContentMainMyStashView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainMyStashView() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,62 @@ | ||||
| // | ||||
| //  ContentMainMyStashViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/12/11. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainMyStashViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var orderList = [GetAudioContentMainItem]() | ||||
|      | ||||
|     func getOrderList() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getMainOrderList() | ||||
|             .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<[GetAudioContentMainItem]>.self, from: responseData) | ||||
|                     self.isLoading = false | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.orderList.removeAll() | ||||
|                         self.orderList.append(contentsOf: data) | ||||
|                     } 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) | ||||
|     } | ||||
| } | ||||
| @@ -10,11 +10,7 @@ import Kingfisher | ||||
| 
 | ||||
| struct ContentMainRankingView: View { | ||||
|      | ||||
|     let sorts: [String] | ||||
|     let item: GetAudioContentRanking | ||||
|      | ||||
|     let selectSort: (String) -> Void | ||||
|     @Binding var selectedSort: String | ||||
|     @StateObject private var viewModel = ContentMainRankingViewModel() | ||||
|      | ||||
|     let rows = [ | ||||
|         GridItem(.fixed(60), alignment: .leading), | ||||
| @@ -23,7 +19,7 @@ struct ContentMainRankingView: View { | ||||
|     ] | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(spacing: 0) { | ||||
|         VStack(spacing: 16.7) { | ||||
|             HStack(spacing: 0) { | ||||
|                 Text("인기 콘텐츠") | ||||
|                     .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
| @@ -38,7 +34,7 @@ struct ContentMainRankingView: View { | ||||
|             } | ||||
|              | ||||
|             VStack(spacing: 8) { | ||||
|                 Text("\(item.startDate) ~ \(item.endDate)") | ||||
|                 Text("\(viewModel.dateString)") | ||||
|                     .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                     .foregroundColor(Color(hex: "eeeeee")) | ||||
|                  | ||||
| @@ -49,15 +45,19 @@ struct ContentMainRankingView: View { | ||||
|             .padding(.vertical, 8) | ||||
|             .frame(width: screenSize().width - 26.7) | ||||
|             .background(Color(hex: "222222")) | ||||
|             .padding(.top, 13.3) | ||||
|              | ||||
|             ContentMainRankingSortView(sorts: sorts, selectSort: selectSort, selectedSort: $selectedSort) | ||||
|                 .padding(.vertical, 16.7) | ||||
|             if !viewModel.contentRankingSortList.isEmpty { | ||||
|                 ContentMainRankingSortView( | ||||
|                     sorts: viewModel.contentRankingSortList, | ||||
|                     selectSort: { viewModel.selectedContentRankingSort = $0 }, | ||||
|                     selectedSort: $viewModel.selectedContentRankingSort | ||||
|                 ) | ||||
|             } | ||||
|              | ||||
|             ScrollView(.horizontal, showsIndicators: false) { | ||||
|                 LazyHGrid(rows: rows, spacing: 13.3) { | ||||
|                     ForEach(0..<item.items.count, id: \.self) { index in | ||||
|                         let content = item.items[index] | ||||
|                     ForEach(0..<viewModel.contentRankingItemList.count, id: \.self) { index in | ||||
|                         let content = viewModel.contentRankingItemList[index] | ||||
|                         HStack(spacing: 0) { | ||||
|                             KFImage(URL(string: content.coverImageUrl)) | ||||
|                                 .resizable() | ||||
| @@ -91,63 +91,17 @@ struct ContentMainRankingView: View { | ||||
|                 } | ||||
|                 .frame(height: 207) | ||||
|             } | ||||
|             .padding(.top, 13.3) | ||||
|         } | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .onAppear { | ||||
|             viewModel.getContentRankingSortType() | ||||
|             viewModel.getContentRanking() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct ContentMainRankingView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         ContentMainRankingView( | ||||
|             sorts: ["매출", "후원", "댓글"], | ||||
|             item: GetAudioContentRanking( | ||||
|                 startDate: "2023년 10월 2일", | ||||
|                 endDate: "10월 9일", | ||||
|                 items: [ | ||||
|                     GetAudioContentRankingItem( | ||||
|                         contentId: 10, | ||||
|                         title: "sdffsfddfs", | ||||
|                         coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456", | ||||
|                         themeStr: "커버곡", | ||||
|                         price: 10, | ||||
|                         duration: "00:30:20", | ||||
|                         creatorId: 1, | ||||
|                         creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ" | ||||
|                     ), | ||||
|                     GetAudioContentRankingItem( | ||||
|                         contentId: 10, | ||||
|                         title: "sdffsfddfs", | ||||
|                         coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456", | ||||
|                         themeStr: "커버곡", | ||||
|                         price: 10, | ||||
|                         duration: "00:30:20", | ||||
|                         creatorId: 1, | ||||
|                         creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ" | ||||
|                     ), | ||||
|                     GetAudioContentRankingItem( | ||||
|                         contentId: 10, | ||||
|                         title: "sdffsfddfs", | ||||
|                         coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456", | ||||
|                         themeStr: "커버곡", | ||||
|                         price: 10, | ||||
|                         duration: "00:30:20", | ||||
|                         creatorId: 1, | ||||
|                         creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ" | ||||
|                     ), | ||||
|                     GetAudioContentRankingItem( | ||||
|                         contentId: 10, | ||||
|                         title: "sdffsfddfs", | ||||
|                         coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456", | ||||
|                         themeStr: "커버곡", | ||||
|                         price: 10, | ||||
|                         duration: "00:30:20", | ||||
|                         creatorId: 1, | ||||
|                         creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ" | ||||
|                     ), | ||||
|                 ] | ||||
|             ), | ||||
|             selectSort: { _ in }, | ||||
|             selectedSort: .constant("매출") | ||||
|         ) | ||||
|         ContentMainRankingView() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,104 @@ | ||||
| // | ||||
| //  ContentMainRankingViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/12/11. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainRankingViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var dateString = "" | ||||
|     @Published var contentRankingSortList = [String]() | ||||
|     @Published var contentRankingItemList = [GetAudioContentRankingItem]() | ||||
|      | ||||
|     @Published var selectedContentRankingSort = "매출" { | ||||
|         didSet { | ||||
|             getContentRanking() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func getContentRankingSortType() { | ||||
|         repository.getContentRankingSortType() | ||||
|             .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<[String]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.contentRankingSortList.removeAll() | ||||
|                         self.contentRankingSortList.append(contentsOf: data) | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
|      | ||||
|     func getContentRanking() { | ||||
|         isLoading = true | ||||
|          | ||||
|         repository.getContentRanking(page: 1, size: 12, sortType: selectedContentRankingSort) | ||||
|             .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<GetAudioContentRanking>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         dateString = "\(data.startDate)~\(data.endDate)" | ||||
|                         self.contentRankingItemList.append(contentsOf: data.items) | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung