350 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  AuditionRoleDetailView.swift
 | 
						|
//  SodaLive
 | 
						|
//
 | 
						|
//  Created by klaus on 1/6/25.
 | 
						|
//
 | 
						|
 | 
						|
import SwiftUI
 | 
						|
import Kingfisher
 | 
						|
 | 
						|
struct AuditionRoleDetailView: View {
 | 
						|
    
 | 
						|
    let roleId: Int
 | 
						|
    let auditionTitle: String
 | 
						|
    
 | 
						|
    @StateObject var viewModel = AuditionRoleDetailViewModel()
 | 
						|
    @StateObject var keyboardHandler = KeyboardHandler()
 | 
						|
    @StateObject var soundManager = AuditionSoundManager.shared
 | 
						|
    
 | 
						|
    @State private var isShowApplyMethodView = false
 | 
						|
    @State private var isShowSelectAudioView = false
 | 
						|
    @State private var isShowRecordingView = false
 | 
						|
    @State private var isShowNoticeAuthView = false
 | 
						|
    @State private var isShowApplyView = false
 | 
						|
    @State private var isShowNoticeReapply = false
 | 
						|
    @State private var isShowApplyCompleteView = false
 | 
						|
    
 | 
						|
    var body: some View {
 | 
						|
        BaseView(isLoading: $viewModel.isLoading) {
 | 
						|
            ZStack(alignment: .bottomTrailing) {
 | 
						|
                VStack(spacing: 0) {
 | 
						|
                    DetailNavigationBar(title: viewModel.name)
 | 
						|
                    
 | 
						|
                    ScrollView(.vertical, showsIndicators: false) {
 | 
						|
                        LazyVStack(spacing: 15) {
 | 
						|
                            if let roleDetail = viewModel.auditionRoleDetail {
 | 
						|
                                KFImage(URL(string: roleDetail.imageUrl))
 | 
						|
                                    .cancelOnDisappear(true)
 | 
						|
                                    .downsampling(size: CGSize(width: 1000, height: 350))
 | 
						|
                                    .resizable()
 | 
						|
                                    .aspectRatio(1000/350, contentMode: .fit)
 | 
						|
                                    .frame(maxWidth: .infinity)
 | 
						|
                                    .cornerRadius(6.7)
 | 
						|
                                    .padding(.top, 3)
 | 
						|
                                
 | 
						|
                                HStack(spacing: 14) {
 | 
						|
                                    if let url = URL(string: roleDetail.originalWorkUrl), UIApplication.shared.canOpenURL(url) {
 | 
						|
                                        Text("원작 보러가기")
 | 
						|
                                            .font(.custom(Font.bold.rawValue, size: 16))
 | 
						|
                                            .foregroundColor(Color.button)
 | 
						|
                                            .padding(.vertical, 12)
 | 
						|
                                            .frame(maxWidth: .infinity)
 | 
						|
                                            .overlay(
 | 
						|
                                                RoundedRectangle(cornerRadius: 10)
 | 
						|
                                                    .stroke(Color.button, lineWidth: 1)
 | 
						|
                                            )
 | 
						|
                                            .contentShape(Rectangle())
 | 
						|
                                            .onTapGesture { UIApplication.shared.open(url) }
 | 
						|
                                    }
 | 
						|
                                    
 | 
						|
                                    if let url = URL(string: roleDetail.auditionScriptUrl), UIApplication.shared.canOpenURL(url) {
 | 
						|
                                        Text("오디션 대본 확인")
 | 
						|
                                            .font(.custom(Font.bold.rawValue, size: 16))
 | 
						|
                                            .foregroundColor(Color.button)
 | 
						|
                                            .padding(.vertical, 12)
 | 
						|
                                            .frame(maxWidth: .infinity)
 | 
						|
                                            .overlay(
 | 
						|
                                                RoundedRectangle(cornerRadius: 10)
 | 
						|
                                                    .stroke(Color.button, lineWidth: 1)
 | 
						|
                                            )
 | 
						|
                                            .contentShape(Rectangle())
 | 
						|
                                            .onTapGesture { UIApplication.shared.open(url) }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                                
 | 
						|
                                VStack(alignment: .leading, spacing: 13.3) {
 | 
						|
                                    Text("오디션 캐릭터 정보")
 | 
						|
                                        .font(.custom(Font.bold.rawValue, size: 14.7))
 | 
						|
                                        .foregroundColor(Color.grayee)
 | 
						|
                                    
 | 
						|
                                    ExpandableTextView(text: roleDetail.information)
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                            
 | 
						|
                            if viewModel.applicantList.isEmpty {
 | 
						|
                                Text("지원자가 없습니다.")
 | 
						|
                                    .font(.custom(Font.medium.rawValue, size: 13))
 | 
						|
                                    .foregroundColor(Color.grayee)
 | 
						|
                                    .padding(.top, 15)
 | 
						|
                            } else {
 | 
						|
                                HStack(spacing: 0) {
 | 
						|
                                    Text("참여자")
 | 
						|
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
						|
                                        .foregroundColor(Color.graybb)
 | 
						|
                                    
 | 
						|
                                    Text("\(viewModel.totalCount)")
 | 
						|
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
						|
                                        .foregroundColor(Color.button)
 | 
						|
                                        .padding(.leading, 2.3)
 | 
						|
                                    
 | 
						|
                                    Text("명")
 | 
						|
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
						|
                                        .foregroundColor(Color.graybb)
 | 
						|
                                    
 | 
						|
                                    Spacer()
 | 
						|
                                    
 | 
						|
                                    Text("최신순")
 | 
						|
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
						|
                                        .foregroundColor(
 | 
						|
                                            viewModel.sortType == .NEWEST ? Color.button : Color.graybb
 | 
						|
                                        )
 | 
						|
                                        .onTapGesture {
 | 
						|
                                            viewModel.setSortType(sortType: .NEWEST)
 | 
						|
                                        }
 | 
						|
                                    
 | 
						|
                                    Text("좋아요순")
 | 
						|
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
						|
                                        .foregroundColor(
 | 
						|
                                            viewModel.sortType == .LIKES ? Color.button : Color.graybb
 | 
						|
                                        )
 | 
						|
                                        .onTapGesture {
 | 
						|
                                            viewModel.setSortType(sortType: .LIKES)
 | 
						|
                                        }
 | 
						|
                                        .padding(.leading, 13.3)
 | 
						|
                                }
 | 
						|
                                .padding(.top, 15)
 | 
						|
                                
 | 
						|
                                VStack(spacing: 5.3) {
 | 
						|
                                    ForEach(0..<viewModel.applicantList.count, id: \.self) {
 | 
						|
                                        let applicant = viewModel.applicantList[$0]
 | 
						|
                                        
 | 
						|
                                        AuditionApplicantItemView(
 | 
						|
                                            item: applicant,
 | 
						|
                                            onClickVote: {
 | 
						|
                                                viewModel.voteApplicant(applicantId: $0)
 | 
						|
                                            }
 | 
						|
                                        ).padding(.bottom, $0 == viewModel.applicantList.count - 1 ? 33 : 0)
 | 
						|
                                        
 | 
						|
                                        if $0 == viewModel.applicantList.count - 1 {
 | 
						|
                                            Color.clear
 | 
						|
                                                .frame(height: 0)
 | 
						|
                                                .onAppear {
 | 
						|
                                                    viewModel.getAuditionApplicantList()
 | 
						|
                                                }
 | 
						|
                                        }
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        .padding(.horizontal, 13.3)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
 | 
						|
                    HStack {
 | 
						|
                        Spacer()
 | 
						|
                        Text(viewModel.errorMessage)
 | 
						|
                            .padding(.vertical, 13.3)
 | 
						|
                            .frame(width: screenSize().width - 66.7, alignment: .center)
 | 
						|
                            .font(.custom(Font.medium.rawValue, size: 12))
 | 
						|
                            .background(Color.button)
 | 
						|
                            .foregroundColor(Color.white)
 | 
						|
                            .multilineTextAlignment(.leading)
 | 
						|
                            .cornerRadius(20)
 | 
						|
                            .padding(.bottom, 66.7)
 | 
						|
                        Spacer()
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                .popup(isPresented: $soundManager.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
 | 
						|
                    HStack {
 | 
						|
                        Spacer()
 | 
						|
                        Text(soundManager.errorMessage)
 | 
						|
                            .padding(.vertical, 13.3)
 | 
						|
                            .frame(width: screenSize().width - 66.7, alignment: .center)
 | 
						|
                            .font(.custom(Font.medium.rawValue, size: 12))
 | 
						|
                            .background(Color.button)
 | 
						|
                            .foregroundColor(Color.white)
 | 
						|
                            .multilineTextAlignment(.leading)
 | 
						|
                            .cornerRadius(20)
 | 
						|
                            .padding(.bottom, 66.7)
 | 
						|
                        Spacer()
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                .onAppear {
 | 
						|
                    viewModel.onFailure = { AppState.shared.back() }
 | 
						|
                    viewModel.auditionRoleId = roleId
 | 
						|
                }
 | 
						|
                .onDisappear {
 | 
						|
                    soundManager.resetPlayer()
 | 
						|
                }
 | 
						|
                
 | 
						|
                if let roleDetail = viewModel.auditionRoleDetail {
 | 
						|
                    Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원")
 | 
						|
                        .font(.custom(Font.bold.rawValue, size: 15.3))
 | 
						|
                        .foregroundColor(Color.white)
 | 
						|
                        .padding(14)
 | 
						|
                        .background(Color.button)
 | 
						|
                        .cornerRadius(44)
 | 
						|
                        .padding(.trailing, 19)
 | 
						|
                        .padding(.bottom, 19)
 | 
						|
                        .onTapGesture {
 | 
						|
                            if UserDefaults.bool(forKey: .auth) {
 | 
						|
                                if viewModel.isShowNoticeReapply {
 | 
						|
                                    isShowNoticeReapply = true
 | 
						|
                                } else {
 | 
						|
                                    isShowApplyMethodView = true
 | 
						|
                                }
 | 
						|
                            } else {
 | 
						|
                                isShowNoticeAuthView = true
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            .fileImporter(
 | 
						|
                isPresented: $isShowSelectAudioView,
 | 
						|
                allowedContentTypes: [.audio],
 | 
						|
                allowsMultipleSelection: false
 | 
						|
            ) { result in
 | 
						|
                handleFileImport(result: result)
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowApplyMethodView {
 | 
						|
                ApplyMethodView(
 | 
						|
                    isShowing: $isShowApplyMethodView,
 | 
						|
                    onClickSelectAudioFile: {
 | 
						|
                        isShowApplyMethodView = false
 | 
						|
                        isShowSelectAudioView = true
 | 
						|
                    },
 | 
						|
                    onClickRecording: {
 | 
						|
                        isShowApplyMethodView = false
 | 
						|
                        isShowRecordingView = true
 | 
						|
                    }
 | 
						|
                )
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowRecordingView {
 | 
						|
                AuditionApplicantRecordingView(
 | 
						|
                    isShowing: $isShowRecordingView,
 | 
						|
                    isShowPopup: $viewModel.isShowPopup,
 | 
						|
                    errorMessage: $viewModel.errorMessage,
 | 
						|
                    onClickCompleteRecording: { fileName, soundData in
 | 
						|
                        viewModel.fileName = fileName
 | 
						|
                        viewModel.soundData = soundData
 | 
						|
                        isShowRecordingView = false
 | 
						|
                        isShowApplyView = true
 | 
						|
                    }
 | 
						|
                )
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowApplyView {
 | 
						|
                AuditionApplyView(
 | 
						|
                    isShowing: $isShowApplyView,
 | 
						|
                    phoneNumber: $viewModel.phoneNumber,
 | 
						|
                    filename: viewModel.fileName,
 | 
						|
                    onClickApply: {
 | 
						|
                        viewModel.applyAudition {
 | 
						|
                            isShowApplyView = false
 | 
						|
                            isShowRecordingView = false
 | 
						|
                            isShowApplyCompleteView = true
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                )
 | 
						|
                .offset(y: 0 - (keyboardHandler.keyboardHeight / 10))
 | 
						|
                .onDisappear {
 | 
						|
                    viewModel.soundData = nil
 | 
						|
                    viewModel.fileName = ""
 | 
						|
                    viewModel.deleteAllRecordingFilesWithNamePrefix("voiceon_now_voice")
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowNoticeReapply {
 | 
						|
                SodaDialog(
 | 
						|
                    title: "재지원 안내",
 | 
						|
                    desc: "재지원 시 이전 지원 내역은 삭제되며 받은 투표수는 무효 처리됩니다.",
 | 
						|
                    confirmButtonTitle: "확인"
 | 
						|
                ) {
 | 
						|
                    isShowNoticeReapply = false
 | 
						|
                    isShowApplyMethodView = true
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowNoticeAuthView {
 | 
						|
                SodaDialog(
 | 
						|
                    title: "- 본인인증 -",
 | 
						|
                    desc: "마이페이지에서 '본인인증'을 하고 다시 오디션에 지원해 주세요.",
 | 
						|
                    confirmButtonTitle: "확인"
 | 
						|
                ) {
 | 
						|
                    isShowNoticeAuthView = false
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            if viewModel.isShowVoteCompleteView {
 | 
						|
                SodaDialog(
 | 
						|
                    title: viewModel.dialogTitle,
 | 
						|
                    desc: viewModel.dialogDesc,
 | 
						|
                    confirmButtonTitle: "확인"
 | 
						|
                ) {
 | 
						|
                    viewModel.isShowVoteCompleteView = false
 | 
						|
                    viewModel.isShowNotifyVote = false
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            if isShowApplyCompleteView {
 | 
						|
                ApplyAuditionCompleteDialog(
 | 
						|
                    auditionTitle: auditionTitle,
 | 
						|
                    roleName: viewModel.name,
 | 
						|
                    isShowing: $isShowApplyCompleteView
 | 
						|
                )
 | 
						|
            }
 | 
						|
            
 | 
						|
            if soundManager.isLoading {
 | 
						|
                LoadingView()
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func handleFileImport(result: Result<[URL], Error>) {
 | 
						|
        switch result {
 | 
						|
        case .success(let url):
 | 
						|
            let fileUrl = url[0]
 | 
						|
            
 | 
						|
            if fileUrl.startAccessingSecurityScopedResource() {
 | 
						|
                defer {
 | 
						|
                    fileUrl.stopAccessingSecurityScopedResource()
 | 
						|
                }
 | 
						|
                
 | 
						|
                if let data = try? Data(contentsOf: fileUrl) {
 | 
						|
                    viewModel.soundData = data
 | 
						|
                    viewModel.fileName = fileUrl.lastPathComponent
 | 
						|
                    isShowApplyView = true
 | 
						|
                } else {
 | 
						|
                    viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
 | 
						|
                    viewModel.isShowPopup = true
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
 | 
						|
                viewModel.isShowPopup = true
 | 
						|
            }
 | 
						|
            
 | 
						|
        case .failure(let error):
 | 
						|
            DEBUG_LOG("error: \(error.localizedDescription)")
 | 
						|
            viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
 | 
						|
            viewModel.isShowPopup = true
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#Preview {
 | 
						|
    AuditionRoleDetailView(roleId: 1, auditionTitle: "스위치온")
 | 
						|
}
 |