217 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  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 = 2
 | 
						|
        
 | 
						|
        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 isShowAuthConfirmView: Bool = false
 | 
						|
    @State private var pendingAction: (() -> Void)? = 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 {
 | 
						|
            pendingAction = {
 | 
						|
                AppState.shared
 | 
						|
                    .setAppStep(step: .characterDetail(characterId: characterId))
 | 
						|
            }
 | 
						|
            isShowAuthConfirmView = true
 | 
						|
            return
 | 
						|
        }
 | 
						|
        AppState.shared.setAppStep(step: .characterDetail(characterId: characterId))
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func handleCharacterSelection() {
 | 
						|
        let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
 | 
						|
        guard !trimmed.isEmpty else {
 | 
						|
            AppState.shared.setAppStep(step: .login)
 | 
						|
            return
 | 
						|
        }
 | 
						|
        if auth == false {
 | 
						|
            pendingAction = {
 | 
						|
                AppState.shared
 | 
						|
                    .setAppStep(step: .newCharacterAll)
 | 
						|
            }
 | 
						|
            isShowAuthConfirmView = true
 | 
						|
            return
 | 
						|
        }
 | 
						|
        AppState.shared.setAppStep(step: .newCharacterAll)
 | 
						|
    }
 | 
						|
    
 | 
						|
    var body: some View {
 | 
						|
        ZStack {
 | 
						|
            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 action = pendingAction {
 | 
						|
                            pendingAction = nil
 | 
						|
                            action()
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    .onClose {
 | 
						|
                        isShowAuthView = false
 | 
						|
                    }
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowAuthConfirmView {
 | 
						|
                SodaDialog(
 | 
						|
                    title: "본인인증",
 | 
						|
                    desc: "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" +
 | 
						|
                    "캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.",
 | 
						|
                    confirmButtonTitle: "본인인증 하러가기",
 | 
						|
                    confirmButtonAction: {
 | 
						|
                        isShowAuthConfirmView = false
 | 
						|
                        isShowAuthView = true
 | 
						|
                    },
 | 
						|
                    cancelButtonTitle: "취소",
 | 
						|
                    cancelButtonAction: {
 | 
						|
                        isShowAuthConfirmView = false
 | 
						|
                        pendingAction = nil
 | 
						|
                    },
 | 
						|
                    textAlignment: .center
 | 
						|
                )
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
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()
 | 
						|
}
 |