// // AuditionRoleDetailViewModel.swift // SodaLive // // Created by klaus on 1/6/25. // import Foundation import Moya import Combine final class AuditionRoleDetailViewModel: ObservableObject { private let repository = AuditionRepository() private var subscription = Set() @Published var errorMessage = "" @Published var isShowPopup = false @Published var isLoading = false @Published var totalCount = 0 @Published var applicantList = [GetAuditionRoleApplicantItem]() @Published var name = "보이스온" @Published var auditionRoleDetail: GetAuditionRoleDetailResponse? = nil @Published private (set) var sortType = AuditionApplicantSortType.NEWEST { didSet { refreshApplicantList() } } @Published var fileName = "" @Published var soundData: Data? = nil @Published var phoneNumber = "" @Published var isShowNotifyVote = true @Published var isShowVoteCompleteView = false @Published var isShowNoticeReapply = false @Published var dialogTitle = "" @Published var dialogDesc = "" var page = 1 var isLast = false private var pageSize = 10 var auditionRoleId = -1 { didSet { if auditionRoleId > 0 { getAuditionRoleDetail() } else { onFailure() } } } var onFailure: () -> Void = {} func setSortType(sortType: AuditionApplicantSortType) { if self.sortType != sortType { self.sortType = sortType } } func getAuditionRoleDetail() { isLoading = true let auditionRoleDetail = repository.getAuditionRoleDetail(auditionRoleId: auditionRoleId) let auditionApplicantList = repository.getAuditionApplicantList(auditionRoleId: auditionRoleId, sortType: sortType, page: page, size: pageSize) Publishers .CombineLatest(auditionRoleDetail, auditionApplicantList) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] (roleDetailResponse, applicantListResponse) in let roleDetail = roleDetailResponse.data let applicantList = applicantListResponse.data let jsonDecoder = JSONDecoder() do { let roleDetailDecoded = try jsonDecoder.decode(ApiResponse.self, from: roleDetail) if let data = roleDetailDecoded.data, roleDetailDecoded.success { self.name = data.name self.isShowNoticeReapply = data.isAlreadyApplicant self.auditionRoleDetail = data } else { if let message = roleDetailDecoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true } } catch { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } do { let applicantListDecoded = try jsonDecoder.decode(ApiResponse.self, from: applicantList) if let data = applicantListDecoded.data, applicantListDecoded.success { self.totalCount = data.totalCount self.applicantList.append(contentsOf: data.items) if data.items.isEmpty { isLast = true } else { page += 1 } } else { if let message = applicantListDecoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.onFailure() } } } catch { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.onFailure() } } self.isLoading = false } .store(in: &subscription) } func getAuditionApplicantList() { if !isLoading && !isLast { isLoading = true repository.getAuditionApplicantList(auditionRoleId: auditionRoleId, sortType: sortType, page: page, size: pageSize) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] response in let responseData = response.data self.isLoading = false do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) if let data = decoded.data, decoded.success { self.totalCount = data.totalCount self.applicantList.append(contentsOf: data.items) if data.items.isEmpty { isLast = true } else { page += 1 } } else { if let message = decoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true } } catch { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } } .store(in: &subscription) } } func applyAudition(onSuccess: @escaping () -> Void) { if phoneNumber.count != 11 { errorMessage = "잘못된 연락처 입니다.\n다시 입력해 주세요." isShowPopup = true return } guard let soundData = soundData else { errorMessage = "잘못된 녹음 파일 입니다.\n다시 선택해 주세요." isShowPopup = true return } isLoading = true let request = ApplyAuditionRoleRequest(roleId: auditionRoleId, phoneNumber: phoneNumber) var multipartData = [MultipartFormData]() let encoder = JSONEncoder() encoder.outputFormatting = .withoutEscapingSlashes let jsonData = try? encoder.encode(request) if let jsonData = jsonData { multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request")) multipartData.append( MultipartFormData( provider: .data(soundData), name: "contentFile", fileName: fileName, mimeType: "audio/*" ) ) repository.applyAudition(parameters: multipartData) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { response in self.isLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { self.deleteAllRecordingFilesWithNamePrefix("voiceon_now_voice") self.phoneNumber = "" self.fileName = "" self.soundData = nil self.refreshApplicantList() onSuccess() } else { if let message = decoded.message { self.errorMessage = message } else { self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true } } catch { self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } } .store(in: &subscription) } else { self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true self.isLoading = false } } func voteApplicant(applicantId: Int) { isLoading = true repository.voteApplicant(applicantId: applicantId) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { response in self.isLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { if self.isShowNotifyVote { self.dialogTitle = "[오디션 응원]" self.dialogDesc = "오디션을 응원하셨습니다\n(무료응원 : 1계정당 1일 1회)\n1캔으로 추가 응원을 해보세요." self.isShowVoteCompleteView = true } if let index = self.applicantList.firstIndex(where: { $0.applicantId == applicantId }) { var applicant = self.applicantList[index] applicant.voteCount += 1 self.applicantList.remove(at: index) self.applicantList.insert(applicant, at: index) } } else { if let message = decoded.message { if message.contains("오늘 응원은 여기까지") { self.dialogTitle = "[오늘 응원 제한]" self.dialogDesc = "오늘 응원은 여기까지!\n하루 최대 10회까지 이용이 가능합니다.\n내일 다시 이용해주세요." self.isShowVoteCompleteView = true } else { self.errorMessage = message self.isShowPopup = true } } else { self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." self.isShowPopup = true } } } catch { self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." self.isShowPopup = true } } .store(in: &subscription) } private func refreshApplicantList() { self.page = 1 self.isLast = false self.totalCount = 0 self.applicantList = [] self.getAuditionApplicantList() } func deleteAllRecordingFilesWithNamePrefix(_ prefix: String) { let fileManager = FileManager.default let documentsURL = getDocumentsDirectory() do { let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil, options: []) for fileURL in fileURLs { if fileURL.lastPathComponent.hasPrefix(prefix) { try fileManager.removeItem(at: fileURL) DEBUG_LOG("녹음 파일 삭제 성공: \(fileURL)") } } } catch { DEBUG_LOG("녹음 파일 삭제 실패: \(error.localizedDescription)") } } private func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } }