// // LiveViewModel.swift // SodaLive // // Created by klaus on 2023/08/09. // import Foundation import Combine final class LiveViewModel: ObservableObject { private let repository = LiveRepository() private let eventRepository = EventRepository() private let liveRecommendRepository = LiveRecommendRepository() private var subscription = Set() @Published private(set) var eventBannerItems = [EventItem]() @Published private(set) var liveNowItems = [GetRoomListResponse]() @Published private(set) var liveReservationItems = [GetRoomListResponse]() @Published private(set) var recommendLiveItems: [GetRecommendLiveResponse] = [] @Published private(set) var recommendChannelItems: [GetRecommendChannelResponse] = [] @Published private(set) var followedChannelItems: [GetRecommendChannelResponse] = [] @Published var errorMessage = "" @Published var isShowPopup = false @Published var isRefresh = false @Published var isLoading = false @Published var isRecommendLiveLoading = false @Published var isRecommendChannelLoading = false @Published var isFollowedChannelLoading = false @Published var paymentDialogTitle = "" @Published var paymentDialogDesc = "" @Published var isShowPaymentDialog = false @Published var paymentDialogConfirmAction = {} @Published var paymentDialogConfirmTitle = "" @Published var secretOrPasswordDialogCoin = 0 @Published var passwordDialogConfirmAction: (String) -> Void = { _ in } @Published var isShowPasswordDialog = false @Published var isFollowingList = UserDefaults.bool(forKey: .isFollowedChannel) { didSet { UserDefaults.set(isFollowingList, forKey: .isFollowedChannel) } } let paymentDialogCancelTitle = "취소" var page = 1 var isLast = false private let pageSize = 10 var selectedDateString: String = "" { didSet { if !selectedDateString.trimmingCharacters(in: .whitespaces).isEmpty { page = 1 isLast = false liveReservationItems.removeAll() getLiveReservationList() } } } func hidePopup() { isShowPaymentDialog = false isShowPasswordDialog = false paymentDialogTitle = "" paymentDialogDesc = "" paymentDialogConfirmAction = {} secretOrPasswordDialogCoin = 0 passwordDialogConfirmAction = { _ in } } func getSummary() { getFollowedChannelList() getRecommendChannelList() getRecommendLive() isLoading = true eventBannerItems.removeAll() liveNowItems.removeAll() liveReservationItems.removeAll() let liveNow = repository.roomList( request: GetRoomListRequest( timezone: TimeZone.current.identifier, dateString: nil, status: .NOW, page: 1, size: 10 ) ) let liveReservation = repository.roomList( request: GetRoomListRequest( timezone: TimeZone.current.identifier, dateString: nil, status: .RESERVATION, page: 1, size: 10 ) ) let event = eventRepository.getEvents() Publishers .CombineLatest3(liveNow, liveReservation, event) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { (now, reservation, eventResponse) in let nowData = now.data let reservationData = reservation.data let eventData = eventResponse.data let jsonDecoder = JSONDecoder() do { let nowDecoded = try jsonDecoder.decode(ApiResponse<[GetRoomListResponse]>.self, from: nowData) if let data = nowDecoded.data, nowDecoded.success { self.liveNowItems.removeAll() self.liveNowItems.append(contentsOf: data) } else { if let message = nowDecoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true } } catch { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } do { let reservationDecoded = try jsonDecoder.decode(ApiResponse<[GetRoomListResponse]>.self, from: reservationData) if let data = reservationDecoded.data, reservationDecoded.success { self.liveReservationItems.removeAll() self.liveReservationItems.append(contentsOf: data) } else { if let message = reservationDecoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true } } catch { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } do { let eventDecoded = try jsonDecoder.decode(ApiResponse.self, from: eventData) if let data = eventDecoded.data, eventDecoded.success { self.eventBannerItems.removeAll() self.eventBannerItems.append(contentsOf: data.eventList) } else { if let message = eventDecoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true } } catch { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } self.isLoading = false self.isRefresh = false } .store(in: &subscription) } func enterRoom(roomId: Int, onSuccess: (() -> Void)? = nil, password: String? = nil) { isLoading = true let request = EnterOrQuitLiveRoomRequest(roomId: roomId, password: password) repository.enterRoom(request: request) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] 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 { AppState.shared.roomId = roomId if let onSuccess = onSuccess { onSuccess() } else { if roomId > 0 { AppState.shared.isShowPlayer = true } } } 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 startLive(roomId: Int) { isLoading = true repository.startLive(roomId: roomId) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] 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 { getSummary() enterRoom(roomId: roomId) } 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 getLiveNowList() { if (!isLast && !isLoading) { isLoading = true repository.roomList( request: GetRoomListRequest( timezone: TimeZone.current.identifier, dateString: nil, status: .NOW, 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 self.isLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse<[GetRoomListResponse]>.self, from: responseData) if let data = decoded.data, decoded.success { if !data.isEmpty { page += 1 self.liveNowItems.append(contentsOf: data) } else { isLast = true } } 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 getLiveReservationList() { if (!isLast && !isLoading) { isLoading = true repository.roomList( request: GetRoomListRequest( timezone: TimeZone.current.identifier, dateString: selectedDateString, status: .RESERVATION, 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 self.isLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse<[GetRoomListResponse]>.self, from: responseData) if let data = decoded.data, decoded.success { if !data.isEmpty { page += 1 self.liveReservationItems.append(contentsOf: data) } else { isLast = true } } 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 reservationLiveRoom(roomId: Int) { getRoomDetail(roomId: roomId) { [unowned self] in if ($0.manager.id == UserDefaults.int(forKey: .userId)) { self.errorMessage = "내가 만든 라이브는 예약할 수 없습니다." self.isShowPopup = true } else { if $0.isPrivateRoom { self.passwordDialogConfirmAction = { password in self.reservation(roomId: roomId, password: password) } self.isShowPasswordDialog = true } else { if ($0.price == 0 || $0.isPaid) { self.reservation(roomId: roomId) } else { self.paymentDialogTitle = "\($0.price)코인으로 예약" self.paymentDialogDesc = "'\($0.title)' 라이브에 참여하기 위해 결제합니다." self.paymentDialogConfirmTitle = "결제 후 예약하기" self.paymentDialogConfirmAction = { [unowned self] in hidePopup() reservation(roomId: roomId) } self.isShowPaymentDialog = true } } } } } private func getRoomDetail(roomId: Int, onSuccess: @escaping (GetRoomDetailResponse) -> Void) { isLoading = true repository.getRoomDetail(roomId: roomId) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { response in let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) if let data = decoded.data, decoded.success { onSuccess(data) } else { if let message = decoded.message { self.errorMessage = message } else { self.errorMessage = "라이브 정보를 가져오지 못했습니다.\n다시 시도해 주세요." } self.isShowPopup = true } } catch { self.errorMessage = "라이브 정보를 가져오지 못했습니다.\n다시 시도해 주세요." self.isShowPopup = true } self.isLoading = false } .store(in: &subscription) } private func reservation(roomId: Int, password: String? = nil) { isLoading = true let request = MakeLiveReservationRequest(roomId: roomId, password: password) repository.makeReservation(request: request) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] response in self.isLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) if let response = decoded.data, decoded.success { self.getSummary() AppState.shared.setAppStep(step: .liveReservationComplete(response: response)) } 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) } private func getFollowedChannelList() { followedChannelItems.removeAll() isFollowedChannelLoading = true liveRecommendRepository.getFollowedChannelList() .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] response in self.isFollowedChannelLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse<[GetRecommendChannelResponse]>.self, from: responseData) if let data = decoded.data, decoded.success { self.followedChannelItems.append(contentsOf: data) } 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) } private func getRecommendChannelList() { recommendChannelItems.removeAll() isRecommendChannelLoading = true liveRecommendRepository.getRecommendChannelList() .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] response in self.isRecommendChannelLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse<[GetRecommendChannelResponse]>.self, from: responseData) if let data = decoded.data, decoded.success { self.recommendChannelItems.append(contentsOf: data) } 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) } private func getRecommendLive() { recommendLiveItems.removeAll() isRecommendLiveLoading = true liveRecommendRepository.getRecommendLive() .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [unowned self] response in self.isRecommendLiveLoading = false let responseData = response.data do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse<[GetRecommendLiveResponse]>.self, from: responseData) if let data = decoded.data, decoded.success { self.recommendLiveItems.append(contentsOf: data) } 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) } }