시리즈 전체보기 페이지
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): |             case .contentAllByTheme(let themeId): | ||||||
|                 ContentAllByThemeView(themeId: themeId) |                 ContentAllByThemeView(themeId: themeId) | ||||||
|                  |                  | ||||||
|  |             case .seriesAll(let creatorId): | ||||||
|  |                 SeriesListAllView(creatorId: creatorId) | ||||||
|  |                  | ||||||
|             default: |             default: | ||||||
|                 EmptyView() |                 EmptyView() | ||||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) |                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||||
|   | |||||||
| @@ -23,15 +23,20 @@ struct UserProfileSeriesView: View { | |||||||
|                 Text("전체보기") |                 Text("전체보기") | ||||||
|                     .font(.custom(Font.light.rawValue, size: 11.3)) |                     .font(.custom(Font.light.rawValue, size: 11.3)) | ||||||
|                     .foregroundColor(Color.grayee) |                     .foregroundColor(Color.grayee) | ||||||
|                     .onTapGesture {} |                     .onTapGesture { | ||||||
|  |                         AppState.shared | ||||||
|  |                             .setAppStep(step: .seriesAll(creatorId: creatorId)) | ||||||
|  |                     } | ||||||
|             } |             } | ||||||
|              |              | ||||||
|             HStack(spacing: 13.3) { |             HStack(spacing: 13.3) { | ||||||
|                 ForEach(0..<items.count, id: \.self) { |                 ForEach(0..<items.count, id: \.self) { | ||||||
|                     SeriesListBigItemView(item: items[$0]) |                     let item = items[$0] | ||||||
|  |                     SeriesListBigItemView(item: item) | ||||||
|  |                         .contentShape(Rectangle()) | ||||||
|                         .onTapGesture { |                         .onTapGesture { | ||||||
|                             AppState.shared |                             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)) |                         SeriesItemBadgeView(title: "총 \(item.numberOfContent)화", backgroundColor: Color.gray33.opacity(0.7)) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 .padding(3.3) | ||||||
|             } |             } | ||||||
|             .frame(width: 116.7, height: 165, alignment: .center) |             .frame(width: 116.7, height: 165, alignment: .center) | ||||||
|              |              | ||||||
| @@ -79,10 +80,6 @@ struct SeriesListBigItemView: View { | |||||||
|                 .foregroundColor(Color.gray77) |                 .foregroundColor(Color.gray77) | ||||||
|         } |         } | ||||||
|         .frame(width: 116.7) |         .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): |         case .searchUser(let nickname): | ||||||
|             return .requestParameters(parameters: ["nickname" : nickname], encoding: URLEncoding.queryString) |             return .requestParameters(parameters: ["nickname" : nickname], encoding: URLEncoding.queryString) | ||||||
|              |              | ||||||
|         case .getMypage, .getMyProfile: |         case .getMypage, .getMyProfile, .getMemberInfo: | ||||||
|             return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString) |             return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString) | ||||||
|              |              | ||||||
|         case .getMemberInfo, .logout, .logoutAllDevice, .getChangeNicknamePrice: |         case .logout, .logoutAllDevice, .getChangeNicknamePrice: | ||||||
|             return .requestPlain |             return .requestPlain | ||||||
|              |              | ||||||
|         case .notification(let request): |         case .notification(let request): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung