1916 lines
77 KiB
Swift
1916 lines
77 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 let rouletteRepository = RouletteRepository()
|
|
private var subscription = Set<AnyCancellable>()
|
|
|
|
@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 coverImageUrl: String?
|
|
|
|
@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 = false {
|
|
didSet {
|
|
if isShowNotice {
|
|
isShowMenuPan = false
|
|
}
|
|
}
|
|
}
|
|
|
|
@Published var isShowMenuPan = false {
|
|
didSet {
|
|
if isShowMenuPan {
|
|
isShowNotice = 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 isShowingNewChat = false
|
|
@Published var isShowPhotoPicker = false
|
|
@Published var noticeViewWidth: CGFloat = UIFont.systemFontSize
|
|
@Published var noticeViewHeight: CGFloat = UIFont.systemFontSize
|
|
|
|
@Published var isBgOn = true
|
|
@Published var isSignatureOn = 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 isShowNoChattingConfirm = false
|
|
|
|
@Published var reportUserId = 0
|
|
@Published var reportUserNickname = ""
|
|
@Published var reportUserIsBlocked = false
|
|
|
|
@Published var noChattingUserId = 0
|
|
@Published var noChattingUserNickname = ""
|
|
@Published var noChattingUserProfileUrl = ""
|
|
|
|
private let noChattingTime = 180
|
|
@Published var isNoChatting = false
|
|
@Published var remainingNoChattingTime = 0
|
|
|
|
@Published var isActiveRoulette = false
|
|
|
|
@Published var isShowRouletteSettings = false
|
|
|
|
@Published var isShowRoulettePreview = false
|
|
@Published var roulettePreview: RoulettePreview? = nil
|
|
|
|
@Published var isShowRoulette = false
|
|
@Published var rouletteItems = [String]()
|
|
@Published var rouletteSelectedItem = ""
|
|
var rouletteCan = 0
|
|
|
|
@Published var signatureImageUrl = "" {
|
|
didSet {
|
|
showSignatureImage()
|
|
}
|
|
}
|
|
|
|
@Published var signature: LiveRoomDonationResponse? = nil {
|
|
didSet {
|
|
showSignatureImage()
|
|
}
|
|
}
|
|
|
|
private var menuId = 0
|
|
@Published var menu = ""
|
|
@Published var menuList = [GetMenuPresetResponse]()
|
|
|
|
@Published var isActivateMenu = false {
|
|
didSet {
|
|
if !isActivateMenu {
|
|
menu = ""
|
|
}
|
|
}
|
|
}
|
|
@Published var selectedMenu: SelectedMenu? = nil
|
|
|
|
var signatureImageUrls = [String]()
|
|
var signatureList = [LiveRoomDonationResponse]()
|
|
var isShowSignatureImage = false
|
|
|
|
var timer: DispatchSourceTimer?
|
|
|
|
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) {
|
|
self.isLoading = false
|
|
if isManager {
|
|
role = .SPEAKER
|
|
} else {
|
|
role = .LISTENER
|
|
}
|
|
|
|
DEBUG_LOG("agoraConnectSuccess")
|
|
|
|
if containNoChatRoom() {
|
|
startNoChatting()
|
|
}
|
|
}
|
|
|
|
func agoraConnectFail() {
|
|
self.isLoading = false
|
|
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
|
|
|
|
if self.coverImageUrl != data.coverImageUrl {
|
|
self.coverImageUrl = data.coverImageUrl
|
|
}
|
|
|
|
self.isActiveRoulette = data.isActiveRoulette
|
|
self.isLoading = true
|
|
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(chatMessage: String, onSuccess: @escaping () -> Void) {
|
|
DispatchQueue.main.async {[unowned self] in
|
|
if isNoChatting {
|
|
self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다"
|
|
self.isShowPopup = true
|
|
} else 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)
|
|
}
|
|
}
|
|
|
|
onSuccess()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
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(ApiResponse<LiveRoomDonationResponse>.self, from: responseData)
|
|
|
|
self.isLoading = false
|
|
|
|
if decoded.success {
|
|
let rawMessage = "\(can)캔을 후원하셨습니다."
|
|
let donationRawMessage = LiveRoomChatRawMessage(
|
|
type: .DONATION,
|
|
message: rawMessage,
|
|
can: can,
|
|
signature: decoded.data,
|
|
signatureImageUrl: decoded.data?.imageUrl,
|
|
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
|
|
addSignature(signature: decoded.data)
|
|
|
|
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 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,
|
|
menuPanId: isActivateMenu ? menuId : 0,
|
|
menuPan: isActivateMenu ? menu : "",
|
|
isActiveMenuPan: isActivateMenu
|
|
)
|
|
|
|
if (request.title == nil && request.notice == nil && coverImage == nil && menu == liveRoomInfo?.menuPan) {
|
|
self.errorMessage = "변경사항이 없습니다."
|
|
self.isShowErrorPopup = true
|
|
return
|
|
}
|
|
|
|
var multipartData = [MultipartFormData]()
|
|
|
|
let encoder = JSONEncoder()
|
|
encoder.outputFormatting = .withoutEscapingSlashes
|
|
|
|
if (request.title != nil || request.notice != nil || menu != liveRoomInfo?.menuPan) {
|
|
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.menuList.removeAll()
|
|
self.isActivateMenu = false
|
|
self.selectedMenu = nil
|
|
self.menu = ""
|
|
self.isShowMenuPan = false
|
|
self.getRoomInfo()
|
|
|
|
let editRoomInfoMessage = LiveRoomChatRawMessage(
|
|
type: .EDIT_ROOM_INFO,
|
|
message: "",
|
|
can: 0,
|
|
donationMessage: ""
|
|
)
|
|
|
|
self.agora.sendRawMessageToGroup(rawMessage: editRoomInfoMessage)
|
|
self.errorMessage = "라이브 정보가 수정되었습니다."
|
|
self.isShowErrorPopup = true
|
|
} else {
|
|
self.errorMessage = decoded.message ?? "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
|
|
self.isShowErrorPopup = true
|
|
}
|
|
} catch {
|
|
self.errorMessage = "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
|
|
self.isShowErrorPopup = true
|
|
}
|
|
}
|
|
.store(in: &subscription)
|
|
}
|
|
|
|
func selectMenuPreset(selectedMenuPreset: SelectedMenu) {
|
|
if menuList.isEmpty && (selectedMenuPreset == .MENU_2 || selectedMenuPreset == .MENU_3) {
|
|
errorMessage = "메뉴 1을 먼저 설정하세요"
|
|
isShowPopup = true
|
|
return
|
|
}
|
|
|
|
if menuList.count == 1 && selectedMenuPreset == .MENU_3 {
|
|
errorMessage = "메뉴 1과 메뉴 2를 먼저 설정하세요"
|
|
isShowPopup = true
|
|
return
|
|
}
|
|
|
|
if self.selectedMenu != selectedMenuPreset {
|
|
self.selectedMenu = selectedMenuPreset
|
|
|
|
if menuList.count > selectedMenuPreset.rawValue {
|
|
let menu = menuList[selectedMenuPreset.rawValue]
|
|
self.menu = menu.menu
|
|
self.menuId = menu.id
|
|
} else {
|
|
self.menu = ""
|
|
self.menuId = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
func getAllMenuPreset(onFailure: @escaping () -> Void) {
|
|
isLoading = true
|
|
|
|
repository.getAllMenuPreset(creatorId: 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
|
|
self.isLoading = false
|
|
let responseData = response.data
|
|
|
|
do {
|
|
let jsonDecoder = JSONDecoder()
|
|
let decoded = try jsonDecoder.decode(ApiResponse<[GetMenuPresetResponse]>.self, from: responseData)
|
|
|
|
if let data = decoded.data, decoded.success {
|
|
self.menuList.removeAll()
|
|
self.menuList.append(contentsOf: data)
|
|
self.isActivateMenu = false
|
|
self.selectedMenu = nil
|
|
|
|
for (index, menuPreset) in self.menuList.enumerated() {
|
|
if menuPreset.isActive {
|
|
switch index {
|
|
case 1:
|
|
self.selectMenuPreset(selectedMenuPreset: .MENU_2)
|
|
|
|
case 2:
|
|
self.selectMenuPreset(selectedMenuPreset: .MENU_3)
|
|
|
|
default:
|
|
self.selectMenuPreset(selectedMenuPreset: .MENU_1)
|
|
}
|
|
|
|
self.isActivateMenu = true
|
|
}
|
|
|
|
}
|
|
} else {
|
|
onFailure()
|
|
if let message = decoded.message {
|
|
self.errorMessage = message
|
|
} else {
|
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
|
}
|
|
|
|
self.isShowPopup = true
|
|
}
|
|
} catch {
|
|
onFailure()
|
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
|
self.isShowPopup = true
|
|
}
|
|
}
|
|
.store(in: &subscription)
|
|
}
|
|
|
|
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
|
|
if UserDefaults.int(forKey: .userId) == liveRoomInfo?.creatorId {
|
|
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
|
|
)
|
|
}
|
|
|
|
func setNoChatting() {
|
|
agora.sendMessageToPeer(
|
|
peerId: String(noChattingUserId),
|
|
rawMessage: LiveRoomRequestType.NO_CHATTING.rawValue.data(using: .utf8)!,
|
|
completion: { [unowned self] errorCode in
|
|
if errorCode == .ok {
|
|
self.popupContent = "\(noChattingUserNickname)님을 3분간 채팅금지를 하였습니다."
|
|
self.isShowPopup = true
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
self.noChattingUserId = 0
|
|
self.noChattingUserNickname = ""
|
|
self.noChattingUserProfileUrl = ""
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
private func setManagerMessage() {
|
|
let setManagerMessage = LiveRoomChatRawMessage(
|
|
type: .SET_MANAGER,
|
|
message: "",
|
|
can: 0,
|
|
donationMessage: ""
|
|
)
|
|
|
|
self.agora.sendRawMessageToGroup(rawMessage: setManagerMessage)
|
|
}
|
|
|
|
func userBlock(onSuccess: @escaping (Int) -> Void) {
|
|
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)
|
|
onSuccess(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)
|
|
}
|
|
|
|
private func containNoChatRoom() -> Bool {
|
|
let noChatRoomList = getNoChatRoomListFromUserDefaults()
|
|
if let _ = noChatRoomList.firstIndex(of: liveRoomInfo!.roomId) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
private func startNoChatting() {
|
|
isNoChatting = true
|
|
remainingNoChattingTime = noChattingTime
|
|
popupContent = "\(self.getUserNicknameAndProfileUrl(accountId: liveRoomInfo!.creatorId).nickname)님이 3분간 채팅을 금지하였습니다."
|
|
isShowPopup = true
|
|
|
|
startCountDown()
|
|
}
|
|
|
|
private func startCountDown() {
|
|
// 1초마다 타이머를 실행
|
|
let queue = DispatchQueue.global(qos: .background)
|
|
timer = DispatchSource.makeTimerSource(queue: queue)
|
|
timer?.schedule(deadline: .now(), repeating: 1.0)
|
|
timer?.setEventHandler { [unowned self] in
|
|
DispatchQueue.main.async {
|
|
self.remainingNoChattingTime -= 1
|
|
|
|
if self.remainingNoChattingTime <= 0 {
|
|
self.isNoChatting = false
|
|
self.timer?.cancel()
|
|
self.removeNoChatRoom()
|
|
self.popupContent = "채팅금지가 해제되었습니다."
|
|
self.isShowPopup = true
|
|
}
|
|
}
|
|
}
|
|
timer?.resume()
|
|
}
|
|
|
|
private func addNoChatRoom() {
|
|
var noChatRoomList = getNoChatRoomListFromUserDefaults()
|
|
noChatRoomList.append(liveRoomInfo!.roomId)
|
|
saveNoChatRoomListToUserDefaults(noChatRoomList: noChatRoomList)
|
|
}
|
|
|
|
private func removeNoChatRoom() {
|
|
var noChatRoomList = getNoChatRoomListFromUserDefaults()
|
|
if let index = noChatRoomList.firstIndex(of: liveRoomInfo!.roomId) {
|
|
noChatRoomList.remove(at: index)
|
|
}
|
|
saveNoChatRoomListToUserDefaults(noChatRoomList: noChatRoomList)
|
|
}
|
|
|
|
private func getNoChatRoomListFromUserDefaults() -> [Int] {
|
|
if let noChatRoomListData = UserDefaults.data(forKey: .noChatRoomList) {
|
|
let jsonDecoder = JSONDecoder()
|
|
if let noChatRoomList = try? jsonDecoder.decode([Int].self, from: noChatRoomListData) {
|
|
return noChatRoomList
|
|
}
|
|
}
|
|
return []
|
|
}
|
|
|
|
private func saveNoChatRoomListToUserDefaults(noChatRoomList: [Int]) {
|
|
let jsonEncoder = JSONEncoder()
|
|
if let jsonData = try? jsonEncoder.encode(noChatRoomList) {
|
|
UserDefaults.set(jsonData, forKey: .noChatRoomList)
|
|
}
|
|
}
|
|
|
|
func setActiveRoulette(isActiveRoulette: Bool, message: String) {
|
|
self.popupContent = message
|
|
self.isShowPopup = true
|
|
self.agora.sendRawMessageToGroup(
|
|
rawMessage: LiveRoomChatRawMessage(
|
|
type: .TOGGLE_ROULETTE,
|
|
message: "",
|
|
can: 0,
|
|
donationMessage: "",
|
|
isActiveRoulette: isActiveRoulette
|
|
)
|
|
)
|
|
}
|
|
|
|
func showRoulette() {
|
|
if let liveRoomInfo = liveRoomInfo, !isLoading {
|
|
isLoading = true
|
|
|
|
rouletteRepository.getRoulette(creatorId: liveRoomInfo.creatorId)
|
|
.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<GetRouletteResponse>.self, from: responseData)
|
|
|
|
if let data = decoded.data, decoded.success, !data.items.isEmpty {
|
|
self.roulettePreview = RoulettePreview(can: data.can, items: calculatePercentages(options: data.items))
|
|
self.isShowRoulettePreview = true
|
|
} else {
|
|
if let message = decoded.message {
|
|
self.errorMessage = message
|
|
} else {
|
|
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
|
|
}
|
|
self.isShowErrorPopup = true
|
|
}
|
|
} catch {
|
|
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
|
|
self.isShowErrorPopup = true
|
|
}
|
|
}
|
|
.store(in: &subscription)
|
|
}
|
|
}
|
|
|
|
func spinRoulette() {
|
|
if !isLoading {
|
|
isLoading = true
|
|
rouletteRepository.spinRoulette(request: SpinRouletteRequest(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<GetRouletteResponse>.self, from: responseData)
|
|
|
|
if let data = decoded.data, decoded.success, !data.items.isEmpty {
|
|
UserDefaults.set(UserDefaults.int(forKey: .can) - data.can, forKey: .can)
|
|
randomSelectRouletteItem(can: data.can, items: data.items)
|
|
} else {
|
|
if let message = decoded.message {
|
|
self.errorMessage = message
|
|
} else {
|
|
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
|
|
}
|
|
self.isShowErrorPopup = true
|
|
}
|
|
} catch {
|
|
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
|
|
self.isShowErrorPopup = true
|
|
}
|
|
}
|
|
.store(in: &subscription)
|
|
}
|
|
}
|
|
|
|
func sendRouletteDonation() {
|
|
let rouletteRawMessage = LiveRoomChatRawMessage(
|
|
type: .ROULETTE_DONATION,
|
|
message: rouletteSelectedItem,
|
|
can: rouletteCan,
|
|
donationMessage: ""
|
|
)
|
|
|
|
self.agora.sendRawMessageToGroup(
|
|
rawMessage: rouletteRawMessage,
|
|
completion: { [unowned self] errorCode in
|
|
if errorCode == .errorOk {
|
|
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
|
|
self.messages.append(
|
|
LiveRoomRouletteDonationChat(
|
|
profileUrl: profileUrl,
|
|
nickname: nickname,
|
|
rouletteResult: rouletteSelectedItem
|
|
)
|
|
)
|
|
|
|
totalDonationCan += rouletteCan
|
|
self.rouletteItems.removeAll()
|
|
self.rouletteSelectedItem = ""
|
|
self.rouletteCan = 0
|
|
|
|
self.messageChangeFlag.toggle()
|
|
if self.messages.count > 100 {
|
|
self.messages.remove(at: 0)
|
|
}
|
|
} else {
|
|
self.refundRouletteDonation()
|
|
}
|
|
},
|
|
fail: { [unowned self] in
|
|
self.refundRouletteDonation()
|
|
}
|
|
)
|
|
}
|
|
|
|
private func calculatePercentages(options: [RouletteItem]) -> [RoulettePreviewItem] {
|
|
let totalWeight = options.reduce(0) { $0 + $1.weight }
|
|
let updatedOptions = options.map { option in
|
|
let percent = floor(Double(option.weight) / Double(totalWeight) * 10000) / 100
|
|
return RoulettePreviewItem(title: option.title, percent: "\(String(format: "%.2f", percent))%")
|
|
}
|
|
|
|
return updatedOptions
|
|
}
|
|
|
|
private func randomSelectRouletteItem(can: Int, items: [RouletteItem]) {
|
|
isLoading = true
|
|
|
|
var rouletteItems = [String]()
|
|
items.forEach {
|
|
var i = 1
|
|
while (i < $0.weight * 10) {
|
|
rouletteItems.append($0.title)
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
isLoading = false
|
|
self.rouletteItems.removeAll()
|
|
self.rouletteItems.append(contentsOf: items.map { $0.title })
|
|
self.rouletteSelectedItem = rouletteItems.randomElement()!
|
|
self.rouletteCan = can
|
|
self.isShowRoulette = true
|
|
}
|
|
|
|
private func refundRouletteDonation() {
|
|
isLoading = true
|
|
|
|
rouletteRepository.refundRouletteDonation(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)
|
|
}
|
|
|
|
private func addSignatureImage(imageUrl: String) {
|
|
if imageUrl.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
|
|
if !isShowSignatureImage {
|
|
isShowSignatureImage = true
|
|
signatureImageUrl = imageUrl
|
|
} else {
|
|
signatureImageUrls.append(imageUrl)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addSignature(signature: LiveRoomDonationResponse?) {
|
|
if let signature = signature {
|
|
if !isShowSignatureImage {
|
|
self.signature = signature
|
|
isShowSignatureImage = true
|
|
} else {
|
|
self.signatureList.append(signature)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func showSignatureImage() {
|
|
if let signature = signature {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(signature.time)) {
|
|
if let nextSignature = self.signatureList.first {
|
|
self.signature = nextSignature
|
|
self.signatureList.removeFirst()
|
|
} else {
|
|
self.signature = nil
|
|
self.isShowSignatureImage = false
|
|
}
|
|
}
|
|
} else if signatureImageUrl.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
|
|
if let imageUrl = self.signatureImageUrls.first {
|
|
self.signatureImageUrl = imageUrl
|
|
self.signatureImageUrls.removeFirst()
|
|
} else {
|
|
self.signatureImageUrl = ""
|
|
self.isShowSignatureImage = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if rawMessageString == LiveRoomRequestType.NO_CHATTING.rawValue {
|
|
DispatchQueue.main.async {
|
|
self.addNoChatRoom()
|
|
self.startNoChatting()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
if let signature = decoded.signature {
|
|
self.addSignature(signature: signature)
|
|
} else if let imageUrl = decoded.signatureImageUrl {
|
|
self.addSignatureImage(imageUrl: imageUrl)
|
|
}
|
|
} else if decoded.type == .ROULETTE_DONATION {
|
|
self.messages.append(
|
|
LiveRoomRouletteDonationChat(
|
|
profileUrl: profileUrl,
|
|
nickname: nickname,
|
|
rouletteResult: decoded.message
|
|
)
|
|
)
|
|
|
|
self.totalDonationCan += decoded.can
|
|
} else if decoded.type == .TOGGLE_ROULETTE && decoded.isActiveRoulette != nil {
|
|
self.isActiveRoulette = decoded.isActiveRoulette!
|
|
} 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()
|
|
}
|
|
}
|
|
}
|