From 721a26bb7b4d02efeb11980b99c528670dddfe8f Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 20 Oct 2025 18:44:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(home):=20=ED=99=88=20=ED=83=AD=EC=97=90=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=20=EC=BA=90=EB=A6=AD=ED=84=B0=20=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/Home/GetHomeResponse.swift | 1 + SodaLive/Sources/Home/HomeTabView.swift | 97 ++++++++++++++++++++ SodaLive/Sources/Home/HomeTabViewModel.swift | 2 + SodaLive/Sources/Main/Home/HomeView.swift | 6 +- 4 files changed, 105 insertions(+), 1 deletion(-) diff --git a/SodaLive/Sources/Home/GetHomeResponse.swift b/SodaLive/Sources/Home/GetHomeResponse.swift index 35e7679..e3a29be 100644 --- a/SodaLive/Sources/Home/GetHomeResponse.swift +++ b/SodaLive/Sources/Home/GetHomeResponse.swift @@ -15,6 +15,7 @@ struct GetHomeResponse: Decodable { let originalAudioDramaList: [SeriesListItem] let auditionList: [GetAuditionListItem] let dayOfWeekSeriesList: [SeriesListItem] + let popularCharacters: [Character] let contentRanking: [GetAudioContentRankingItem] let recommendChannelList: [RecommendChannelResponse] let freeContentList: [AudioContentMainItem] diff --git a/SodaLive/Sources/Home/HomeTabView.swift b/SodaLive/Sources/Home/HomeTabView.swift index d238ad6..a925cb7 100644 --- a/SodaLive/Sources/Home/HomeTabView.swift +++ b/SodaLive/Sources/Home/HomeTabView.swift @@ -6,6 +6,8 @@ // import SwiftUI +import Bootpay +import BootpayUI struct HomeTabView: View { @StateObject var viewModel = HomeTabViewModel() @@ -13,6 +15,32 @@ struct HomeTabView: View { @AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token) @AppStorage("role") private var role: String = UserDefaults.string(forKey: UserDefaultsKey.role) + @AppStorage("auth") private var auth: Bool = UserDefaults.bool(forKey: UserDefaultsKey.auth) + + @State private var isShowAuthView: Bool = false + @State private var isShowAuthConfirmView: Bool = false + @State private var pendingAction: (() -> Void)? = nil + @State private var payload = Payload() + + var onTapPopularCharacterAllView: (() -> Void)? = nil + + // CharacterView에서 전달받는 단일 진입 함수 + private func handleCharacterSelection(_ characterId: Int) { + let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { + AppState.shared.setAppStep(step: .login) + return + } + if auth == false { + pendingAction = { + AppState.shared + .setAppStep(step: .characterDetail(characterId: characterId)) + } + isShowAuthConfirmView = true + return + } + AppState.shared.setAppStep(step: .characterDetail(characterId: characterId)) + } var body: some View { BaseView(isLoading: $viewModel.isLoading) { @@ -205,6 +233,24 @@ struct HomeTabView: View { viewModel.getDayOfWeekSeriesList(dayOfWeek: $0) } + // 인기 캐릭터 섹션 + if !viewModel.popularCharacters.isEmpty { + CharacterSectionView( + title: "인기 캐릭터", + items: viewModel.popularCharacters, + isShowRank: true, + trailingTitle: "전체보기", + onTapTrailing: { + if let onTapPopularCharacterAllView = onTapPopularCharacterAllView { + onTapPopularCharacterAllView() + } + }, + onTap: { ch in + handleCharacterSelection(ch.characterId) + } + ) + } + if !viewModel.contentRanking.isEmpty { HomeWeeklyChartView(contentList: viewModel.contentRanking) } @@ -332,10 +378,61 @@ struct HomeTabView: View { AppState.shared.setAppStep(step: .createContent) } } + + if isShowAuthConfirmView { + SodaDialog( + title: "본인인증", + desc: "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" + + "캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.", + confirmButtonTitle: "본인인증 하러가기", + confirmButtonAction: { + isShowAuthConfirmView = false + isShowAuthView = true + }, + cancelButtonTitle: "취소", + cancelButtonAction: { + isShowAuthConfirmView = false + pendingAction = nil + }, + textAlignment: .center + ) + } } .onAppear { + payload.applicationId = BOOTPAY_APP_ID + payload.price = 0 + payload.pg = "다날" + payload.method = "본인인증" + payload.orderName = "본인인증" + payload.authenticationId = "\(UserDefaults.string(forKey: .nickname))__\(String(NSTimeIntervalSince1970))" + viewModel.fetchData() } + .fullScreenCover(isPresented: $isShowAuthView) { + BootpayUI(payload: payload, requestType: BootpayRequest.TYPE_AUTHENTICATION) + .onConfirm { _ in + true + } + .onCancel { _ in + isShowAuthView = false + } + .onError { _ in + AppState.shared.errorMessage = "본인인증 중 오류가 발생했습니다." + AppState.shared.isShowErrorPopup = true + isShowAuthView = false + } + .onDone { _ in + auth = true + isShowAuthView = false + if let action = pendingAction { + pendingAction = nil + action() + } + } + .onClose { + isShowAuthView = false + } + } } .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { HStack { diff --git a/SodaLive/Sources/Home/HomeTabViewModel.swift b/SodaLive/Sources/Home/HomeTabViewModel.swift index f7cc9ac..2eee903 100644 --- a/SodaLive/Sources/Home/HomeTabViewModel.swift +++ b/SodaLive/Sources/Home/HomeTabViewModel.swift @@ -28,6 +28,7 @@ final class HomeTabViewModel: ObservableObject { @Published var eventBannerList: [GetAudioContentBannerResponse] = [] @Published var originalAudioDramaList: [SeriesListItem] = [] @Published var dayOfWeekSeriesList: [SeriesListItem] = [] + @Published private(set) var popularCharacters: [Character] = [] @Published var contentRanking: [GetAudioContentRankingItem] = [] @Published var recommendChannelList: [RecommendChannelResponse] = [] @Published var freeContentList: [AudioContentMainItem] = [] @@ -59,6 +60,7 @@ final class HomeTabViewModel: ObservableObject { self.eventBannerList = data.bannerList self.originalAudioDramaList = data.originalAudioDramaList self.dayOfWeekSeriesList = data.dayOfWeekSeriesList + self.popularCharacters = data.popularCharacters self.contentRanking = data.contentRanking self.recommendChannelList = data.recommendChannelList self.freeContentList = data.freeContentList diff --git a/SodaLive/Sources/Main/Home/HomeView.swift b/SodaLive/Sources/Main/Home/HomeView.swift index fd7cd6b..10c7c8c 100644 --- a/SodaLive/Sources/Main/Home/HomeView.swift +++ b/SodaLive/Sources/Main/Home/HomeView.swift @@ -18,7 +18,7 @@ struct HomeView: View { @StateObject var contentPlayerPlayManager = ContentPlayerPlayManager.shared private let liveView = LiveView() - private let homeTabView = HomeTabView() + @State var homeTabView = HomeTabView() private let chatTabView = ChatTabView() @State private var isShowPlayer = false @@ -166,6 +166,10 @@ struct HomeView: View { } } .onAppear { + homeTabView.onTapPopularCharacterAllView = { + viewModel.currentTab = .chat + } + if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { pushTokenUpdate() viewModel.getMemberInfo()