diff --git a/SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/Contents.json new file mode 100644 index 0000000..a46aebb --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/Contents.json @@ -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 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/ic_card_can_gray.png b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/ic_card_can_gray.png new file mode 100644 index 0000000..2cd8889 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray.imageset/ic_card_can_gray.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/Contents.json new file mode 100644 index 0000000..3129717 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/Contents.json @@ -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 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/ic_card_time_small_gray.png b/SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/ic_card_time_small_gray.png new file mode 100644 index 0000000..e5f4a44 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_card_time_small_gray.imageset/ic_card_time_small_gray.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/Contents.json new file mode 100644 index 0000000..fe3aec7 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/Contents.json @@ -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 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/ic_short_play.png b/SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/ic_short_play.png new file mode 100644 index 0000000..51b75d6 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_short_play.imageset/ic_short_play.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/Contents.json new file mode 100644 index 0000000..d705adf --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/Contents.json @@ -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 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/ic_thumb_play_blue.png b/SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/ic_thumb_play_blue.png new file mode 100644 index 0000000..2d392a6 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_thumb_play_blue.imageset/ic_thumb_play_blue.png differ diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index f685933..f5fbc4f 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -119,4 +119,6 @@ enum AppStep { case creatorCommunityModify(postId: Int, onSuccess: () -> Void) case canCoupon + + case contentAllByTheme(themeId: Int) } diff --git a/SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift b/SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift new file mode 100644 index 0000000..e49ff33 --- /dev/null +++ b/SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift @@ -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..() + + @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.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) + } + } +} diff --git a/SodaLive/Sources/Content/All/ByTheme/GetContentByThemeResponse.swift b/SodaLive/Sources/Content/All/ByTheme/GetContentByThemeResponse.swift new file mode 100644 index 0000000..5953501 --- /dev/null +++ b/SodaLive/Sources/Content/All/ByTheme/GetContentByThemeResponse.swift @@ -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] +} diff --git a/SodaLive/Sources/Content/ContentApi.swift b/SodaLive/Sources/Content/ContentApi.swift index d3ac20b..de343ff 100644 --- a/SodaLive/Sources/Content/ContentApi.swift +++ b/SodaLive/Sources/Content/ContentApi.swift @@ -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) } } diff --git a/SodaLive/Sources/Content/ContentRepository.swift b/SodaLive/Sources/Content/ContentRepository.swift index adf4e09..649d4b7 100644 --- a/SodaLive/Sources/Content/ContentRepository.swift +++ b/SodaLive/Sources/Content/ContentRepository.swift @@ -125,4 +125,8 @@ final class ContentRepository { func getCategoryList(creatorId: Int) -> AnyPublisher { return categoryApi.requestPublisher(.getCategoryList(creatorId: creatorId)) } + + func getAudioContentByTheme(themeId: Int, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort) -> AnyPublisher { + return api.requestPublisher(.getAudioContentByTheme(themeId: themeId, page: page, size: size, sort: sort)) + } } diff --git a/SodaLive/Sources/Content/Main/ContentMainView.swift b/SodaLive/Sources/Content/Main/ContentMainView.swift index e528f1e..86fbd5b 100644 --- a/SodaLive/Sources/Content/Main/ContentMainView.swift +++ b/SodaLive/Sources/Content/Main/ContentMainView.swift @@ -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() diff --git a/SodaLive/Sources/Content/Main/Order/ContentMainMyStashView.swift b/SodaLive/Sources/Content/Main/Order/ContentMainMyStashView.swift index 8a949db..919e068 100644 --- a/SodaLive/Sources/Content/Main/Order/ContentMainMyStashView.swift +++ b/SodaLive/Sources/Content/Main/Order/ContentMainMyStashView.swift @@ -39,6 +39,7 @@ struct ContentMainMyStashView: View { } } } + .padding(.bottom, 40) } if viewModel.isLoading { diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index 9e2d228..af23a02 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -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)