// // ChatTabView.swift // SodaLive // // Created by klaus on 8/29/25. // import SwiftUI import Bootpay import BootpayUI import PopupView struct ChatTabView: View { @AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token) @AppStorage("auth") private var auth: Bool = UserDefaults.bool(forKey: UserDefaultsKey.auth) private enum InnerTab: Int, CaseIterable { case character = 0 case talk = 1 var title: String { switch self { case .character: return "캐릭터" case .talk: return "톡" } } } @State private var selectedTab: InnerTab = .character @State private var isShowAuthView: Bool = false @State private var pendingCharacterId: Int? = nil @State private var payload = Payload() // 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 { // 본인인증 전체화면 표시 후 완료 시 바로 이동 pendingCharacterId = characterId isShowAuthView = true return } AppState.shared.setAppStep(step: .characterDetail(characterId: characterId)) } var body: some View { VStack(alignment: .leading, spacing: 0) { // 앱 바 HStack(spacing: 24) { Image("img_text_logo") Spacer() if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Image("ic_search_white") .onTapGesture { AppState .shared .setAppStep(step: .search) } Image("ic_can") .onTapGesture { AppState .shared .setAppStep(step: .canCharge(refresh: {})) } } } .padding(.horizontal, 24) .padding(.vertical, 20) // 내부 탭 (캐릭터 / 톡) HStack(spacing: 0) { ChatInnerTab( title: InnerTab.character.title, isSelected: selectedTab == .character, onTap: { if selectedTab != .character { selectedTab = .character } } ) ChatInnerTab( title: InnerTab.talk.title, isSelected: selectedTab == .talk, onTap: { if selectedTab != .talk { selectedTab = .talk } } ) } .padding(.bottom, 12) Group { switch selectedTab { case .character: CharacterView(onSelectCharacter: handleCharacterSelection) case .talk: TalkView() } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } .onAppear { payload.applicationId = BOOTPAY_APP_ID payload.price = 0 payload.pg = "다날" payload.method = "본인인증" payload.orderName = "본인인증" payload.authenticationId = "\(UserDefaults.string(forKey: .nickname))__\(String(NSTimeIntervalSince1970))" } .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 chId = pendingCharacterId { pendingCharacterId = nil AppState.shared.setAppStep(step: .characterDetail(characterId: chId)) } } .onClose { isShowAuthView = false } } } } struct ChatInnerTab: View { let title: String let isSelected: Bool let onTap: () -> Void var body: some View { Button(action: onTap) { VStack(spacing: 0) { Spacer() Text(title) .font( .custom( isSelected ? Font.preBold.rawValue : Font.preRegular.rawValue, size: 18 ) ) .foregroundColor(Color(hex: isSelected ? "3bb9f1" : "b0bec5")) .frame(maxWidth: .infinity) Spacer() Rectangle() .frame(maxWidth: .infinity) .frame(height: 4) .foregroundColor(Color(hex: "3bb9f1").opacity(isSelected ? 1 : 0)) } .frame(height: 50) .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) } } #Preview { ChatTabView() }