sodalive-ios/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift

1420 lines
58 KiB
Swift

//
// LiveRoomViewModel.swift
// SodaLive
//
// Created by klaus on 2023/08/14.
//
import Foundation
import Moya
import Combine
import AgoraRtcKit
import AgoraRtmKit
import FirebaseDynamicLinks
final class LiveRoomViewModel: NSObject, ObservableObject {
private var agora: Agora = Agora.shared
private let repository = LiveRepository()
private let userRepository = UserRepository()
private let reportRepository = ReportRepository()
private var subscription = Set<AnyCancellable>()
@Published var chatMessage = ""
@Published var isSpeakerMute = false
@Published var isMute = false
@Published var role = LiveRoomMemberRole.LISTENER
@Published var messageChangeFlag = false
@Published var messages = [LiveRoomChat]()
@Published var activeSpeakers = [UInt]()
@Published var muteSpeakers = [UInt]()
@Published var liveRoomInfo: GetRoomInfoResponse?
@Published var userProfile: GetLiveRoomUserProfileResponse?
@Published var isLoading = false
@Published var errorMessage = ""
@Published var reportMessage = ""
@Published var isShowReportPopup = false
@Published var isShowErrorPopup = false
@Published var isShowUserProfilePopup = false
@Published var popupContent = ""
@Published var popupCancelTitle: String? = nil
@Published var popupCancelAction: (() -> Void)? = nil
@Published var popupConfirmTitle: String? = nil
@Published var popupConfirmAction: (() -> Void)? = nil
@Published var isShowPopup = false {
didSet {
if !isShowPopup {
resetPopupContent()
}
}
}
@Published var isShowProfileList = false
@Published var isShowProfilePopup = false {
didSet {
if !isShowProfilePopup {
selectedProfile = nil
}
}
}
@Published var selectedProfile: LiveRoomMember?
@Published var isShowNotice = true {
didSet {
if !isShowNotice {
isExpandNotice = false
}
}
}
@Published var isExpandNotice = false
@Published var isShowDonationPopup = false
@Published var isShowDonationMessagePopup = false
@Published var isShowDonationRankingPopup = false
@Published var isSpeakerFold = false
@Published var isShowQuitPopup = false
@Published var isShowLiveEndPopup = false
@Published var isShowEditRoomInfoDialog = false
@Published var isShowShareView = false
@Published var shareMessage = ""
@Published var isShowKickOutPopup = false
@Published var kickOutDesc = ""
@Published var kickOutId = 0 {
didSet {
kickOutDesc = "\(getUserNicknameAndProfileUrl(accountId: kickOutId).nickname)님을 내보내시겠어요?"
}
}
@Published var totalDonationCan = 0
@Published var donationMessageList = [LiveRoomDonationMessage]()
@Published var donationMessageCount = 0
@Published var isBgOn = true
@Published var donationStatus: GetLiveRoomDonationStatusResponse?
@Published private(set) var offset: CGFloat = 0
@Published private(set) var originOffset: CGFloat = 0
private var isCheckedOriginOffset: Bool = false
@Published var coverImage: UIImage? = nil
@Published var isShowReportMenu = false
@Published var isShowUesrBlockConfirm = false
@Published var isShowUesrReportView = false
@Published var isShowProfileReportConfirm = false
@Published var reportUserId = 0
@Published var reportUserNickname = ""
@Published var reportUserIsBlocked = false
func setOriginOffset(_ offset: CGFloat) {
guard !isCheckedOriginOffset else { return }
self.originOffset = offset
self.offset = offset
isCheckedOriginOffset = true
}
func setOffset(_ offset: CGFloat) {
guard isCheckedOriginOffset else { return }
self.offset = offset
}
func initAgoraEngine() {
agora.rtcEngineDelegate = self
agora.rtmDelegate = self
agora.initialize()
}
private func deInitAgoraEngine() {
agora.deInit()
}
func agoraConnectSuccess(isManager: Bool) {
if isManager {
role = .SPEAKER
} else {
role = .LISTENER
}
DEBUG_LOG("agoraConnectSuccess")
}
func agoraConnectFail() {
DEBUG_LOG("agoraConnectFail")
AppState.shared.roomId = 0
AppState.shared.isShowPlayer = false
}
func quitRoom() {
isLoading = true
if let index = muteSpeakers.firstIndex(of: UInt(UserDefaults.int(forKey: .userId))) {
muteSpeakers.remove(at: index)
}
repository.quitRoom(roomId: AppState.shared.roomId)
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success {
self.deInitAgoraEngine()
self.liveRoomInfo = nil
AppState.shared.roomId = 0
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowErrorPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowErrorPopup = true
}
self.isLoading = false
}
.store(in: &subscription)
}
func getRoomInfo(userId: Int = 0, onSuccess: @escaping (String) -> Void = { _ in }) {
isLoading = true
repository.getRoomInfo(roomId: AppState.shared.roomId)
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<GetRoomInfoResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.liveRoomInfo = data
self.agora.joinChannel(
roomInfo: data,
rtmChannelDelegate: self,
onConnectSuccess: self.agoraConnectSuccess,
onConnectFail: self.agoraConnectFail
)
getTotalDonationCan()
if (userId > 0 && data.creatorId == UserDefaults.int(forKey: .userId)) {
let nickname = getUserNicknameAndProfileUrl(accountId: userId).nickname
onSuccess(nickname)
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowErrorPopup = true
}
self.isLoading = false
} catch {
self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowErrorPopup = true
}
}
.store(in: &subscription)
}
func toggleMute() {
isMute.toggle()
agora.mute(isMute)
if isMute {
muteSpeakers.append(UInt(UserDefaults.int(forKey: .userId)))
} else {
if let index = muteSpeakers.firstIndex(of: UInt(UserDefaults.int(forKey: .userId))) {
muteSpeakers.remove(at: index)
}
}
}
func toggleSpeakerMute() {
isSpeakerMute.toggle()
agora.speakerMute(isSpeakerMute)
}
func sendMessage() {
DispatchQueue.main.async {[unowned self] in
if chatMessage.count > 0 {
agora.sendMessageToGroup(textMessage: chatMessage, completion: { [unowned self] errorCode in
if errorCode == .errorOk {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
let rank = getUserRank(userId: UserDefaults.int(forKey: .userId))
self.messages.append(LiveRoomNormalChat(userId: UserDefaults.int(forKey: .userId), profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chatMessage))
self.messageChangeFlag.toggle()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
}
self.chatMessage = ""
})
}
}
}
func donation(can: Int, message: String = "") {
if can > 0 {
isLoading = true
repository.donation(roomId: AppState.shared.roomId, can: can, message: message)
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
self.isLoading = false
if decoded.success {
let rawMessage = "\(can)캔을 후원하셨습니다."
let donationRawMessage = LiveRoomChatRawMessage(
type: .DONATION,
message: rawMessage,
can: can,
donationMessage: message
)
UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can)
agora.sendRawMessageToGroup(
rawMessage: donationRawMessage,
completion: { [unowned self] errorCode in
if errorCode == .errorOk {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
self.messages.append(
LiveRoomDonationChat(
profileUrl: profileUrl,
nickname: nickname,
chat: rawMessage,
can: can,
donationMessage: message
)
)
totalDonationCan += can
self.messageChangeFlag.toggle()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} else {
refundDonation()
}
},
fail: { [unowned self] in
refundDonation()
}
)
} else {
if let message = decoded.message {
self.popupContent = message
} else {
self.popupContent = "후원에 실패했습니다.\n다시 후원해주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.isLoading = false
self.popupContent = "후원에 실패했습니다.\n다시 후원해주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
} else {
popupContent = "1캔 이상 후원하실 수 있습니다."
isShowPopup = true
}
}
private func refundDonation() {
isLoading = true
repository.refundDonation(roomId: AppState.shared.roomId)
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
self.isLoading = false
if decoded.success {
self.popupContent = "후원에 실패했습니다.\n다시 후원해주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.popupContent = "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
}
self.isShowPopup = true
}
} catch {
self.isLoading = false
self.popupContent = "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func requestSpeaker() {
agora.sendMessageToPeer(peerId: String(liveRoomInfo!.creatorId), rawMessage: LiveRoomRequestType.REQUEST_SPEAKER.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in
if errorCode == .ok {
self.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
self.isShowPopup = true
}
})
}
func inviteSpeaker(peerId: Int) {
agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.INVITE_SPEAKER.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in
if errorCode == .ok {
self.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
self.isShowPopup = true
}
})
}
func changeListener(peerId: Int, isFromManager: Bool = false) {
agora.sendMessageToPeer(peerId: String(peerId), rawMessage: LiveRoomRequestType.CHANGE_LISTENER.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in
if errorCode == .ok {
if isFromManager {
getRoomInfo()
setManagerMessage()
releaseManagerMessageToPeer(userId: peerId)
self.popupContent = "\(getUserNicknameAndProfileUrl(accountId: peerId).nickname)님을 스탭에서 해제했어요."
} else {
self.popupContent = "\(getUserNicknameAndProfileUrl(accountId: peerId).nickname)님을 리스너로 변경했어요."
}
self.isShowPopup = true
}
})
}
private func getUserNicknameAndProfileUrl(accountId: Int) -> (nickname: String, profileUrl: String) {
for staff in liveRoomInfo!.managerList {
if staff.id == accountId {
return (staff.nickname, staff.profileImage)
}
}
for speaker in liveRoomInfo!.speakerList {
if speaker.id == accountId {
return (speaker.nickname, speaker.profileImage)
}
}
for listener in liveRoomInfo!.listenerList {
if listener.id == accountId {
return (listener.nickname, listener.profileImage)
}
}
return ("", "")
}
func isEqualToStaffId(creatorId: Int) -> Bool {
for staff in liveRoomInfo!.managerList {
if staff.id == creatorId {
return true
}
}
return false
}
func setListener() {
repository.setListener(roomId: AppState.shared.roomId, userId: UserDefaults.int(forKey: .userId))
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success {
self.role = .LISTENER
self.agora.setRole(role: .audience)
self.isMute = false
self.agora.mute(isMute)
if let index = self.muteSpeakers.firstIndex(of: UInt(UserDefaults.int(forKey: .userId))) {
self.muteSpeakers.remove(at: index)
}
self.getRoomInfo()
}
} catch {
}
}
.store(in: &subscription)
}
private func setSpeaker() {
repository.setSpeaker(roomId: AppState.shared.roomId, userId: UserDefaults.int(forKey: .userId))
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success {
self.role = .SPEAKER
self.agora.setRole(role: .broadcaster)
self.popupContent = "스피커가 되었어요!"
self.isShowPopup = true
self.isMute = false
self.getRoomInfo()
}
} catch {
}
}
.store(in: &subscription)
}
private func resetPopupContent() {
errorMessage = ""
popupContent = ""
popupCancelTitle = nil
popupCancelAction = nil
popupConfirmTitle = nil
popupConfirmAction = nil
}
private func requestSpeakerAllow(_ peerId: String) {
agora.sendMessageToPeer(peerId: peerId, rawMessage: LiveRoomRequestType.REQUEST_SPEAKER_ALLOW.rawValue.data(using: .utf8)!, completion: nil)
}
func editLiveRoomInfo(title: String, notice: String) {
let request = EditLiveRoomInfoRequest(
title: liveRoomInfo!.title != title ? title : nil,
notice: liveRoomInfo!.notice != notice ? notice : nil,
numberOfPeople: nil,
beginDateTimeString: nil,
timezone: nil
)
if (request.title == nil && request.notice == nil && coverImage == nil) {
self.errorMessage = "변경사항이 없습니다."
self.isShowErrorPopup = true
return
}
var multipartData = [MultipartFormData]()
let encoder = JSONEncoder()
encoder.outputFormatting = .withoutEscapingSlashes
if (request.title != nil || request.notice != nil) {
let jsonData = try? encoder.encode(request)
if let jsonData = jsonData {
multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request"))
}
}
if let coverImage = coverImage, let imageData = coverImage.jpegData(compressionQuality: 0.8) {
multipartData.append(
MultipartFormData(
provider: .data(imageData),
name: "coverImage",
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg",
mimeType: "image/*")
)
}
repository.editLiveRoomInfo(roomId: AppState.shared.roomId, parameters: multipartData)
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success {
self.coverImage = nil
self.getRoomInfo()
let editRoomInfoMessage = LiveRoomChatRawMessage(
type: .EDIT_ROOM_INFO,
message: "",
can: 0,
donationMessage: ""
)
self.agora.sendRawMessageToGroup(rawMessage: editRoomInfoMessage)
} else {
self.errorMessage = decoded.message ?? "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
self.isShowPopup = true
}
} catch {
self.errorMessage = "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func shareRoom() {
guard let link = URL(string: "https://sodalive.net/?room_id=\(AppState.shared.roomId)") else { return }
let dynamicLinksDomainURIPrefix = "https://sodalive.page.link"
guard let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) else {
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
self.isShowErrorPopup = true
return
}
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "kr.co.vividnext.sodalive")
linkBuilder.iOSParameters?.appStoreID = "6461721697"
linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "kr.co.vividnext.sodalive")
guard let longDynamicLink = linkBuilder.url else {
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
self.isShowErrorPopup = true
return
}
DEBUG_LOG("The long URL is: \(longDynamicLink)")
DynamicLinkComponents.shortenURL(longDynamicLink, options: nil) { [unowned self] url, warnings, error in
let shortUrl = url?.absoluteString
if let liveRoomInfo = self.liveRoomInfo {
let urlString = shortUrl != nil ? shortUrl! : longDynamicLink.absoluteString
if liveRoomInfo.isPrivateRoom {
shareMessage = "\(UserDefaults.string(forKey: .nickname))님이 귀하를 소다라이브 비공개라이브에 초대하였습니다.\n" +
"※ 라이브 참여: \(urlString)\n" +
"(입장 비밀번호: \(liveRoomInfo.password!))"
} else {
shareMessage = "\(UserDefaults.string(forKey: .nickname))님이 귀하를 소다라이브 공개라이브에 초대하였습니다.\n" +
"※ 라이브 참여: \(urlString)"
}
isShowShareView = true
} else {
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
self.isShowErrorPopup = true
return
}
}
}
func kickOut() {
repository.kickOut(roomId: AppState.shared.roomId, userId: kickOutId)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { _ in
}
.store(in: &subscription)
let nickname = getUserNicknameAndProfileUrl(accountId: kickOutId).nickname
agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in
if errorCode == .ok {
self.popupContent = "\(nickname)님을 내보냈습니다."
self.isShowPopup = true
}
})
if let index = muteSpeakers.firstIndex(of: UInt(kickOutId)) {
muteSpeakers.remove(at: index)
}
isShowKickOutPopup = false
kickOutDesc = ""
kickOutId = 0
}
func getDonationStatus() {
isLoading = true
repository.donationStatus(roomId: AppState.shared.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(ApiResponse<GetLiveRoomDonationStatusResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.donationStatus = data
} else {
self.errorMessage = "후원현황을 가져오지 못했습니다\n다시 시도해 주세요."
self.isShowPopup = true
}
} catch {
self.isLoading = false
self.errorMessage = "후원현황을 가져오지 못했습니다\n다시 시도해 주세요."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func creatorFollow(creatorId: Int? = nil, isGetUserProfile: Bool = false) {
var userId = 0
if let creatorId = creatorId {
userId = creatorId
} else if let liveRoomInfo = liveRoomInfo {
userId = liveRoomInfo.creatorId
}
if userId > 0 {
isLoading = true
userRepository.creatorFollow(creatorId: userId)
.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 {
self.getRoomInfo()
if isGetUserProfile {
getUserProfile(userId: userId)
}
} 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 creatorUnFollow(creatorId: Int? = nil, isGetUserProfile: Bool = false) {
var userId = 0
if let creatorId = creatorId {
userId = creatorId
} else if let liveRoomInfo = liveRoomInfo {
userId = liveRoomInfo.creatorId
}
if userId > 0 {
isLoading = true
userRepository.creatorUnFollow(creatorId: userId)
.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 {
self.getRoomInfo()
if isGetUserProfile {
getUserProfile(userId: userId)
}
} 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 getUserRank(userId: Int) -> Int {
// -> -2
// -> -3
// ->
if userId == liveRoomInfo!.creatorId {
return -2
} else if isEqualToStaffId(creatorId: userId) {
return -3
} else {
return liveRoomInfo!.donationRankingTop3UserIds.firstIndex(of: userId) ?? -1
}
}
func getTotalDonationCan() {
repository.getTotalDoantionCan(roomId: AppState.shared.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(ApiResponse<GetLiveRoomDonationTotalResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.totalDonationCan = data.totalDonationCan
}
} catch {
}
}
.store(in: &subscription)
}
func getMemberCan() {
userRepository.getMemberCan()
.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<GetMemberCanResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
UserDefaults.set(data.can, forKey: .can)
}
} catch {
}
}
.store(in: &subscription)
}
func getDonationMessageList() {
isLoading = true
repository.getDonationMessageList(roomId: AppState.shared.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(ApiResponse<[LiveRoomDonationMessage]>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.donationMessageList.removeAll()
self.donationMessageList.append(contentsOf: data)
self.donationMessageCount = data.count
} 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 deleteDonationMessage(uuid: String) {
isLoading = true
repository.deleteDonationMessage(roomId: AppState.shared.roomId, messageUUID: uuid)
.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 {
self.donationMessageCount -= 1
let filteredDonationMessageList = self.donationMessageList.filter { $0.uuid != uuid }
self.donationMessageList.removeAll()
self.donationMessageList.append(contentsOf: filteredDonationMessageList)
} else {
self.errorMessage = "메시지를 삭제하지 못했습니다.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
} catch {
self.errorMessage = "메시지를 삭제하지 못했습니다.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func getUserProfile(userId: Int) {
isLoading = true
repository.getUserProfile(roomId: AppState.shared.roomId, userId: userId)
.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<GetLiveRoomUserProfileResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
userProfile = data
isShowUserProfilePopup = 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 setManager(userId: Int) {
isLoading = true
repository.setManager(roomId: AppState.shared.roomId, userId: userId)
.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 {
getRoomInfo()
setManagerMessage()
self.popupContent = "\(getUserNicknameAndProfileUrl(accountId: userId).nickname)님을 스탭으로 지정했습니다."
self.isShowPopup = 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 releaseManagerMessageToPeer(userId: Int) {
agora.sendMessageToPeer(
peerId: String(userId),
rawMessage: LiveRoomRequestType.RELEASE_MANAGER.rawValue.data(using: .utf8)!,
completion: nil
)
}
func setManagerMessageToPeer(userId: Int) {
agora.sendMessageToPeer(
peerId: String(userId),
rawMessage: LiveRoomRequestType.SET_MANAGER.rawValue.data(using: .utf8)!,
completion: nil
)
}
private func setManagerMessage() {
let setManagerMessage = LiveRoomChatRawMessage(
type: .SET_MANAGER,
message: "",
can: 0,
donationMessage: ""
)
self.agora.sendRawMessageToGroup(rawMessage: setManagerMessage)
}
func userBlock() {
isLoading = true
userRepository.memberBlock(userId: reportUserId)
.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 {
self.reportMessage = "차단하였습니다."
self.getUserProfile(userId: reportUserId)
self.reportUserId = 0
self.reportUserNickname = ""
self.reportUserIsBlocked = false
} else {
if let message = decoded.message {
self.reportMessage = message
} else {
self.reportMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
}
self.isShowReportPopup = true
} catch {
self.reportMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowReportPopup = true
}
}
.store(in: &subscription)
}
func userUnBlock() {
isLoading = true
userRepository.memberUnBlock(userId: reportUserId)
.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 {
self.reportMessage = "차단이 해제 되었습니다."
self.getUserProfile(userId: reportUserId)
self.reportUserId = 0
self.reportUserNickname = ""
self.reportUserIsBlocked = false
} else {
if let message = decoded.message {
self.reportMessage = message
} else {
self.reportMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
}
self.isShowReportPopup = true
} catch {
self.reportMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowReportPopup = true
}
}
.store(in: &subscription)
}
func report(type: ReportType, reason: String = "프로필 신고") {
isLoading = true
let request = ReportRequest(type: type, reason: reason, reportedMemberId: reportUserId, cheersId: nil, audioContentId: nil)
reportRepository.report(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
self.reportUserId = 0
self.reportUserNickname = ""
self.reportUserIsBlocked = false
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if let message = decoded.message {
self.reportMessage = message
} else {
self.reportMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowReportPopup = true
} catch {
self.reportMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowReportPopup = true
}
}
.store(in: &subscription)
}
}
extension LiveRoomViewModel: AgoraRtcEngineDelegate {
func rtcEngine(_ engine: AgoraRtcEngineKit, reportAudioVolumeIndicationOfSpeakers speakers: [AgoraRtcAudioVolumeInfo], totalVolume: Int) {
let activeSpeakerIds = speakers
.filter { $0.volume > 0 }
.map { $0.uid }
DEBUG_LOG("activeSpeakerIds::: \(activeSpeakerIds)")
activeSpeakers.removeAll()
activeSpeakers.append(contentsOf: activeSpeakerIds)
}
func rtcEngine(_ engine: AgoraRtcEngineKit, didAudioMuted muted: Bool, byUid uid: UInt) {
if muted && !muteSpeakers.contains(uid){
muteSpeakers.append(uid)
} else {
if let index = muteSpeakers.firstIndex(of: uid) {
muteSpeakers.remove(at: index)
}
}
}
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
getRoomInfo()
}
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
DispatchQueue.main.async {[unowned self] in
if uid == UInt(self.liveRoomInfo!.creatorId) {
//
self.liveRoomInfo = nil
self.errorMessage = "라이브가 종료되었습니다."
self.isShowErrorPopup = true
} else {
// get room info
self.getRoomInfo()
}
}
}
}
extension LiveRoomViewModel: AgoraRtmDelegate {
func rtmKit(_ kit: AgoraRtmKit, messageReceived message: AgoraRtmMessage, fromPeer peerId: String) {
if message.type == .raw, let rawMessage = message as? AgoraRtmRawMessage {
let rawMessageString = String(data: rawMessage.rawData, encoding: .utf8)
DispatchQueue.main.async { [unowned self] in
if rawMessageString == LiveRoomRequestType.CHANGE_LISTENER.rawValue {
self.setListener()
return
}
if rawMessageString == LiveRoomRequestType.REQUEST_SPEAKER.rawValue {
self.popupContent = "\(getUserNicknameAndProfileUrl(accountId: Int(peerId)!).nickname)님이 스피커 요청을 했어요!\n스퍼커로 초대할까요?"
self.popupCancelTitle = "건너뛰기"
self.popupCancelAction = {
self.isShowPopup = false
}
self.popupConfirmTitle = "스피커로 초대"
self.popupConfirmAction = {
self.isShowPopup = false
if self.liveRoomInfo!.speakerList.count <= 4 {
self.requestSpeakerAllow(peerId)
} else {
self.errorMessage = "스피커 정원이 초과되었습니다."
self.isShowErrorPopup = true
}
}
self.isShowPopup = true
return
}
if rawMessageString == LiveRoomRequestType.INVITE_SPEAKER.rawValue && self.role == .LISTENER {
self.popupContent = "스피커로 초대되었어요"
self.popupCancelTitle = "다음에요"
self.popupCancelAction = {
self.isShowPopup = false
}
self.popupConfirmTitle = "스피커로 참여하기"
self.popupConfirmAction = {
self.isShowPopup = false
self.setSpeaker()
}
self.isShowPopup = true
return
}
if rawMessageString == LiveRoomRequestType.REQUEST_SPEAKER_ALLOW.rawValue && self.role == .LISTENER {
self.setSpeaker()
return
}
if rawMessageString == LiveRoomRequestType.KICK_OUT.rawValue {
if let roomInfo = self.liveRoomInfo {
self.popupContent = "\(self.getUserNicknameAndProfileUrl(accountId: roomInfo.creatorId).nickname)님이 라이브에서 내보냈습니다."
} else {
self.popupContent = "방장님이 라이브에서 내보냈습니다."
}
self.isShowPopup = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [unowned self] in
self.quitRoom()
}
return
}
if rawMessageString == LiveRoomRequestType.SET_MANAGER.rawValue {
if self.role == .SPEAKER {
self.role = .LISTENER
self.isMute = false
self.agora.mute(isMute)
self.agora.setRole(role: .audience)
}
if let roomInfo = self.liveRoomInfo {
self.popupContent = "\(self.getUserNicknameAndProfileUrl(accountId: roomInfo.creatorId).nickname)님이 스탭으로 지정했습니다."
} else {
self.popupContent = "방장님이 스탭으로 지정했습니다"
}
self.isShowPopup = true
}
if rawMessageString == LiveRoomRequestType.RELEASE_MANAGER.rawValue {
if let roomInfo = self.liveRoomInfo {
self.popupContent = "\(self.getUserNicknameAndProfileUrl(accountId: roomInfo.creatorId).nickname)님이 스탭에서 해제했습니다."
} else {
self.popupContent = "방장님이 스탭에서 해제했습니다."
}
self.isShowPopup = true
}
}
}
}
}
extension LiveRoomViewModel: AgoraRtmChannelDelegate {
func channel(_ channel: AgoraRtmChannel, messageReceived message: AgoraRtmMessage, from member: AgoraRtmMember) {
let (nickname, profileUrl) = getUserNicknameAndProfileUrl(accountId: Int(member.userId)!)
if message.type == .raw, let rawMessage = message as? AgoraRtmRawMessage {
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: rawMessage.rawData)
if decoded.type == .DONATION {
self.messages.append(
LiveRoomDonationChat(
profileUrl: profileUrl,
nickname: nickname,
chat: decoded.message,
can: decoded.can,
donationMessage: decoded.donationMessage ?? ""
)
)
self.totalDonationCan += decoded.can
} else if decoded.type == .EDIT_ROOM_INFO || decoded.type == .SET_MANAGER {
self.getRoomInfo()
}
} catch {
}
} else {
let chat = message.text
let rank = getUserRank(userId: Int(member.userId) ?? 0)
if !chat.trimmingCharacters(in: .whitespaces).isEmpty {
messages.append(LiveRoomNormalChat(userId: Int(member.userId)!, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chat))
}
}
DispatchQueue.main.async { [unowned self] in
self.messageChangeFlag.toggle()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
}
}
func channel(_ channel: AgoraRtmChannel, memberJoined member: AgoraRtmMember) {
getRoomInfo(userId: Int(member.userId)!) { [unowned self] nickname in
if !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
DispatchQueue.main.async { [unowned self] in
self.messages.append(LiveRoomJoinChat(nickname: nickname))
self.messageChangeFlag.toggle()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
}
}
}
}
func channel(_ channel: AgoraRtmChannel, memberLeft member: AgoraRtmMember) {
if let liveRoomInfo = liveRoomInfo, liveRoomInfo.creatorId != Int(member.userId)! {
getRoomInfo()
}
}
}