From b985af4497f32c4f8fc783b2922ebccc69801777 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 2 Feb 2026 11:48:12 +0900 Subject: [PATCH] =?UTF-8?q?=EC=84=B1=EC=9D=B8=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=20=EC=9E=85=EC=9E=A5=EC=97=90=20=EB=B3=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=9D=90=EB=A6=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/HomeTabView.swift | 38 ++++--- SodaLive/Sources/Live/LiveView.swift | 9 ++ .../Sources/Live/Now/SectionLiveNowView.swift | 23 +--- SodaLive/Sources/Main/Home/HomeView.swift | 103 +++++++++++++++++- 4 files changed, 130 insertions(+), 43 deletions(-) diff --git a/SodaLive/Sources/Home/HomeTabView.swift b/SodaLive/Sources/Home/HomeTabView.swift index 16aac3f..e54cdd5 100644 --- a/SodaLive/Sources/Home/HomeTabView.swift +++ b/SodaLive/Sources/Home/HomeTabView.swift @@ -26,7 +26,16 @@ struct HomeTabView: View { @State private var pendingUnfollowCreatorId: Int? = nil @State private var pendingUnfollowCreatorName = "" - var onTapPopularCharacterAllView: (() -> Void)? = nil + let onTapPopularCharacterAllView: (() -> Void)? + let onTapLiveNowItem: ((Int, Bool) -> Void)? + + init( + onTapPopularCharacterAllView: (() -> Void)? = nil, + onTapLiveNowItem: ((Int, Bool) -> Void)? = nil + ) { + self.onTapPopularCharacterAllView = onTapPopularCharacterAllView + self.onTapLiveNowItem = onTapLiveNowItem + } // CharacterView에서 전달받는 단일 진입 함수 private func handleCharacterSelection(_ characterId: Int) { @@ -45,6 +54,10 @@ struct HomeTabView: View { } AppState.shared.setAppStep(step: .characterDetail(characterId: characterId)) } + + private func handleLiveNowItemTap(roomId: Int, isAdult: Bool) { + onTapLiveNowItem?(roomId, isAdult) + } var body: some View { BaseView(isLoading: $viewModel.isLoading) { @@ -93,23 +106,12 @@ struct HomeTabView: View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 16) { ForEach(0.. Void + + init(onTapLiveNowItem: @escaping (Int, Bool) -> Void = { _, _ in }) { + self.onTapLiveNowItem = onTapLiveNowItem + } var body: some View { ZStack { @@ -61,6 +67,9 @@ struct LiveView: View { AppState.shared.setAppStep(step: .login) } }, + onTapItem: { item in + onTapLiveNowItem(item.roomId, item.isAdult) + }, onTapCreateLive: { if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { AppState.shared.setAppStep(step: .createLive(timeSettingMode: .NOW, onSuccess: onCreateSuccess)) diff --git a/SodaLive/Sources/Live/Now/SectionLiveNowView.swift b/SodaLive/Sources/Live/Now/SectionLiveNowView.swift index 3824c12..a9998a9 100644 --- a/SodaLive/Sources/Live/Now/SectionLiveNowView.swift +++ b/SodaLive/Sources/Live/Now/SectionLiveNowView.swift @@ -12,11 +12,10 @@ struct SectionLiveNowView: View { let items: [GetRoomListResponse] let onClickParticipant: (Int) -> Void + let onTapItem: (GetRoomListResponse) -> Void let onTapCreateLive: () -> Void let onClickRefresh: () -> Void - @AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token) - var body: some View { LazyVStack(spacing: 13.3) { HStack(spacing: 0) { @@ -43,24 +42,7 @@ struct SectionLiveNowView: View { LiveNowItemView(item: item, itemWidth: nil) .contentShape(Rectangle()) .onTapGesture { - if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - AppState.shared.setAppStep( - step: .liveDetail( - roomId: item.roomId, - onClickParticipant: { - AppState.shared.isShowPlayer = false - onClickParticipant(item.roomId) - }, - onClickReservation: {}, - onClickStart: { - }, - onClickCancel: { - } - ) - ) - } else { - AppState.shared.setAppStep(step: .login) - } + onTapItem(item) } } } @@ -114,6 +96,7 @@ struct SectionLiveNowView_Previews: PreviewProvider { SectionLiveNowView( items: [], onClickParticipant: { _ in }, + onTapItem: { _ in }, onTapCreateLive: {}, onClickRefresh: {} ) diff --git a/SodaLive/Sources/Main/Home/HomeView.swift b/SodaLive/Sources/Main/Home/HomeView.swift index a9934c4..959972c 100644 --- a/SodaLive/Sources/Main/Home/HomeView.swift +++ b/SodaLive/Sources/Main/Home/HomeView.swift @@ -9,6 +9,8 @@ import SwiftUI import Firebase import Kingfisher +import Bootpay +import BootpayUI struct HomeView: View { @StateObject var viewModel = HomeViewModel() @@ -16,13 +18,25 @@ struct HomeView: View { @StateObject var appState = AppState.shared @StateObject var contentPlayManager = ContentPlayManager.shared @StateObject var contentPlayerPlayManager = ContentPlayerPlayManager.shared + @StateObject var mypageViewModel = MyPageViewModel() - private let liveView = LiveView() - @State var homeTabView = HomeTabView() + private var liveView: LiveView { LiveView(onTapLiveNowItem: handleLiveNowItemTap) } + private var homeTabView: HomeTabView { + HomeTabView( + onTapPopularCharacterAllView: { viewModel.currentTab = .chat }, + onTapLiveNowItem: handleLiveNowItemTap + ) + } private let chatTabView = ChatTabView() @State private var isShowPlayer = false @AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token) + @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 body: some View { GeometryReader { proxy in @@ -166,9 +180,12 @@ struct HomeView: View { } } .onAppear { - homeTabView.onTapPopularCharacterAllView = { - viewModel.currentTab = .chat - } + payload.applicationId = BOOTPAY_APP_ID + payload.price = 0 + payload.pg = "다날" + payload.method = "본인인증" + payload.orderName = "본인인증" + payload.authenticationId = "\(UserDefaults.string(forKey: .nickname))__\(String(NSTimeIntervalSince1970))" if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { pushTokenUpdate() @@ -181,6 +198,25 @@ struct HomeView: View { if appState.isShowNotificationSettingsDialog { NotificationSettingsDialog() } + + if isShowAuthConfirmView { + SodaDialog( + title: "본인인증", + desc: "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" + + "캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.", + confirmButtonTitle: "본인인증 하러가기", + confirmButtonAction: { + isShowAuthConfirmView = false + isShowAuthView = true + }, + cancelButtonTitle: "취소", + cancelButtonAction: { + isShowAuthConfirmView = false + pendingAction = nil + }, + textAlignment: .center + ) + } if liveViewModel.isShowPaymentDialog { LivePaymentDialog( @@ -231,6 +267,34 @@ struct HomeView: View { } } .edgesIgnoringSafeArea(.bottom) + .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 { + DEBUG_LOG("onDone: \($0)") + mypageViewModel.authVerify($0) { + auth = true + isShowAuthView = false + if let action = pendingAction { + pendingAction = nil + action() + } + } + } + .onClose { + isShowAuthView = false + } + } .valueChanged(value: appState.pushRoomId) { value in DispatchQueue.main.async { appState.setAppStep(step: .main) @@ -285,6 +349,35 @@ struct HomeView: View { } } + private func handleLiveNowItemTap(roomId: Int, isAdult: Bool) { + let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { + AppState.shared.setAppStep(step: .login) + return + } + if isAdult && auth == false { + pendingAction = { openLiveDetail(roomId: roomId) } + isShowAuthConfirmView = true + return + } + openLiveDetail(roomId: roomId) + } + + private func openLiveDetail(roomId: Int) { + AppState.shared.setAppStep( + step: .liveDetail( + roomId: roomId, + onClickParticipant: { + AppState.shared.isShowPlayer = false + liveViewModel.enterLiveRoom(roomId: roomId) + }, + onClickReservation: {}, + onClickStart: {}, + onClickCancel: {} + ) + ) + } + private func pushTokenUpdate() { let pushToken = UserDefaults.string(forKey: .pushToken) if !pushToken.trimmingCharacters(in: .whitespaces).isEmpty {