콘텐츠 메인 - 숏플, 라이브 다시 듣기 추가
This commit is contained in:
		
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "ic_card_can_gray.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/ic_card_can_gray.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/ic_card_can_gray.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 552 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "ic_card_time_small_gray.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/ic_card_time_small_gray.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/ic_card_time_small_gray.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 543 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "ic_short_play.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/ic_short_play.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/ic_short_play.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "ic_thumb_play_blue.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/ic_thumb_play_blue.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/ic_thumb_play_blue.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.9 KiB | 
| @@ -119,4 +119,6 @@ enum AppStep { | ||||
|     case creatorCommunityModify(postId: Int, onSuccess: () -> Void) | ||||
|      | ||||
|     case canCoupon | ||||
|      | ||||
|     case contentAllByTheme(themeId: Int) | ||||
| } | ||||
|   | ||||
							
								
								
									
										111
									
								
								SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| // | ||||
| //  ContentAllByThemeView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/14/24. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentAllByThemeView: View { | ||||
|     @StateObject var viewModel = ContentAllByThemeViewModel() | ||||
|      | ||||
|     let themeId: Int | ||||
|      | ||||
|     let columns = [ | ||||
|         GridItem(.flexible(), alignment: .top), | ||||
|         GridItem(.flexible(), alignment: .top), | ||||
|         GridItem(.flexible(), alignment: .top) | ||||
|     ] | ||||
|      | ||||
|     var body: some View { | ||||
|         NavigationView { | ||||
|             BaseView(isLoading: $viewModel.isLoading) { | ||||
|                 VStack(alignment: .leading, spacing: 0) { | ||||
|                     DetailNavigationBar(title: viewModel.theme) | ||||
|                      | ||||
|                     HStack(spacing: 13.3) { | ||||
|                         Spacer() | ||||
|                          | ||||
|                         Text("최신순") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                             .foregroundColor( | ||||
|                                 Color(hex: "e2e2e2") | ||||
|                                     .opacity(viewModel.sort == .NEWEST ? 1 : 0.5) | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 if viewModel.sort != .NEWEST { | ||||
|                                     viewModel.sort = .NEWEST | ||||
|                                 } | ||||
|                             } | ||||
|                          | ||||
|                         Text("높은 가격순") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                             .foregroundColor( | ||||
|                                 Color(hex: "e2e2e2") | ||||
|                                     .opacity(viewModel.sort == .PRICE_HIGH ? 1 : 0.5) | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 if viewModel.sort != .PRICE_HIGH { | ||||
|                                     viewModel.sort = .PRICE_HIGH | ||||
|                                 } | ||||
|                             } | ||||
|                          | ||||
|                         Text("낮은 가격순") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                             .foregroundColor( | ||||
|                                 Color(hex: "e2e2e2") | ||||
|                                     .opacity(viewModel.sort == .PRICE_LOW ? 1 : 0.5) | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 if viewModel.sort != .PRICE_LOW { | ||||
|                                     viewModel.sort = .PRICE_LOW | ||||
|                                 } | ||||
|                             } | ||||
|                     } | ||||
|                     .padding(.vertical, 13.3) | ||||
|                     .padding(.horizontal, 20) | ||||
|                     .background(Color(hex: "161616")) | ||||
|                     .padding(.top, 13.3) | ||||
|                      | ||||
|                     HStack(spacing: 0) { | ||||
|                         Text("전체") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                             .foregroundColor(Color(hex: "e2e2e2")) | ||||
|                          | ||||
|                         Text("\(viewModel.totalCount)") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                             .foregroundColor(Color(hex: "ff5c49")) | ||||
|                             .padding(.leading, 8) | ||||
|                          | ||||
|                         Text("개") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                             .foregroundColor(Color(hex: "e2e2e2")) | ||||
|                             .padding(.leading, 2) | ||||
|                          | ||||
|                         Spacer() | ||||
|                     } | ||||
|                     .padding(.vertical, 13.3) | ||||
|                     .padding(.horizontal, 20) | ||||
|                      | ||||
|                     ScrollView(.vertical, showsIndicators: false) { | ||||
|                         LazyVGrid(columns: columns, spacing: 13.3) { | ||||
|                             ForEach(0..<viewModel.contentList.count, id: \.self) { index in | ||||
|                                 ContentNewAllItemView(item: viewModel.contentList[index]) | ||||
|                                     .onAppear { | ||||
|                                         if index == viewModel.contentList.count - 1 { | ||||
|                                             viewModel.getContentList() | ||||
|                                         } | ||||
|                                     } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 .onAppear { | ||||
|                     viewModel.themeId = themeId | ||||
|                     viewModel.getContentList() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,96 @@ | ||||
| // | ||||
| //  ContentAllByThemeViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/14/24. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class ContentAllByThemeViewModel: ObservableObject { | ||||
|     enum Sort: String { | ||||
|         case NEWEST, PRICE_HIGH, PRICE_LOW | ||||
|     } | ||||
|      | ||||
|     private let repository = ContentRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var isLoading = false | ||||
|      | ||||
|     @Published var theme = "" | ||||
|     @Published var totalCount = 0 | ||||
|     @Published var sort = Sort.NEWEST { | ||||
|         didSet { | ||||
|             page = 1 | ||||
|             isLast = false | ||||
|             getContentList() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @Published var contentList: [GetAudioContentMainItem] = [] | ||||
|      | ||||
|     var themeId = 0 | ||||
|     var page = 1 | ||||
|     var isLast = false | ||||
|     private let pageSize = 10 | ||||
|      | ||||
|     func getContentList() { | ||||
|         if (!isLast && !isLoading) { | ||||
|             isLoading = true | ||||
|              | ||||
|             repository.getAudioContentByTheme( | ||||
|                 themeId: themeId, | ||||
|                 page: page, | ||||
|                 size: pageSize, | ||||
|                 sort: sort | ||||
|             ) | ||||
|             .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<GetContentByThemeResponse>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         if page == 1 { | ||||
|                             self.contentList.removeAll() | ||||
|                         } | ||||
|                          | ||||
|                         if !data.items.isEmpty { | ||||
|                             page += 1 | ||||
|                             self.theme = data.theme | ||||
|                             self.totalCount = data.totalCount | ||||
|                             self.contentList.append(contentsOf: data.items) | ||||
|                         } else { | ||||
|                             isLast = true | ||||
|                         } | ||||
|                     } else { | ||||
|                         if let message = decoded.message { | ||||
|                             self.errorMessage = message | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         } | ||||
|                          | ||||
|                         self.isShowPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowPopup = true | ||||
|                 } | ||||
|                  | ||||
|                 self.isLoading = false | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| // | ||||
| //  GetContentByThemeResponse.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/14/24. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| struct GetContentByThemeResponse: Decodable { | ||||
|     let theme: String | ||||
|     let totalCount: Int | ||||
|     let items: [GetAudioContentMainItem] | ||||
| } | ||||
| @@ -36,6 +36,7 @@ enum ContentApi { | ||||
|     case getContentRankingSortType | ||||
|     case pinContent(contentId: Int) | ||||
|     case unpinContent(contentId: Int) | ||||
|     case getAudioContentByTheme(themeId: Int, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort) | ||||
| } | ||||
|  | ||||
| extension ContentApi: TargetType { | ||||
| @@ -125,6 +126,9 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .unpinContent(let contentId): | ||||
|             return  "/audio-content/unpin-at-the-top/\(contentId)" | ||||
|              | ||||
|         case .getAudioContentByTheme(let themeId, _, _, _): | ||||
|             return "/audio-content/theme/\(themeId)/content" | ||||
|         } | ||||
|     } | ||||
|      | ||||
| @@ -136,7 +140,7 @@ extension ContentApi: TargetType { | ||||
|                 .getContentRankingSortType: | ||||
|             return .get | ||||
|              | ||||
|         case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList: | ||||
|         case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList, .getAudioContentByTheme: | ||||
|             return .get | ||||
|              | ||||
|         case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent: | ||||
| @@ -271,6 +275,15 @@ extension ContentApi: TargetType { | ||||
|              | ||||
|         case .pinContent, .unpinContent: | ||||
|             return .requestPlain | ||||
|              | ||||
|         case .getAudioContentByTheme(_, let page, let size, let sort): | ||||
|             let parameters = [ | ||||
|                 "page": page - 1, | ||||
|                 "size": size, | ||||
|                 "sort-type": sort | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -125,4 +125,8 @@ final class ContentRepository { | ||||
|     func getCategoryList(creatorId: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return categoryApi.requestPublisher(.getCategoryList(creatorId: creatorId)) | ||||
|     } | ||||
|      | ||||
|     func getAudioContentByTheme(themeId: Int, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getAudioContentByTheme(themeId: themeId, page: page, size: size, sort: sort)) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -35,11 +35,60 @@ struct ContentMainView: View { | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         ContentMainBannerView() | ||||
|                             .padding(.bottom, 40) | ||||
|                             .padding(.bottom, 26.7) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         HStack(spacing: 13.3) { | ||||
|                             VStack(spacing: 2.7) { | ||||
|                                 HStack(spacing: 2.7) { | ||||
|                                     Image("ic_short_play") | ||||
|                                      | ||||
|                                     Text("숏플") | ||||
|                                         .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                                         .foregroundColor(Color(hex: "dd158d")) | ||||
|                                 } | ||||
|                                  | ||||
|                                 Text("짧지만 꼴릿한 이야기") | ||||
|                                     .font(.custom(Font.light.rawValue, size: 10)) | ||||
|                                     .foregroundColor(Color(hex: "dd158d")) | ||||
|                             } | ||||
|                             .padding(.vertical, 10) | ||||
|                             .frame(maxWidth: .infinity) | ||||
|                             .background(Color(hex: "ffecf7")) | ||||
|                             .cornerRadius(2.6) | ||||
|                             .onTapGesture { | ||||
|                                 AppState.shared.setAppStep( | ||||
|                                     step: .contentAllByTheme(themeId: 11) | ||||
|                                 ) | ||||
|                             } | ||||
|                              | ||||
|                             VStack(spacing: 2.7) { | ||||
|                                 HStack(spacing: 2.7) { | ||||
|                                     Image("ic_thumb_play_blue") | ||||
|                                      | ||||
|                                     Text("라이브 다시듣기") | ||||
|                                         .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                                         .foregroundColor(Color(hex: "0057ff")) | ||||
|                                 } | ||||
|                                  | ||||
|                                 Text("놓쳤던 라이브 다시듣기") | ||||
|                                     .font(.custom(Font.light.rawValue, size: 10)) | ||||
|                                     .foregroundColor(Color(hex: "0057ff")) | ||||
|                             } | ||||
|                             .padding(.vertical, 10) | ||||
|                             .frame(maxWidth: .infinity) | ||||
|                             .background(Color(hex: "ecf9ff")) | ||||
|                             .cornerRadius(2.6) | ||||
|                             .onTapGesture { | ||||
|                                 AppState.shared.setAppStep( | ||||
|                                     step: .contentAllByTheme(themeId: 7) | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                         .padding(.bottom, 40) | ||||
|                         .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         ContentMainMyStashView() | ||||
|                             .padding(.bottom, 40) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         ContentMainNewContentView() | ||||
|   | ||||
| @@ -39,6 +39,7 @@ struct ContentMainMyStashView: View { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 .padding(.bottom, 40) | ||||
|             } | ||||
|              | ||||
|             if viewModel.isLoading { | ||||
|   | ||||
| @@ -175,6 +175,9 @@ struct ContentView: View { | ||||
|             case .canCoupon: | ||||
|                 CanCouponView() | ||||
|                  | ||||
|             case .contentAllByTheme(let themeId): | ||||
|                 ContentAllByThemeView(themeId: themeId) | ||||
|                  | ||||
|             default: | ||||
|                 EmptyView() | ||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung