diff --git a/EasyCrypto.xcodeproj/project.pbxproj b/EasyCrypto.xcodeproj/project.pbxproj index 1ce23b8..bb00f69 100644 --- a/EasyCrypto.xcodeproj/project.pbxproj +++ b/EasyCrypto.xcodeproj/project.pbxproj @@ -119,7 +119,7 @@ A851A22929890C0B007F4CF9 /* WorkScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A22329890C0A007F4CF9 /* WorkScheduler.swift */; }; A851A22B29890C0B007F4CF9 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A22529890C0A007F4CF9 /* Configuration.swift */; }; A851A22C29890C0B007F4CF9 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A22629890C0A007F4CF9 /* APIError.swift */; }; - A851A22D29890C0B007F4CF9 /* BaseAPIDebuger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A22729890C0B007F4CF9 /* BaseAPIDebuger.swift */; }; + A851A22D29890C0B007F4CF9 /* APIDebuger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A22729890C0B007F4CF9 /* APIDebuger.swift */; }; A851A23329891243007F4CF9 /* URL + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A23229891243007F4CF9 /* URL + Extension.swift */; }; A851A2362989158F007F4CF9 /* MessageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A2352989158F007F4CF9 /* MessageHelper.swift */; }; A851A23829891867007F4CF9 /* HTTPURLResponse + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A851A23729891867007F4CF9 /* HTTPURLResponse + Extension.swift */; }; @@ -266,7 +266,7 @@ A851A22329890C0A007F4CF9 /* WorkScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkScheduler.swift; sourceTree = ""; }; A851A22529890C0A007F4CF9 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; A851A22629890C0A007F4CF9 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; - A851A22729890C0B007F4CF9 /* BaseAPIDebuger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseAPIDebuger.swift; sourceTree = ""; }; + A851A22729890C0B007F4CF9 /* APIDebuger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIDebuger.swift; sourceTree = ""; }; A851A23229891243007F4CF9 /* URL + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL + Extension.swift"; sourceTree = ""; }; A851A2352989158F007F4CF9 /* MessageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHelper.swift; sourceTree = ""; }; A851A23729891867007F4CF9 /* HTTPURLResponse + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTTPURLResponse + Extension.swift"; sourceTree = ""; }; @@ -976,7 +976,7 @@ isa = PBXGroup; children = ( A851A22629890C0A007F4CF9 /* APIError.swift */, - A851A22729890C0B007F4CF9 /* BaseAPIDebuger.swift */, + A851A22729890C0B007F4CF9 /* APIDebuger.swift */, A851A22529890C0A007F4CF9 /* Configuration.swift */, ); path = Utility; @@ -1234,7 +1234,7 @@ 027A1B0829D0830C008B10D2 /* Log.swift in Sources */, A82CB996298A7AB900699C22 /* URLRequest + Extension.swift in Sources */, 020EE2AA29D8199F0065F5EB /* CoreDataDeletePublisher.swift in Sources */, - A851A22D29890C0B007F4CF9 /* BaseAPIDebuger.swift in Sources */, + A851A22D29890C0B007F4CF9 /* APIDebuger.swift in Sources */, A851A2362989158F007F4CF9 /* MessageHelper.swift in Sources */, A851A22029890BDC007F4CF9 /* Encoding.swift in Sources */, 02BC669029B88CF600785196 /* CoinDetailHeaderView.swift in Sources */, diff --git a/EasyCrypto/Core/Components/TabItemView/TabItemView.swift b/EasyCrypto/Core/Components/TabItemView/TabItemView.swift index d7b957c..977cb7c 100644 --- a/EasyCrypto/Core/Components/TabItemView/TabItemView.swift +++ b/EasyCrypto/Core/Components/TabItemView/TabItemView.swift @@ -10,7 +10,7 @@ import SwiftUI struct TabItemView: View { @Binding var index: Int - + var titles = ["Coins", "Whishlists"] private let leftOffset: CGFloat = 0.1 diff --git a/EasyCrypto/Core/Networking/Client/NetworkClient.swift b/EasyCrypto/Core/Networking/Client/NetworkClient.swift index 8ea3ce1..0fd936b 100644 --- a/EasyCrypto/Core/Networking/Client/NetworkClient.swift +++ b/EasyCrypto/Core/Networking/Client/NetworkClient.swift @@ -16,13 +16,11 @@ final class NetworkClient: NetworkClientProtocol { /// Default: `URLSession(configuration: .shared)`. /// let session: URLSession - let debugger: BaseAPIDebuger - var subscriber = Set() + let logging: Logging - init(session: URLSession = .shared, - debugger: BaseAPIDebuger = BaseAPIDebuger()) { + init(session: URLSession = .shared, loggin: Logging = APIDebugger()) { self.session = session - self.debugger = debugger + self.logging = loggin } @discardableResult @@ -31,6 +29,7 @@ final class NetworkClient: NetworkClientProtocol { scheduler: T, responseObject type: M.Type) -> AnyPublisher where M: Decodable, T: Scheduler { let urlRequest = request.buildURLRequest() + self.logging.logRequest(request: urlRequest) return publisher(request: urlRequest) .receive(on: scheduler) .tryMap { result, _ -> Data in @@ -47,6 +46,7 @@ final class NetworkClient: NetworkClientProtocol { return self.session.dataTaskPublisher(for: request) .mapError { APIError.urlError($0) } .map { response -> AnyPublisher<(data: Data, response: URLResponse), APIError> in + self.logging.logResponse(response: response.response, data: response.data) guard let httpResponse = response.response as? HTTPURLResponse else { return Fail(error: APIError.invalidResponse(httpStatusCode: 0)) .eraseToAnyPublisher() diff --git a/EasyCrypto/Core/Networking/Client/NetworkClientProtocol.swift b/EasyCrypto/Core/Networking/Client/NetworkClientProtocol.swift index 52164ce..c26c29f 100644 --- a/EasyCrypto/Core/Networking/Client/NetworkClientProtocol.swift +++ b/EasyCrypto/Core/Networking/Client/NetworkClientProtocol.swift @@ -9,7 +9,7 @@ import Foundation import Combine -typealias BaseAPIProtocol = NetworkClientProtocol & DebuggerProtocol +typealias BaseAPIProtocol = NetworkClientProtocol typealias AnyPublisherResult = AnyPublisher @@ -23,15 +23,13 @@ protocol NetworkClientProtocol: AnyObject { @available(iOS 13.0, *) @discardableResult - // swiftlint:disable line_length func perform(with request: RequestBuilder, decoder: JSONDecoder, scheduler: T, responseObject type: M.Type) -> AnyPublisher where M: Decodable, T: Scheduler } -protocol DebuggerProtocol { - var debugger: BaseAPIDebuger { get } +protocol Logging { + func logRequest(request: URLRequest) + func logResponse(response: URLResponse?, data: Data?) } - -// swiftlint:enable line_length diff --git a/EasyCrypto/Core/Networking/Type/HTTPMethod.swift b/EasyCrypto/Core/Networking/Type/HTTPMethod.swift index 44e9990..6a3e643 100644 --- a/EasyCrypto/Core/Networking/Type/HTTPMethod.swift +++ b/EasyCrypto/Core/Networking/Type/HTTPMethod.swift @@ -23,6 +23,7 @@ public enum HTTPMethod: Equatable { The DELETE method deletes the specified resource. */ case delete + var name: String { switch self { case .get: diff --git a/EasyCrypto/Core/Networking/Utility/APIDebuger.swift b/EasyCrypto/Core/Networking/Utility/APIDebuger.swift new file mode 100644 index 0000000..0da7b1d --- /dev/null +++ b/EasyCrypto/Core/Networking/Utility/APIDebuger.swift @@ -0,0 +1,51 @@ +// +// BaseAPIDebuger.swift +// Amadeus +// +// Created by Mehran on 6/4/1401 AP. +// + +import Foundation + +struct APIDebugger: Logging { + + func logRequest(request: URLRequest) { + print("--- API Request ---") + print("URL: \(request.url?.absoluteString ?? "N/A")") + print("Method: \(request.httpMethod ?? "N/A")") + if let headers = request.allHTTPHeaderFields { + print("Headers:") + for (key, value) in headers { + print("\t\(key): \(value)") + } + } + if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) { + print("Body:") + print(bodyString) + } + print("-------------------") + } + + func logResponse(response: URLResponse?, data: Data?) { + guard let httpResponse = response as? HTTPURLResponse else { + print("--- API Response ---") + print("Invalid or no response received.") + print("--------------------") + return + } + + print("--- API Response ---") + print("Status Code: \(httpResponse.statusCode)") + if let headers = httpResponse.allHeaderFields as? [String: String] { + print("Headers:") + for (key, value) in headers { + print("\t\(key): \(value)") + } + } + if let responseData = data, let responseString = String(data: responseData, encoding: .utf8) { + print("Response Body:") + print(responseString) + } + print("--------------------") + } +} diff --git a/EasyCrypto/Core/Networking/Utility/BaseAPIDebuger.swift b/EasyCrypto/Core/Networking/Utility/BaseAPIDebuger.swift deleted file mode 100644 index aa743e8..0000000 --- a/EasyCrypto/Core/Networking/Utility/BaseAPIDebuger.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// BaseAPIDebuger.swift -// Amadeus -// -// Created by Mehran on 6/4/1401 AP. -// - -import Foundation - -struct BaseAPIDebuger { - - func log(request: URLRequest?, error: Error?) { - - debugPrint("", terminator: "\n\n") - debugPrint("--------------- Request Log Starts ---------------", terminator: "\n\n") - if let request = request, let allHTTPHeaderFields = request.allHTTPHeaderFields { - if let url = request.url { - debugPrint("Request URL - \(String(describing: url))", terminator: "\n") - } - if let body = request.httpBody, let json = body.printableJSON { - debugPrint("Request Body -", terminator: "\n") - debugPrint(json, terminator: "\n") - } else { - debugPrint("", terminator: "\n") - } - debugPrint("--------------- Request Headers ---------------", terminator: "\n\n") - for (headerKey, headerValue) in allHTTPHeaderFields { - debugPrint("\(headerKey) - \(headerValue)", terminator: "\n") - } - } - debugPrint("", terminator: "\n\n") - debugPrint("--------------- Response Headers ---------------", terminator: "\n\n") - - if let error = error, let errorFound = error as NSError? { - debugPrint("--------------- Error ---------------", terminator: "\n\n") - debugPrint("Error Code - \(errorFound.code)", terminator: "\n") - debugPrint("Error Domain - \(errorFound.domain)", terminator: "\n") - debugPrint("Error UserInfo - \(errorFound.userInfo)", terminator: "\n\n") - } - debugPrint("--------------- Request Log Ends ---------------", terminator: "\n\n") - } -} - -extension Data { - var printableJSON: NSString? { - var jsonStr: NSString? - if let jsonObject = try? JSONSerialization.jsonObject(with: self, options: []) { - if let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]) { - jsonStr = NSString(data: data, - encoding: String.Encoding.utf8.rawValue) - } - } - return jsonStr - } -} diff --git a/EasyCrypto/Presentation/CoinDetail/View/CoinDetailView.swift b/EasyCrypto/Presentation/CoinDetail/View/CoinDetailView.swift index c8915e5..9fd6949 100644 --- a/EasyCrypto/Presentation/CoinDetail/View/CoinDetailView.swift +++ b/EasyCrypto/Presentation/CoinDetail/View/CoinDetailView.swift @@ -20,7 +20,7 @@ struct CoinDetailView: Coordinatable { @State private var isLoading: Bool = false @State private var presentAlert = false - @State private var alertMessage: String = "" + @State private var alertMessage: String = .empty let subscriber = Cancelable() @@ -70,7 +70,7 @@ struct CoinDetailView: Coordinatable { .navigationBarTitle(String.empty) .navigationBarHidden(true) .onAppear { - self.viewModel.apply(.onAppear(id: self.id.orWhenNilOrEmpty(""))) + self.viewModel.apply(.onAppear(id: self.id.orWhenNilOrEmpty(.empty))) } .handleViewModelState(viewModel: viewModel, isLoading: $isLoading, diff --git a/EasyCrypto/Presentation/Detail/View/CoinDetail.swift b/EasyCrypto/Presentation/Detail/View/CoinDetail.swift index 200e549..ce18696 100644 --- a/EasyCrypto/Presentation/Detail/View/CoinDetail.swift +++ b/EasyCrypto/Presentation/Detail/View/CoinDetail.swift @@ -16,7 +16,7 @@ struct CoinDetailAreaView: View { let marketCapFormat = item.marketCap?.formatUsingAbbrevation() CoinDetailCell(title: Constants.PlaceHolder.marketCap, price: marketCapFormat.orWhenNilOrEmpty(.empty)) - if let price24Hours = CurrencyFormatter.sharedInstance.string(from: item.priceChange24H?.toNSNumber ?? 0) { + if let price24Hours = CurrencyFormatter.shared.string(from: item.priceChange24H?.toNSNumber ?? 0) { CoinDetailCell(title: Constants.PlaceHolder.volume24, price: price24Hours) } @@ -28,11 +28,11 @@ struct CoinDetailAreaView: View { CoinDetailCell(title: Constants.PlaceHolder.totalSupply, price: totalSupply) } - if let low24H = CurrencyFormatter.sharedInstance.string(from: item.low24H?.toNSNumber ?? 0) { + if let low24H = CurrencyFormatter.shared.string(from: item.low24H?.toNSNumber ?? 0) { CoinDetailCell(title: Constants.PlaceHolder.low24h, price: low24H) } - if let high24H = CurrencyFormatter.sharedInstance.string(from: item.high24H?.toNSNumber ?? 0) { + if let high24H = CurrencyFormatter.shared.string(from: item.high24H?.toNSNumber ?? 0) { CoinDetailCell(title: Constants.PlaceHolder.high24h, price: high24H) } diff --git a/EasyCrypto/Presentation/Detail/View/PriceView.swift b/EasyCrypto/Presentation/Detail/View/PriceView.swift index 610fd49..e6b6037 100644 --- a/EasyCrypto/Presentation/Detail/View/PriceView.swift +++ b/EasyCrypto/Presentation/Detail/View/PriceView.swift @@ -39,7 +39,7 @@ struct PriceView: View { Spacer() } HStack { - let price = CurrencyFormatter.sharedInstance.string(from: item.currentPrice?.toNSNumber ?? 0)! + let price = CurrencyFormatter.shared.string(from: item.currentPrice?.toNSNumber ?? 0)! Text(price) .foregroundColor(Color.white) .font(FontManager.headLine) @@ -49,7 +49,7 @@ struct PriceView: View { } .padding(.top) HStack { - if let priceChange = CurrencyFormatter.sharedInstance.string(from: item.priceChangePercentage24H?.toNSNumber ?? 0) { + if let priceChange = CurrencyFormatter.shared.string(from: item.priceChangePercentage24H?.toNSNumber ?? 0) { Text(priceChange) .foregroundColor(item.priceChangePercentage24H?.sign == .minus ? Color.red : Color.lightGreen) .font(FontManager.title) diff --git a/EasyCrypto/Presentation/Main/View/CryotoCell.swift b/EasyCrypto/Presentation/Main/View/CryotoCell.swift index 33e4d89..238b36f 100644 --- a/EasyCrypto/Presentation/Main/View/CryotoCell.swift +++ b/EasyCrypto/Presentation/Main/View/CryotoCell.swift @@ -26,9 +26,9 @@ struct CryptoCellView: View { .padding(.leading, 5) Spacer() - + VStack(alignment: .trailing, spacing: 5) { - if let price = CurrencyFormatter.sharedInstance.string(from: item.currentPrice?.toNSNumber ?? 0) { + if let price = CurrencyFormatter.shared.string(from: item.currentPrice?.toNSNumber ?? 0) { Text(price) .foregroundColor(Color.white) .font(FontManager.body) diff --git a/EasyCrypto/Presentation/Main/View/MainView.swift b/EasyCrypto/Presentation/Main/View/MainView.swift index 1a4b5c5..12493c3 100644 --- a/EasyCrypto/Presentation/Main/View/MainView.swift +++ b/EasyCrypto/Presentation/Main/View/MainView.swift @@ -25,7 +25,7 @@ struct MainView: Coordinatable { @State private var searchText: String = .empty @State private var isLoading: Bool = false @State private var presentAlert = false - @State private var alertMessage: String = "" + @State private var alertMessage: String = .empty let subscriber = Cancelable() diff --git a/EasyCrypto/Support/ViewModifier/HandleViewModelStateModifier.swift b/EasyCrypto/Support/ViewModifier/HandleViewModelStateModifier.swift index 423ec52..b6fdd77 100644 --- a/EasyCrypto/Support/ViewModifier/HandleViewModelStateModifier.swift +++ b/EasyCrypto/Support/ViewModifier/HandleViewModelStateModifier.swift @@ -23,7 +23,7 @@ struct HandleViewModelStateModifier: ViewModifier { isLoading = true case .dismissAlert: isLoading = false - alertMessage = "" + alertMessage = .empty presentAlert = false case .emptyStateHandler(let message): isLoading = false