[] 채팅(Base) 잡는 중: WebSocket 기반으로 구현 중

This commit is contained in:
Seonkyu_Kim 2025-02-17 17:50:28 +09:00
parent 875f92c81b
commit 9fb60762c4
211 changed files with 285 additions and 47 deletions

View File

@ -14,6 +14,7 @@
A78774722CF586AF002FE2EE /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = A78774712CF586AF002FE2EE /* Alamofire */; }; A78774722CF586AF002FE2EE /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = A78774712CF586AF002FE2EE /* Alamofire */; };
A7A518CF2CF555E200822D0D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = A7A518CE2CF555E200822D0D /* README.md */; }; A7A518CF2CF555E200822D0D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = A7A518CE2CF555E200822D0D /* README.md */; };
A7A518D12CF5588500822D0D /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = A7A518D02CF5588500822D0D /* .gitignore */; }; A7A518D12CF5588500822D0D /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = A7A518D02CF5588500822D0D /* .gitignore */; };
FB0119D32D62EEF000C1FA82 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = FB0119D22D62EEF000C1FA82 /* Starscream */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -53,6 +54,7 @@
A73892252D526A9D00659A62 /* FirebaseCrashlytics in Frameworks */, A73892252D526A9D00659A62 /* FirebaseCrashlytics in Frameworks */,
A771FFF22CFB70D100367DA6 /* KakaoSDK in Frameworks */, A771FFF22CFB70D100367DA6 /* KakaoSDK in Frameworks */,
A73892212D526A9D00659A62 /* FirebaseAnalytics in Frameworks */, A73892212D526A9D00659A62 /* FirebaseAnalytics in Frameworks */,
FB0119D32D62EEF000C1FA82 /* Starscream in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -102,6 +104,7 @@
A73892202D526A9D00659A62 /* FirebaseAnalytics */, A73892202D526A9D00659A62 /* FirebaseAnalytics */,
A73892222D526A9D00659A62 /* FirebaseAppCheck */, A73892222D526A9D00659A62 /* FirebaseAppCheck */,
A73892242D526A9D00659A62 /* FirebaseCrashlytics */, A73892242D526A9D00659A62 /* FirebaseCrashlytics */,
FB0119D22D62EEF000C1FA82 /* Starscream */,
); );
productName = AcaMate; productName = AcaMate;
productReference = A7A518BB2CF5558B00822D0D /* AcaMate.app */; productReference = A7A518BB2CF5558B00822D0D /* AcaMate.app */;
@ -135,6 +138,7 @@
A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */, A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */,
A771FFF02CFB70D100367DA6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */, A771FFF02CFB70D100367DA6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */,
A738921F2D526A9D00659A62 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, A738921F2D526A9D00659A62 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
FB0119D12D62EEF000C1FA82 /* XCRemoteSwiftPackageReference "Starscream" */,
); );
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = A7A518BC2CF5558B00822D0D /* Products */; productRefGroup = A7A518BC2CF5558B00822D0D /* Products */;
@ -420,6 +424,14 @@
minimumVersion = 5.10.2; minimumVersion = 5.10.2;
}; };
}; };
FB0119D12D62EEF000C1FA82 /* XCRemoteSwiftPackageReference "Starscream" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/daltoniam/Starscream";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.8;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -448,6 +460,11 @@
package = A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */; package = A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire; productName = Alamofire;
}; };
FB0119D22D62EEF000C1FA82 /* Starscream */ = {
isa = XCSwiftPackageProductDependency;
package = FB0119D12D62EEF000C1FA82 /* XCRemoteSwiftPackageReference "Starscream" */;
productName = Starscream;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = A7A518B32CF5558B00822D0D /* Project object */; rootObject = A7A518B32CF5558B00822D0D /* Project object */;

View File

@ -1,5 +1,5 @@
{ {
"originHash" : "3b609245b8d633048f6670834279f82d0601cc0879a2d8c9c86fa0dd25734ea3", "originHash" : "2aab34be4ec6f8de8e42f37bee06d0f181ea2ab2db742d0c279bace3d5a0bcfb",
"pins" : [ "pins" : [
{ {
"identity" : "abseil-cpp-binary", "identity" : "abseil-cpp-binary",
@ -127,6 +127,15 @@
"version" : "2.4.0" "version" : "2.4.0"
} }
}, },
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
"location" : "https://github.com/daltoniam/Starscream",
"state" : {
"revision" : "c6bfd1af48efcc9a9ad203665db12375ba6b145a",
"version" : "4.0.8"
}
},
{ {
"identity" : "swift-protobuf", "identity" : "swift-protobuf",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -7,13 +7,18 @@
import SwiftUI import SwiftUI
// MARK: - ACAMATE // MARK: - ACAMATE
// APPSTORE_URL : https://apps.apple.com/us/app/%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8%EB%A9%94%EC%9D%B4%ED%8A%B8/id6739448113 // APPSTORE_URL : https://apps.apple.com/us/app/%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8%EB%A9%94%EC%9D%B4%ED%8A%B8/id6739448113
//#if DEV && LOCAL #if LOCAL
//public let API_URL: String = "http://localhost:5144" public let API_URL: String = "http://localhost:5144"
public let WS_URL: String = "ws://localhost:5144"
//public let WS_URL: String = "ws://169.254.53.148:5144"
//#else //#else
#if DEV #elseif DEV
public let API_URL: String = "https://devacamate.ipstein.myds.me" public let API_URL: String = "https://devacamate.ipstein.myds.me"
public let WS_URL: String = "wss://devacamate.ipstein.myds.me"
#else #else
public let API_URL: String = "https://acamate.ipstein.myds.me" public let API_URL: String = "https://acamate.ipstein.myds.me"
public let WS_URL: String = "wss://acamate.ipstein.myds.me"
#endif #endif

View File

@ -12,26 +12,30 @@ import Combine
/// ///
struct NavigationView: View { struct NavigationView: View {
@EnvironmentObject var appVM: AppViewModel @EnvironmentObject var appVM: AppViewModel
@State private var naviState : NaviState = .init(act: .NONE, path: .Intro)
@State private var history: [PathName] = [.Intro] @State private var history: [PathName] = [.Intro]
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ZStack { ZStack {
switch naviState.path { switch appVM.naviState.path {
case .NONE: case .NONE:
EmptyView() EmptyView()
case .Intro: case .Intro:
IntroView(naviState: $naviState) // IntroView(naviState: $naviState)
IntroView()
case .Login : case .Login :
LoginView(naviState: $naviState) // LoginView(naviState: $naviState)
LoginView()
case .Main: case .Main:
MainView(naviState: $naviState) // MainView(naviState: $naviState)
MainView()
case .ChatRoom(let id):
ChattingRoomView(roomID: id)
} }
} }
} }
.onChange(of: naviState) { old, new in .onChange(of: appVM.naviState) { old, new in
switch new.act { switch new.act {
case .NONE: case .NONE:
break break
@ -54,6 +58,7 @@ struct NavigationView: View {
.setAlert() .setAlert()
.setNetwork() .setNetwork()
.loadingView(isLoading: $appVM.isLoading) .loadingView(isLoading: $appVM.isLoading)
} }
/// ///
@ -64,7 +69,8 @@ struct NavigationView: View {
/// ///
private func popHistory() { private func popHistory() {
history.removeLast() history.removeLast()
naviState.set(act: .NONE, path: history.last ?? .NONE) appVM.naviState.set(act: .NONE, path: history.last ?? .NONE)
// naviState.set(act: .NONE, path: history.last ?? .NONE)
} }
/// ///
@ -76,17 +82,20 @@ struct NavigationView: View {
/// , /// ,
private func moveHistory(path: PathName) { private func moveHistory(path: PathName) {
if path == .NONE { if path == .NONE {
naviState.set(act: .RESET, path: history.first ?? .Main) appVM.naviState.set(act: .RESET, path: history.first ?? .Main)
// naviState.set(act: .RESET, path: history.first ?? .Main)
} }
if history.contains(path) { if history.contains(path) {
let remove = history.count - history.firstIndex(of: path)! - 1 let remove = history.count - history.firstIndex(of: path)! - 1
history.removeLast(remove) history.removeLast(remove)
if remove > 0 { if remove > 0 {
naviState.set(act: .NONE, path: path) appVM.naviState.set(act: .NONE, path: path)
// naviState.set(act: .NONE, path: path)
return return
} }
} }
naviState.set(act: .RESET, path: path) appVM.naviState.set(act: .RESET, path: path)
// naviState.set(act: .RESET, path: path)
} }
private func showHistory() { private func showHistory() {

View File

@ -12,7 +12,7 @@ struct IntroView: View {
@EnvironmentObject var appVM: AppViewModel @EnvironmentObject var appVM: AppViewModel
@State var cancellables: Set<AnyCancellable> = [] @State var cancellables: Set<AnyCancellable> = []
@Binding var naviState : NaviState // @Binding var naviState : NaviState
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -40,7 +40,7 @@ struct IntroView: View {
.onAppear { .onAppear {
printLog("IntroView_onAppear") printLog("IntroView_onAppear")
#if LOCAL #if LOCAL
naviState.set(act: .RESET, path: .Login) appVM.naviState.set(act: .RESET, path: .Login)
#else #else
subscribeAlertAction() subscribeAlertAction()
loadVersion() loadVersion()
@ -71,7 +71,8 @@ struct IntroView: View {
appVM.showAlert.toggle() appVM.showAlert.toggle()
} else { } else {
// //
naviState.set(act: .RESET, path: .Login) appVM.naviState.set(act: .RESET, path: .Login)
// naviState.set(act: .RESET, path: .Login)
} }
@ -90,7 +91,7 @@ struct IntroView: View {
exit(1) exit(1)
//MARK: - TODO ( ) //MARK: - TODO ( )
} else { } else {
naviState.set(act: .RESET, path: .Login) appVM.naviState.set(act: .RESET, path: .Login)
} }
}.store(in: &cancellables) }.store(in: &cancellables)
} }

View File

@ -12,7 +12,7 @@ struct LoginView: View {
@EnvironmentObject var appVM: AppViewModel @EnvironmentObject var appVM: AppViewModel
@StateObject private var loginVM = LoginViewModel() @StateObject private var loginVM = LoginViewModel()
@State var cancellables: Set<AnyCancellable> = [] @State var cancellables: Set<AnyCancellable> = []
@Binding var naviState : NaviState // @Binding var naviState : NaviState
@State var selectIdLogin: Bool = false @State var selectIdLogin: Bool = false
@ -43,7 +43,7 @@ struct LoginView: View {
Button { Button {
// MARK: - TODO, // MARK: - TODO,
naviState.set(act: .MOVE, path: .Main) appVM.naviState.set(act: .MOVE, path: .Main)
} label: { } label: {
makeButton(image: Image(.Logo.appleIcon), color: Color(.Text.black), "애플 계정으로 시작하기") makeButton(image: Image(.Logo.appleIcon), color: Color(.Text.black), "애플 계정으로 시작하기")
} }

View File

@ -31,7 +31,7 @@ struct HomeView: View {
} }
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
VStack(spacing: 24) { LazyVStack(spacing: 24) {
TopProfileView(myType: myType) TopProfileView(myType: myType)

View File

@ -20,6 +20,8 @@ struct ChatListView: View {
} }
struct ChatCellView: View { struct ChatCellView: View {
@EnvironmentObject var appVM: AppViewModel
var summaryChat: SummaryChat var summaryChat: SummaryChat
var body: some View { var body: some View {
@ -41,7 +43,6 @@ struct ChatCellView: View {
.font(.nps(size: 8)) .font(.nps(size: 8))
.foregroundStyle(Color(.Text.detail)) .foregroundStyle(Color(.Text.detail))
} }
// .frame(height: 24)
} }
Spacer(minLength: 1) Spacer(minLength: 1)
@ -84,6 +85,7 @@ struct ChatCellView: View {
.onTapGesture { .onTapGesture {
printLog("채팅 내부 셀 클릭") printLog("채팅 내부 셀 클릭")
// MARK: TO-DO // MARK: TO-DO
appVM.naviState.set(act: .ADD, path: .ChatRoom(id: summaryChat.id))
} }
} }
} }

View File

@ -0,0 +1,77 @@
//
// ChattingRoomView.swift
// AcaMate
//
// Created by TAnine on 2/17/25.
//
import SwiftUI
struct ChattingRoomView: View {
@EnvironmentObject var appVM: AppViewModel
@StateObject private var topVM = TopViewModel()
// @StateObject private var chatVM = ChatViewModel()
@StateObject private var btnVM = ButtonViewModel()
@State var sendBtnID = UUID()
@State var addSomeBtnID = UUID()
@State var callCameraBtnID = UUID()
@State var addPhotoBtnID = UUID()
@State private var scrollOffset: CGPoint = .zero
@StateObject private var socketManager = WebSocketManager()
@State private var message = ""
let roomID: String
var body: some View {
VStack(alignment: .center, spacing: 0) {
TopView(topVM: topVM)
Text("Hello, World! \(roomID)")
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
}.frame(maxWidth: .infinity, maxHeight: .infinity)
TextField("SEND MESSAGE", text: $message)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
HStack(spacing: 10) {
Button {
socketManager.connect()
} label: {
Text("CONNECT")
}
Button {
socketManager.sendMessage(message)
message = ""
} label: {
Text("SEND")
}
Button {
socketManager.disconnect()
} label: {
Text("DISCONNECT")
}
}
List(socketManager.receivedMessage, id: \.self) { msg in
Text(msg)
.foregroundStyle(Color(.Other.cell))
}
.fullDrawView(Color(.Other.cell))
}
.onAppear {
topVM.titleName = "클래스 명"
topVM.setLeftBtn(Image(.Icon.back), size: CGPoint(x:40, y:40)) {
appVM.naviState.set(act: .POP, path: .ChatRoom(id: roomID))
}
topVM.setRightBtn(Image(.Icon.setting), size: CGPoint(x: 40, y: 40)) {
// MARK: TO-DO
//
}
}
}
}

View File

@ -34,14 +34,14 @@ struct ChattingView: View {
@State var chatMenu: chatType = .Class @State var chatMenu: chatType = .Class
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
TopView(topVM: topVM) TopView(topVM: topVM)
if myType == .ETC || myType == .Employee { if myType == .ETC || myType == .Employee {
EmptyBoxView(title: "이용하실 수 없는 기능입니다.") EmptyBoxView(title: "이용하실 수 없는 기능입니다.")
.padding(24) .padding(24)
Spacer(minLength: 1) Spacer(minLength: 1)
} else { }
else {
if myType == .Teacher || myType == .Admin { if myType == .Teacher || myType == .Admin {
if myType == .Admin { if myType == .Admin {
HStack { HStack {
@ -55,7 +55,6 @@ struct ChattingView: View {
} }
.padding(EdgeInsets(top: 12, leading: 24, bottom: 0, trailing: 24)) .padding(EdgeInsets(top: 12, leading: 24, bottom: 0, trailing: 24))
} }
HStack { HStack {
SelectChatMenu(chatMenu: $chatMenu, tag: .Class, image: Image(.Icon.group), title: "클래스") SelectChatMenu(chatMenu: $chatMenu, tag: .Class, image: Image(.Icon.group), title: "클래스")
Spacer(minLength: 1) Spacer(minLength: 1)
@ -67,19 +66,36 @@ struct ChattingView: View {
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24)) .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
} }
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
VStack(spacing: 24) { LazyVStack(spacing: 24) {
if myType == .Student || myType == .Parent {
Group { Group {
if myType != .Parent {
DashBoardView(image: Image(.Icon.group), title: "클래스") { DashBoardView(image: Image(.Icon.group), title: "클래스") {
ChatListView(chatList: classList) ChatListView(chatList: classList)
} }
}
if myType != .Teacher || myType != .Admin {
DashBoardView(image: Image(.Icon.talk), title: "선생님과 1:1") { DashBoardView(image: Image(.Icon.talk), title: "선생님과 1:1") {
} }
DashBoardView(image: Image(.Icon.talk), title: "부모님과 1:1") {
} }
if myType == .Teacher { }
.background {
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color(.Other.cell))
}
} else {
Group {
switch chatMenu {
case .Class:
DashBoardView(image: Image(.Icon.group), title: "클래스") {
ChatListView(chatList: classList)
}
case .Student:
DashBoardView(image: Image(.Icon.talk), title: "학생과 1:1") {
}
case .Parent:
DashBoardView(image: Image(.Icon.talk), title: "부모님과 1:1") { DashBoardView(image: Image(.Icon.talk), title: "부모님과 1:1") {
} }
@ -90,12 +106,13 @@ struct ChattingView: View {
.foregroundStyle(Color(.Other.cell)) .foregroundStyle(Color(.Other.cell))
} }
} }
}
.padding(EdgeInsets( .padding(EdgeInsets(
top: (myType == .Student || myType == .Parent) ? 24 : 12, leading: 24, bottom: 24, trailing: 24)) top: (myType == .Student || myType == .Parent) ? 24 : 12, leading: 24, bottom: 24, trailing: 24))
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
} }
} }
.onAppear { .onAppear {
// MARK: TO-DO // MARK: TO-DO

View File

@ -12,7 +12,7 @@ struct MainView: View {
@EnvironmentObject var appVM: AppViewModel @EnvironmentObject var appVM: AppViewModel
@EnvironmentObject var alertController: AlertController @EnvironmentObject var alertController: AlertController
@State var cancellables: Set<AnyCancellable> = [] @State var cancellables: Set<AnyCancellable> = []
@Binding var naviState : NaviState // @Binding var naviState : NaviState
@State private var myType: UserType = .Admin @State private var myType: UserType = .Admin

View File

@ -7,6 +7,12 @@
import Foundation import Foundation
enum chatType {
case Class
case Student
case Parent
}
struct SummaryChat { struct SummaryChat {
var id: String var id: String
@ -19,11 +25,12 @@ struct SummaryChat {
var notiState: Bool var notiState: Bool
var groupNum: Int var groupNum: Int
} }
enum chatType { struct ChatMesage {
case Class var roomID: String
case Student var chatID: String
case Parent var senderID: String
var message: String
var sendTime: String
} }

View File

@ -13,8 +13,6 @@ struct NaviState: Equatable {
var act: NaviAction var act: NaviAction
var path: PathName var path: PathName
static func == (lhs: NaviState, rhs: NaviState) -> Bool { static func == (lhs: NaviState, rhs: NaviState) -> Bool {
return lhs.act == rhs.act && lhs.path == rhs.path return lhs.act == rhs.act && lhs.path == rhs.path
} }
@ -45,7 +43,7 @@ enum PathName: Hashable {
case Intro case Intro
case Login case Login
case Main case Main
case ChatRoom(id: String)
case NONE case NONE
} }

View File

@ -12,6 +12,7 @@ class AppViewModel: ObservableObject {
@Published var isLoading: Bool = false @Published var isLoading: Bool = false
@Published var showAlert: Bool = false @Published var showAlert: Bool = false
@Published var menuName: MenuName = .Home @Published var menuName: MenuName = .Home
@Published var naviState: NaviState = .init(act: .NONE, path: .Intro)
var alertData: AlertData = .init(body: "") var alertData: AlertData = .init(body: "")

View File

@ -0,0 +1,14 @@
//
// ChatViewModel.swift
// AcaMate
//
// Created by TAnine on 2/17/25.
//
import SwiftUI
class ChatViewModel: ObservableObject {
@Published var messages: [ChatMesage] = []
}

View File

@ -4,6 +4,7 @@
// //
// Created by Sean Kim on 11/26/24. // Created by Sean Kim on 11/26/24.
// //
// ?
import Foundation import Foundation
import Combine import Combine

View File

@ -0,0 +1,79 @@
//
// WebSocketManager.swift
// AcaMate
//
// Created by TAnine on 2/17/25.
//
import Foundation
import Combine
import Starscream
// SignalR \u{1E}
class WebSocketManager: ObservableObject ,WebSocketDelegate {
@Published var socket: WebSocket?
@Published var receivedMessage: [String] = []
init() {
guard let url = URL(string: "\(WS_URL)/chatHub?transport=webSockets") else { return }
// guard let url = URL(string: "\(API_URL)/chatHub?transport=websockets3") else { return }
var request = URLRequest(url: url)
request.timeoutInterval = 5
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Upgrade", forHTTPHeaderField: "Connection")
socket = WebSocket(request: request)
socket?.delegate = self
}
func connect() {
socket?.connect()
printLog("TRY CONNECT SOCKET")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let handShakeMsg = """
{"protocol":"json","version":1}\u{1E}
"""
self.socket?.write(string: handShakeMsg)
}
}
func disconnect() {
socket?.disconnect()
printLog("ClOSE SOCKET")
}
func sendMessage(_ message: String) {
let json = """
{
"type": 1,
"target":"SendMessage",
"arguments":["iOS", "\(message)"]
}\u{1E}
"""
socket?.write(string: json)
printLog("SEND THE MESSAGE: \(message)")
}
func didReceive(event: Starscream.WebSocketEvent, client: any Starscream.WebSocketClient) {
switch event {
case .connected(let header):
printLog("CONNECTED : \(header)")
case .disconnected(let reason, let code):
printLog("DISCONNECTED: [\(code)] - \(reason)")
case .text(let text):
receivedMessage.append(text)
printLog("SERVER SAID: \(text)")
case .binary(let binary):
printLog("BINARY?: \(binary)")
case .error(let error):
printLog("ERROR!: \(error)")
case .cancelled:
printLog("SOCKET CONNECTED CANCELLED!!!")
default:
break
}
}
}

View File

Before

Width:  |  Height:  |  Size: 811 KiB

After

Width:  |  Height:  |  Size: 811 KiB

View File

Before

Width:  |  Height:  |  Size: 811 KiB

After

Width:  |  Height:  |  Size: 811 KiB

View File

Before

Width:  |  Height:  |  Size: 811 KiB

After

Width:  |  Height:  |  Size: 811 KiB

Some files were not shown because too many files have changed in this diff Show More