From 48ebc1eaefc86cbfdd498dd456054e854a5152d7 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 20 Feb 2025 23:31:59 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=ED=99=88=20UI=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json | 21 ++ .../ic_card_can_gray_32.png | Bin 0 -> 588 bytes .../ic_category_alarm.imageset/Contents.json | 21 ++ .../ic_category_alarm.png | Bin 0 -> 3453 bytes .../ic_category_asmr.imageset/Contents.json | 21 ++ .../ic_category_asmr.png | Bin 0 -> 4236 bytes .../Contents.json | 21 ++ .../ic_category_audio_book.png | Bin 0 -> 3244 bytes .../Contents.json | 21 ++ .../ic_category_audio_toon.png | Bin 0 -> 3684 bytes .../Contents.json | 21 ++ .../ic_category_content.png | Bin 0 -> 3392 bytes .../ic_category_free.imageset/Contents.json | 21 ++ .../ic_category_free.png | Bin 0 -> 3029 bytes .../ic_category_replay.imageset/Contents.json | 21 ++ .../ic_category_replay.png | Bin 0 -> 3242 bytes .../ic_category_series.imageset/Contents.json | 21 ++ .../ic_category_series.png | Bin 0 -> 3228 bytes SodaLive/Sources/Content/ContentApi.swift | 18 ++ .../Main/GetAudioContentMainResponse.swift | 5 +- .../ContentMainTabRankContentView.swift | 154 ++++++++++++ .../Main/V2/ContentByChannelView.swift | 193 ++++++++++++++ .../Content/Main/V2/ContentCreatorView.swift | 54 ++++ .../Main/V2/ContentMainBannerViewV2.swift | 134 ++++++++++ .../Category/ContentMainTabCategoryView.swift | 38 +++ .../Home/ContentMainTabHomeNoticeView.swift | 47 ++++ .../Home/ContentMainTabHomeRepository.swift | 23 ++ .../Main/V2/Home/ContentMainTabHomeView.swift | 238 ++++++++++++++++++ .../V2/Home/ContentMainTabHomeViewModel.swift | 152 +++++++++++ .../Home/GetContentMainTabHomeResponse.swift | 18 ++ .../ContentMainTabHomeRankCreatorView.swift | 170 +++++++++++++ .../ContentMainTabHomeRankSeriesView.swift | 82 ++++++ .../EventBanner/SectionEventBannerView.swift | 2 + SodaLive/Sources/Main/Home/HomeView.swift | 2 +- .../UI/Component/SeriesListBigItemView.swift | 2 +- 35 files changed, 1517 insertions(+), 4 deletions(-) create mode 100644 SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/ic_card_can_gray_32.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_alarm.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_alarm.imageset/ic_category_alarm.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/ic_category_asmr.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_audio_book.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_audio_book.imageset/ic_category_audio_book.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_audio_toon.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_audio_toon.imageset/ic_category_audio_toon.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_content.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_content.imageset/ic_category_content.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_free.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_free.imageset/ic_category_free.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_replay.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_replay.imageset/ic_category_replay.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/ic_category_series.png create mode 100644 SodaLive/Sources/Content/Main/V2/Content/ContentMainTabRankContentView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/ContentByChannelView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/ContentCreatorView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/ContentMainBannerViewV2.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/Category/ContentMainTabCategoryView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeNoticeView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeRepository.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeViewModel.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/GetContentMainTabHomeResponse.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/Rank/ContentMainTabHomeRankCreatorView.swift create mode 100644 SodaLive/Sources/Content/Main/V2/Home/Rank/ContentMainTabHomeRankSeriesView.swift diff --git a/SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/Contents.json new file mode 100644 index 0000000..71a95d9 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_card_can_gray_32.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/ic_card_can_gray_32.png b/SodaLive/Resources/Assets.xcassets/ic_card_can_gray_32.imageset/ic_card_can_gray_32.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff0e30dfd9b8994ca18d6cd861ecb1ab2ffa5e8 GIT binary patch literal 588 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 literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/Contents.json new file mode 100644 index 0000000..1e88b32 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_category_asmr.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/ic_category_asmr.png b/SodaLive/Resources/Assets.xcassets/ic_category_asmr.imageset/ic_category_asmr.png new file mode 100644 index 0000000000000000000000000000000000000000..f411f57bc3f918ee8fe6b3316ad9038e907f51d6 GIT binary patch literal 4236 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 WNm5r`-5QJl0000L=vfo=H*BvwlEGWu%s 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 literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/Contents.json new file mode 100644 index 0000000..c81b5a9 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_category_series.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/ic_category_series.png b/SodaLive/Resources/Assets.xcassets/ic_category_series.imageset/ic_category_series.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d7724df23160644ca7a9fcc432e1bc81a78fbe GIT binary patch literal 3228 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 O0000 Void + + let sortList: [String] + let onClickSort: (String) -> Void + + let contentList: [GetAudioContentRankingItem] + + @State private var selectedSort = "" + + var body: some View { + VStack(spacing: 13.3) { + HStack(spacing: 0) { + Text(title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + if isMore { + Image("ic_forward") + .onTapGesture { onClickMore() } + } + } + + if !sortList.isEmpty { + ContentMainRankingSortView( + sorts: sortList, + selectSort: { + selectedSort = $0 + onClickSort($0) + }, + selectedSort: $selectedSort + ) + } + + ScrollView(.horizontal, showsIndicators: false) { + LazyHGrid(rows: rows, spacing: 13.3) { + ForEach(0.. Void + + @State private var selectedCreatorId = 0 + + let columns = [GridItem(.flexible()), GridItem(.flexible())] + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + Text(title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + + ScrollView(.horizontal) { + HStack(spacing: 22) { + ForEach(0.. 0 { + Image("ic_card_can_gray_32") + } + + Text(content.price > 0 ? "\(content.price)" : "무료") + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.white) + } + .padding(4) + .background(Color.gray33.opacity(0.7)) + .cornerRadius(10) + + Spacer() + + Text(content.duration) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.white) + .padding(4) + .background(Color.gray33.opacity(0.7)) + .cornerRadius(10) + } + .padding(.horizontal, 2.7) + .padding(.bottom, 2.7) + } + + Text(content.title) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(.grayd2) + .lineLimit(1) + + HStack(spacing: 5.3) { + KFImage(URL(string: content.creatorProfileImageUrl)) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 21, height: 21)) + .resizable() + .frame(width: 21, height: 21) + .clipShape(Circle()) + .clipped() + + Text(content.creatorNickname) + .font(.custom(Font.medium.rawValue, size: 10)) + .foregroundColor(.gray77) + } + .onTapGesture { + AppState + .shared + .setAppStep(step: .creatorDetail(userId: content.creatorId)) + } + } + .onTapGesture { + AppState + .shared + .setAppStep(step: .contentDetail(contentId: content.contentId)) + } + } + } + + } + .onAppear { + if !self.creatorList.isEmpty { + selectedCreatorId = creatorList[0].creatorId + } + } + } +} + +#Preview { + ContentByChannelView( + title: "채널별 인기 콘텐츠", + creatorList: [ + ContentCreatorResponse( + creatorId: 1, + creatorNickname: "유저1", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ), + ContentCreatorResponse( + creatorId: 2, + creatorNickname: "유저2", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ) + ], + contentList: [ + GetAudioContentRankingItem( + contentId: 1, + title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....", + coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + themeStr: "커버곡", + price: 100, + duration: "00:30:20", + creatorId: 1, + creatorNickname: "유저1", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ), + GetAudioContentRankingItem( + contentId: 2, + title: "안녕하세요 오늘은 커버곡을 들려드릴께요....", + coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + themeStr: "커버곡", + price: 0, + duration: "00:30:20", + creatorId: 1, + creatorNickname: "유저1", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ), + GetAudioContentRankingItem( + contentId: 3, + title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....", + coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + themeStr: "커버곡", + price: 50, + duration: "00:30:20", + creatorId: 1, + creatorNickname: "유저1", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ), + GetAudioContentRankingItem( + contentId: 4, + title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....", + coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + themeStr: "커버곡", + price: 50, + duration: "00:30:20", + creatorId: 1, + creatorNickname: "유저1", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ) + ] + ) { _ in } +} diff --git a/SodaLive/Sources/Content/Main/V2/ContentCreatorView.swift b/SodaLive/Sources/Content/Main/V2/ContentCreatorView.swift new file mode 100644 index 0000000..daa4a23 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/ContentCreatorView.swift @@ -0,0 +1,54 @@ +// +// ContentCreatorView.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import SwiftUI +import Kingfisher + +struct ContentCreatorView: View { + + let isSelected: Bool + let item: ContentCreatorResponse + + var body: some View { + VStack(spacing: 13.3) { + KFImage(URL(string: item.creatorProfileImageUrl)) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 60, height: 60)) + .resizable() + .frame(width: 60, height: 60) + .clipShape(Circle()) + .overlay( + Circle() + .strokeBorder(lineWidth: 3) + .foregroundColor( + .button + .opacity(isSelected ? 1 : 0) + ) + ) + + Text(item.creatorNickname) + .font(.custom(Font.medium.rawValue, size: 11.3)) + .foregroundColor( + isSelected ? + Color.button : + Color.graybb + + ) + } + } +} + +#Preview { + ContentCreatorView( + isSelected: true, + item: ContentCreatorResponse( + creatorId: 1, + creatorNickname: "유저1", + creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ) + ) +} diff --git a/SodaLive/Sources/Content/Main/V2/ContentMainBannerViewV2.swift b/SodaLive/Sources/Content/Main/V2/ContentMainBannerViewV2.swift new file mode 100644 index 0000000..3ff828b --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/ContentMainBannerViewV2.swift @@ -0,0 +1,134 @@ +// +// ContentMainBannerViewV2.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import SwiftUI + +import Kingfisher + +struct ContentMainBannerViewV2: View { + + let bannerList: [GetAudioContentBannerResponse] + + @State var currentIndex = 0 + @State var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() + + @State var width: CGFloat = 0 + @State var height: CGFloat = 0 + + var body: some View { + VStack(spacing: 0) { + TabView(selection: $currentIndex) { + ForEach(0.. 0, let url = URL(string: link), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } + } + } +} diff --git a/SodaLive/Sources/Content/Main/V2/Home/Category/ContentMainTabCategoryView.swift b/SodaLive/Sources/Content/Main/V2/Home/Category/ContentMainTabCategoryView.swift new file mode 100644 index 0000000..40b2256 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/Category/ContentMainTabCategoryView.swift @@ -0,0 +1,38 @@ +// +// ContentMainTabCategoryView.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import SwiftUI + +struct ContentMainTabCategoryView: View { + + let imageName: String + let title: String + let onClick: () -> Void + + var body: some View { + VStack(spacing: 5.3) { + Image(imageName) + .resizable() + .frame(width: 43, height: 43) + + Text(title) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(.gray77) + } + .onTapGesture { + onClick() + } + } +} + +#Preview { + ContentMainTabCategoryView( + imageName: "ic_category_series", + title: "시리즈", + onClick: {} + ) +} diff --git a/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeNoticeView.swift b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeNoticeView.swift new file mode 100644 index 0000000..7e6acca --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeNoticeView.swift @@ -0,0 +1,47 @@ +// +// ContentMainTabHomeNoticeView.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import SwiftUI + +struct ContentMainTabHomeNoticeView: View { + + let notice: NoticeItem + let onClick: (NoticeItem) -> Void + + var body: some View { + HStack(spacing: 0) { + Text(notice.title) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(.white) + + Spacer() + + Text("자세히 >") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(.white) + .onTapGesture { + onClick(notice) + } + } + .padding(.horizontal, 13.3) + .padding(.vertical, 10) + .background(Color.gray22) + .cornerRadius(5.3) + } +} + +#Preview { + ContentMainTabHomeNoticeView( + notice: NoticeItem( + title: "[업데이트] 1.28.0 버전 업데이트", + content: "test", + date: "2025-02-07" + ) + ) { + AppState.shared.setAppStep(step: .noticeDetail(notice: $0)) + } +} diff --git a/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeRepository.swift b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeRepository.swift new file mode 100644 index 0000000..f723da6 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeRepository.swift @@ -0,0 +1,23 @@ +// +// ContentMainTabHomeRepository.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +class ContentMainTabHomeRepository { + private let api = MoyaProvider() + + func getContentMainHome() -> AnyPublisher { + return api.requestPublisher(.getContentMainHome) + } + + func getPopularContentByCreator(creatorId: Int) -> AnyPublisher { + return api.requestPublisher(.getPopularContentByCreator(creatorId: creatorId)) + } +} diff --git a/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeView.swift b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeView.swift new file mode 100644 index 0000000..dfa50dd --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeView.swift @@ -0,0 +1,238 @@ +// +// ContentMainTabHomeView.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import SwiftUI + +struct ContentMainTabHomeView: View { + + @StateObject var viewModel = ContentMainTabHomeViewModel() + + var body: some View { + BaseView(isLoading: $viewModel.isLoading) { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 0) { + Text("콘텐츠 마켓") + .font(.custom(Font.bold.rawValue, size: 21.3)) + .foregroundColor(Color.button) + + Spacer() + + Image("ic_content_keep") + .onTapGesture { + AppState.shared.setAppStep(step: .myBox(currentTab: .orderlist)) + } + } + .padding(.bottom, 26.7) + .padding(.horizontal, 13.3) + + if let notice = viewModel.noticeItem { + ContentMainTabHomeNoticeView(notice: notice) { + AppState.shared + .setAppStep(step: .noticeDetail(notice: $0)) + } + .padding(.horizontal, 13.3) + } + + if viewModel.bannerList.count > 0 { + ContentMainBannerViewV2(bannerList: viewModel.bannerList) + .padding(.top, 30) + .padding(.horizontal, 13.3) + } + + HStack(spacing: 0) { + Image("ic_title_search_black") + + Text("채널명을 입력해 보세요") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.gray55) + .keyboardType(.default) + .padding(.horizontal, 13.3) + + Spacer() + } + .padding(.horizontal, 21.3) + .frame(height: 50) + .frame(maxWidth: .infinity) + .background(Color.gray22) + .overlay( + RoundedRectangle(cornerRadius: 6.7) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color.graybb) + ) + .padding(.top, 30) + .padding(.horizontal, 13.3) + .onTapGesture { + UserDefaults.set("", forKey: .searchChannel) + AppState.shared.setAppStep(step: .searchChannel) + } + + VStack(spacing: 13.3) { + HStack(spacing: 0) { + ContentMainTabCategoryView( + imageName: "ic_category_series", + title: "시리즈", + onClick: {} + ) + .frame(maxWidth: .infinity) + + ContentMainTabCategoryView( + imageName: "ic_category_content", + title: "단편", + onClick: {} + ) + .frame(maxWidth: .infinity) + + ContentMainTabCategoryView( + imageName: "ic_category_audio_book", + title: "오디오북", + onClick: {} + ) + .frame(maxWidth: .infinity) + + ContentMainTabCategoryView( + imageName: "ic_category_alarm", + title: "모닝콜", + onClick: {} + ) + .frame(maxWidth: .infinity) + } + + HStack(spacing: 0) { + ContentMainTabCategoryView( + imageName: "ic_category_asmr", + title: "ASMR", + onClick: {} + ) + .frame(maxWidth: .infinity) + + ContentMainTabCategoryView( + imageName: "ic_category_replay", + title: "다시듣기", + onClick: {} + ) + .frame(maxWidth: .infinity) + + ContentMainTabCategoryView( + imageName: "ic_category_audio_toon", + title: "오디오툰", + onClick: {} + ) + .frame(maxWidth: .infinity) + + ContentMainTabCategoryView( + imageName: "ic_category_free", + title: "무료", + onClick: {} + ) + .frame(maxWidth: .infinity) + } + } + .padding(.vertical, 13.3) + .background(Color.gray22) + .cornerRadius(5.3) + .padding(.top, 30) + .padding(.horizontal, 13.3) + + if let response = viewModel.rankCreatorResponse { + ContentMainTabHomeRankCreatorView(response: response) + .padding(.top, 30) + .padding(.horizontal, 13.3) + } + + if !viewModel.rankSeriesList.isEmpty { + ContentMainTabHomeRankSeriesView(seriesList: viewModel.rankSeriesList) + .padding(.top, 30) + .padding(.horizontal, 13.3) + } + + if !viewModel.rankSortTypeList.isEmpty { + ContentMainTabRankContentView( + title: "인기 단편", + isMore: true, + onClickMore: { + AppState.shared.setAppStep(step: .contentRankingAll) + }, + sortList: !viewModel.rankSortTypeList.isEmpty ? + viewModel.rankSortTypeList : + [], + onClickSort: { viewModel.getContentRanking(sort: $0) }, + contentList: viewModel.rankContentList + ) + .padding(.top, 30) + .padding(.horizontal, 13.3) + } + + if viewModel.eventBannerList.count > 0 { + SectionEventBannerView(items: viewModel.eventBannerList) + .frame( + width: viewModel.eventBannerList.count > 0 ? screenSize().width : 0, + height: viewModel.eventBannerList.count > 0 ? screenSize().width * 300 / 1000 : 0, + alignment: .center + ) + .padding(.top, 30) + } + + if !viewModel.contentRankCreatorList.isEmpty { + ContentByChannelView( + title: "채널별 인기 콘텐츠", + creatorList: viewModel.contentRankCreatorList, + contentList: viewModel.salesCountRankContentList, + onClickCreator: { + viewModel.getPopularContentByCreator(creatorId: $0) + } + ) + .padding(.top, 30) + .padding(.horizontal, 13.3) + } + + Text(""" +- 회사명 : 주식회사 소다라이브 + +- 대표자 : 이재형 + +- 주소 : 경기도 성남시 분당구 황새울로335번길 10, 5층 563A호 + +- 사업자등록번호 : 870-81-03220 + +- 통신판매업신고 : 제2024-성남분당B-1012호 + +- 고객센터 : 02.2055.1477 (이용시간 10:00~19:00) + +- 대표 이메일 : sodalive.official@gmail.com +""") + .font(.custom(Font.medium.rawValue, size: 11)) + .foregroundColor(Color.gray77) + .padding(.top, 30) + .padding(.horizontal, 13.3) + } + .onAppear { + viewModel.fetchData() + } + } + .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) { + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .frame(width: screenSize().width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color.button) + .foregroundColor(Color.white) + .multilineTextAlignment(.leading) + .cornerRadius(20) + .padding(.bottom, 66.7) + Spacer() + } + } + } + } +} + +#Preview { + ContentMainTabHomeView() +} diff --git a/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeViewModel.swift b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeViewModel.swift new file mode 100644 index 0000000..95ce5d4 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/ContentMainTabHomeViewModel.swift @@ -0,0 +1,152 @@ +// +// ContentMainTabHomeViewModel.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import Foundation +import Combine + +final class ContentMainTabHomeViewModel: ObservableObject { + + private let repository = ContentMainTabHomeRepository() + private let contentRepository = ContentRepository() + private var subscription = Set() + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + + @Published var noticeItem: NoticeItem? = nil + @Published var bannerList = [GetAudioContentBannerResponse]() + @Published var rankCreatorResponse: GetExplorerSectionResponse? = nil + @Published var rankSeriesList = [SeriesListItem]() + @Published var rankSortTypeList: [String] = [] + @Published var rankContentList: [GetAudioContentRankingItem] = [] + @Published var eventBannerList: [EventItem] = [] + @Published var contentRankCreatorList: [ContentCreatorResponse] = [] + @Published var salesCountRankContentList: [GetAudioContentRankingItem] = [] + + func fetchData() { + isLoading = true + + repository.getContentMainHome() + .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 { + self.noticeItem = data.latestNotice + self.bannerList = data.bannerList + self.rankCreatorResponse = data.rankCreatorList + self.rankSortTypeList = data.rankSortTypeList + self.rankContentList = data.rankContentList + self.eventBannerList = data.eventBannerList.eventList + self.contentRankCreatorList = data.contentRankCreatorList + self.salesCountRankContentList = data.salesCountRankContentList + } 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) + } + + func getContentRanking(sort: String = "매출") { + isLoading = true + contentRepository.getContentRanking(page: 1, size: 12, sortType: 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 { + self.rankContentList = data.items + } 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) + } + + func getPopularContentByCreator(creatorId: Int) { + isLoading = true + repository.getPopularContentByCreator(creatorId: creatorId) + .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<[GetAudioContentRankingItem]>.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.salesCountRankContentList = data + } 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/Main/V2/Home/GetContentMainTabHomeResponse.swift b/SodaLive/Sources/Content/Main/V2/Home/GetContentMainTabHomeResponse.swift new file mode 100644 index 0000000..e79b04f --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/GetContentMainTabHomeResponse.swift @@ -0,0 +1,18 @@ +// +// GetContentMainTabHomeResponse.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +struct GetContentMainTabHomeResponse: Decodable { + let latestNotice: NoticeItem? + let bannerList: [GetAudioContentBannerResponse] + let rankCreatorList: GetExplorerSectionResponse + let rankSeriesList: [SeriesListItem] + let rankSortTypeList: [String] + let rankContentList: [GetAudioContentRankingItem] + let eventBannerList: GetEventResponse + let contentRankCreatorList: [ContentCreatorResponse] + let salesCountRankContentList: [GetAudioContentRankingItem] +} diff --git a/SodaLive/Sources/Content/Main/V2/Home/Rank/ContentMainTabHomeRankCreatorView.swift b/SodaLive/Sources/Content/Main/V2/Home/Rank/ContentMainTabHomeRankCreatorView.swift new file mode 100644 index 0000000..a1a16c8 --- /dev/null +++ b/SodaLive/Sources/Content/Main/V2/Home/Rank/ContentMainTabHomeRankCreatorView.swift @@ -0,0 +1,170 @@ +// +// ContentMainTabHomeRankCreatorView.swift +// SodaLive +// +// Created by klaus on 2/20/25. +// + +import SwiftUI +import Kingfisher + +struct ContentMainTabHomeRankCreatorView: View { + + let response: GetExplorerSectionResponse + + let rankingCrawns = ["ic_crown_1", "ic_crown_2", "ic_crown_3"] + let rankingColors = [ + [Color(hex: "ffdc00"), Color(hex: "ffb600")], + [Color(hex: "ffffff"), Color(hex: "9f9f9f")], + [Color(hex: "e6a77a"), Color(hex: "c67e4a")], + [Color(hex: "ffffff").opacity(0), Color(hex: "ffffff").opacity(0)] + ] + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + if let desc = response.desc { + VStack(spacing: 8) { + Text("\(desc)") + .font(.custom(Font.bold.rawValue, size: 14.7)) + .foregroundColor(Color.grayee) + + Text("※ 인기 순위는 매주 업데이트됩니다.") + .font(.custom(Font.light.rawValue, size: 13.3)) + .foregroundColor(Color.graybb) + } + .padding(.vertical, 8) + .frame(maxWidth: .infinity) + .background(Color.gray22) + .padding(.top, 13.3) + } + + if let coloredTitle = response.coloredTitle, let color = response.color { + let titleArray = response.title.components(separatedBy: coloredTitle) + HStack(spacing: 0) { + Text(titleArray[0]) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color.grayee) + + Text(coloredTitle) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: color)) + + if titleArray.count > 1 { + Text(titleArray[1]) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color.grayee) + } + } + .padding(.top, 30) + } else { + Text(response.title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color.grayee) + .padding(.top, 30) + } + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 13.3) { + ForEach(0..