// // HomeView.swift // SodaLive // // Created by klaus on 2023/08/09. // import SwiftUI import Firebase import Kingfisher import Bootpay import BootpayUI struct HomeView: View { @StateObject var viewModel = HomeViewModel() @StateObject var liveViewModel = LiveViewModel() @StateObject var appState = AppState.shared @StateObject var contentPlayManager = ContentPlayManager.shared @StateObject var contentPlayerPlayManager = ContentPlayerPlayManager.shared @StateObject var mypageViewModel = MyPageViewModel() 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 ZStack(alignment: .bottom) { VStack(spacing: 0) { ZStack { homeTabView .frame(width: viewModel.currentTab == .home ? proxy.size.width : 0) .opacity(viewModel.currentTab == .home ? 1.0 : 0.01) liveView .frame(width: viewModel.currentTab == .live ? proxy.size.width : 0) .opacity(viewModel.currentTab == .live ? 1.0 : 0.01) chatTabView .frame(width: viewModel.currentTab == .chat ? proxy.size.width : 0) .opacity(viewModel.currentTab == .chat ? 1.0 : 0.01) if viewModel.currentTab == .mypage { MyPageView() } } .padding(.bottom, appState.isShowPlayer ? 72 : 0) Spacer() if contentPlayerPlayManager.isShowingMiniPlayer { HStack(spacing: 0) { KFImage(URL(string: contentPlayerPlayManager.coverImageUrl)) .cancelOnDisappear(true) .downsampling( size: CGSize( width: 36.7, height: 36.7 ) ) .resizable() .frame(width: 36.7, height: 36.7) .cornerRadius(5.3) VStack(alignment: .leading, spacing: 2.3) { Text(contentPlayerPlayManager.title) .appFont(size: 13, weight: .medium) .foregroundColor(Color.grayee) .lineLimit(2) Text(contentPlayerPlayManager.nickname) .appFont(size: 11, weight: .medium) .foregroundColor(Color.grayd2) } .padding(.horizontal, 10.7) Spacer() Image(contentPlayerPlayManager.isPlaying ? "ic_noti_pause" : "btn_bar_play") .resizable() .frame(width: 25, height: 25) .onTapGesture { contentPlayerPlayManager.playOrPause() } Image("ic_noti_stop") .resizable() .frame(width: 25, height: 25) .padding(.leading, 16) .onTapGesture { contentPlayerPlayManager.resetPlayer() } } .padding(.vertical, 10.7) .padding(.horizontal, 13.3) .background(Color.gray22) .contentShape(Rectangle()) .onTapGesture { isShowPlayer = true } } if contentPlayManager.isShowingMiniPlayer { HStack(spacing: 0) { KFImage(URL(string: contentPlayManager.coverImage)) .cancelOnDisappear(true) .downsampling( size: CGSize( width: 36.7, height: 36.7 ) ) .resizable() .frame(width: 36.7, height: 36.7) .cornerRadius(5.3) VStack(alignment: .leading, spacing: 2.3) { Text(contentPlayManager.title) .appFont(size: 13, weight: .medium) .foregroundColor(Color.grayee) .lineLimit(2) Text(contentPlayManager.nickname) .appFont(size: 11, weight: .medium) .foregroundColor(Color.grayd2) } .padding(.horizontal, 10.7) Spacer() Image(contentPlayManager.isPlaying ? "ic_noti_pause" : "btn_bar_play") .resizable() .frame(width: 25, height: 25) .onTapGesture { if contentPlayManager.isPlaying { contentPlayManager.pauseAudio() } else { contentPlayManager .playAudio(contentId: contentPlayManager.contentId) } } Image("ic_noti_stop") .resizable() .frame(width: 25, height: 25) .padding(.leading, 16) .onTapGesture { contentPlayManager.stopAudio() } } .padding(.vertical, 10.7) .padding(.horizontal, 13.3) .background(Color.gray22) .contentShape(Rectangle()) .onTapGesture { appState .setAppStep( step: .contentDetail(contentId: contentPlayManager.contentId) ) } } BottomTabView(width: proxy.size.width, currentTab: $viewModel.currentTab) if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color.gray11) .frame(width: proxy.size.width, height: 15.3) } } .onAppear { 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() viewModel.getMemberInfo() viewModel.getEventPopup() viewModel.addAllPlaybackTracking() } } 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( title: liveViewModel.paymentDialogTitle, desc: liveViewModel.paymentDialogDesc, desc2: liveViewModel.paymentDialogDesc2, confirmButtonTitle: liveViewModel.paymentDialogConfirmTitle, confirmButtonAction: liveViewModel.paymentDialogConfirmAction, cancelButtonTitle: liveViewModel.paymentDialogCancelTitle, cancelButtonAction: liveViewModel.hidePopup, startDateTime: liveViewModel.liveStartDate, nowDateTime: liveViewModel.nowDate ) } if liveViewModel.isShowPasswordDialog { LiveRoomPasswordDialog( isShowing: $liveViewModel.isShowPasswordDialog, can: liveViewModel.secretOrPasswordDialogCan, confirmAction: liveViewModel.passwordDialogConfirmAction ) } if let eventItem = appState.eventPopup { VStack(spacing: 0) { Spacer() EventPopupDialogView(eventPopup: eventItem) if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: proxy.size.width, height: 15.3) } } .background(Color(hex: "222222").opacity(0.7)) .onTapGesture { AppState.shared.eventPopup = nil } } if isShowPlayer { ContentPlayerView(isShowing: $isShowPlayer, playlist: []) } if appState.isShowPlayer { LiveRoomViewV2() } } .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) if value > 0 { liveViewModel.enterLiveRoom(roomId: value) } } } .valueChanged(value: appState.pushChannelId) { value in DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if value > 0 { appState.setAppStep(step: .main) appState.setAppStep(step: .creatorDetail(userId: value)) } } } .valueChanged(value: appState.pushMessageId) { value in DispatchQueue.main.async { appState.setAppStep(step: .main) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if value > 0 { appState.setAppStep(step: .message) } } } } .valueChanged(value: appState.pushAudioContentId) { value in DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if value > 0 { appState.setAppStep(step: .main) appState.setAppStep(step: .contentDetail(contentId: value)) } } } .valueChanged(value: appState.pushSeriesId) { value in DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if value > 0 { appState.setAppStep(step: .main) appState.setAppStep(step: .seriesDetail(seriesId: value)) } } } .onAppear { if appState.pushMessageId > 0 { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { appState.setAppStep(step: .message) } } } } } 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 { self.viewModel.pushTokenUpdate(pushToken: pushToken) } } } struct HomeView_Previews: PreviewProvider { static var previews: some View { HomeView() } }