시리즈 전체보기 페이지
This commit is contained in:
		
							
								
								
									
										51
									
								
								SodaLive/Sources/Content/Series/SeriesApi.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								SodaLive/Sources/Content/Series/SeriesApi.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| // | ||||
| //  SeriesApi.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 4/29/24. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Moya | ||||
|  | ||||
| enum SeriesApi { | ||||
|     case getSeriesList(creatorId: Int, sortType: SeriesListAllViewModel.SeriesSortType, page: Int, size: Int) | ||||
| } | ||||
|  | ||||
| extension SeriesApi: TargetType { | ||||
|     var baseURL: URL { | ||||
|         return URL(string: BASE_URL)! | ||||
|     } | ||||
|      | ||||
|     var path: String { | ||||
|         switch self { | ||||
|         case .getSeriesList: | ||||
|             return "/audio-content/series" | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var method: Moya.Method { | ||||
|         switch self { | ||||
|         case .getSeriesList: | ||||
|             return .get | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var task: Moya.Task { | ||||
|         switch self { | ||||
|         case .getSeriesList(let creatorId, let sortType, let page, let size): | ||||
|             let parameters = [ | ||||
|                 "creatorId": creatorId, | ||||
|                 "sortType": sortType, | ||||
|                 "page": page - 1, | ||||
|                 "size": size | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var headers: [String : String]? { | ||||
|         return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"] | ||||
|     } | ||||
| } | ||||
							
								
								
									
										57
									
								
								SodaLive/Sources/Content/Series/SeriesListAllView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								SodaLive/Sources/Content/Series/SeriesListAllView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| // | ||||
| //  SeriesListAllView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 4/29/24. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct SeriesListAllView: View { | ||||
|      | ||||
|     @ObservedObject var viewModel = SeriesListAllViewModel() | ||||
|      | ||||
|     let creatorId: Int | ||||
|      | ||||
|     let columns = [ | ||||
|         GridItem(.flexible()), | ||||
|         GridItem(.flexible()), | ||||
|         GridItem(.flexible()) | ||||
|     ] | ||||
|      | ||||
|     var body: some View { | ||||
|         BaseView(isLoading: $viewModel.isLoading) { | ||||
|             VStack(spacing: 0) { | ||||
|                 DetailNavigationBar(title: "시리즈 전체보기") | ||||
|                  | ||||
|                 ScrollView(.vertical, showsIndicators: false) { | ||||
|                     LazyVGrid(columns: columns, spacing: 13.3) { | ||||
|                         ForEach(0..<viewModel.seriesList.count, id: \.self) { index in | ||||
|                             let item = viewModel.seriesList[index] | ||||
|                             SeriesListItemView(item: item) | ||||
|                                 .contentShape(Rectangle()) | ||||
|                                 .onTapGesture { | ||||
|                                     AppState.shared | ||||
|                                         .setAppStep(step: .seriesDetail(seriesId: item.seriesId)) | ||||
|                                 } | ||||
|                                 .onAppear { | ||||
|                                     if index == viewModel.seriesList.count - 1 { | ||||
|                                         viewModel.getSeriesList() | ||||
|                                     } | ||||
|                                 } | ||||
|                         } | ||||
|                     } | ||||
|                     .padding(13.3) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         .onAppear { | ||||
|             viewModel.creatorId = creatorId | ||||
|             viewModel.getSeriesList() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     SeriesListAllView(creatorId: 0) | ||||
| } | ||||
							
								
								
									
										79
									
								
								SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| // | ||||
| //  SeriesListAllViewModel.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 4/29/24. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| final class SeriesListAllViewModel: ObservableObject { | ||||
|     private let repository = SeriesRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
|      | ||||
|     enum SeriesSortType: String { | ||||
|         case NEWEST, POPULAR | ||||
|     } | ||||
|      | ||||
|     var creatorId: Int = 0 | ||||
|      | ||||
|     @Published var isLoading = false | ||||
|     @Published var errorMessage = "" | ||||
|     @Published var isShowPopup = false | ||||
|     @Published var seriesList = [SeriesListItem]() | ||||
|      | ||||
|     var page = 1 | ||||
|     var isLast = false | ||||
|     private let pageSize = 10 | ||||
|      | ||||
|     func getSeriesList() { | ||||
|         if !isLoading && !isLast { | ||||
|             repository | ||||
|                 .getSeriesList(creatorId: creatorId, sortType: .NEWEST, page: page, size: pageSize) | ||||
|                 .sink { result in | ||||
|                     switch result { | ||||
|                     case .finished: | ||||
|                         DEBUG_LOG("finish") | ||||
|                     case .failure(let error): | ||||
|                         ERROR_LOG(error.localizedDescription) | ||||
|                     } | ||||
|                 } receiveValue: { [unowned self] response in | ||||
|                     let responseData = response.data | ||||
|                      | ||||
|                     do { | ||||
|                         let jsonDecoder = JSONDecoder() | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<GetSeriesListResponse>.self, from: responseData) | ||||
|                          | ||||
|                         if let data = decoded.data, decoded.success { | ||||
|                             if page == 1 { | ||||
|                                 self.seriesList.removeAll() | ||||
|                             } | ||||
|                              | ||||
|                             if !data.items.isEmpty { | ||||
|                                 page += 1 | ||||
|                                 self.seriesList.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) | ||||
|         } | ||||
|     } | ||||
|      | ||||
| } | ||||
							
								
								
									
										19
									
								
								SodaLive/Sources/Content/Series/SeriesRepository.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								SodaLive/Sources/Content/Series/SeriesRepository.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| // | ||||
| //  SeriesRepository.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 4/29/24. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
| import CombineMoya | ||||
| import Combine | ||||
| import Moya | ||||
|  | ||||
| class SeriesRepository { | ||||
|     private let api = MoyaProvider<SeriesApi>() | ||||
|      | ||||
|     func getSeriesList(creatorId: Int, sortType: SeriesListAllViewModel.SeriesSortType, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getSeriesList(creatorId: creatorId, sortType: sortType, page: page, size: size)) | ||||
|     } | ||||
| } | ||||
| @@ -178,6 +178,9 @@ struct ContentView: View { | ||||
|             case .contentAllByTheme(let themeId): | ||||
|                 ContentAllByThemeView(themeId: themeId) | ||||
|                  | ||||
|             case .seriesAll(let creatorId): | ||||
|                 SeriesListAllView(creatorId: creatorId) | ||||
|                  | ||||
|             default: | ||||
|                 EmptyView() | ||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||
|   | ||||
| @@ -23,15 +23,20 @@ struct UserProfileSeriesView: View { | ||||
|                 Text("전체보기") | ||||
|                     .font(.custom(Font.light.rawValue, size: 11.3)) | ||||
|                     .foregroundColor(Color.grayee) | ||||
|                     .onTapGesture {} | ||||
|                     .onTapGesture { | ||||
|                         AppState.shared | ||||
|                             .setAppStep(step: .seriesAll(creatorId: creatorId)) | ||||
|                     } | ||||
|             } | ||||
|              | ||||
|             HStack(spacing: 13.3) { | ||||
|                 ForEach(0..<items.count, id: \.self) { | ||||
|                     SeriesListBigItemView(item: items[$0]) | ||||
|                     let item = items[$0] | ||||
|                     SeriesListBigItemView(item: item) | ||||
|                         .contentShape(Rectangle()) | ||||
|                         .onTapGesture { | ||||
|                             AppState.shared | ||||
|                                 .setAppStep(step: .seriesAll(creatorId: creatorId)) | ||||
|                                 .setAppStep(step: .seriesDetail(seriesId: item.seriesId)) | ||||
|                         } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -54,6 +54,7 @@ struct SeriesListBigItemView: View { | ||||
|                         SeriesItemBadgeView(title: "총 \(item.numberOfContent)화", backgroundColor: Color.gray33.opacity(0.7)) | ||||
|                     } | ||||
|                 } | ||||
|                 .padding(3.3) | ||||
|             } | ||||
|             .frame(width: 116.7, height: 165, alignment: .center) | ||||
|              | ||||
| @@ -79,10 +80,6 @@ struct SeriesListBigItemView: View { | ||||
|                 .foregroundColor(Color.gray77) | ||||
|         } | ||||
|         .frame(width: 116.7) | ||||
|         .onTapGesture { | ||||
|             AppState.shared | ||||
|                 .setAppStep(step: .seriesDetail(seriesId: item.seriesId)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										104
									
								
								SodaLive/Sources/UI/Component/SeriesListItemView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								SodaLive/Sources/UI/Component/SeriesListItemView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| // | ||||
| //  SeriesListItemView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 4/29/24. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| import Kingfisher | ||||
|  | ||||
| struct SeriesListItemView: View { | ||||
|      | ||||
|     let item: SeriesListItem | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(alignment: .leading, spacing: 8) { | ||||
|             ZStack { | ||||
|                 KFImage(URL(string: item.coverImage)) | ||||
|                     .resizable() | ||||
|                     .scaledToFill() | ||||
|                     .frame(width: 102, height: 144, alignment: .center) | ||||
|                     .cornerRadius(5) | ||||
|                     .clipped() | ||||
|                  | ||||
|                 LinearGradient( | ||||
|                     colors: [Color.black.opacity(0.1), Color.black.opacity(0.8)], | ||||
|                     startPoint: .top, | ||||
|                     endPoint: .bottom | ||||
|                 ) | ||||
|                  | ||||
|                 VStack(alignment: .leading, spacing: 0) { | ||||
|                     HStack(spacing: 3.3) { | ||||
|                         if !item.isComplete && item.isNew { | ||||
|                             SeriesItemBadgeView(title: "신작", backgroundColor: .button) | ||||
|                         } | ||||
|                          | ||||
|                         if item.isComplete { | ||||
|                             SeriesItemBadgeView(title: "완결", backgroundColor: Color(hex: "002abd")) | ||||
|                         } | ||||
|                          | ||||
|                         if item.isPopular { | ||||
|                             SeriesItemBadgeView(title: "인기", backgroundColor: Color(hex: "ec6033")) | ||||
|                         } | ||||
|                          | ||||
|                         Spacer() | ||||
|                     } | ||||
|                      | ||||
|                     Spacer() | ||||
|                      | ||||
|                     HStack { | ||||
|                         Spacer() | ||||
|                          | ||||
|                         SeriesItemBadgeView(title: "총 \(item.numberOfContent)화", backgroundColor: Color.gray33.opacity(0.7)) | ||||
|                     } | ||||
|                 } | ||||
|                 .padding(3.3) | ||||
|             } | ||||
|             .frame(width: 102, height: 144, alignment: .center) | ||||
|              | ||||
|             Text(item.title) | ||||
|                 .font(.custom(Font.medium.rawValue, size: 12)) | ||||
|                 .foregroundColor(Color.grayee) | ||||
|                 .lineLimit(2) | ||||
|              | ||||
|             HStack(spacing: 3) { | ||||
|                 KFImage(URL(string: item.creator.profileImage)) | ||||
|                     .resizable() | ||||
|                     .scaledToFill() | ||||
|                     .frame(width: 16, height: 16, alignment: .center) | ||||
|                     .clipShape(Circle()) | ||||
|                  | ||||
|                 Text(item.creator.nickname) | ||||
|                     .font(.custom(Font.medium.rawValue, size: 10)) | ||||
|                     .foregroundColor(Color.gray77) | ||||
|             } | ||||
|              | ||||
|             Text(item.publishedDaysOfWeek) | ||||
|                 .font(.custom(Font.medium.rawValue, size: 11)) | ||||
|                 .foregroundColor(Color.gray77) | ||||
|         } | ||||
|         .frame(width: 116.7) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     SeriesListItemView( | ||||
|         item: SeriesListItem( | ||||
|             seriesId: 1, | ||||
|             title: "제목, 관심사,프로필+방장, 참여인원(어딘가..)", | ||||
|             coverImage: "https://test-cf.sodalive.net/profile/default-profile.png", | ||||
|             publishedDaysOfWeek: "매주 수, 토요일", | ||||
|             isComplete: true, | ||||
|             creator: SeriesListItemCreator( | ||||
|                 creatorId: 1, | ||||
|                 nickname: "creator", | ||||
|                 profileImage: "https://test-cf.sodalive.net/profile/default-profile.png" | ||||
|             ), | ||||
|             numberOfContent: 10, | ||||
|             isNew: true, | ||||
|             isPopular: true | ||||
|         ) | ||||
|     ) | ||||
| } | ||||
| @@ -133,10 +133,10 @@ extension UserApi: TargetType { | ||||
|         case .searchUser(let nickname): | ||||
|             return .requestParameters(parameters: ["nickname" : nickname], encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getMypage, .getMyProfile: | ||||
|         case .getMypage, .getMyProfile, .getMemberInfo: | ||||
|             return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString) | ||||
|              | ||||
|         case .getMemberInfo, .logout, .logoutAllDevice, .getChangeNicknamePrice: | ||||
|         case .logout, .logoutAllDevice, .getChangeNicknamePrice: | ||||
|             return .requestPlain | ||||
|              | ||||
|         case .notification(let request): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung