diff --git a/iOS/Ringo/Ringo.xcodeproj/project.pbxproj b/iOS/Ringo/Ringo.xcodeproj/project.pbxproj index 39b7997f..0f03d0dd 100644 --- a/iOS/Ringo/Ringo.xcodeproj/project.pbxproj +++ b/iOS/Ringo/Ringo.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 940C25872B7A89AB00E069D0 /* WebRTCClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25812B7A89AB00E069D0 /* WebRTCClient.swift */; }; 940C25882B7A89AB00E069D0 /* SignalingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25822B7A89AB00E069D0 /* SignalingClient.swift */; }; 940C258A2B7A8FA900E069D0 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940C25892B7A8FA900E069D0 /* CallService.swift */; }; + 9410C9402C87067A00B006A7 /* VideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9410C93F2C87067A00B006A7 /* VideoViewController.swift */; }; + 941C657E2C69D5BB002BA61E /* ConnectingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941C657D2C69D5BB002BA61E /* ConnectingView.swift */; }; 94470A8A2B71050700F0A942 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 94470A892B71050700F0A942 /* Alamofire */; }; 94470A8D2B710A3100F0A942 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 94470A8C2B710A3100F0A942 /* Starscream */; }; 94470A902B710CE500F0A942 /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = 94470A8F2B710CE500F0A942 /* WebRTC */; }; @@ -39,8 +41,16 @@ 945CF9842B67E1CE00396E4E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 945CF9832B67E1CE00396E4E /* Assets.xcassets */; }; 945CF9872B67E1CE00396E4E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 945CF9852B67E1CE00396E4E /* LaunchScreen.storyboard */; }; 945CF9902B69341800396E4E /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 945CF98F2B69341800396E4E /* SnapKit */; }; - 94FB0A6B2B858F66005A4915 /* TestSTTViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FB0A6A2B858F66005A4915 /* TestSTTViewController.swift */; }; + 94C3E5412C3398C900EBF588 /* AuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C3E5402C3398C900EBF588 /* AuthInterceptor.swift */; }; + 94C3E5432C351A6A00EBF588 /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C3E5422C351A6A00EBF588 /* UserManager.swift */; }; + 94C3E5452C4DF99200EBF588 /* SignupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C3E5442C4DF99200EBF588 /* SignupViewController.swift */; }; + 94C3E5472C4E26AE00EBF588 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C3E5462C4E26AE00EBF588 /* Language.swift */; }; + 94FB0A6B2B858F66005A4915 /* STTViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FB0A6A2B858F66005A4915 /* STTViewController.swift */; }; 94FB0A6D2BAAD64A005A4915 /* TTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FB0A6C2BAAD64A005A4915 /* TTS.swift */; }; + 94FFD49B2C57BB2800C60F05 /* FriendRequestListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFD49A2C57BB2800C60F05 /* FriendRequestListViewController.swift */; }; + 94FFD49D2C57BB7C00C60F05 /* FriendRequestListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFD49C2C57BB7C00C60F05 /* FriendRequestListTableViewCell.swift */; }; + 94FFD49F2C58E59B00C60F05 /* FriendRequestList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFD49E2C58E59B00C60F05 /* FriendRequestList.swift */; }; + 94FFD4A12C60968100C60F05 /* EditProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFD4A02C60968100C60F05 /* EditProfileViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -57,6 +67,8 @@ 940C25812B7A89AB00E069D0 /* WebRTCClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebRTCClient.swift; sourceTree = ""; }; 940C25822B7A89AB00E069D0 /* SignalingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalingClient.swift; sourceTree = ""; }; 940C25892B7A8FA900E069D0 /* CallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallService.swift; sourceTree = ""; }; + 9410C93F2C87067A00B006A7 /* VideoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoViewController.swift; sourceTree = ""; }; + 941C657D2C69D5BB002BA61E /* ConnectingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectingView.swift; sourceTree = ""; }; 94470A922B7163E900F0A942 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 94470A942B71680100F0A942 /* SigninService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigninService.swift; sourceTree = ""; }; 94470A962B717DC300F0A942 /* ConnectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionViewController.swift; sourceTree = ""; }; @@ -74,8 +86,16 @@ 945CF9832B67E1CE00396E4E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 945CF9862B67E1CE00396E4E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 945CF9882B67E1CE00396E4E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 94FB0A6A2B858F66005A4915 /* TestSTTViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSTTViewController.swift; sourceTree = ""; }; + 94C3E5402C3398C900EBF588 /* AuthInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthInterceptor.swift; sourceTree = ""; }; + 94C3E5422C351A6A00EBF588 /* UserManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManager.swift; sourceTree = ""; }; + 94C3E5442C4DF99200EBF588 /* SignupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupViewController.swift; sourceTree = ""; }; + 94C3E5462C4E26AE00EBF588 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; + 94FB0A6A2B858F66005A4915 /* STTViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STTViewController.swift; sourceTree = ""; }; 94FB0A6C2BAAD64A005A4915 /* TTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTS.swift; sourceTree = ""; }; + 94FFD49A2C57BB2800C60F05 /* FriendRequestListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestListViewController.swift; sourceTree = ""; }; + 94FFD49C2C57BB7C00C60F05 /* FriendRequestListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestListTableViewCell.swift; sourceTree = ""; }; + 94FFD49E2C58E59B00C60F05 /* FriendRequestList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestList.swift; sourceTree = ""; }; + 94FFD4A02C60968100C60F05 /* EditProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -138,12 +158,21 @@ 94470A912B71157C00F0A942 /* Screens */ = { isa = PBXGroup; children = ( + 940C25632B73D20900E069D0 /* ConnectionCollectionViewCell.swift */, + 94470A962B717DC300F0A942 /* ConnectionViewController.swift */, + 9410C93F2C87067A00B006A7 /* VideoViewController.swift */, + 941C657D2C69D5BB002BA61E /* ConnectingView.swift */, 945CF97E2B67E1CD00396E4E /* ViewController.swift */, + 94C3E5442C4DF99200EBF588 /* SignupViewController.swift */, 945603122B6AC07D002F4B33 /* TabBarViewController.swift */, + 945603222B6D31D9002F4B33 /* ContactsTableViewCell.swift */, 945603142B6AC22A002F4B33 /* ContactsViewController.swift */, + 94FFD49A2C57BB2800C60F05 /* FriendRequestListViewController.swift */, + 94FFD49C2C57BB7C00C60F05 /* FriendRequestListTableViewCell.swift */, 9456031C2B6BF44A002F4B33 /* FriendRequestViewController.swift */, 945603162B6AC2D3002F4B33 /* RecentsViewController.swift */, 945603182B6AC365002F4B33 /* AccountViewController.swift */, + 94FFD4A02C60968100C60F05 /* EditProfileViewController.swift */, ); path = Screens; sourceTree = ""; @@ -167,17 +196,18 @@ 945CF9792B67E1CD00396E4E /* Ringo */ = { isa = PBXGroup; children = ( + 94C3E53F2C3397FF00EBF588 /* JWT */, 940C256A2B79EC5700E069D0 /* WebRTC */, 94470A912B71157C00F0A942 /* Screens */, 945CF97A2B67E1CD00396E4E /* AppDelegate.swift */, 945CF97C2B67E1CD00396E4E /* SceneDelegate.swift */, - 940C25632B73D20900E069D0 /* ConnectionCollectionViewCell.swift */, - 945603222B6D31D9002F4B33 /* ContactsTableViewCell.swift */, 94470A922B7163E900F0A942 /* Model.swift */, + 94FFD49E2C58E59B00C60F05 /* FriendRequestList.swift */, + 94C3E5422C351A6A00EBF588 /* UserManager.swift */, + 94C3E5462C4E26AE00EBF588 /* Language.swift */, 94470A942B71680100F0A942 /* SigninService.swift */, 940C25652B74EF4D00E069D0 /* FriendService.swift */, - 94470A962B717DC300F0A942 /* ConnectionViewController.swift */, - 94FB0A6A2B858F66005A4915 /* TestSTTViewController.swift */, + 94FB0A6A2B858F66005A4915 /* STTViewController.swift */, 94FB0A6C2BAAD64A005A4915 /* TTS.swift */, 945603202B6D2F06002F4B33 /* Canvas.swift */, 945CF9832B67E1CE00396E4E /* Assets.xcassets */, @@ -187,6 +217,14 @@ path = Ringo; sourceTree = ""; }; + 94C3E53F2C3397FF00EBF588 /* JWT */ = { + isa = PBXGroup; + children = ( + 94C3E5402C3398C900EBF588 /* AuthInterceptor.swift */, + ); + path = JWT; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -272,20 +310,29 @@ 945603212B6D2F06002F4B33 /* Canvas.swift in Sources */, 94470A972B717DC300F0A942 /* ConnectionViewController.swift in Sources */, 945603132B6AC07D002F4B33 /* TabBarViewController.swift in Sources */, + 94FFD49D2C57BB7C00C60F05 /* FriendRequestListTableViewCell.swift in Sources */, 940C25782B7A899500E069D0 /* RTCStates.swift in Sources */, 940C25852B7A89AB00E069D0 /* NativeWebSocket.swift in Sources */, 945CF97F2B67E1CD00396E4E /* ViewController.swift in Sources */, 940C25642B73D20900E069D0 /* ConnectionCollectionViewCell.swift in Sources */, + 94FFD4A12C60968100C60F05 /* EditProfileViewController.swift in Sources */, 94FB0A6D2BAAD64A005A4915 /* TTS.swift in Sources */, 940C257A2B7A899500E069D0 /* IceCandidate.swift in Sources */, + 941C657E2C69D5BB002BA61E /* ConnectingView.swift in Sources */, + 94FFD49F2C58E59B00C60F05 /* FriendRequestList.swift in Sources */, + 94C3E5412C3398C900EBF588 /* AuthInterceptor.swift in Sources */, 945603192B6AC365002F4B33 /* AccountViewController.swift in Sources */, + 94C3E5452C4DF99200EBF588 /* SignupViewController.swift in Sources */, 940C258A2B7A8FA900E069D0 /* CallService.swift in Sources */, + 94C3E5472C4E26AE00EBF588 /* Language.swift in Sources */, 945CF97B2B67E1CD00396E4E /* AppDelegate.swift in Sources */, 945603172B6AC2D3002F4B33 /* RecentsViewController.swift in Sources */, 940C25872B7A89AB00E069D0 /* WebRTCClient.swift in Sources */, - 94FB0A6B2B858F66005A4915 /* TestSTTViewController.swift in Sources */, + 94FB0A6B2B858F66005A4915 /* STTViewController.swift in Sources */, 945603232B6D31D9002F4B33 /* ContactsTableViewCell.swift in Sources */, 945CF97D2B67E1CD00396E4E /* SceneDelegate.swift in Sources */, + 9410C9402C87067A00B006A7 /* VideoViewController.swift in Sources */, + 94C3E5432C351A6A00EBF588 /* UserManager.swift in Sources */, 94470A932B7163E900F0A942 /* Model.swift in Sources */, 940C25662B74EF4D00E069D0 /* FriendService.swift in Sources */, 940C25792B7A899500E069D0 /* SessionDescription.swift in Sources */, @@ -293,6 +340,7 @@ 940C25722B7A88FB00E069D0 /* Config.swift in Sources */, 940C25842B7A89AB00E069D0 /* WebSocketProvider.swift in Sources */, 940C25882B7A89AB00E069D0 /* SignalingClient.swift in Sources */, + 94FFD49B2C57BB2800C60F05 /* FriendRequestListViewController.swift in Sources */, 940C25832B7A89AB00E069D0 /* Message.swift in Sources */, 940C25862B7A89AB00E069D0 /* StarscreamProvider.swift in Sources */, 9456031D2B6BF44A002F4B33 /* FriendRequestViewController.swift in Sources */, diff --git a/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate index e16ba19b..486e0bca 100644 Binary files a/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate and b/iOS/Ringo/Ringo.xcodeproj/project.xcworkspace/xcuserdata/jinhyuk.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iOS/Ringo/Ringo.xcodeproj/xcuserdata/jinhyuk.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/iOS/Ringo/Ringo.xcodeproj/xcuserdata/jinhyuk.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 42a90323..e399f7e7 100644 --- a/iOS/Ringo/Ringo.xcodeproj/xcuserdata/jinhyuk.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/iOS/Ringo/Ringo.xcodeproj/xcuserdata/jinhyuk.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -30,8 +30,8 @@ filePath = "Ringo/Screens/ContactsViewController.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "59" - endingLineNumber = "59" + startingLineNumber = "83" + endingLineNumber = "83" landmarkName = "unknown" landmarkType = "0"> diff --git a/iOS/Ringo/Ringo/FriendRequestList.swift b/iOS/Ringo/Ringo/FriendRequestList.swift new file mode 100644 index 00000000..67dd8d2e --- /dev/null +++ b/iOS/Ringo/Ringo/FriendRequestList.swift @@ -0,0 +1,47 @@ +// +// FriendRequestList.swift +// Ringo +// +// Created by 강진혁 on 7/30/24. +// + +import Foundation + +class FriendRequestList { + static let shared = FriendRequestList() + private var list: [FriendInfo] = [] + func getList() -> [FriendInfo] { + return list + } + func remove(at index: Int) { + list.remove(at: index) + } + func reload() { + guard let email = UserManager.getData(type: String.self, forKey: .email) else { return } + FriendService.shared.loadRequestList(email: email) { response in + switch response { + case .success(let data): + guard let data = data as? [FriendInfo] else { return } + if !data.isEmpty { + self.list = data + } else { + self.list = data + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } + func reset(){ + list = [] + } +} diff --git a/iOS/Ringo/Ringo/FriendService.swift b/iOS/Ringo/Ringo/FriendService.swift index 13fd3ec6..c269c0ca 100644 --- a/iOS/Ringo/Ringo/FriendService.swift +++ b/iOS/Ringo/Ringo/FriendService.swift @@ -14,22 +14,165 @@ class FriendService { private init() {} - func loadFriendsList(userId: Int64, completion: @escaping(NetworkResult) -> Void) + func loadFriendsList(email: String, completion: @escaping(NetworkResult) -> Void) { -// let url = "http://192.168.0.7:7080/friendship/findByUserIdAndStatusOrFriendIdAndStatus" let url = "https://4kringo.shop:8080/friendship/findByUserIdAndStatusOrFriendIdAndStatus" + let header : HTTPHeaders = [ + "Content-Type" : "application/json" + ] + let body : Parameters = [ + "email" : email + ] + + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header, + interceptor: AuthInterceptor()) + + dataRequest.responseData{ + response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + let networkResult = self.judgeStatusLFL(by: statusCode, value) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + func loadRequestList(email: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/friendship/findByFriendIdAndStatus" + + let header : HTTPHeaders = [ + "Content-Type" : "application/json" + ] + let body : Parameters = [ + "email" : email + ] + + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header, + interceptor: AuthInterceptor()) + + dataRequest.responseData{ + response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + let networkResult = self.judgeStatusLFL(by: statusCode, value) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + private func judgeStatusLFL(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case ..<300 : return isVaildDataLFL(data: data) + case 400..<500 : return .pathErr + case 500..<600 : return .serverErr + default : return .networkFail + } + } + private func isVaildDataLFL(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode([FriendInfo].self, from: data) + else { return .pathErr } + return .success(decodedData as Any) + } + + func sendFriendRequest(sender: String, receiver: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/friendship/sendFriendRequest" + let header : HTTPHeaders = ["Content-Type" : "application/json"] + let body : Parameters = [ + "senderEmail" : sender, + "receiverEmail" : receiver + ] + + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header, + interceptor: AuthInterceptor()) + + dataRequest.responseData{ + response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + func acceptRequest(sender: String, receiver: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/friendship/acceptFriendRequestById" + let header : HTTPHeaders = ["Content-Type" : "application/json"] let body : Parameters = [ - "userId" : userId + "senderEmail" : sender, + "receiverEmail" : receiver ] let dataRequest = AF.request(url, method: .post, parameters: body, encoding: JSONEncoding.default, - headers: header) + headers: header, + interceptor: AuthInterceptor()) + + dataRequest.responseData{ + response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + func rejectRequest(sender: String, receiver: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/friendship/rejectFriendRequestById" + + let header : HTTPHeaders = ["Content-Type" : "application/json"] + let body : Parameters = [ + "senderEmail" : sender, + "receiverEmail" : receiver + ] + + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header, + interceptor: AuthInterceptor()) dataRequest.responseData{ response in @@ -47,20 +190,20 @@ class FriendService { } } private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + print(statusCode) switch statusCode { case ..<300 : return isVaildData(data: data) case 400..<500 : return .pathErr - case 500..<600 : return .serverErr + case 500..<600 : + print(statusCode) + print(data) + return .serverErr default : return .networkFail } } - //통신이 성공하고 원하는 데이터가 올바르게 들어왔을때 처리하는 함수 private func isVaildData(data: Data) -> NetworkResult { - let decoder = JSONDecoder() //서버에서 준 데이터를 Codable을 채택, response가 json일 경우 - guard let decodedData = try? decoder.decode([String].self, from: data) - //데이터가 변환이 되게끔 Response 모델 구조체로 데이터를 변환해서 넣고, 그 데이터를 NetworkResult Success 파라미터로 전달 + guard let decodedData = String(data: data, encoding: .utf8) else { return .pathErr } - return .success(decodedData as Any) } } diff --git a/iOS/Ringo/Ringo/Info.plist b/iOS/Ringo/Ringo/Info.plist index 23be9eee..6c7292e0 100644 --- a/iOS/Ringo/Ringo/Info.plist +++ b/iOS/Ringo/Ringo/Info.plist @@ -23,5 +23,7 @@ NSMicrophoneUsageDescription Need Mic Access + NSCameraUsageDescription + Need Camera Access diff --git a/iOS/Ringo/Ringo/JWT/AuthInterceptor.swift b/iOS/Ringo/Ringo/JWT/AuthInterceptor.swift new file mode 100644 index 00000000..2640e5c7 --- /dev/null +++ b/iOS/Ringo/Ringo/JWT/AuthInterceptor.swift @@ -0,0 +1,63 @@ +// +// AuthInterceptor.swift +// Ringo +// +// Created by 강진혁 on 7/2/24. +// + +import Foundation +import Alamofire +import UIKit + +final class AuthInterceptor : RequestInterceptor { + + private let retryLimit = 2 + + func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { + guard let accessToken = UserManager.getData(type: String.self, forKey: .accessToken) else { + print("accessToken error") + return + } + var urlRequest = urlRequest + urlRequest.setValue(accessToken, forHTTPHeaderField: "AccessToken") + completion(.success(urlRequest)) + } + func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) { + guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else { + return completion(.doNotRetryWithError(error)) + } + guard request.retryCount < retryLimit else { return completion(.doNotRetryWithError(error)) } + Task { + guard let refreshToken = UserManager.getData(type: String.self, forKey: .refreshToken) else { + print("refreshToken error") + return completion(.doNotRetryWithError(error)) + } + //let api = API.refreshToken(refreshToken) + //let token = try await apiManager.request(api, type: TokenReissueResponse.self) refreshToken 갱신 api 호출 후 결과 받아오기 +// var newAccessToken:String = "" + SigninSercive.shared.refresh(token: refreshToken) { response in + switch response { + case .success(let data): + print(data) + case .requestErr(let err): + print(err) + return completion(.doNotRetryWithError(error)) + case .pathErr: + print("pathErr") + return completion(.doNotRetryWithError(error)) + case .serverErr: + print("serverErr") + return completion(.doNotRetryWithError(error)) + case .networkFail: + print("networkFail") + return completion(.doNotRetryWithError(error)) + case .dataErr: + print("dataErr") + return completion(.doNotRetryWithError(error)) + } + } +// UserManager.setData(value: newAccessToken, key: .accessToken) // 토큰 갱신 코드 + return completion(.retry) + } + } +} diff --git a/iOS/Ringo/Ringo/Language.swift b/iOS/Ringo/Ringo/Language.swift new file mode 100644 index 00000000..ee79b4c3 --- /dev/null +++ b/iOS/Ringo/Ringo/Language.swift @@ -0,0 +1,23 @@ +// +// Language.swift +// Ringo +// +// Created by 강진혁 on 7/22/24. +// + +import Foundation + +class Language { + static let shared = Language() + private let list = ["ko":"Korean","en":"English","zh":"Chinese","fr":"French","de":"German","es":"Spanish"] + func getCount() -> Int { + return list.count + } + func getList() -> Array { + let languages = list.keys.sorted() + return languages + } + func getLanguageByCode(key:String) -> String { + return list[key] ?? "" + } +} diff --git a/iOS/Ringo/Ringo/Model.swift b/iOS/Ringo/Ringo/Model.swift index f9abc75a..41f59354 100644 --- a/iOS/Ringo/Ringo/Model.swift +++ b/iOS/Ringo/Ringo/Model.swift @@ -8,16 +8,21 @@ import Foundation struct SigninData: Codable { - let id: Int - let eamil: String - let jwtToken: String + let language: String + let accessToken: String let refreshToken: String } struct SigninDataResponse: Codable { + let status: String let data: SigninData? - let message: String - let result: String + let message: String? +} + +struct FriendInfo: Codable { + let name: String + let language: String + let email: String } enum NetworkResult { @@ -26,4 +31,5 @@ enum NetworkResult { case pathErr case serverErr case networkFail + case dataErr } diff --git a/iOS/Ringo/Ringo/TestSTTViewController.swift b/iOS/Ringo/Ringo/STTViewController.swift similarity index 77% rename from iOS/Ringo/Ringo/TestSTTViewController.swift rename to iOS/Ringo/Ringo/STTViewController.swift index 5f158b2d..300920a6 100644 --- a/iOS/Ringo/Ringo/TestSTTViewController.swift +++ b/iOS/Ringo/Ringo/STTViewController.swift @@ -1,5 +1,5 @@ // -// TestSTTViewController.swift +// STTViewController.swift // Ringo // // Created by 강진혁 on 2/21/24. @@ -9,9 +9,11 @@ import UIKit import SnapKit import Speech -class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { +class STTViewController: UIViewController, SFSpeechRecognizerDelegate { - private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ko-KR"))! + static let shared = STTViewController() + + private var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en"))! private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? @@ -20,6 +22,7 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { private let audioEngine = AVAudioEngine() var textView = UITextView() + var textView2 = UITextView() var recordButton = UIButton() @@ -33,6 +36,7 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { func setUpView() { view.addSubview(textView) + view.addSubview(textView2) view.addSubview(recordButton) } @@ -40,7 +44,16 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { view.backgroundColor = .systemBackground - textView.font = .systemFont(ofSize: 30) + textView.font = .systemFont(ofSize: 20) + textView.layer.cornerRadius = 25 + textView.backgroundColor = .secondarySystemFill + textView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + + textView2.textColor = .white + textView2.font = .systemFont(ofSize: 20) + textView2.layer.cornerRadius = 25 + textView2.backgroundColor = .systemBlue + textView2.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) recordButton.configuration = .plain() recordButton.isEnabled = false @@ -51,10 +64,16 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { func setConstraints() { textView.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide) - make.leading.equalTo(view.safeAreaLayoutGuide) - make.trailing.equalTo(view.safeAreaLayoutGuide) - make.bottom.equalTo(recordButton.snp.top) + make.top.equalTo(view.safeAreaLayoutGuide).offset(20) + make.leading.equalTo(view.safeAreaLayoutGuide).offset(20) + make.trailing.equalTo(view.safeAreaLayoutGuide).inset(120) + make.bottom.equalTo(view.snp.centerY).offset(-35) + } + textView2.snp.makeConstraints { make in + make.top.equalTo(textView.snp.bottom).offset(20) + make.leading.equalTo(view.safeAreaLayoutGuide).offset(120) + make.trailing.equalTo(view.safeAreaLayoutGuide).inset(20) + make.bottom.equalTo(recordButton.snp.top).offset(-20) } recordButton.snp.makeConstraints { make in make.bottom.equalTo(view.safeAreaLayoutGuide).inset(30) @@ -127,11 +146,11 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { if let result = result { // Update the text view with the results. - self.textView.text = result.bestTranscription.formattedString + self.textView2.text = result.bestTranscription.formattedString isFinal = result.isFinal debugPrint(result.bestTranscription.formattedString) if result.speechRecognitionMetadata != nil { - var message = Message(type: .stt_message, name: "rkdwlsgur@naver.com", target: "rkdwltjr@naver.com") + var message = Message(type: .stt_message, name: UserManager.getData(type: String.self, forKey: .email)!, target: UserManager.getData(type: String.self, forKey: .receiver)!) message.data = .response(result.bestTranscription.formattedString) CallService.shared.signalClient.send(message: message) debugPrint("send trans msg") @@ -161,7 +180,8 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { try audioEngine.start() // Let the user know to start talking. - textView.text = "(말해보세요)" + textView.text = "..." + textView2.text = "..." } // MARK: SFSpeechRecognizerDelegate @@ -169,7 +189,7 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { public func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) { if available { recordButton.isEnabled = true - recordButton.setTitle("말하기", for: []) + recordButton.setTitle("Translate start", for: []) } else { recordButton.isEnabled = false recordButton.setTitle("Recognition Not Available", for: .disabled) @@ -187,19 +207,23 @@ class TestSTTViewController: UIViewController, SFSpeechRecognizerDelegate { } else { do { try startRecording() - recordButton.setTitle("그만 말하기", for: []) + recordButton.setTitle("Translate stop", for: []) } catch { recordButton.setTitle("Recording Not Available", for: []) } } } + func reinit(lang: String){ + speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: lang))! + } + } // MARK: - canvas 이용하기 import SwiftUI @available(iOS 13.0.0, *) struct TestSTTViewPreview: PreviewProvider { static var previews: some View { - TestSTTViewController().toPreview() + STTViewController().toPreview() } } diff --git a/iOS/Ringo/Ringo/SceneDelegate.swift b/iOS/Ringo/Ringo/SceneDelegate.swift index b081d288..3006d9fd 100644 --- a/iOS/Ringo/Ringo/SceneDelegate.swift +++ b/iOS/Ringo/Ringo/SceneDelegate.swift @@ -11,6 +11,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + static var shared: SceneDelegate? { return UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate } func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { diff --git a/iOS/Ringo/Ringo/Screens/AccountViewController.swift b/iOS/Ringo/Ringo/Screens/AccountViewController.swift index 06d85d71..3ded4b69 100644 --- a/iOS/Ringo/Ringo/Screens/AccountViewController.swift +++ b/iOS/Ringo/Ringo/Screens/AccountViewController.swift @@ -9,22 +9,200 @@ import UIKit class AccountViewController: UIViewController { + let mainText = UILabel() + let userInfo = UIButton() + let signoutBtn = UIButton() + let withdrawalBtn = UIButton() + override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .blue + setUpView() + setUpValue() + setConstraints() // Do any additional setup after loading the view. } - + func setUpView() { + view.addSubview(mainText) + view.addSubview(userInfo) + view.addSubview(signoutBtn) + view.addSubview(withdrawalBtn) + } + func setUpValue() { + view.backgroundColor = .secondarySystemBackground + + mainText.text = "Account" +// mainText.font = .preferredFont(forTextStyle: .largeTitle) + mainText.font = UIFont.systemFont(ofSize: 35, weight: .bold) + + userInfo.configuration = UIButton.Configuration.filled() + userInfo.configurationUpdateHandler = { btn in + + var titleContainer = AttributeContainer() + titleContainer.font = UIFont.boldSystemFont(ofSize: 20) - /* - // MARK: - Navigation + var subtitleContainer = AttributeContainer() + subtitleContainer.foregroundColor = UIColor.secondaryLabel - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + btn.configuration?.attributedTitle = AttributedString( UserManager.getData(type: String.self, forKey: .name) ?? "UserName", attributes: titleContainer) + btn.configuration?.attributedSubtitle = AttributedString( UserManager.getData(type: String.self, forKey: .email) ?? "email@apple.com", attributes: subtitleContainer) + + btn.configuration?.image = UIImage(systemName: "person.crop.square") + btn.configuration?.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 40) + btn.configuration?.imagePadding = 20 + btn.contentHorizontalAlignment = .leading + btn.configuration?.contentInsets = .init(top: 30, leading: 20, bottom: 20, trailing: 20) + btn.configuration?.titlePadding = 10 + + btn.tintColor = .tertiarySystemBackground + + btn.addTarget(self, action: #selector(self.onPressUserInfo(_:)), for: .touchUpInside) + } + + signoutBtn.configuration = UIButton.Configuration.filled() + signoutBtn.configurationUpdateHandler = { btn in + btn.configuration?.buttonSize = .large + btn.configuration?.baseBackgroundColor = .systemRed + btn.configuration?.title = "Sign out" + btn.addTarget(self, action: #selector(self.onPressSignout(_:)), for: .touchUpInside) + } + + withdrawalBtn.configuration = UIButton.Configuration.filled() + withdrawalBtn.configurationUpdateHandler = { btn in + btn.configuration?.buttonSize = .large + btn.configuration?.baseBackgroundColor = .systemGray4 + btn.configuration?.title = "Withdrawal" + btn.addTarget(self, action: #selector(self.onPressWithdrawal(_:)), for: .touchUpInside) + } + } + func setConstraints() { + mainText.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(45) + make.leading.equalTo(view.safeAreaLayoutGuide).offset(20) + make.trailing.equalTo(view.safeAreaLayoutGuide).offset(-20) + } + userInfo.snp.makeConstraints { make in + make.top.equalTo(mainText.snp.bottom).offset(10) + make.leading.equalTo(mainText.snp.leading) + make.trailing.equalTo(mainText.snp.trailing) + } + signoutBtn.snp.makeConstraints { make in + make.bottom.equalTo(withdrawalBtn.snp.top).offset(-30) + make.leading.equalTo(mainText.snp.leading) + make.trailing.equalTo(mainText.snp.trailing) + } + withdrawalBtn.snp.makeConstraints { make in + make.bottom.equalTo(view.safeAreaLayoutGuide).inset(30) + make.leading.equalTo(mainText.snp.leading) + make.trailing.equalTo(mainText.snp.trailing) + } + } + @objc func onPressUserInfo(_ sender: UIButton) { + present(UINavigationController(rootViewController: EditProfileViewController()), animated: true) + } + @objc func onPressSignout(_ sender: UIButton) { + signout() + } + @objc func onPressWithdrawal(_ sender: UIButton) { + let alert = UIAlertController(title: "탈퇴하시겠습니까?", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil)) + alert.addAction(UIAlertAction(title: "확인", style: .default){ action in + self.withdrawal() + }) + self.present(alert, animated: true, completion: nil) + } +} +// MARK: - Sign out +extension AccountViewController { + + func signout() { + + guard let refreshtoken = UserManager.getData(type: String.self, forKey: .refreshToken) else { return } + + SigninSercive.shared.signout(refreshToken: refreshtoken) { response in + switch response { + case .success(let data): + + guard let data = data as? String else { return } + if data == "로그아웃 성공"{ + self.dismiss(animated: true) + CallService.shared.signalClient.forceDisconnect() + UserManager.resetData() + FriendRequestList.shared.reset() + } else { + let alert = UIAlertController(title: data, message: data, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } +} +// MARK: - Apply +extension AccountViewController { + + func withdrawal() { + + guard let email = UserManager.getData(type: String.self, forKey: .email) else { return } + guard let password = UserManager.getData(type: String.self, forKey: .password) else { return } + + SigninSercive.shared.delete(email: email, password: password) { response in + switch response { + case .success(let data): + + guard let data = data as? SigninDataResponse else { + print(data) + return + } + if data.status == "success"{ + + let alert = UIAlertController(title: "탈퇴하였습니다.", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel){ action in + self.dismiss(animated: true) + }) + self.present(alert, animated: true, completion: nil) + CallService.shared.signalClient.forceDisconnect() + UserManager.resetData() + FriendRequestList.shared.reset() + + } else { + let alert = UIAlertController(title: data.status, message: data.message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } +} +// MARK: - canvas 이용하기 +import SwiftUI +@available(iOS 13.0.0, *) +struct AccountViewPreview: PreviewProvider { + static var previews: some View { + AccountViewController().toPreview() } - */ - } diff --git a/iOS/Ringo/Ringo/Screens/ConnectingView.swift b/iOS/Ringo/Ringo/Screens/ConnectingView.swift new file mode 100644 index 00000000..3caf3130 --- /dev/null +++ b/iOS/Ringo/Ringo/Screens/ConnectingView.swift @@ -0,0 +1,109 @@ +// +// ConnectingView.swift +// Ringo +// +// Created by 강진혁 on 8/12/24. +// + +import UIKit +import SnapKit + +final class ConnectingView: UIView { + + static let shared = ConnectingView() + let gradientLayer = CAGradientLayer() + let name = UILabel() + private let loadingView = UIActivityIndicatorView(style: .large) + let hangUpBtn = UIButton() + + var isLoading = true { + didSet { + self.isHidden = !self.isLoading + self.isLoading ? self.loadingView.startAnimating() : self.loadingView.stopAnimating() + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.backgroundColor = .clear + self.gradientLayer.frame = self.bounds + self.gradientLayer.type = .radial + self.gradientLayer.startPoint = CGPoint(x: -0.3, y: -0.15) + self.gradientLayer.endPoint = CGPoint(x: 2.0, y: 1.0) + self.gradientLayer.locations = [0,0.1,0.8,1] + self.gradientLayer.colors = [ + CGColor(red: 111/255.0, green: 122/255.0, blue: 130/255.0, alpha: 1), + CGColor(red: 93/255.0, green: 114/255.0, blue: 126/255.0, alpha: 1), + CGColor(red: 17/255.0, green: 43/255.0, blue: 74/255.0, alpha: 1), + CGColor(red: 13/255.0, green: 36/255.0, blue: 69/255.0, alpha: 1) + ] + + name.text = "Name" + name.font = .systemFont(ofSize: 40) + name.textColor = .white + + loadingView.color = .white + + hangUpBtn.configuration = .filled() + hangUpBtn.configurationUpdateHandler = { btn in + btn.configuration?.image = UIImage(systemName: "phone.down.circle")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.white,.clear])) + btn.configuration?.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 50) + btn.configuration?.buttonSize = .mini + btn.configuration?.cornerStyle = .capsule + btn.configuration?.baseBackgroundColor = .systemRed + btn.addTarget(self, action: #selector(self.onPressHangUpBtn(_:)), for: .touchUpInside) + } + + self.layer.addSublayer(gradientLayer) + self.addSubview(name) + self.addSubview(loadingView) + self.addSubview(hangUpBtn) + + name.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(80) + } + loadingView.snp.makeConstraints { make in + make.center.equalToSuperview() + } + hangUpBtn.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(80) + } + } + + @objc func onPressHangUpBtn(_ sender: UIButton) { + CallService.shared.webRTCClient.endCall() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + // 그라데이션 레이어가 뷰 크기에 맞게 조정되도록 프레임 업데이트 + gradientLayer.frame = self.bounds + } + func show() { + guard let window = SceneDelegate.shared?.window else { + print("UIWindow를 찾을 수 없습니다.") + return + } + window.addSubview(self) + self.snp.makeConstraints { + $0.edges.equalToSuperview() + } + isLoading = true + self.layoutIfNeeded() + } + func hide(completion: @escaping () -> () = {}) { + isLoading = false + self.removeFromSuperview() + completion() + } + func setName(name:String){ + self.name.text = name + } +} diff --git a/iOS/Ringo/Ringo/ConnectionCollectionViewCell.swift b/iOS/Ringo/Ringo/Screens/ConnectionCollectionViewCell.swift similarity index 100% rename from iOS/Ringo/Ringo/ConnectionCollectionViewCell.swift rename to iOS/Ringo/Ringo/Screens/ConnectionCollectionViewCell.swift diff --git a/iOS/Ringo/Ringo/ConnectionViewController.swift b/iOS/Ringo/Ringo/Screens/ConnectionViewController.swift similarity index 69% rename from iOS/Ringo/Ringo/ConnectionViewController.swift rename to iOS/Ringo/Ringo/Screens/ConnectionViewController.swift index 988e35f2..cccfc487 100644 --- a/iOS/Ringo/Ringo/ConnectionViewController.swift +++ b/iOS/Ringo/Ringo/Screens/ConnectionViewController.swift @@ -21,7 +21,9 @@ class ConnectionViewController: UIViewController { let translateBtn = UIButton() let hangUpBtn = UIButton() - let randomNames = ["example"] + var Names = ["example"] + + private lazy var videoVC = VideoViewController(webRTCClient: CallService.shared.webRTCClient) override func viewDidLoad() { super.viewDidLoad() @@ -74,23 +76,41 @@ class ConnectionViewController: UIViewController { let imageSize = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 25)) - muteBtn.backgroundColor = .white.withAlphaComponent(0.16) - muteBtn.tintColor = .white muteBtn.configuration = .plain() muteBtn.setImage(UIImage(systemName: "mic.slash.fill",withConfiguration: imageSize), for: .normal) muteBtn.clipsToBounds = true muteBtn.invalidateIntrinsicContentSize() muteBtn.configuration?.contentInsets = NSDirectionalEdgeInsets(top: 25, leading: 25, bottom: 25, trailing: 25) muteBtn.layer.cornerRadius = 40 + muteBtn.configurationUpdateHandler = { btn in + switch btn.state { + case .selected: + btn.tintColor = .black + btn.backgroundColor = .white + default: + btn.tintColor = .white + btn.backgroundColor = .white.withAlphaComponent(0.16) + } + } + muteBtn.addTarget(self, action: #selector(pressedMuteBtn), for: .touchUpInside) - speakerBtn.backgroundColor = .white.withAlphaComponent(0.16) - speakerBtn.tintColor = .white speakerBtn.configuration = .plain() speakerBtn.setImage(UIImage(systemName: "speaker.wave.3.fill",withConfiguration: imageSize), for: .normal) speakerBtn.clipsToBounds = true speakerBtn.invalidateIntrinsicContentSize() speakerBtn.configuration?.contentInsets = NSDirectionalEdgeInsets(top: 25, leading: 15, bottom: 25, trailing: 15) speakerBtn.layer.cornerRadius = 40 + speakerBtn.configurationUpdateHandler = { btn in + switch btn.state { + case .selected: + btn.tintColor = .black + btn.backgroundColor = .white + default: + btn.tintColor = .white + btn.backgroundColor = .white.withAlphaComponent(0.16) + } + } + speakerBtn.addTarget(self, action: #selector(pressedSpeakerBtn), for: .touchUpInside) translateBtn.backgroundColor = .white.withAlphaComponent(0.16) translateBtn.tintColor = .white @@ -134,39 +154,106 @@ class ConnectionViewController: UIViewController { } + @objc func pressedMuteBtn() { + switch self.muteBtn.state { + case .selected: + muteBtn.isSelected.toggle() + CallService.shared.webRTCClient.unmuteAudio() + default: + muteBtn.isSelected.toggle() + CallService.shared.webRTCClient.muteAudio() + } + } + + @objc func pressedSpeakerBtn() { + switch self.speakerBtn.state { + case .selected: + speakerBtn.isSelected.toggle() + CallService.shared.webRTCClient.speakerOff() + default: + speakerBtn.isSelected.toggle() + CallService.shared.webRTCClient.speakerOn() + } + } + @objc func hangUpBtnAction() { CallService.shared.webRTCClient.endCall() dismiss(animated: true) } @objc func pressedTransBtn() { - let sttVC = TestSTTViewController() + let sttVC = STTViewController.shared + sttVC.reinit(lang: UserManager.getData(type: String.self, forKey: .language)!) sttVC.modalPresentationStyle = .automatic present(sttVC, animated: true,completion: nil) } + func setName(name:String){ + Names.removeAll() + Names.append(name) + } + func showAlertGoToSetting() { + let alertController = UIAlertController( + title: "현재 카메라 사용에 대한 접근 권한이 없습니다.", + message: "설정 > {앱 이름}탭에서 접근을 활성화 할 수 있습니다.", + preferredStyle: .alert + ) + let cancelAlert = UIAlertAction( + title: "취소", + style: .cancel + ) { _ in + alertController.dismiss(animated: true, completion: nil) + } + let goToSettingAlert = UIAlertAction( + title: "설정으로 이동하기", + style: .default) { _ in + guard + let settingURL = URL(string: UIApplication.openSettingsURLString), + UIApplication.shared.canOpenURL(settingURL) + else { return } + UIApplication.shared.open(settingURL, options: [:]) + } + [cancelAlert, goToSettingAlert] + .forEach(alertController.addAction(_:)) + DispatchQueue.main.async { + self.present(alertController, animated: true) // must be used from main thread only + } + } } //MARK: - CollectionViewDelegate extension ConnectionViewController: UICollectionViewDelegate { - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + AVCaptureDevice.requestAccess(for: .video) { isAuthorized in + guard isAuthorized else { + self.showAlertGoToSetting() // 밑에서 계속 구현 + return + } + CallService.shared.webRTCClient.showVideo() + DispatchQueue.main.async { + let videoVC = self.videoVC + // videoVC.modalPresentationStyle = .fullScreen + self.present(videoVC, animated: true) + } + } + } } //MARK: - CollectionViewDataSource extension ConnectionViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return randomNames.count + return Names.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: connectionCollectionViewCell, for: indexPath) as! ConnectionCollectionViewCell - cell.nameLabel.text = randomNames[indexPath.row] + cell.nameLabel.text = Names[indexPath.row] return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { //6개까지 커버 var cellWidth = 300 - switch randomNames.count { + switch Names.count { case 5...: cellWidth = Int((collectionView.frame.width - (40 * 2) - 20)/2) return CGSize(width: cellWidth, height: Int((collectionView.frame.height - (60 * 2))/3)) @@ -176,7 +263,7 @@ extension ConnectionViewController: UICollectionViewDataSource { return CGSize(width: cellWidth, height: cellWidth) default: cellWidth = Int((collectionView.frame.width - (40 * 2))) - if randomNames.count == 1 { + if Names.count == 1 { collectionView.contentInset.top = ((collectionView.frame.height/2) - 40 - CGFloat(cellWidth/4)) } else { collectionView.contentInset.top = ((collectionView.frame.height/2) - 50 - CGFloat(cellWidth/2)) @@ -214,6 +301,7 @@ extension ConnectionViewController: UICollectionViewDelegateFlowLayout { // MARK: - canvas 이용하기 import SwiftUI +import AVFoundation @available(iOS 13.0.0, *) struct ConnectionViewPreview: PreviewProvider { static var previews: some View { diff --git a/iOS/Ringo/Ringo/ContactsTableViewCell.swift b/iOS/Ringo/Ringo/Screens/ContactsTableViewCell.swift similarity index 100% rename from iOS/Ringo/Ringo/ContactsTableViewCell.swift rename to iOS/Ringo/Ringo/Screens/ContactsTableViewCell.swift diff --git a/iOS/Ringo/Ringo/Screens/ContactsViewController.swift b/iOS/Ringo/Ringo/Screens/ContactsViewController.swift index a4df3d47..91b93c82 100644 --- a/iOS/Ringo/Ringo/Screens/ContactsViewController.swift +++ b/iOS/Ringo/Ringo/Screens/ContactsViewController.swift @@ -12,21 +12,24 @@ class ContactsViewController: UIViewController { let contactsTableViewCell = ContactsTableViewCell.identifier - var randomNames = [String]() + var friendsList = [FriendInfo]() let searchController = UISearchController() var tableView: UITableView! + let ifEmptyList = UILabel() override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = .systemBackground //navigation self.navigationItem.title = "Contacts" self.navigationController?.navigationBar.prefersLargeTitles = true self.navigationItem.searchController = searchController self.navigationItem.hidesSearchBarWhenScrolling = false - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "person.badge.plus"), style: .plain, target: self, action: #selector(requestFriend)) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "person"), style: .plain, target: self, action: #selector(test)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "person.badge.plus"), style: .plain, target: self, action: #selector(requestFriend)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "envelope"), style: .plain, target: self, action: #selector(requestList)) + //table tableView = UITableView() @@ -35,11 +38,24 @@ class ContactsViewController: UIViewController { tableView.dataSource = self self.view.addSubview(tableView) + ifEmptyList.text = "No friends yet" + ifEmptyList.textColor = .systemGray2 + ifEmptyList.textAlignment = .center + ifEmptyList.sizeToFit() + tableView.backgroundView = ifEmptyList + setConstraints() + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) loadFriends() + FriendRequestList.shared.reload() + } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + checkRequestList() } - func setConstraints(){ tableView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) @@ -47,13 +63,21 @@ class ContactsViewController: UIViewController { } //navigaion rightBtn action @objc private func requestFriend(_ sender: UIBarButtonItem) { - self.navigationController?.pushViewController(FriendRequestViewController(), animated: true) + present(UINavigationController(rootViewController: FriendRequestViewController()), animated: true) +// self.navigationController?.pushViewController(FriendRequestViewController(), animated: true) } - @objc private func test() { - DispatchQueue.global().async { - CallService.shared.signalClient.store(id: "rkdwltjr@naver.com") - } + @objc private func requestList() { + // 네비게이션 Push + self.navigationController?.pushViewController(FriendRequestListViewController(), animated: true) + // modal +// present(UINavigationController(rootViewController: FriendRequestListViewController()), animated: true) + } + private func checkRequestList() { + print(FriendRequestList.shared.getList()) + if FriendRequestList.shared.getList().isEmpty { + navigationItem.rightBarButtonItem?.image = UIImage(systemName: "envelope") + } else { navigationItem.rightBarButtonItem?.image = UIImage(systemName: "envelope.badge")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.systemRed,.systemBlue])) } } } @@ -96,13 +120,13 @@ extension ContactsViewController: UITableViewDelegate { extension ContactsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return randomNames.count + return friendsList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: contactsTableViewCell, for: indexPath) as! ContactsTableViewCell - cell.name.text = randomNames[indexPath.row] + cell.name.text = friendsList[indexPath.row].name cell.selectionStyle = .none cell.delegate = self @@ -114,19 +138,18 @@ extension ContactsViewController { func loadFriends() { //id ??인 유저의 친구목록 - FriendService.shared.loadFriendsList(userId: 8) { response in + FriendService.shared.loadFriendsList(email: UserManager.getData(type: String.self, forKey: .email) ?? "") { response in switch response { case .success(let data): - guard let data = data as? [String] else { return } + guard let data = data as? [FriendInfo] else { return } if !data.isEmpty { - self.randomNames = data + self.friendsList = data + self.tableView.backgroundView?.isHidden = true self.tableView.reloadData() } else { - let alert = UIAlertController(title: "친구가 없습니다", message: "", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) - self.present(alert, animated: true, completion: nil) - } + self.tableView.backgroundView?.isHidden = false + } case .requestErr(let err): print(err) @@ -136,16 +159,20 @@ extension ContactsViewController { print("serverErr") case .networkFail: print("networkFail") + case .dataErr: + print("dataErr") } } } } -// MARK: - Load Friends +// MARK: - Cell Delegate extension ContactsViewController: ContactsTableViewCellDelegate { func pressedButton() { - CallService.shared.signalClient.startcall(id: "8", target: "10") + let rowNum = tableView.indexPathForSelectedRow!.row + CallService.shared.signalClient.startcall(user: UserManager.getData(type: String.self, forKey: .email)!, target: friendsList[rowNum].email) + UserManager.setData(value: friendsList[rowNum].email, key: .receiver) } } diff --git a/iOS/Ringo/Ringo/Screens/EditProfileViewController.swift b/iOS/Ringo/Ringo/Screens/EditProfileViewController.swift new file mode 100644 index 00000000..038c633e --- /dev/null +++ b/iOS/Ringo/Ringo/Screens/EditProfileViewController.swift @@ -0,0 +1,399 @@ +// +// EditProfileViewController.swift +// Ringo +// +// Created by 강진혁 on 8/5/24. +// + +import UIKit + +class EditProfileViewController: UIViewController { + + let scrollView = UIScrollView() + let contentView = UIView() + let email = UILabel() + let passwd = UILabel() + let input_passwd = UITextField() + var showBtn = UIButton() + let error = UIButton(type: .custom) + let stackView = UIStackView() + let confirmPw = UILabel() + let input_cfpw = UITextField() + let error2 = UIButton(type: .custom) + let stackView2 = UIStackView() + let name = UILabel() + let input_name = UITextField() + let language = UILabel() + let input_language = UITextField() + let pickerLang = UIPickerView() + let toolbar = UIToolbar() + let editBtn = UIButton() + let applyBtn = UIButton() + + var boolConfirm: Bool = false + var selectedLang: String? + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + setUpValue() + setConstraints() + } + func setUpView(){ + view.addSubview(scrollView) + scrollView.addSubview(contentView) + + contentView.addSubview(email) + + contentView.addSubview(stackView) + stackView.addArrangedSubview(passwd) + stackView.addArrangedSubview(input_passwd) + stackView.addArrangedSubview(error) + + contentView.addSubview(stackView2) + stackView2.addArrangedSubview(confirmPw) + stackView2.addArrangedSubview(input_cfpw) + stackView2.addArrangedSubview(error2) + + contentView.addSubview(name) + contentView.addSubview(input_name) + + contentView.addSubview(language) + contentView.addSubview(input_language) + + contentView.addSubview(editBtn) + contentView.addSubview(applyBtn) + } + func setUpValue(){ + view.backgroundColor = .systemBackground + navigationItem.title = "Profile" + + email.text = UserManager.getData(type: String.self, forKey: .email) ?? "E-mail" + email.font = .preferredFont(forTextStyle: .largeTitle) + + stackView.axis = .vertical + stackView.alignment = .leading + stackView.distribution = .equalSpacing + stackView.spacing = 10 + + passwd.text = "Password" + passwd.font = .preferredFont(forTextStyle: .body) + + showBtn = UIButton.init(primaryAction: UIAction(handler: { _ in + self.input_passwd.isSecureTextEntry.toggle() + self.showBtn.isSelected.toggle() + })) + + showBtn.configuration = UIButton.Configuration.plain() + showBtn.configuration?.baseBackgroundColor = .clear + showBtn.configurationUpdateHandler = { btn in + switch btn.state { + case .selected: + btn.configuration?.image = UIImage(systemName: "eye") + default: + btn.configuration?.image = UIImage(systemName: "eye.slash") + } + } + + input_passwd.text = UserManager.getData(type: String.self, forKey: .password) ?? "password" + input_passwd.borderStyle = .roundedRect + input_passwd.isSecureTextEntry = true + input_passwd.rightView = showBtn + input_passwd.rightViewMode = .always + input_passwd.autocapitalizationType = .none + input_passwd.delegate = self + input_passwd.autocorrectionType = .no + input_passwd.spellCheckingType = .no + input_passwd.backgroundColor = .systemGray6 + input_passwd.isEnabled = false + + error.layer.isHidden = true + error.setTitle(" Invaild password. Please follow the password rules.", for: .normal) + error.setTitleColor(.red, for: .normal) + error.titleLabel?.font = .systemFont(ofSize: 13) + error.setImage(UIImage(systemName: "info.circle"), for: .normal) + error.setImage(UIImage(systemName: "info.circle"), for: .highlighted) + error.tintColor = .red + + stackView2.axis = .vertical + stackView2.alignment = .leading + stackView2.distribution = .equalSpacing + stackView2.spacing = 10 + + confirmPw.text = "Confirm Password" + confirmPw.font = .preferredFont(forTextStyle: .body) + + input_cfpw.placeholder = "Enter password accurately" + input_cfpw.borderStyle = .roundedRect + input_cfpw.isSecureTextEntry = true + input_cfpw.autocapitalizationType = .none + input_cfpw.delegate = self + input_cfpw.autocorrectionType = .no + input_cfpw.spellCheckingType = .no + input_cfpw.clearButtonMode = .always + input_cfpw.backgroundColor = .systemGray6 + input_cfpw.addTarget(self, action: #selector(checkPasswd(_:)), for: .editingDidEnd) + input_cfpw.isEnabled = false + + error2.isHidden = true + error2.setTitle(" Passwords do not match.", for: .normal) + error2.setTitleColor(.red, for: .normal) + error2.titleLabel?.font = .systemFont(ofSize: 13) + error2.setImage(UIImage(systemName: "info.circle"), for: .normal) + error2.setImage(UIImage(systemName: "info.circle"), for: .highlighted) + error2.tintColor = .red + + name.text = "User Name" + name.font = .preferredFont(forTextStyle: .body) + + input_name.text = UserManager.getData(type: String.self, forKey: .name) ?? "name" + input_name.borderStyle = .roundedRect + input_name.clearButtonMode = .whileEditing + input_name.autocapitalizationType = .none + input_name.delegate = self + input_name.autocorrectionType = .no + input_name.spellCheckingType = .no + input_name.backgroundColor = .systemGray6 + input_name.isEnabled = false + + language.text = "Language" + language.font = .preferredFont(forTextStyle: .body) + + input_language.text = Language.shared.getLanguageByCode(key: UserManager.getData(type: String.self, forKey: .language)!) + input_language.borderStyle = .roundedRect + input_language.delegate = self + input_language.tintColor = .clear + input_language.backgroundColor = .systemGray6 + input_language.isEnabled = false + + input_language.inputView = pickerLang + input_language.inputAccessoryView = toolbar + + selectedLang = UserManager.getData(type: String.self, forKey: .language)! + + pickerLang.delegate = self + pickerLang.dataSource = self + + toolbar.sizeToFit() + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let button = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(selectLang(_:))) + toolbar.setItems([space,button], animated: true) + toolbar.isUserInteractionEnabled = true + + editBtn.configuration = UIButton.Configuration.tinted() + editBtn.configurationUpdateHandler = { btn in + btn.configuration?.buttonSize = .large + btn.configuration?.baseBackgroundColor = .systemBlue + btn.addTarget(self, action: #selector(self.onPressEditBtn(_:)), for: .touchUpInside) + switch btn.state { + case .selected: + btn.configuration?.title = "Editing..." + btn.configuration?.baseBackgroundColor = .systemBlue.withAlphaComponent(0.7) + self.input_passwd.isEnabled = true + self.input_cfpw.isEnabled = true + self.input_name.isEnabled = true + self.input_language.isEnabled = true + self.applyBtn.isEnabled = false + default: + btn.configuration?.title = "Edit" + self.input_passwd.isEnabled = false + self.input_cfpw.isEnabled = false + self.input_name.isEnabled = false + self.input_language.isEnabled = false + self.checkAll() + } + } + applyBtn.configuration = UIButton.Configuration.filled() + applyBtn.configurationUpdateHandler = { btn in + btn.configuration?.title = "Apply" + btn.configuration?.buttonSize = .large + btn.configuration?.baseBackgroundColor = .systemBlue + btn.addTarget(self, action: #selector(self.onPressApplyBtn(_:)), for: .touchUpInside) + } + } + func setConstraints(){ + scrollView.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.bottom.equalTo(view.keyboardLayoutGuide.snp.top) // 키보드 크기 고려 + } + contentView.snp.makeConstraints { make in + make.width.equalToSuperview() + make.centerX.top.bottom.equalToSuperview() + } + email.snp.makeConstraints { make in + make.top.equalTo(contentView.safeAreaLayoutGuide).offset(40) + make.centerX.equalTo(contentView.snp.centerX) + } + stackView.snp.makeConstraints { make in + make.top.equalTo(email.snp.bottom).offset(40) + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(20) + make.trailing.equalTo(contentView.safeAreaLayoutGuide).offset(-20) + } + input_passwd.snp.makeConstraints { make in + make.trailing.equalTo(stackView.snp.trailing) + } + stackView2.snp.makeConstraints { make in + make.top.equalTo(stackView.snp.bottom).offset(30) + make.leading.equalTo(stackView.snp.leading) + make.trailing.equalTo(stackView.snp.trailing) + } + input_cfpw.snp.makeConstraints { make in + make.trailing.equalTo(stackView2.snp.trailing) + } + name.snp.makeConstraints { make in + make.top.equalTo(stackView2.snp.bottom).offset(30) + make.leading.equalTo(input_cfpw.snp.leading) + make.trailing.equalTo(input_cfpw.snp.trailing) + } + input_name.snp.makeConstraints { make in + make.top.equalTo(name.snp.bottom).offset(10) + make.leading.equalTo(name.snp.leading) + make.trailing.equalTo(name.snp.trailing) + } + language.snp.makeConstraints { make in + make.top.equalTo(input_name.snp.bottom).offset(30) + make.leading.equalTo(input_name.snp.leading) + make.trailing.equalTo(input_name.snp.trailing) + } + input_language.snp.makeConstraints { make in + make.top.equalTo(language.snp.bottom).offset(10) + make.leading.equalTo(language.snp.leading) + make.trailing.equalTo(language.snp.trailing) + } + editBtn.snp.makeConstraints { make in + make.top.equalTo(input_language.snp.bottom).offset(50) + make.leading.equalTo(input_language.snp.leading) + make.trailing.equalTo(view.snp.centerX).offset(-10) + } + applyBtn.snp.makeConstraints { make in + make.top.equalTo(input_language.snp.bottom).offset(50) + make.leading.equalTo(view.snp.centerX).offset(10) + make.trailing.equalTo(input_language.snp.trailing) + make.bottom.equalToSuperview().inset(30) + } + } + @objc func selectLang(_ sender: UIButton) { + if input_language.text?.isEmpty ?? false { + pickerView(pickerLang, didSelectRow: 0, inComponent: 0) + } + input_language.resignFirstResponder() + } + func isSamePasswd(_ first: UITextField, _ second: UITextField) -> Bool { + if(first.text == second.text) { + return true + } else { + return false + } + } + @objc func checkPasswd(_ sender: UITextField) { + if isSamePasswd(input_passwd, input_cfpw){ + input_cfpw.layer.borderColor = UIColor.clear.cgColor + error2.isHidden = true + boolConfirm = true + } else { + input_cfpw.layer.borderWidth = 1 + input_cfpw.layer.cornerRadius = 5 + input_cfpw.layer.borderColor = UIColor.systemRed.cgColor + error2.isHidden = false + boolConfirm = false + } + } + func checkAll() { + if !(input_passwd.text?.isEmpty ?? true) && boolConfirm && !(input_name.text?.isEmpty ?? true) && !(input_language.text?.isEmpty ?? true) { + applyBtn.isEnabled = true + } else { + applyBtn.isEnabled = false + } + } + @objc func onPressEditBtn(_ sender: UIButton){ + editBtn.isSelected.toggle() + } + @objc func onPressApplyBtn(_ sender: UIButton){ + apply() + } +} +// MARK: - 키보드 사라지게 하기 +extension EditProfileViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } +} +// MARK: - PickerView +extension EditProfileViewController: UIPickerViewDelegate, UIPickerViewDataSource { + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return Language.shared.getCount() + } + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + let languages = Language.shared.getList() + return Language.shared.getLanguageByCode(key: languages[row]) + } + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + let languages = Language.shared.getList() + input_language.text = Language.shared.getLanguageByCode(key: languages[row]) + selectedLang = languages[row] + } +} +// MARK: - Apply +extension EditProfileViewController { + + func apply() { + + guard let email = UserManager.getData(type: String.self, forKey: .email) else { return } + guard let password = UserManager.getData(type: String.self, forKey: .password) else { return } + guard let newPassword = input_passwd.text else { return } + guard let name = input_name.text else { return } + guard let languageCode = selectedLang else { return } + + SigninSercive.shared.update(email: email, password: password, newPassword: newPassword, name: name, languageCode: languageCode) { response in + switch response { + case .success(let data): + + guard let data = data as? SigninDataResponse else { + print(data) + return + } + if data.status == "success"{ + + let alert = UIAlertController(title: "변경되었습니다.", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel){ action in + self.dismiss(animated: true) + }) + self.present(alert, animated: true, completion: nil) + UserManager.setData(value: newPassword, key: .password) + UserManager.setData(value: name, key: .name) + UserManager.setData(value: languageCode, key: .language) + + } else { + let alert = UIAlertController(title: data.status, message: data.message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } +} +// MARK: - canvas 이용하기 +import SwiftUI +@available(iOS 13.0.0, *) +struct EditProfileViewPreview: PreviewProvider { + static var previews: some View { + EditProfileViewController().toPreview() + } +} diff --git a/iOS/Ringo/Ringo/Screens/FriendRequestListTableViewCell.swift b/iOS/Ringo/Ringo/Screens/FriendRequestListTableViewCell.swift new file mode 100644 index 00000000..632c0ae9 --- /dev/null +++ b/iOS/Ringo/Ringo/Screens/FriendRequestListTableViewCell.swift @@ -0,0 +1,103 @@ +// +// FriendRequestListTableViewCell.swift +// Ringo +// +// Created by 강진혁 on 7/29/24. +// + +import UIKit + +class FriendRequestListTableViewCell: UITableViewCell { + + static let identifier = "FriendRequestListTableViewCell" + + let name = UILabel() + let acceptBtn = UIButton() + let rejectBtn = UIButton() + + var pressedAcceptBtn: (() -> Void)? + var pressedRejectBtn: (() -> Void)? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setUpView() + setUpValue() + setConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setUpView(){ + contentView.addSubview(name) + contentView.addSubview(acceptBtn) + contentView.addSubview(rejectBtn) + } + func setUpValue(){ + name.text = "Name" + name.font = .preferredFont(forTextStyle: .title2) + + acceptBtn.configuration = UIButton.Configuration.plain() + acceptBtn.configurationUpdateHandler = { btn in + btn.configuration?.image = UIImage(systemName: "checkmark.circle.fill") + btn.configuration?.baseForegroundColor = .systemGreen + btn.configuration?.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 25) + btn.addTarget(self, action: #selector(self.acceptBtnAction), for: .touchUpInside) + } + + rejectBtn.configuration = UIButton.Configuration.plain() + rejectBtn.configurationUpdateHandler = { btn in + btn.configuration?.image = UIImage(systemName: "xmark.circle.fill") + btn.configuration?.baseForegroundColor = .systemRed + btn.configuration?.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 25) + btn.addTarget(self, action: #selector(self.rejectBtnAction), for: .touchUpInside) + } + } + func setConstraints(){ + name.snp.makeConstraints { make in + make.top.equalTo(contentView.safeAreaLayoutGuide).offset(15) + make.bottom.equalTo(contentView.safeAreaLayoutGuide).inset(15) + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(20) + } + acceptBtn.snp.makeConstraints { make in + make.top.equalTo(contentView.safeAreaLayoutGuide).offset(15) + make.bottom.equalTo(contentView.safeAreaLayoutGuide).inset(15) + make.trailing.equalTo(rejectBtn.snp.leading) + } + rejectBtn.snp.makeConstraints { make in + make.top.equalTo(contentView.safeAreaLayoutGuide).offset(15) + make.bottom.equalTo(contentView.safeAreaLayoutGuide).inset(15) + make.trailing.equalTo(contentView.safeAreaLayoutGuide).inset(10) + } + } + + @objc func acceptBtnAction() { + pressedAcceptBtn?() + } + @objc func rejectBtnAction() { + pressedRejectBtn?() + } + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + // Configure the view for the selected state + } + +} +// MARK: - canvas 이용하기 +import SwiftUI +@available(iOS 13.0.0, *) +struct FriendRequestListTableViewCellPreView: PreviewProvider { + static var previews: some View { + UIViewPreview { + let button = FriendRequestListTableViewCell(frame: .zero) + return button + } + } +} diff --git a/iOS/Ringo/Ringo/Screens/FriendRequestListViewController.swift b/iOS/Ringo/Ringo/Screens/FriendRequestListViewController.swift new file mode 100644 index 00000000..f60b1b14 --- /dev/null +++ b/iOS/Ringo/Ringo/Screens/FriendRequestListViewController.swift @@ -0,0 +1,199 @@ +// +// FriendRequestListViewController.swift +// Ringo +// +// Created by 강진혁 on 7/29/24. +// + +import UIKit + +class FriendRequestListViewController: UIViewController { + + let friendRequestListTableViewCell = FriendRequestListTableViewCell.identifier + var tableView = UITableView() + let ifEmptyList = UILabel() + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + setUpValue() + setConstraints() + } + func setUpView(){ + view.addSubview(tableView) + } + func setUpValue(){ + navigationItem.title = "Request List" +// navigationController?.navigationBar.prefersLargeTitles = false + tabBarController?.tabBar.isHidden = true + view.backgroundColor = .systemBackground + + tableView.register(FriendRequestListTableViewCell.self, forCellReuseIdentifier: friendRequestListTableViewCell) + tableView.dataSource = self + tableView.delegate = self + + ifEmptyList.text = "No requests" + ifEmptyList.textColor = .systemGray2 + ifEmptyList.textAlignment = .center + ifEmptyList.sizeToFit() + tableView.backgroundView = ifEmptyList + } + func setConstraints(){ + tableView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + tabBarController?.tabBar.isAppearedWithAnimation() + } +} +// MARK: - UITableViewDataSource +extension FriendRequestListViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if FriendRequestList.shared.getList().isEmpty { + tableView.backgroundView?.isHidden = false + } else { + tableView.backgroundView?.isHidden = true + } + return FriendRequestList.shared.getList().count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: friendRequestListTableViewCell, for: indexPath) as! FriendRequestListTableViewCell + + cell.name.text = FriendRequestList.shared.getList()[indexPath.row].name + cell.selectionStyle = .none + cell.pressedAcceptBtn = { + print("accept btn") + self.acceptRequest(receiver: FriendRequestList.shared.getList()[indexPath.row].email) + self.deleteRow(index: indexPath.row) + } + cell.pressedRejectBtn = { + print("reject btn") + self.rejectRequest(receiver: FriendRequestList.shared.getList()[indexPath.row].email) + self.deleteRow(index: indexPath.row) + } + + return cell + } + func deleteRow(index: Int){ + FriendRequestList.shared.remove(at: index) + tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + print("loading") + self.tableView.reloadData() + } + } +} +// MARK: - UITableViewDelegate +extension FriendRequestListViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } +} +// MARK: - TabBar Animation +extension UITabBar { + func isHiddenWithAnimation() { + let orig = self.frame + var target = self.frame + target.origin.x = target.origin.x - target.size.width + UIView.animate(withDuration: 0.2, animations: { + self.frame = target + }) { (true) in + self.isHidden = true + self.frame = orig + } + } + func isAppearedWithAnimation() { + let orig = self.frame + var target = self.frame + target.origin.x = target.origin.x - target.size.width + self.frame = target + self.isHidden = false + UIView.animate(withDuration: 0.3, animations: { + self.frame = orig + }) + } +} +// MARK: - firend request +extension FriendRequestListViewController { + + func acceptRequest(receiver: String) { + + guard let sender = UserManager.getData(type: String.self, forKey: .email) else { return } + + FriendService.shared.acceptRequest(sender: sender, receiver: receiver) { response in + switch response { + case .success(let data): + guard let data = data as? String else { return } + if data == "{\"message\":\"성공\"}" { + let alert = UIAlertController(title: "요청 수락", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + } else { + let alert = UIAlertController(title: data, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } + func rejectRequest(receiver: String) { + + guard let sender = UserManager.getData(type: String.self, forKey: .email) else { return } + + FriendService.shared.rejectRequest(sender: sender, receiver: receiver) { response in + switch response { + case .success(let data): + guard let data = data as? String else { return } + if data == "{\"message\":\"성공\"}" { + let alert = UIAlertController(title: "요청 거절", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + } else { + let alert = UIAlertController(title: data, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } +} +// MARK: - canvas 이용하기 +import SwiftUI +@available(iOS 13.0.0, *) +struct FriendRequestListViewControllerPreView: PreviewProvider { + static var previews: some View { + // 사용할 뷰 컨트롤러를 넣어주세요 + FriendRequestListViewController() + .toPreview() + } +} diff --git a/iOS/Ringo/Ringo/Screens/FriendRequestViewController.swift b/iOS/Ringo/Ringo/Screens/FriendRequestViewController.swift index 7e387876..7a8f59e7 100644 --- a/iOS/Ringo/Ringo/Screens/FriendRequestViewController.swift +++ b/iOS/Ringo/Ringo/Screens/FriendRequestViewController.swift @@ -9,25 +9,129 @@ import UIKit class FriendRequestViewController: UIViewController { + let input_friendEamil = UITextField() + let sendRequestBtn = UIButton() + override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .brown + setUpView() + setUpValue() + setConstraints() + // Do any additional setup after loading the view. + } + func setUpView() { + view.addSubview(input_friendEamil) + view.addSubview(sendRequestBtn) + } + func setUpValue() { + view.backgroundColor = .systemBackground + view.keyboardLayoutGuide.followsUndockedKeyboard = true self.navigationItem.title = "Request Friend" - self.navigationItem.largeTitleDisplayMode = .never - // Do any additional setup after loading the view. + + input_friendEamil.becomeFirstResponder() + + input_friendEamil.placeholder = "Enter your name" + input_friendEamil.borderStyle = .roundedRect + input_friendEamil.font = .preferredFont(forTextStyle: .title3) + input_friendEamil.clearButtonMode = .always + input_friendEamil.autocapitalizationType = .none + input_friendEamil.delegate = self + input_friendEamil.autocorrectionType = .no + input_friendEamil.spellCheckingType = .no + input_friendEamil.backgroundColor = .systemGray6 + + sendRequestBtn.configuration = UIButton.Configuration.filled() + sendRequestBtn.configurationUpdateHandler = { btn in + + var titleContainer = AttributeContainer() + titleContainer.font = UIFont.boldSystemFont(ofSize: 20) + + btn.configuration?.attributedTitle = AttributedString( "Send a friend request", attributes: titleContainer) + btn.configuration?.image = UIImage(systemName: "paperplane.fill") + btn.configuration?.imagePadding = 10 + btn.configuration?.buttonSize = .large + btn.configuration?.baseBackgroundColor = .systemBlue + btn.addTarget(self, action: #selector(self.onPressSendBtn(_:)), for: .touchUpInside) + } + } + func setConstraints() { + input_friendEamil.snp.makeConstraints { make in + make.top.left.equalTo(view.safeAreaLayoutGuide).offset(20) + make.trailing.equalTo(view.safeAreaLayoutGuide).inset(20) + make.bottom.equalTo(input_friendEamil.snp.top).offset(45) + } + sendRequestBtn.snp.makeConstraints { make in + make.leading.trailing.equalTo(input_friendEamil) + make.bottom.equalTo(view.keyboardLayoutGuide.snp.top).offset(-20) + } } + @objc func onPressSendBtn(_ sender: UIButton) { + print("btn press") + sendFriendRequest() + } + // 빈 화면 터치 시 키보드 내리기 + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + view.endEditing(true) + } +} +// MARK: - 키보드 사라지게 하기 +extension FriendRequestViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } +} +// MARK: - firend request +extension FriendRequestViewController { - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + func sendFriendRequest() { + + guard let sender = UserManager.getData(type: String.self, forKey: .email) else { return } + guard let receiver = input_friendEamil.text else { + print("receiver empty") + return } + print("send request") + FriendService.shared.sendFriendRequest(sender: sender, receiver: receiver) { response in + switch response { + case .success(let data): + + guard let data = data as? String else { return } + if data == "{\"message\":\"success\"}" { + let alert = UIAlertController(title: "요청을 보냈습니다.", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: { action in + self.dismiss(animated: true) + })) + self.present(alert, animated: true, completion: nil) + print(data) + } else { + let alert = UIAlertController(title: "존재하지 않은 이메일입니다.", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } } - */ +} +// MARK: - canvas 이용하기 +import SwiftUI +@available(iOS 13.0.0, *) +struct FriendRequestViewPreview: PreviewProvider { + static var previews: some View { + FriendRequestViewController().toPreview() + } } diff --git a/iOS/Ringo/Ringo/Screens/SignupViewController.swift b/iOS/Ringo/Ringo/Screens/SignupViewController.swift new file mode 100644 index 00000000..2c805af3 --- /dev/null +++ b/iOS/Ringo/Ringo/Screens/SignupViewController.swift @@ -0,0 +1,464 @@ +// +// SignupViewController.swift +// Ringo +// +// Created by 강진혁 on 7/22/24. +// + +import UIKit +import SnapKit + +class SignupViewController: UIViewController { + + let scrollView = UIScrollView() + let contentView = UIView() + let Text1 = UILabel() + let Text2 = UILabel() + let Text3 = UILabel() + let signinBtn = UIButton() + let email = UILabel() + let input_email = UITextField() + let passwd = UILabel() + let input_passwd = UITextField() + var showBtn = UIButton() + let error = UIButton(type: .custom) + let stackView = UIStackView() + let confirmPw = UILabel() + let input_cfpw = UITextField() + let error2 = UIButton(type: .custom) + let stackView2 = UIStackView() + let name = UILabel() + let input_name = UITextField() + let language = UILabel() + let input_language = UITextField() + let pickerLang = UIPickerView() + let toolbar = UIToolbar() + let signupBtn = UIButton() + + var boolConfirm: Bool = false + var selectedLang: String? + + override func viewDidLoad() { + super.viewDidLoad() + + setUpView() + setUpValue() + setConstraints() + // Do any additional setup after loading the view. + } + + func setUpView(){ + + view.addSubview(scrollView) + scrollView.addSubview(contentView) + + contentView.addSubview(Text1) + contentView.addSubview(Text2) + contentView.addSubview(Text3) + contentView.addSubview(signinBtn) + contentView.addSubview(email) + contentView.addSubview(input_email) + + contentView.addSubview(stackView) + stackView.addArrangedSubview(passwd) + stackView.addArrangedSubview(input_passwd) + stackView.addArrangedSubview(error) + + contentView.addSubview(stackView2) + stackView2.addArrangedSubview(confirmPw) + stackView2.addArrangedSubview(input_cfpw) + stackView2.addArrangedSubview(error2) + + contentView.addSubview(name) + contentView.addSubview(input_name) + + contentView.addSubview(language) + contentView.addSubview(input_language) + + contentView.addSubview(signupBtn) + } + + func setUpValue(){ + view.backgroundColor = .systemBackground + + //스크롤뷰는 스크롤때문에 한 번 터치 무시, 평소에 썼던 빈 곳 터치시 키보드 내리기는 불가, 따로 추가 설정해야 함 + let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing)) + tap.cancelsTouchesInView = false + tap.addTarget(self, action: #selector(checkAll)) + scrollView.addGestureRecognizer(tap) + + Text1.text = "Sign up" + Text1.font = .preferredFont(forTextStyle: .title1) + + Text2.text = "If you already have an account register" + Text2.font = .preferredFont(forTextStyle: .body) + + Text3.text = "You can" + Text3.font = .preferredFont(forTextStyle: .body) + + signinBtn.setTitle("Sign in here!", for: .normal) + signinBtn.setTitleColor(.systemBlue, for: .normal) + signinBtn.titleLabel?.font = .systemFont(ofSize: 18) + signinBtn.setTitleColor(.systemBlue.withAlphaComponent(0.5), for: .highlighted) + signinBtn.addTarget(self, action: #selector(onPressSignin(_:)), for: .touchUpInside) + + email.text = "E-mail" + email.font = .preferredFont(forTextStyle: .body) + + input_email.placeholder = "Enter your email" + input_email.borderStyle = .roundedRect + input_email.keyboardType = .emailAddress + input_email.autocapitalizationType = .none + input_email.autocorrectionType = .no + input_email.spellCheckingType = .no + input_email.clearButtonMode = .always + input_email.backgroundColor = .systemGray6 + input_email.delegate = self +// input_email.addTarget(self, action: #selector(checkEmail(_:)), for: .editingDidEnd) + + stackView.axis = .vertical + stackView.alignment = .leading + stackView.distribution = .equalSpacing + stackView.spacing = 10 + + passwd.text = "Password" + passwd.font = .preferredFont(forTextStyle: .body) + + showBtn = UIButton.init(primaryAction: UIAction(handler: { _ in + self.input_passwd.isSecureTextEntry.toggle() + self.showBtn.isSelected.toggle() + })) + + showBtn.configuration = UIButton.Configuration.plain() + showBtn.configuration?.baseBackgroundColor = .clear + showBtn.configurationUpdateHandler = { btn in + switch btn.state { + case .selected: + btn.configuration?.image = UIImage(systemName: "eye") + default: + btn.configuration?.image = UIImage(systemName: "eye.slash") + } + } + + input_passwd.placeholder = "Enter your password" + input_passwd.borderStyle = .roundedRect + input_passwd.isSecureTextEntry = true + input_passwd.rightView = showBtn + input_passwd.rightViewMode = .always + input_passwd.autocapitalizationType = .none + input_passwd.delegate = self + input_passwd.autocorrectionType = .no + input_passwd.spellCheckingType = .no + input_passwd.backgroundColor = .systemGray6 + + error.layer.isHidden = true + error.setTitle(" Invaild password. Please follow the password rules.", for: .normal) + error.setTitleColor(.red, for: .normal) + error.titleLabel?.font = .systemFont(ofSize: 13) + error.setImage(UIImage(systemName: "info.circle"), for: .normal) + error.setImage(UIImage(systemName: "info.circle"), for: .highlighted) + error.tintColor = .red + + stackView2.axis = .vertical + stackView2.alignment = .leading + stackView2.distribution = .equalSpacing + stackView2.spacing = 10 + + confirmPw.text = "Confirm Password" + confirmPw.font = .preferredFont(forTextStyle: .body) + + input_cfpw.placeholder = "Enter password accurately" + input_cfpw.borderStyle = .roundedRect + input_cfpw.isSecureTextEntry = true + input_cfpw.autocapitalizationType = .none + input_cfpw.delegate = self + input_cfpw.autocorrectionType = .no + input_cfpw.spellCheckingType = .no + input_cfpw.clearButtonMode = .always + input_cfpw.backgroundColor = .systemGray6 + input_cfpw.addTarget(self, action: #selector(checkPasswd(_:)), for: .editingDidEnd) + + error2.isHidden = true + error2.setTitle(" Passwords do not match.", for: .normal) + error2.setTitleColor(.red, for: .normal) + error2.titleLabel?.font = .systemFont(ofSize: 13) + error2.setImage(UIImage(systemName: "info.circle"), for: .normal) + error2.setImage(UIImage(systemName: "info.circle"), for: .highlighted) + error2.tintColor = .red + + name.text = "User Name" + name.font = .preferredFont(forTextStyle: .body) + + input_name.placeholder = "Enter your name" + input_name.borderStyle = .roundedRect + input_name.clearButtonMode = .always + input_name.autocapitalizationType = .none + input_name.delegate = self + input_name.autocorrectionType = .no + input_name.spellCheckingType = .no + input_name.backgroundColor = .systemGray6 + + language.text = "Language" + language.font = .preferredFont(forTextStyle: .body) + + input_language.placeholder = "Choose your language" + input_language.borderStyle = .roundedRect + input_language.delegate = self + input_language.tintColor = .clear + input_language.backgroundColor = .systemGray6 + + input_language.inputView = pickerLang + input_language.inputAccessoryView = toolbar + + pickerLang.delegate = self + pickerLang.dataSource = self + + toolbar.sizeToFit() + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let button = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(selectLang(_:))) + toolbar.setItems([space,button], animated: true) + toolbar.isUserInteractionEnabled = true + + signupBtn.configuration = UIButton.Configuration.filled() + signupBtn.configuration?.buttonSize = .large + signupBtn.setTitle("Sign up", for: .normal) + signupBtn.layer.shadowColor = UIColor.systemBlue.cgColor + signupBtn.layer.shadowRadius = 15 + signupBtn.layer.shadowOpacity = 0.5 + signupBtn.layer.shadowOffset = CGSize(width: 0, height: 0) + signupBtn.layer.masksToBounds = false + signupBtn.addTarget(self, action: #selector(onPressSignup(_:)), for: .touchUpInside) + signupBtn.isEnabled = false + } + + func setConstraints(){ + + scrollView.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.bottom.equalTo(view.keyboardLayoutGuide.snp.top) // 키보드 크기 고려 + } + contentView.snp.makeConstraints { make in + make.width.equalToSuperview() + make.centerX.top.bottom.equalToSuperview() + } + + Text1.snp.makeConstraints { make in + make.top.equalTo(contentView.safeAreaLayoutGuide).offset(30) + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(30) + } + Text2.snp.makeConstraints { make in + make.top.equalTo(Text1.snp.bottom).offset(20) + make.leading.equalTo(Text1.snp.leading) + } + Text3.snp.makeConstraints { make in + make.top.equalTo(Text2.snp.bottom).offset(10) + make.leading.equalTo(Text2.snp.leading) + } + signinBtn.snp.makeConstraints { make in + make.top.equalTo(Text2.snp.top).offset(24) + make.leading.equalTo(Text2.snp.leading).offset(70) + } + + email.snp.makeConstraints { make in + make.top.equalTo(Text2.snp.bottom).offset(100) + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(20) + } + input_email.snp.makeConstraints { make in + make.top.equalTo(email.snp.bottom).offset(10) + make.leading.equalTo(email.snp.leading) + make.trailing.equalTo(contentView.safeAreaLayoutGuide).offset(-20) + } + + stackView.snp.makeConstraints { make in + make.top.equalTo(input_email.snp.bottom).offset(20) + make.leading.equalTo(input_email.snp.leading) + make.trailing.equalTo(input_email.snp.trailing) + } + input_passwd.snp.makeConstraints { make in + make.trailing.equalTo(stackView.snp.trailing) + } + + stackView2.snp.makeConstraints { make in + make.top.equalTo(stackView.snp.bottom).offset(20) + make.leading.equalTo(stackView.snp.leading) + make.trailing.equalTo(stackView.snp.trailing) + } + input_cfpw.snp.makeConstraints { make in + make.trailing.equalTo(stackView2.snp.trailing) + } + + name.snp.makeConstraints { make in + make.top.equalTo(stackView2.snp.bottom).offset(20) + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(20) + } + input_name.snp.makeConstraints { make in + make.top.equalTo(name.snp.bottom).offset(10) + make.leading.equalTo(name.snp.leading) + make.trailing.equalTo(contentView.safeAreaLayoutGuide).offset(-20) + } + + language.snp.makeConstraints { make in + make.top.equalTo(input_name.snp.bottom).offset(20) + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(20) + } + input_language.snp.makeConstraints { make in + make.top.equalTo(language.snp.bottom).offset(10) + make.leading.equalTo(language.snp.leading) + make.trailing.equalTo(contentView.safeAreaLayoutGuide).offset(-20) + } + + signupBtn.snp.makeConstraints { make in + make.top.equalTo(input_language.snp.bottom).offset(90) + make.leading.equalTo(input_language.snp.leading) + make.trailing.equalTo(input_language.snp.trailing) + make.bottom.equalToSuperview().inset(30) //scrollview contentview의 높이를 지정함 + } + } + + @objc func checkEmail(_ sender: UITextField) { + let regex = "^([a-zA-Z0-9._-])+@[a-zA-Z0-9.-]+.[a-zA-Z]$" + if ( sender.text?.range(of: regex,options: .regularExpression) != nil ) { + input_email.layer.borderColor = UIColor.clear.cgColor + } else { + input_email.layer.borderWidth = 1 + input_email.layer.cornerRadius = 5 + input_email.layer.borderColor = UIColor.systemRed.cgColor + } + } + func isSamePasswd(_ first: UITextField, _ second: UITextField) -> Bool { + if(first.text == second.text) { + return true + } else { + return false + } + } + @objc func checkPasswd(_ sender: UITextField) { + if isSamePasswd(input_passwd, input_cfpw){ + input_cfpw.layer.borderColor = UIColor.clear.cgColor + error2.isHidden = true + boolConfirm = true + } else { + input_cfpw.layer.borderWidth = 1 + input_cfpw.layer.cornerRadius = 5 + input_cfpw.layer.borderColor = UIColor.systemRed.cgColor + error2.isHidden = false + boolConfirm = false + } + } + + @objc func onPressSignin(_ sender: UIButton) { + dismiss(animated: true) + } + + @objc func selectLang(_ sender: UIButton) { + if input_language.text?.isEmpty ?? false { + pickerView(pickerLang, didSelectRow: 0, inComponent: 0) + } + input_language.resignFirstResponder() + checkAll() + } + @objc func checkAll() { + if !(input_email.text?.isEmpty ?? true) && !(input_passwd.text?.isEmpty ?? true) && boolConfirm && !(input_name.text?.isEmpty ?? true) && !(input_language.text?.isEmpty ?? true) { + signupBtn.isEnabled = true + } else { + signupBtn.isEnabled = false + } + } + @objc func onPressSignup(_ sender: UIButton) { + signup() + } +} + +// MARK: - 키보드 사라지게 하기 +extension SignupViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + checkAll() + return true + } + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + signupBtn.isEnabled = false + return true + } + func textFieldDidChangeSelection(_ textField: UITextField) { + signupBtn.isEnabled = false + } +} + +// MARK: - PickerView +extension SignupViewController: UIPickerViewDelegate, UIPickerViewDataSource { + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return Language.shared.getCount() + } + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + let languages = Language.shared.getList() + return Language.shared.getLanguageByCode(key: languages[row]) + } + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + let languages = Language.shared.getList() + input_language.text = Language.shared.getLanguageByCode(key: languages[row]) + selectedLang = languages[row] + } +} +// MARK: - Sign up +extension SignupViewController { + + func signup() { + + guard let email = input_email.text else { return } + guard let password = input_passwd.text else { return } + guard let name = input_name.text else { return } + guard let languageCode = selectedLang else { return } +// let languageCode = Language.shared.getCode(key: language) + + print(email,password,name,languageCode) + + SigninSercive.shared.signup(email: email, password: password, name: name, languageCode: languageCode) { response in + switch response { + case .success(let data): + + guard let data = data as? SigninDataResponse else { return } + if data.status == "success"{ + + let alert = UIAlertController(title: data.status, message: data.message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "회원가입 성공", style: .cancel){ action in + self.dismiss(animated: true) + }) + self.present(alert, animated: true, completion: nil) + + } else { + let alert = UIAlertController(title: data.status, message: data.message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) + // alert.addAction(UIAlertAction(title: "DEFAULT", style: .default, handler: nil)) + // alert.addAction(UIAlertAction(title: "DESTRUCTIVE", style: .destructive, handler: nil)) + + self.present(alert, animated: true, completion: nil) + print(data) + } + + case .requestErr(let err): + print(err) + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + case .dataErr: + print("dataErr") + } + } + } +} +// MARK: - canvas 이용하기 +import SwiftUI +@available(iOS 13.0.0, *) +struct SignupViewPreview: PreviewProvider { + static var previews: some View { + SignupViewController().toPreview() + } +} diff --git a/iOS/Ringo/Ringo/Screens/TabBarViewController.swift b/iOS/Ringo/Ringo/Screens/TabBarViewController.swift index 8fb7ce26..9cf59cb7 100644 --- a/iOS/Ringo/Ringo/Screens/TabBarViewController.swift +++ b/iOS/Ringo/Ringo/Screens/TabBarViewController.swift @@ -11,14 +11,15 @@ import WebRTC class TabBarViewController: UITabBarController{ + var connectionVC: ConnectionViewController? + override func viewDidLoad() { super.viewDidLoad() CallService.shared.webRTCClient.delegate = self CallService.shared.signalClient.delegate = self CallService.shared.signalClient.connect() - - view.backgroundColor = .systemBackground + CallService.shared.signalClient.store(user: UserManager.getData(type: String.self, forKey: .email) ?? "") tabBar.backgroundColor = .systemGray6 @@ -29,8 +30,6 @@ class TabBarViewController: UITabBarController{ contactsVC.tabBarItem = UITabBarItem(title: "Contacts", image: UIImage(systemName: "person.3"), tag: 1) recentsVC.tabBarItem = UITabBarItem(title: "Recents", image: UIImage(systemName: "clock"), tag: 2) accountVC.tabBarItem = UITabBarItem(title: "Account", image: UIImage(systemName: "person.text.rectangle"), tag: 3) - - contactsVC.navigationBar.backgroundColor = .systemBackground setViewControllers([contactsVC,recentsVC,accountVC], animated: false) } @@ -45,16 +44,23 @@ extension TabBarViewController: SignalClientDelegate { print("signal disconnect") } - func signalClient(_ signalClient: SignalingClient, didReceiveRemoteSdp sdp: RTCSessionDescription) { + func signalClientDidForceDisconnect(_ signalClient: SignalingClient) { + print("signal forceDisconnect") + } + + func signalClient(_ signalClient: SignalingClient, didReceiveRemoteSdp sdp: RTCSessionDescription, sender: String) { print("Received remote sdp") CallService.shared.webRTCClient.set(remoteSdp: sdp) { (error) in - DispatchQueue.main.async { - let alert = UIAlertController(title: "Call", message: "", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Refuse", style: .destructive, handler: nil)) - alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { action in - self.acceptCall() - })) - self.present(alert, animated: true, completion: nil) + if sdp.type == .offer { + DispatchQueue.main.async { + let alert = UIAlertController(title: "Call", message: sender, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Refuse", style: .destructive, handler: nil)) + alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { action in + UserManager.setData(value: sender, key: .receiver) + self.acceptCall(sender: sender) + })) + self.present(alert, animated: true, completion: nil) + } } } } @@ -68,14 +74,13 @@ extension TabBarViewController: SignalClientDelegate { func signalClient(_ signalClient: SignalingClient, didReceiveCallResponse response: String) { if response == "user is ready for call" { CallService.shared.webRTCClient.offer { (sdp) in - var message = Message(type: .create_offer, name: "rkdwltjr@naver.com", target: "rkdwlsgur@naver.com") + var message = Message(type: .create_offer, name: UserManager.getData(type: String.self, forKey: .email)!, target: UserManager.getData(type: String.self, forKey: .receiver)!) message.data = .sdp(SessionDescription(from: sdp)) CallService.shared.signalClient.send(message: message) } - DispatchQueue.main.async { [self] in - let connectionVC = ConnectionViewController() - connectionVC.modalPresentationStyle = .fullScreen - present(connectionVC, animated: true,completion: nil) + DispatchQueue.main.async { + ConnectingView.shared.setName(name: UserManager.getData(type: String.self, forKey: .receiver)!) + ConnectingView.shared.show() } } else { debugPrint("response message") @@ -83,20 +88,20 @@ extension TabBarViewController: SignalClientDelegate { } } - func signalClient(_ signalClient: SignalingClient, didReceiveTranslation msg: String, language: String) { - TTS.shared.play(msg, language) + func signalClient(_ signalClient: SignalingClient, didReceiveTranslation msg: String) { + DispatchQueue.main.async { + STTViewController.shared.textView.text = msg + } + TTS.shared.play(msg, UserManager.getData(type: String.self, forKey: .language)!) } - func acceptCall() { + func acceptCall(sender: String) { CallService.shared.webRTCClient.answer { (localSdp) in - var message = Message(type: .create_answer, name: "rkdwltjr@naver.com", target: "rkdwlsgur@naver.com") + var message = Message(type: .create_answer, name: UserManager.getData(type: String.self, forKey: .email)!, target: sender) message.data = .sdp(SessionDescription(from: localSdp)) CallService.shared.signalClient.send(message: message) // CallService.shared.signalClient.send(sdp: localSdp) } - let connectionVC = ConnectionViewController() - connectionVC.modalPresentationStyle = .fullScreen - self.present(connectionVC, animated: true,completion: nil) } } @@ -104,7 +109,7 @@ extension TabBarViewController: SignalClientDelegate { extension TabBarViewController: WebRTCClientDelegate { func webRTCClient(_ client: WebRTCClient, didDiscoverLocalCandidate candidate: RTCIceCandidate) { print("discovered local candidate") - var message = Message(type: .ice_candidate, name: "rkdwltjr@naver.com", target: "rkdwlsgur@naver.com") + var message = Message(type: .ice_candidate, name: UserManager.getData(type: String.self, forKey: .email)!, target: UserManager.getData(type: String.self, forKey: .receiver)!) message.data = .candidate(IceCandidate(from: candidate)) CallService.shared.signalClient.send(message: message) // CallService.shared.signalClient.send(candidate: candidate) @@ -112,8 +117,27 @@ extension TabBarViewController: WebRTCClientDelegate { func webRTCClient(_ client: WebRTCClient, didChangeConnectionState state: RTCIceConnectionState) { print("change connection state : \(state)") + switch state { + case .connected: + DispatchQueue.main.async { [self] in + let connectionVC = ConnectionViewController() + connectionVC.setName(name: UserManager.getData(type: String.self, forKey: .receiver)!) + connectionVC.modalPresentationStyle = .fullScreen + self.connectionVC = connectionVC + self.present(connectionVC, animated: true,completion: nil) + } + case .disconnected,.failed,.closed: + DispatchQueue.main.async { + ConnectingView.shared.hide() + CallService.shared.refresh() + self.connectionVC?.dismiss(animated: true) + } + default: break +// DispatchQueue.main.async { +// self.loading.isLoading = false +// } + } } - func webRTCClient(_ client: WebRTCClient, didReceiveData data: Data) { DispatchQueue.main.async { let message = String(data: data, encoding: .utf8) ?? "(Binary: \(data.count) bytes)" diff --git a/iOS/Ringo/Ringo/Screens/VideoViewController.swift b/iOS/Ringo/Ringo/Screens/VideoViewController.swift new file mode 100644 index 00000000..ae959883 --- /dev/null +++ b/iOS/Ringo/Ringo/Screens/VideoViewController.swift @@ -0,0 +1,85 @@ +// +// VideoViewController.swift +// Ringo +// +// Created by 강진혁 on 9/3/24. +// + +import UIKit +import SnapKit +import WebRTC + +class VideoViewController: UIViewController { + + private let localVideoView = UIView() + private let backBtn = UIButton() + private let state = UILabel() + private let webRTCClient: WebRTCClient + + init(webRTCClient: WebRTCClient) { + self.webRTCClient = webRTCClient + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame) + let remoteRenderer = RTCMTLVideoView(frame: self.view.frame) + localRenderer.videoContentMode = .scaleAspectFill + remoteRenderer.videoContentMode = .scaleAspectFill + + self.webRTCClient.startCaptureLocalVideo(renderer: localRenderer) + self.webRTCClient.renderRemoteVideo(to: remoteRenderer) + + localVideoView.addSubview(localRenderer) + self.view.addSubview(remoteRenderer) + self.view.addSubview(state) + self.view.sendSubviewToBack(state) + view.addSubview(localVideoView) + view.addSubview(backBtn) + + view.backgroundColor = .black + localVideoView.backgroundColor = .systemBlue + localVideoView.layer.cornerRadius = 20 + state.text = "The caller's camera is turned off." + state.textColor = .lightGray + backBtn.configuration = .filled() + backBtn.configurationUpdateHandler = { btn in + btn.configuration?.image = UIImage(systemName: "x.circle.fill")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [.white,.clear])) + btn.configuration?.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 40) + btn.configuration?.buttonSize = .mini + btn.configuration?.cornerStyle = .capsule + btn.configuration?.baseBackgroundColor = .systemRed + btn.addTarget(self, action: #selector(self.backDidTap(_:)), for: .touchUpInside) + } + + localVideoView.snp.makeConstraints({ make in + make.trailing.equalTo(view.safeAreaLayoutGuide).inset(20) + make.bottom.equalTo(view.safeAreaLayoutGuide).inset(20) + make.width.equalToSuperview().multipliedBy(0.4) + make.height.equalToSuperview().multipliedBy(0.3) + }) + localRenderer.snp.makeConstraints { make in + make.edges.equalTo(localVideoView.safeAreaLayoutGuide) + } + backBtn.snp.makeConstraints { make in + make.leading.equalTo(view.safeAreaLayoutGuide).offset(20) + make.bottom.equalTo(view.safeAreaLayoutGuide).inset(20) + } + state.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview() + } + + } + + @IBAction private func backDidTap(_ sender: Any) { + self.webRTCClient.hideVideo() + self.dismiss(animated: true) + } +} diff --git a/iOS/Ringo/Ringo/Screens/ViewController.swift b/iOS/Ringo/Ringo/Screens/ViewController.swift index f8d15ff1..92f287df 100644 --- a/iOS/Ringo/Ringo/Screens/ViewController.swift +++ b/iOS/Ringo/Ringo/Screens/ViewController.swift @@ -43,17 +43,19 @@ class ViewController: UIViewController { view.addSubview(signupBtn) view.addSubview(email) view.addSubview(input_email) + + view.addSubview(stackView) + stackView.addArrangedSubview(passwd) + stackView.addArrangedSubview(input_passwd) + stackView.addArrangedSubview(error) view.addSubview(forgotPwBtn) + view.addSubview(signinBtn) view.addSubview(Text4) view.addSubview(google) view.addSubview(apple) view.addSubview(facebook) - view.addSubview(stackView) - stackView.addArrangedSubview(passwd) - stackView.addArrangedSubview(input_passwd) - stackView.addArrangedSubview(error) } func setUpValue() { @@ -64,7 +66,7 @@ class ViewController: UIViewController { Text1.text = "Sign in" Text1.font = .preferredFont(forTextStyle: .title1) - Text2.text = "If you don't have an account register \nYou can" + Text2.text = "If you don't have an account register" Text2.font = .preferredFont(forTextStyle: .body) Text3.text = "You can" @@ -74,19 +76,23 @@ class ViewController: UIViewController { signupBtn.setTitleColor(.systemBlue, for: .normal) signupBtn.titleLabel?.font = .systemFont(ofSize: 18) signupBtn.setTitleColor(.systemBlue.withAlphaComponent(0.5), for: .highlighted) + signupBtn.addTarget(self, action: #selector(onPressSignup(_:)), for: .touchUpInside) email.text = "E-mail" email.font = .preferredFont(forTextStyle: .body) input_email.placeholder = "Enter your email" input_email.borderStyle = .roundedRect - input_email.layer.borderWidth = 1.5 - input_email.layer.borderColor = UIColor(hexCode: "E2E8F0").cgColor - input_email.layer.cornerRadius = 5 +// input_email.layer.borderWidth = 1.5 +// input_email.layer.borderColor = UIColor(hexCode: "E2E8F0").cgColor +// input_email.layer.cornerRadius = 5 input_email.keyboardType = .emailAddress input_email.autocapitalizationType = .none input_email.autocorrectionType = .no input_email.spellCheckingType = .no + input_email.clearButtonMode = .unlessEditing + input_email.backgroundColor = .systemGray6 + input_email.delegate = self stackView.axis = .vertical stackView.alignment = .fill @@ -121,8 +127,9 @@ class ViewController: UIViewController { input_passwd.delegate = self input_passwd.autocorrectionType = .no input_passwd.spellCheckingType = .no + input_passwd.backgroundColor = .systemGray6 - error.layer.isHidden = true + error.isHidden = true error.setTitle(" Incorrect password. Please check your password.", for: .normal) error.setTitleColor(.red, for: .normal) error.titleLabel?.font = .systemFont(ofSize: 13) @@ -227,7 +234,14 @@ class ViewController: UIViewController { // 빈 화면 터치 시 키보드 내리기 override func touchesBegan(_ touches: Set, with event: UIEvent?) { view.endEditing(true) - } + } + + @objc func onPressSignup(_ sender: UIButton) { + let signupVC = SignupViewController() + signupVC.modalPresentationStyle = .fullScreen + signupVC.modalTransitionStyle = .crossDissolve + present(signupVC, animated: true) + } @objc func onPressSignin(_ sender: UIButton) { login() @@ -269,15 +283,23 @@ extension ViewController { func login() { guard let email = input_email.text else { return } - guard let passward = input_passwd.text else { return } + guard let password = input_passwd.text else { return } - SigninSercive.shared.login(email: email, password: passward) { response in + SigninSercive.shared.login(email: email, password: password) { response in switch response { case .success(let data): - guard let data = data as? String else { return } - if data == "success"{ -// UserDefaults.standard.set(data.data?.jwtToken, forKey: "jwtToken") + guard let data = data as? SigninDataResponse else { return } + if data.status == "success"{ + + UserManager.setData(value: email, key: .email) + UserManager.setData(value: password, key: .password) + UserManager.setData(value: data.data?.language, key: .language) + UserManager.setData(value: data.data?.accessToken, key: .accessToken) + UserManager.setData(value: data.data?.refreshToken, key: .refreshToken) + self.input_email.text = "" + self.input_passwd.text = "" + let nav = UINavigationController() nav.modalPresentationStyle = .fullScreen @@ -288,7 +310,13 @@ extension ViewController { nav.viewControllers = [controller] self.present(nav, animated: true, completion: nil) } else { - let alert = UIAlertController(title: data, message: "", preferredStyle: .alert) + + self.input_passwd.layer.borderWidth = 1 + self.input_passwd.layer.cornerRadius = 5 + self.input_passwd.layer.borderColor = UIColor.systemRed.cgColor + self.error.isHidden = false + + let alert = UIAlertController(title: data.status, message: data.message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "확인", style: .cancel, handler: nil)) // alert.addAction(UIAlertAction(title: "DEFAULT", style: .default, handler: nil)) // alert.addAction(UIAlertAction(title: "DESTRUCTIVE", style: .destructive, handler: nil)) @@ -305,14 +333,23 @@ extension ViewController { print("serverErr") case .networkFail: print("networkFail") + case .dataErr: + print("dataErr") } } } } -// MARK: +// MARK: - 키보드 사라지게 하기 extension ViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + if textField == input_passwd && !error.isHidden { + error.isHidden = true + input_passwd.layer.borderColor = UIColor.clear.cgColor + } + return true + } } diff --git a/iOS/Ringo/Ringo/SigninService.swift b/iOS/Ringo/Ringo/SigninService.swift index f7a8baa4..33b90fda 100644 --- a/iOS/Ringo/Ringo/SigninService.swift +++ b/iOS/Ringo/Ringo/SigninService.swift @@ -17,7 +17,6 @@ class SigninSercive { func login(email: String, password: String, completion: @escaping(NetworkResult) -> Void) { -// let url = "http://192.168.0.7:7080/member/login" //통신할 API 주소 let url = "https://4kringo.shop:8080/member/login" //통신할 API 주소 //HTTP Headers : 요청 헤더 @@ -25,10 +24,54 @@ class SigninSercive { //요청 바디 let body : Parameters = [ - "loginEmail" : email, - "password" : password + "email" : email, + "password" : password, + "token" : UserManager.getData(type: String.self, forKey: .refreshToken) ?? "" + ] + //요청서 //Request 생성 + //통신할 주소, HTTP메소드, 요청방식, 인코딩방식, 요청헤더 + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header) + + //request 시작 ,responseData를 호출하면서 데이터 통신 시작 + dataRequest.responseData{ + response in //데이터 통신의 결과가 response에 담기게 된다 + switch response.result { + case .success: //데이터 통신이 성공한 경우에 + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + +// 디버그 +// print(statusCode) +// print((response.response?.allHeaderFields)!) +// print(String(data: response.data!, encoding: .utf8)!) + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + + case .failure: + completion(.networkFail) + } + } + } + + func refresh(token: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/member/reissue" //통신할 API 주소 + + //HTTP Headers : 요청 헤더 + let header : HTTPHeaders = [ + "Content-Type" : "application/json", + "Refresh-Token" : token ] + //요청 바디 + let body : Parameters? = nil + //요청서 //Request 생성 //통신할 주소, HTTP메소드, 요청방식, 인코딩방식, 요청헤더 let dataRequest = AF.request(url, @@ -44,7 +87,56 @@ class SigninSercive { case .success: //데이터 통신이 성공한 경우에 guard let statusCode = response.response?.statusCode else {return} guard let value = response.value else {return} + guard let access = response.response?.value(forHTTPHeaderField: "AccessToken") else { + print("get access fail") + return } + guard let refresh = response.response?.value(forHTTPHeaderField: "RefreshToken") else { + print("get refresh fail") + return } + if statusCode < 300 { + UserManager.setData(value: access, key: .accessToken) + UserManager.setData(value: refresh, key: .refreshToken) + } + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + + func signup(email: String, password: String, name: String, languageCode: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/member/signup" //통신할 API 주소 + + //HTTP Headers : 요청 헤더 + let header : HTTPHeaders = ["Content-Type" : "application/json"] + + //요청 바디 + let body : Parameters = [ + "email" : email, + "password" : password, + "name" : name, + "language" : languageCode + ] + + //요청서 //Request 생성 + //통신할 주소, HTTP메소드, 요청방식, 인코딩방식, 요청헤더 + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header) + + //request 시작 ,responseData를 호출하면서 데이터 통신 시작 + dataRequest.responseData{ + response in //데이터 통신의 결과가 response에 담기게 된다 + switch response.result { + case .success: //데이터 통신이 성공한 경우에 + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} let networkResult = self.judgeStatus(by: statusCode, value) completion(networkResult) @@ -54,22 +146,145 @@ class SigninSercive { } } } + + func signout(refreshToken: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/member/logout" //통신할 API 주소 + + //HTTP Headers : 요청 헤더 + let header : HTTPHeaders = [ + "Content-Type" : "application/json", + "refreshToken" : refreshToken + ] + + //요청서 //Request 생성 + //통신할 주소, HTTP메소드, 요청방식, 인코딩방식, 요청헤더 + let dataRequest = AF.request(url, + method: .get, + parameters: nil, + encoding: JSONEncoding.default, + headers: header) + + //request 시작 ,responseData를 호출하면서 데이터 통신 시작 + dataRequest.responseData{ + response in //데이터 통신의 결과가 response에 담기게 된다 + switch response.result { + case .success: //데이터 통신이 성공한 경우에 + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + + case .failure: + completion(.networkFail) + } + } + } + + func update(email: String, password: String, newPassword: String, name: String, languageCode: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/member/update" //통신할 API 주소 + + //HTTP Headers : 요청 헤더 + let header : HTTPHeaders = ["Content-Type" : "application/json"] + + //요청 바디 + let body : Parameters = [ + "email" : email, + "password" : password, + "newPassword" : newPassword, + "name" : name, + "language" : languageCode + ] + + //요청서 //Request 생성 + //통신할 주소, HTTP메소드, 요청방식, 인코딩방식, 요청헤더 + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header, + interceptor: AuthInterceptor()) + + //request 시작 ,responseData를 호출하면서 데이터 통신 시작 + dataRequest.responseData{ + response in //데이터 통신의 결과가 response에 담기게 된다 + switch response.result { + case .success: //데이터 통신이 성공한 경우에 + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + + case .failure: + completion(.networkFail) + } + } + } + + func delete(email: String, password: String, completion: @escaping(NetworkResult) -> Void) + { + let url = "https://4kringo.shop:8080/member/delete" //통신할 API 주소 + + //HTTP Headers : 요청 헤더 + let header : HTTPHeaders = ["Content-Type" : "application/json"] + + //요청 바디 + let body : Parameters = [ + "email" : email, + "password" : password + ] + + //요청서 //Request 생성 + //통신할 주소, HTTP메소드, 요청방식, 인코딩방식, 요청헤더 + let dataRequest = AF.request(url, + method: .post, + parameters: body, + encoding: JSONEncoding.default, + headers: header, + interceptor: AuthInterceptor()) + + //request 시작 ,responseData를 호출하면서 데이터 통신 시작 + dataRequest.responseData{ + response in //데이터 통신의 결과가 response에 담기게 된다 + switch response.result { + case .success: //데이터 통신이 성공한 경우에 + guard let statusCode = response.response?.statusCode else {return} + guard let value = response.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + + case .failure: + completion(.networkFail) + } + } + } + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + print(statusCode) //디버그 switch statusCode { case ..<300 : return isVaildData(data: data) - case 400..<500 : return .pathErr + case 400 : return isVaildData(data: data) + case 401..<500 : return .pathErr case 500..<600 : return .serverErr default : return .networkFail } } //통신이 성공하고 원하는 데이터가 올바르게 들어왔을때 처리하는 함수 private func isVaildData(data: Data) -> NetworkResult { - // response가 string일 경우 - let decodedata = String(data: data, encoding: .utf8) -// let decoder = JSONDecoder() //서버에서 준 데이터를 Codable을 채택, response가 json일 경우 - guard let decodedData = /*try? decoder.decode(SigninDataResponse.self, from: data)*/ decodedata + + let decoder = JSONDecoder() //서버에서 준 데이터를 Codable을 채택, response가 json일 경우 + guard let decodedData = try? decoder.decode(SigninDataResponse.self, from: data) //데이터가 변환이 되게끔 Response 모델 구조체로 데이터를 변환해서 넣고, 그 데이터를 NetworkResult Success 파라미터로 전달 - else { return .pathErr } + else { + print("decode fail (SigninDataResponse)") + let decodedata = String(data: data, encoding: .utf8) + return .success(decodedata as Any) + } return .success(decodedData as Any) } diff --git a/iOS/Ringo/Ringo/UserManager.swift b/iOS/Ringo/Ringo/UserManager.swift new file mode 100644 index 00000000..513b3682 --- /dev/null +++ b/iOS/Ringo/Ringo/UserManager.swift @@ -0,0 +1,38 @@ +// +// UserManager.swift +// Ringo +// +// Created by 강진혁 on 7/3/24. +// + +import Foundation + +class UserManager { + enum UserDefaultsKeys: String, CaseIterable { + case email + case name + case language + case password + case accessToken + case refreshToken + case receiver + } + + static func setData(value: T, key: UserDefaultsKeys) { + UserDefaults.standard.set(value, forKey: key.rawValue) + } + + static func getData(type: T.Type, forKey: UserDefaultsKeys) -> T? { + let value = UserDefaults.standard.object(forKey: forKey.rawValue) as? T + return value + } + + static func removeData(key: UserDefaultsKeys) { + UserDefaults.standard.removeObject(forKey: key.rawValue) + } + static func resetData() { + for key in UserDefaults.standard.dictionaryRepresentation().keys { + UserDefaults.standard.removeObject(forKey: key.description) + } + } +} diff --git a/iOS/Ringo/Ringo/WebRTC/CallService.swift b/iOS/Ringo/Ringo/WebRTC/CallService.swift index 2934a7af..c9928fdb 100644 --- a/iOS/Ringo/Ringo/WebRTC/CallService.swift +++ b/iOS/Ringo/Ringo/WebRTC/CallService.swift @@ -19,4 +19,7 @@ class CallService { self.signalClient = SignalingClient(webSocket: NativeWebSocket(url: self.config.signalingServerUrl)) self.webRTCClient = WebRTCClient(iceServers: self.config.webRTCIceServers) } + func refresh() { + self.webRTCClient = WebRTCClient(iceServers: self.config.webRTCIceServers) + } } diff --git a/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift b/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift index 44a143b1..23f7c51d 100644 --- a/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift +++ b/iOS/Ringo/Ringo/WebRTC/Services/SignalingClient.swift @@ -12,10 +12,11 @@ import WebRTC protocol SignalClientDelegate: AnyObject { func signalClientDidConnect(_ signalClient: SignalingClient) func signalClientDidDisconnect(_ signalClient: SignalingClient) - func signalClient(_ signalClient: SignalingClient, didReceiveRemoteSdp sdp: RTCSessionDescription) + func signalClientDidForceDisconnect(_ signalClient: SignalingClient) + func signalClient(_ signalClient: SignalingClient, didReceiveRemoteSdp sdp: RTCSessionDescription, sender: String) func signalClient(_ signalClient: SignalingClient, didReceiveCandidate candidate: RTCIceCandidate) func signalClient(_ signalClient: SignalingClient, didReceiveCallResponse response: String) - func signalClient(_ signalClient: SignalingClient, didReceiveTranslation msg: String, language: String) + func signalClient(_ signalClient: SignalingClient, didReceiveTranslation msg: String) } final class SignalingClient { @@ -33,9 +34,12 @@ final class SignalingClient { self.webSocket.delegate = self self.webSocket.connect() } + func forceDisconnect() { + self.webSocket.forceDisconnect() + } - func store(id: String) { - let message = ["type":"store_user","name":id] + func store(user: String) { + let message = ["type":"store_user","name":user] do { let dataMessage = try self.encoder.encode(message) @@ -51,8 +55,8 @@ final class SignalingClient { } } - func startcall(id: String, target: String) { - let message = ["type":"start_call","name":id,"target":target] + func startcall(user: String, target: String) { + let message = ["type":"start_call","name":user,"target":target] do { let dataMessage = try self.encoder.encode(message) @@ -108,12 +112,16 @@ extension SignalingClient: WebSocketProviderDelegate { func webSocketDidDisconnect(_ webSocket: WebSocketProvider) { self.delegate?.signalClientDidDisconnect(self) - - // try to reconnect every two seconds - DispatchQueue.global().asyncAfter(deadline: .now() + 2) { - debugPrint("Trying to reconnect to signaling server...") - self.webSocket.connect() - } +// +// // try to reconnect every two seconds +// DispatchQueue.global().asyncAfter(deadline: .now() + 2) { +// debugPrint("Trying to reconnect to signaling server...") +// self.webSocket.connect() +// } + } + + func webSocketDidForceDisconnect(_ webSocket: any WebSocketProvider) { + self.delegate?.signalClientDidForceDisconnect(self) } func webSocket(_ webSocket: WebSocketProvider, didReceiveData data: Data) { @@ -143,7 +151,7 @@ extension SignalingClient: WebSocketProviderDelegate { // debugPrint(message) switch message.data { case .sdp(let sessionDescription): - self.delegate?.signalClient(self, didReceiveRemoteSdp: sessionDescription.rtcSessionDescription) + self.delegate?.signalClient(self, didReceiveRemoteSdp: sessionDescription.rtcSessionDescription, sender: message.name!) default: print("??") } @@ -159,7 +167,7 @@ extension SignalingClient: WebSocketProviderDelegate { case .translate_message: debugPrint("receive trans msg") debugPrint(message) - self.delegate?.signalClient(self, didReceiveTranslation: message.dataString(), language: message.target!) + self.delegate?.signalClient(self, didReceiveTranslation: message.dataString()) default: debugPrint("message.type is nothing") debugPrint(message) diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift index 7d88335f..1238785a 100644 --- a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/NativeWebSocket.swift @@ -62,6 +62,11 @@ class NativeWebSocket: NSObject, WebSocketProvider { self.socket = nil self.delegate?.webSocketDidDisconnect(self) } + func forceDisconnect() { + self.socket?.cancel() + self.socket = nil + self.delegate?.webSocketDidForceDisconnect(self) + } } @available(iOS 13.0, *) diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift index 051643b7..dd84d67c 100644 --- a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/StarscreamProvider.swift @@ -30,6 +30,10 @@ class StarscreamWebSocket: WebSocketProvider { func send(string: String) { self.socket.write(string: string) } + + func forceDisconnect() { + self.socket.forceDisconnect() + } } extension StarscreamWebSocket: Starscream.WebSocketDelegate { diff --git a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift index 1bd8b3eb..a5b75683 100644 --- a/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift +++ b/iOS/Ringo/Ringo/WebRTC/Services/WebSocketProvider/WebSocketProvider.swift @@ -13,10 +13,12 @@ protocol WebSocketProvider: AnyObject { func connect() func send(data: Data) func send(string: String) + func forceDisconnect() } protocol WebSocketProviderDelegate: AnyObject { func webSocketDidConnect(_ webSocket: WebSocketProvider) func webSocketDidDisconnect(_ webSocket: WebSocketProvider) + func webSocketDidForceDisconnect(_ webSocket: WebSocketProvider) func webSocket(_ webSocket: WebSocketProvider, didReceiveData data: Data) }