From fd356451ae728b6f9861c576037790fe3860e22b Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Wed, 27 Sep 2023 19:25:00 +0900 Subject: [PATCH] =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/App/AppStep.swift | 2 + .../Content/All/ContentNewAllItemView.swift | 56 ++++++++ .../Content/All/ContentNewAllView.swift | 81 +++++++++++ .../Content/All/ContentNewAllViewModel.swift | 130 ++++++++++++++++++ .../All/GetNewContentAllResponse.swift | 11 ++ SodaLive/Sources/Content/ContentApi.swift | 22 ++- .../Sources/Content/ContentRepository.swift | 8 ++ .../Main/ContentMainCurationItemView.swift | 17 ++- .../Main/ContentMainCurationView.swift | 9 -- .../Main/ContentMainNewContentThemeView.swift | 6 +- .../Main/ContentMainNewContentView.swift | 17 ++- SodaLive/Sources/ContentView.swift | 3 + 12 files changed, 345 insertions(+), 17 deletions(-) create mode 100644 SodaLive/Sources/Content/All/ContentNewAllItemView.swift create mode 100644 SodaLive/Sources/Content/All/ContentNewAllView.swift create mode 100644 SodaLive/Sources/Content/All/ContentNewAllViewModel.swift create mode 100644 SodaLive/Sources/Content/All/GetNewContentAllResponse.swift diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index 37ca6d5..e9579ca 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -107,4 +107,6 @@ enum AppStep { case followingList case orderListAll + + case newContentAll } diff --git a/SodaLive/Sources/Content/All/ContentNewAllItemView.swift b/SodaLive/Sources/Content/All/ContentNewAllItemView.swift new file mode 100644 index 0000000..bd6c24c --- /dev/null +++ b/SodaLive/Sources/Content/All/ContentNewAllItemView.swift @@ -0,0 +1,56 @@ +// +// ContentNewAllItemView.swift +// SodaLive +// +// Created by klaus on 2023/09/27. +// + +import SwiftUI +import Kingfisher + +struct ContentNewAllItemView: View { + + let item: GetAudioContentMainItem + + @State var width: CGFloat = 0 + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + ZStack(alignment: .topLeading) { + KFImage(URL(string: item.coverImageUrl)) + .resizable() + .scaledToFill() + .frame(width: width, height: width, alignment: .top) + .cornerRadius(2.7) + } + + Text(item.title) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "d2d2d2")) + .frame(width: width, alignment: .leading) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + .lineLimit(2) + + HStack(spacing: 5.3) { + KFImage(URL(string: item.creatorProfileImageUrl)) + .resizable() + .scaledToFill() + .frame(width: 21.3, height: 21.3) + .clipShape(Circle()) + .onTapGesture { AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId)) } + + Text(item.creatorNickname) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "777777")) + .lineLimit(1) + } + .padding(.bottom, 10) + } + .frame(width: width, alignment: .leading) + .onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) } + .onAppear { + width = (screenSize().width - 40) / 2 + } + } +} diff --git a/SodaLive/Sources/Content/All/ContentNewAllView.swift b/SodaLive/Sources/Content/All/ContentNewAllView.swift new file mode 100644 index 0000000..818a902 --- /dev/null +++ b/SodaLive/Sources/Content/All/ContentNewAllView.swift @@ -0,0 +1,81 @@ +// +// ContentNewAllView.swift +// SodaLive +// +// Created by klaus on 2023/09/27. +// + +import SwiftUI + +struct ContentNewAllView: View { + + @StateObject var viewModel = ContentNewAllViewModel() + + let columns = [ + GridItem(.flexible()), + GridItem(.flexible()) + ] + + var body: some View { + BaseView(isLoading: $viewModel.isLoading) { + VStack(spacing: 0) { + DetailNavigationBar(title: "새로운 콘텐츠") + + Text("※ 최근 2주간 등록된 새로운 콘텐츠 입니다.") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "bbbbbb")) + .padding(.horizontal, 13.3) + .padding(.vertical, 8) + .frame(width: screenSize().width, alignment: .leading) + .background(Color(hex: "222222")) + .padding(.top, 13.3) + + ContentMainNewContentThemeView( + themes: viewModel.themeList, + selectTheme: { + viewModel.selectedTheme = $0 + }, + selectedTheme: $viewModel.selectedTheme + ).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..() + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + + @Published var themeList = [String]() + @Published var newContentList = [GetAudioContentMainItem]() + + @Published var selectedTheme = "전체" { + didSet { + page = 1 + isLast = false + getNewContentList() + } + } + @Published var totalCount = 0 + + var page = 1 + var isLast = false + private let pageSize = 10 + + func getNewContentList() { + if (!isLast && !isLoading) { + isLoading = true + + repository.getNewContentAllOfTheme( + theme: selectedTheme == "전체" ? "" : selectedTheme, + 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 + self.isLoading = false + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + self.isLoading = false + + if let data = decoded.data, decoded.success { + if page == 1 { + newContentList.removeAll() + } + + self.totalCount = data.totalCount + + if !data.items.isEmpty { + page += 1 + self.newContentList.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) + } else { + isLoading = false + } + } + + 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) + } +} diff --git a/SodaLive/Sources/Content/All/GetNewContentAllResponse.swift b/SodaLive/Sources/Content/All/GetNewContentAllResponse.swift new file mode 100644 index 0000000..4ab7877 --- /dev/null +++ b/SodaLive/Sources/Content/All/GetNewContentAllResponse.swift @@ -0,0 +1,11 @@ +// +// GetNewContentAllResponse.swift +// SodaLive +// +// Created by klaus on 2023/09/27. +// + +struct GetNewContentAllResponse: Decodable { + let totalCount: Int + let items: [GetAudioContentMainItem] +} diff --git a/SodaLive/Sources/Content/ContentApi.swift b/SodaLive/Sources/Content/ContentApi.swift index e9d36f1..a4d6d47 100644 --- a/SodaLive/Sources/Content/ContentApi.swift +++ b/SodaLive/Sources/Content/ContentApi.swift @@ -26,6 +26,8 @@ enum ContentApi { case getNewContentOfTheme(theme: String) case donation(request: AudioContentDonationRequest) case modifyComment(request: ModifyCommentRequest) + case getNewContentThemeList + case getNewContentAllOfTheme(theme: String, page: Int, size: Int) } extension ContentApi: TargetType { @@ -85,12 +87,18 @@ extension ContentApi: TargetType { case .modifyComment: return "/audio-content/comment" + + case .getNewContentThemeList: + return "/audio-content/main/theme" + + case .getNewContentAllOfTheme: + return "/audio-content/main/new/all" } } var method: Moya.Method { switch self { - case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme: + case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme, .getNewContentThemeList, .getNewContentAllOfTheme: return .get case .likeContent, .modifyAudioContent, .modifyComment: @@ -180,6 +188,18 @@ extension ContentApi: TargetType { case .modifyComment(let request): return .requestJSONEncodable(request) + + case .getNewContentThemeList: + return .requestPlain + + case .getNewContentAllOfTheme(let theme, let page, let size): + let parameters = [ + "theme": theme, + "page": page - 1, + "size": size + ] as [String : Any] + + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/Content/ContentRepository.swift b/SodaLive/Sources/Content/ContentRepository.swift index 20b9863..ee0615a 100644 --- a/SodaLive/Sources/Content/ContentRepository.swift +++ b/SodaLive/Sources/Content/ContentRepository.swift @@ -80,4 +80,12 @@ final class ContentRepository { func modifyComment(request: ModifyCommentRequest) -> AnyPublisher { return api.requestPublisher(.modifyComment(request: request)) } + + func getNewContentThemeList() -> AnyPublisher { + return api.requestPublisher(.getNewContentThemeList) + } + + func getNewContentAllOfTheme(theme: String, page: Int, size: Int) -> AnyPublisher { + return api.requestPublisher(.getNewContentAllOfTheme(theme: theme, page: page, size: size)) + } } diff --git a/SodaLive/Sources/Content/Main/ContentMainCurationItemView.swift b/SodaLive/Sources/Content/Main/ContentMainCurationItemView.swift index c4b64ec..8458a21 100644 --- a/SodaLive/Sources/Content/Main/ContentMainCurationItemView.swift +++ b/SodaLive/Sources/Content/Main/ContentMainCurationItemView.swift @@ -13,9 +13,20 @@ struct ContentMainCurationItemView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - Text(item.title) - .font(.custom(Font.bold.rawValue, size: 18.3)) - .foregroundColor(Color(hex: "eeeeee")) + HStack(spacing: 0) { + Text(item.title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + Image("ic_forward") + .resizable() + .frame(width: 20, height: 20) + .onTapGesture { + + } + } Text(item.description) .font(.custom(Font.medium.rawValue, size: 13)) diff --git a/SodaLive/Sources/Content/Main/ContentMainCurationView.swift b/SodaLive/Sources/Content/Main/ContentMainCurationView.swift index 9214820..e329215 100644 --- a/SodaLive/Sources/Content/Main/ContentMainCurationView.swift +++ b/SodaLive/Sources/Content/Main/ContentMainCurationView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import GoogleMobileAds struct ContentMainCurationView: View { @@ -18,14 +17,6 @@ struct ContentMainCurationView: View { let item = items[$0] ContentMainCurationItemView(item: item) .padding(.horizontal, 13.3) - - if $0 % 3 == 2 { - BannerAdView(adUnitId: CURATION_BANNER_AD_UNIT_ID) - .frame( - width: screenSize().width, - height: GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(screenSize().width).size.height - ) - } } } } diff --git a/SodaLive/Sources/Content/Main/ContentMainNewContentThemeView.swift b/SodaLive/Sources/Content/Main/ContentMainNewContentThemeView.swift index 4806e00..07d91ec 100644 --- a/SodaLive/Sources/Content/Main/ContentMainNewContentThemeView.swift +++ b/SodaLive/Sources/Content/Main/ContentMainNewContentThemeView.swift @@ -33,7 +33,11 @@ struct ContentMainNewContentThemeView: View { .stroke(lineWidth: 0.5) .foregroundColor(Color(hex: selectedTheme == theme ? "9970ff" : "eeeeee")) ) - .onTapGesture { selectTheme(theme) } + .onTapGesture { + if selectedTheme != theme { + selectTheme(theme) + } + } } } } diff --git a/SodaLive/Sources/Content/Main/ContentMainNewContentView.swift b/SodaLive/Sources/Content/Main/ContentMainNewContentView.swift index 6d5bd29..5d6e385 100644 --- a/SodaLive/Sources/Content/Main/ContentMainNewContentView.swift +++ b/SodaLive/Sources/Content/Main/ContentMainNewContentView.swift @@ -17,9 +17,20 @@ struct ContentMainNewContentView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - Text("새로운 콘텐츠") - .font(.custom(Font.bold.rawValue, size: 18.3)) - .foregroundColor(Color(hex: "eeeeee")) + 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) diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index cdcf75c..11068cb 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -157,6 +157,9 @@ struct ContentView: View { case .userProfileFanTalkAll(let userId): UserProfileFanTalkAllView(userId: userId) + case .newContentAll: + ContentNewAllView() + default: EmptyView() .frame(width: 0, height: 0, alignment: .topLeading)