콘텐츠 메인
- 인기 크리에이터 추가
This commit is contained in:
		| @@ -13,6 +13,7 @@ import Moya | ||||
| final class ContentRepository { | ||||
|     private let api = MoyaProvider<ContentApi>() | ||||
|     private let categoryApi = MoyaProvider<CategoryApi>() | ||||
|     private let explorerApi = MoyaProvider<ExplorerApi>() | ||||
|      | ||||
|     func getAudioContentList(userId: Int, categoryId: Int,  page: Int, size: Int, sort: ContentListViewModel.Sort) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getAudioContentList(userId: userId, categoryId: categoryId, page: page, size: size, sort: sort)) | ||||
| @@ -168,4 +169,8 @@ final class ContentRepository { | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|      | ||||
|     func getCreatorRanking() -> AnyPublisher<Response, MoyaError> { | ||||
|         return explorerApi.requestPublisher(.getCreatorRank) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -37,6 +37,10 @@ struct ContentMainView: View { | ||||
|                             .padding(.bottom, 26.7) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         ContentMainCreatorRankingView() | ||||
|                             .padding(.bottom, 26.7) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         ContentMainRecommendSeriesView() | ||||
|                          | ||||
|                         HStack(spacing: 8) { | ||||
|   | ||||
| @@ -0,0 +1,150 @@ | ||||
| // | ||||
| //  ContentMainCreatorRankingView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 1/5/25. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import Kingfisher | ||||
|  | ||||
| struct ContentMainCreatorRankingView: View { | ||||
|      | ||||
|     @StateObject var viewModel = ContentMainCreatorRankingViewModel() | ||||
|      | ||||
|     let rankingCrawns = ["ic_crown_1", "ic_crown_2", "ic_crown_3"] | ||||
|     let rankingColors = [ | ||||
|         [Color(hex: "ffdc00"), Color(hex: "ffb600")], | ||||
|         [Color(hex: "ffffff"), Color(hex: "9f9f9f")], | ||||
|         [Color(hex: "e6a77a"), Color(hex: "c67e4a")], | ||||
|         [Color(hex: "ffffff").opacity(0), Color(hex: "ffffff").opacity(0)] | ||||
|     ] | ||||
|      | ||||
|     var body: some View { | ||||
|         ZStack { | ||||
|             if let response = viewModel.creatorRankingResponse { | ||||
|                 VStack(alignment: .leading, spacing: 13.3) { | ||||
|                     VStack(alignment: .leading, spacing: 4) { | ||||
|                         if let coloredTitle = response.coloredTitle, let color = response.color { | ||||
|                             let titleArray = response.title.components(separatedBy: coloredTitle) | ||||
|                             HStack(spacing: 0) { | ||||
|                                 Text(titleArray[0]) | ||||
|                                     .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                                     .foregroundColor(Color.grayee) | ||||
|                                  | ||||
|                                 Text(coloredTitle) | ||||
|                                     .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                                     .foregroundColor(Color(hex: color)) | ||||
|                                  | ||||
|                                 if titleArray.count > 1 { | ||||
|                                     Text(titleArray[1]) | ||||
|                                         .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                                         .foregroundColor(Color.grayee) | ||||
|                                 } | ||||
|                             } | ||||
|                         } else { | ||||
|                             Text(response.title) | ||||
|                                 .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
|                                 .foregroundColor(Color.grayee) | ||||
|                         } | ||||
|                          | ||||
|                         if let desc = response.desc { | ||||
|                             VStack(spacing: 8) { | ||||
|                                 Text("\(desc)") | ||||
|                                     .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                                     .foregroundColor(Color.grayee) | ||||
|                                  | ||||
|                                 Text("※ 인기 크리에이터의 순위는 매주 업데이트됩니다.") | ||||
|                                     .font(.custom(Font.light.rawValue, size: 13.3)) | ||||
|                                     .foregroundColor(Color.graybb) | ||||
|                             } | ||||
|                             .padding(.vertical, 8) | ||||
|                             .frame(maxWidth: .infinity) | ||||
|                             .background(Color.gray22) | ||||
|                             .padding(.top, 13.3) | ||||
|                         } | ||||
|                     } | ||||
|                     .frame(maxWidth: .infinity) | ||||
|                     .frame(alignment: .leading) | ||||
|                      | ||||
|                     ScrollView(.horizontal, showsIndicators: false) { | ||||
|                         HStack(spacing: 13.3) { | ||||
|                             ForEach(0..<response.creators.count, id: \.self) { index in | ||||
|                                 let creator = response.creators[index] | ||||
|                                 VStack(spacing: 0) { | ||||
|                                     if let _ = response.desc { | ||||
|                                         ZStack { | ||||
|                                             KFImage(URL(string: creator.profileImageUrl)) | ||||
|                                                 .cancelOnDisappear(true) | ||||
|                                                 .downsampling(size: CGSize(width: 90, height: 90)) | ||||
|                                                 .resizable() | ||||
|                                                 .clipShape(Circle()) | ||||
|                                                 .frame(width: 90, height: 90) | ||||
|                                                 .overlay( | ||||
|                                                     Circle() | ||||
|                                                         .stroke( | ||||
|                                                             AngularGradient(colors: rankingColors[index < 4 ? index : 3], center: .center), | ||||
|                                                             lineWidth: 3 | ||||
|                                                         ) | ||||
|                                                 ) | ||||
|                                              | ||||
|                                             if index < 3 { | ||||
|                                                 VStack(alignment: .trailing, spacing: 0) { | ||||
|                                                     Spacer() | ||||
|                                                      | ||||
|                                                     Image(rankingCrawns[index]) | ||||
|                                                         .resizable() | ||||
|                                                         .frame(width: 37, height: 37) | ||||
|                                                 } | ||||
|                                                 .frame(width: 93.3, height: 93.3, alignment: .trailing) | ||||
|                                             } | ||||
|                                         } | ||||
|                                         .frame(width: 93.3, height: 93.3) | ||||
|                                     } else { | ||||
|                                         KFImage(URL(string: creator.profileImageUrl)) | ||||
|                                             .cancelOnDisappear(true) | ||||
|                                             .downsampling(size: CGSize(width: 93, height: 93)) | ||||
|                                             .resizable() | ||||
|                                             .clipShape(Circle()) | ||||
|                                             .frame(width: 93, height: 93) | ||||
|                                     } | ||||
|                                      | ||||
|                                     Text(creator.nickname) | ||||
|                                         .font(.custom(Font.medium.rawValue, size: 11.3)) | ||||
|                                         .foregroundColor(Color.grayee) | ||||
|                                         .lineLimit(1) | ||||
|                                         .frame(width: 93.3) | ||||
|                                         .padding(.top, 13.3) | ||||
|                                      | ||||
|                                     Text(creator.tags) | ||||
|                                         .font(.custom(Font.medium.rawValue, size: 10)) | ||||
|                                         .foregroundColor(Color.button) | ||||
|                                         .lineLimit(1) | ||||
|                                         .frame(width: 93.3) | ||||
|                                         .padding(.top, 3.3) | ||||
|                                 } | ||||
|                                 .contentShape(Rectangle()) | ||||
|                                 .onTapGesture { | ||||
|                                     AppState.shared | ||||
|                                         .setAppStep(step: .creatorDetail(userId: creator.id)) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     .frame(maxWidth: .infinity) | ||||
|                     .frame(alignment: .leading) | ||||
|                 } | ||||
|             } else { | ||||
|                 EmptyView() | ||||
|                     .frame(width: 0, height: 0) | ||||
|             } | ||||
|         } | ||||
|         .onAppear { | ||||
|             viewModel.getCreatorRanking() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     ContentMainCreatorRankingView() | ||||
| } | ||||
| @@ -0,0 +1,54 @@ | ||||
| // | ||||
| //  ContentMainCreatorRankingViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 1/5/25. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentMainCreatorRankingViewModel: ObservableObject { | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var creatorRankingResponse: GetExplorerSectionResponse? = nil | ||||
|      | ||||
|     func getCreatorRanking() { | ||||
|         repository.getCreatorRanking() | ||||
|             .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<GetExplorerSectionResponse>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.creatorRankingResponse = 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) | ||||
|     } | ||||
| } | ||||
| @@ -9,6 +9,7 @@ import Foundation | ||||
| import Moya | ||||
|  | ||||
| enum ExplorerApi { | ||||
|     case getCreatorRank | ||||
|     case getExplorer | ||||
|     case searchChannel(channel: String) | ||||
|     case getCreatorProfile(userId: Int) | ||||
| @@ -27,6 +28,9 @@ extension ExplorerApi: TargetType { | ||||
|      | ||||
|     var path: String { | ||||
|         switch self { | ||||
|         case .getCreatorRank: | ||||
|             return "/explorer/creator-rank" | ||||
|              | ||||
|         case .getExplorer: | ||||
|             return "/explorer" | ||||
|              | ||||
| @@ -58,7 +62,7 @@ extension ExplorerApi: TargetType { | ||||
|      | ||||
|     var method: Moya.Method { | ||||
|         switch self { | ||||
|         case .getExplorer, .searchChannel, .getCreatorProfile, .getFollowerList, .getCreatorProfileCheers, .getCreatorProfileDonationRanking: | ||||
|         case .getExplorer, .searchChannel, .getCreatorProfile, .getFollowerList, .getCreatorProfileCheers, .getCreatorProfileDonationRanking, .getCreatorRank: | ||||
|             return .get | ||||
|              | ||||
|         case .writeCheers, .writeCreatorNotice: | ||||
| @@ -71,7 +75,7 @@ extension ExplorerApi: TargetType { | ||||
|      | ||||
|     var task: Task { | ||||
|         switch self { | ||||
|         case .getExplorer: | ||||
|         case .getExplorer, .getCreatorRank: | ||||
|             return .requestPlain | ||||
|              | ||||
|         case .searchChannel(let channel): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung