From 282ee73de197bc662bd48dcfb3de3c1da4c882a0 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Wed, 11 Oct 2023 19:25:12 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B1=84=EA=B8=88=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/UserDefaultsExtension.swift | 1 + .../Dialog/LiveRoomNoChattingDialogView.swift | 85 +++++++++++++ .../Dialog/LiveRoomProfileItemTitleView.swift | 21 ++++ .../Dialog/LiveRoomProfilesDialogView.swift | 15 ++- .../LiveRoomUserProfileDialogView.swift | 17 +++ .../Live/Room/LiveRoomRequestType.swift | 2 +- SodaLive/Sources/Live/Room/LiveRoomView.swift | 75 ++++++++--- .../Sources/Live/Room/LiveRoomViewModel.swift | 119 +++++++++++++++++- 8 files changed, 309 insertions(+), 26 deletions(-) create mode 100644 SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift diff --git a/SodaLive/Sources/Extensions/UserDefaultsExtension.swift b/SodaLive/Sources/Extensions/UserDefaultsExtension.swift index b8b6add..3570579 100644 --- a/SodaLive/Sources/Extensions/UserDefaultsExtension.swift +++ b/SodaLive/Sources/Extensions/UserDefaultsExtension.swift @@ -17,6 +17,7 @@ enum UserDefaultsKey: String, CaseIterable { case nickname case pushToken case profileImage + case noChatRoomList case devicePushToken case isContentPlayLoop case isFollowedChannel diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift new file mode 100644 index 0000000..92a10f3 --- /dev/null +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift @@ -0,0 +1,85 @@ +// +// LiveRoomNoChattingDialogView.swift +// SodaLive +// +// Created by klaus on 2023/10/11. +// + +import SwiftUI +import Kingfisher + +struct LiveRoomNoChattingDialogView: View { + + let nickname: String + let profileUrl: String + let confirmAction: () -> Void + let cancelAction: () -> Void + + var body: some View { + ZStack { + Color.black + .opacity(0.5) + .frame(width: screenSize().width, height: screenSize().height) + + VStack(spacing: 21) { + Text("채팅금지") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "bbbbbb")) + + HStack(spacing: 8) { + KFImage(URL(string: profileUrl)) + .resizable() + .frame(width: 26.7, height: 26.7) + + Text(nickname) + .font(.custom(Font.medium.rawValue, size: 16.7)) + .foregroundColor(Color(hex: "bbbbbb")) + } + + Text("3분간 채팅금지를 하겠습니까?") + .font(.custom(Font.medium.rawValue, size: 15)) + .foregroundColor(Color(hex: "bbbbbb")) + + HStack(spacing: 13.3) { + Text("취소") + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(Color(hex: "9970ff")) + .padding(.vertical, 16) + .frame(width: (screenSize().width - 80) / 2) + .background(Color(hex: "9970ff").opacity(0.13)) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(hex: "9970ff"), lineWidth: 1) + ) + .onTapGesture { cancelAction() } + + Text("확인") + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(Color(hex: "ffffff")) + .padding(.vertical, 16) + .frame(width: (screenSize().width - 80) / 2) + .background(Color(hex: "9970ff")) + .cornerRadius(8) + .onTapGesture { confirmAction() } + } + } + .padding(.top, 40) + .padding(.bottom, 16.7) + .padding(.horizontal, 16.7) + .background(Color(hex: "222222")) + .cornerRadius(10) + } + } +} + +struct LiveRoomNoChattingDialogView_Previews: PreviewProvider { + static var previews: some View { + LiveRoomNoChattingDialogView( + nickname: "닉네임", + profileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + confirmAction: {}, + cancelAction: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift index 71feff1..77a3e90 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift @@ -74,6 +74,7 @@ struct LiveRoomProfileItemMasterView: View { struct LiveRoomProfileItemUserView: View { let isStaff: Bool let userId: Int + let creatorId: Int let nickname: String let profileUrl: String let role: LiveRoomMemberRole @@ -82,6 +83,7 @@ struct LiveRoomProfileItemUserView: View { let onClickInviteSpeaker: (Int) -> Void let onClickKickOut: (Int) -> Void let onClickProfile: (Int) -> Void + let onClickNoChatting: (Int, String, String) -> Void var body: some View { ZStack { @@ -145,6 +147,25 @@ struct LiveRoomProfileItemUserView: View { } } + if role != .MANAGER && creatorId == UserDefaults.int(forKey: .userId) { + Text("채금") + .font(.custom(Font.medium.rawValue, size: 10)) + .foregroundColor(Color(hex: "ffffff")) + .padding(.horizontal, 5.5) + .padding(.vertical, 12) + .background(Color(hex: "9970ff").opacity(0.3)) + .cornerRadius(6.7) + .overlay( + RoundedRectangle(cornerRadius: 6.7) + .stroke(Color(hex: "9970ff"), lineWidth: 1) + ) + .cornerRadius(6.7) + .padding(.leading, 10) + .onTapGesture { + onClickNoChatting(userId, nickname, profileUrl) + } + } + if role != .MANAGER && isStaff { Image("ic_kick_out") .padding(.leading, 10) diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift index 36f852b..5928fb8 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift @@ -24,7 +24,8 @@ struct LiveRoomProfilesDialogView: View { roomInfo: GetRoomInfoResponse, registerNotification: @escaping () -> Void, unRegisterNotification: @escaping () -> Void, - onClickProfile: @escaping (Int) -> Void + onClickProfile: @escaping (Int) -> Void, + onClickNoChatting: @escaping (Int, String, String) -> Void ) { self._isShowing = isShowing self.viewModel = viewModel @@ -52,13 +53,15 @@ struct LiveRoomProfilesDialogView: View { LiveRoomProfileItemUserView( isStaff: isStaff , userId: manager.id, + creatorId: roomInfo.creatorId, nickname: manager.nickname, profileUrl: manager.profileImage, role: manager.role, onClickChangeListener: { _ in }, onClickInviteSpeaker: { _ in }, onClickKickOut: { _ in }, - onClickProfile: onClickProfile + onClickProfile: onClickProfile, + onClickNoChatting: { _, _, _ in } ) ) ) @@ -96,6 +99,7 @@ struct LiveRoomProfilesDialogView: View { LiveRoomProfileItemUserView( isStaff: isStaff, userId: speaker.id, + creatorId: roomInfo.creatorId, nickname: speaker.nickname, profileUrl: speaker.profileImage, role: speaker.role, @@ -112,7 +116,8 @@ struct LiveRoomProfilesDialogView: View { viewModel.kickOutId = $0 viewModel.isShowKickOutPopup = true }, - onClickProfile: onClickProfile + onClickProfile: onClickProfile, + onClickNoChatting: onClickNoChatting ) ) ) @@ -137,6 +142,7 @@ struct LiveRoomProfilesDialogView: View { LiveRoomProfileItemUserView( isStaff: isStaff, userId: listener.id, + creatorId: roomInfo.creatorId, nickname: listener.nickname, profileUrl: listener.profileImage, role: listener.role, @@ -155,7 +161,8 @@ struct LiveRoomProfilesDialogView: View { viewModel.kickOutId = $0 viewModel.isShowKickOutPopup = true }, - onClickProfile: onClickProfile + onClickProfile: onClickProfile, + onClickNoChatting: onClickNoChatting ) ) ) diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift index 6cdbfe2..2a68747 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift @@ -23,6 +23,7 @@ struct LiveRoomUserProfileDialogView: View { let onClickInviteSpeaker: (Int) -> Void let onClickChangeListener: (Int) -> Void let onClickMenu: (Int, String, Bool) -> Void + let onClickNoChatting: (Int, String, String) -> Void var body: some View { ZStack { @@ -210,6 +211,22 @@ struct LiveRoomUserProfileDialogView: View { .fixedSize(horizontal: false, vertical: true) .padding(.top, 21.3) + if let _ = userProfile.isManager { + Text("3분간 채팅금지") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color(hex: "9970ff")) + .frame(maxWidth: .infinity) + .padding(.vertical, 13) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color(hex: "9970ff")) + ) + .onTapGesture { onClickNoChatting(userProfile.userId, userProfile.nickname, userProfile.profileUrl) } + .padding(.top, 21.3) + } + Text(userProfile.tags) .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "9970ff")) diff --git a/SodaLive/Sources/Live/Room/LiveRoomRequestType.swift b/SodaLive/Sources/Live/Room/LiveRoomRequestType.swift index 3dc37bc..0f50d71 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomRequestType.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomRequestType.swift @@ -8,5 +8,5 @@ import Foundation enum LiveRoomRequestType: String { - case REQUEST_SPEAKER, REQUEST_SPEAKER_ALLOW, INVITE_SPEAKER, CHANGE_LISTENER, KICK_OUT, SET_MANAGER, RELEASE_MANAGER + case REQUEST_SPEAKER, REQUEST_SPEAKER_ALLOW, INVITE_SPEAKER, CHANGE_LISTENER, KICK_OUT, SET_MANAGER, RELEASE_MANAGER, NO_CHATTING } diff --git a/SodaLive/Sources/Live/Room/LiveRoomView.swift b/SodaLive/Sources/Live/Room/LiveRoomView.swift index 4a43d61..0d24d6a 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomView.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomView.swift @@ -485,26 +485,6 @@ struct LiveRoomView: View { } ) } - - if viewModel.isShowPopup { - LiveRoomDialogView( - content: viewModel.popupContent, - cancelTitle: viewModel.popupCancelTitle, - cancelAction: viewModel.popupCancelAction, - confirmTitle: viewModel.popupConfirmTitle, - confirmAction: viewModel.popupConfirmAction - ).onAppear { - if viewModel.popupConfirmTitle == nil && viewModel.popupConfirmAction == nil { - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - viewModel.isShowPopup = false - viewModel.popupCancelTitle = nil - viewModel.popupCancelAction = nil - viewModel.popupConfirmTitle = nil - viewModel.popupConfirmAction = nil - } - } - } - } } ZStack { @@ -519,6 +499,12 @@ struct LiveRoomView: View { if $0 != UserDefaults.int(forKey: .userId) { viewModel.getUserProfile(userId: $0) } + }, + onClickNoChatting: { userId, nickname, profileUrl in + viewModel.noChattingUserId = userId + viewModel.noChattingUserNickname = nickname + viewModel.noChattingUserProfileUrl = profileUrl + viewModel.isShowNoChattingConfirm = true } ) } @@ -547,6 +533,12 @@ struct LiveRoomView: View { viewModel.reportUserNickname = userNickname viewModel.reportUserIsBlocked = isBlocked viewModel.isShowReportMenu = true + }, + onClickNoChatting: { userId, nickname, profileUrl in + viewModel.noChattingUserId = userId + viewModel.noChattingUserNickname = nickname + viewModel.noChattingUserProfileUrl = profileUrl + viewModel.isShowNoChattingConfirm = true } ) .padding(20) @@ -612,6 +604,43 @@ struct LiveRoomView: View { } ) } + + if viewModel.isShowNoChattingConfirm && viewModel.noChattingUserId > 0 { + LiveRoomNoChattingDialogView( + nickname: viewModel.noChattingUserNickname, + profileUrl: viewModel.noChattingUserProfileUrl, + confirmAction: { + viewModel.isShowNoChattingConfirm = false + viewModel.setNoChatting() + }, + cancelAction: { + viewModel.noChattingUserId = 0 + viewModel.noChattingUserNickname = "" + viewModel.noChattingUserProfileUrl = "" + viewModel.isShowNoChattingConfirm = false + } + ) + } + + if viewModel.isShowPopup { + LiveRoomDialogView( + content: viewModel.popupContent, + cancelTitle: viewModel.popupCancelTitle, + cancelAction: viewModel.popupCancelAction, + confirmTitle: viewModel.popupConfirmTitle, + confirmAction: viewModel.popupConfirmAction + ).onAppear { + if viewModel.popupConfirmTitle == nil && viewModel.popupConfirmAction == nil { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + viewModel.isShowPopup = false + viewModel.popupCancelTitle = nil + viewModel.popupCancelAction = nil + viewModel.popupConfirmTitle = nil + viewModel.popupConfirmAction = nil + } + } + } + } } if viewModel.isLoading && viewModel.liveRoomInfo == nil { @@ -815,6 +844,12 @@ struct LiveRoomView: View { .accentColor(Color(hex: "3bb9f1")) .keyboardType(.default) .padding(.horizontal, 13.3) + .onTapGesture { + if viewModel.isNoChatting { + viewModel.popupContent = "\(viewModel.remainingNoChattingTime)초 동안 채팅하실 수 없습니다" + viewModel.isShowPopup = true + } + } Spacer() diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index 526a075..2c7f1cb 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -118,11 +118,22 @@ final class LiveRoomViewModel: NSObject, ObservableObject { @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 + + var timer: DispatchSourceTimer? + func setOriginOffset(_ offset: CGFloat) { guard !isCheckedOriginOffset else { return } self.originOffset = offset @@ -146,6 +157,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } func agoraConnectSuccess(isManager: Bool) { + self.isLoading = false if isManager { role = .SPEAKER } else { @@ -153,9 +165,14 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } DEBUG_LOG("agoraConnectSuccess") + + if containNoChatRoom() { + startNoChatting() + } } func agoraConnectFail() { + self.isLoading = false DEBUG_LOG("agoraConnectFail") AppState.shared.roomId = 0 AppState.shared.isShowPlayer = false @@ -226,6 +243,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { if let data = decoded.data, decoded.success { self.liveRoomInfo = data + self.isLoading = true self.agora.joinChannel( roomInfo: data, rtmChannelDelegate: self, @@ -279,7 +297,10 @@ final class LiveRoomViewModel: NSObject, ObservableObject { func sendMessage() { DispatchQueue.main.async {[unowned self] in - if chatMessage.count > 0 { + 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)) @@ -1078,6 +1099,25 @@ final class LiveRoomViewModel: NSObject, ObservableObject { ) } + 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, @@ -1209,6 +1249,76 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } .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 + 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.remainingNoChattingTime = self.noChattingTime + 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) + } + } } extension LiveRoomViewModel: AgoraRtcEngineDelegate { @@ -1340,6 +1450,13 @@ extension LiveRoomViewModel: AgoraRtmDelegate { } self.isShowPopup = true } + + if rawMessageString == LiveRoomRequestType.NO_CHATTING.rawValue { + DispatchQueue.main.async { + self.addNoChatRoom() + self.startNoChatting() + } + } } } }