From 3de76138fc13dc8691144aa78ac98b43729d2d58 Mon Sep 17 00:00:00 2001 From: BilalKhanWDI Date: Tue, 7 May 2024 11:12:59 +0530 Subject: [PATCH] - Added the network library with proper versioning - Made the network adapter - Made the config file to hold the url auth --- WOKA/Assets/Fonts/FontCustom.swift | 8 + WOKA/Authentication/Model/UserDataDM.swift | 8 + WOKA/Config.xcconfig | 9 + WOKA/Constants K/ConstantString.swift | 23 +++ .../ExtensionVCToastAlert.swift | 8 + .../ActivityToast&Indicator/LLSpinner.swift | 55 ++++++ .../ActivityToast&Indicator/Toast.swift | 8 + .../ActivityToast&Indicator/Utilities.swift | 108 +++++++++++ .../Helpers/UIApplication/UIApplication.swift | 22 +++ WOKA/Network Adapter/APIEndPoints.swift | 8 + WOKA/Network Adapter/BaseResponseModel.swift | 8 + WOKA/Network Adapter/NetworkManager.swift | 173 ++++++++++++++++++ .../Network Adapter/NetworkReachibility.swift | 43 +++++ WOKA/Network Adapter/QueueHelper.swift | 25 +++ WOKA/Network Adapter/ValueWrapper.swift | 98 ++++++++++ 15 files changed, 604 insertions(+) create mode 100644 WOKA/Assets/Fonts/FontCustom.swift create mode 100644 WOKA/Authentication/Model/UserDataDM.swift create mode 100644 WOKA/Config.xcconfig create mode 100644 WOKA/Constants K/ConstantString.swift create mode 100644 WOKA/Helpers/ActivityToast&Indicator/ExtensionVCToastAlert.swift create mode 100644 WOKA/Helpers/ActivityToast&Indicator/LLSpinner.swift create mode 100644 WOKA/Helpers/ActivityToast&Indicator/Toast.swift create mode 100644 WOKA/Helpers/ActivityToast&Indicator/Utilities.swift create mode 100644 WOKA/Helpers/UIApplication/UIApplication.swift create mode 100644 WOKA/Network Adapter/APIEndPoints.swift create mode 100644 WOKA/Network Adapter/BaseResponseModel.swift create mode 100644 WOKA/Network Adapter/NetworkManager.swift create mode 100644 WOKA/Network Adapter/NetworkReachibility.swift create mode 100644 WOKA/Network Adapter/QueueHelper.swift create mode 100644 WOKA/Network Adapter/ValueWrapper.swift diff --git a/WOKA/Assets/Fonts/FontCustom.swift b/WOKA/Assets/Fonts/FontCustom.swift new file mode 100644 index 0000000..ab312dd --- /dev/null +++ b/WOKA/Assets/Fonts/FontCustom.swift @@ -0,0 +1,8 @@ +// +// FontCusto.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation diff --git a/WOKA/Authentication/Model/UserDataDM.swift b/WOKA/Authentication/Model/UserDataDM.swift new file mode 100644 index 0000000..d244a6f --- /dev/null +++ b/WOKA/Authentication/Model/UserDataDM.swift @@ -0,0 +1,8 @@ +// +// UserDataDM.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation diff --git a/WOKA/Config.xcconfig b/WOKA/Config.xcconfig new file mode 100644 index 0000000..3bb5153 --- /dev/null +++ b/WOKA/Config.xcconfig @@ -0,0 +1,9 @@ +// +// Config.xcconfig +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 diff --git a/WOKA/Constants K/ConstantString.swift b/WOKA/Constants K/ConstantString.swift new file mode 100644 index 0000000..acdc0d0 --- /dev/null +++ b/WOKA/Constants K/ConstantString.swift @@ -0,0 +1,23 @@ +// +// File.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation + +extension K{ + + struct ConstantString{ + static let dollar = "$" + + static let passRequirement = "- Be at least 8 characters in length. \n- Contain both upper and lowercase alphabetic characters (e.g. A-Z, a-z). \n- Have at least one numerical character (e.g. 0-9). \n- Have at least one special character (e.g. ~!@#$%^&*()_-+=)." + + static let unRecognised = "Unreccognised error" + + static let noInternet = "Make sure you are connected to the internet!" + + static let minAge = "Minimum age requirement is 18 or above" + } +} diff --git a/WOKA/Helpers/ActivityToast&Indicator/ExtensionVCToastAlert.swift b/WOKA/Helpers/ActivityToast&Indicator/ExtensionVCToastAlert.swift new file mode 100644 index 0000000..0ccb993 --- /dev/null +++ b/WOKA/Helpers/ActivityToast&Indicator/ExtensionVCToastAlert.swift @@ -0,0 +1,8 @@ +// +// ExtensionVCToastAlert.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation diff --git a/WOKA/Helpers/ActivityToast&Indicator/LLSpinner.swift b/WOKA/Helpers/ActivityToast&Indicator/LLSpinner.swift new file mode 100644 index 0000000..2315c3a --- /dev/null +++ b/WOKA/Helpers/ActivityToast&Indicator/LLSpinner.swift @@ -0,0 +1,55 @@ +// +// File.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import UIKit + +open class LLSpinner { + internal static var spinnerView: UIActivityIndicatorView? + + public static var style: UIActivityIndicatorView.Style = .large + public static var backgroundColor: UIColor = UIColor(white: 0, alpha: 0.6) + + + internal static var touchHandler: (() -> Void)? + + public static func spin(style: UIActivityIndicatorView.Style = style, backgroundColor: UIColor = backgroundColor, touchHandler: (() -> Void)? = nil) { + if spinnerView == nil, + let window = UIApplication.shared.mainKeyWindow { + let frame = UIScreen.main.bounds + + spinnerView = UIActivityIndicatorView(frame: frame) + spinnerView?.center = CGPoint(x: window.frame.size.width / 2, + y: window.frame.size.height / 2) + spinnerView!.backgroundColor = backgroundColor + spinnerView!.style = style + spinnerView!.color = .white + window.addSubview(spinnerView!) + spinnerView!.startAnimating() + + } + + if touchHandler != nil { + self.touchHandler = touchHandler + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(runTouchHandler)) + spinnerView!.addGestureRecognizer(tapGestureRecognizer) + } + } + + @objc internal static func runTouchHandler() { + if touchHandler != nil { + touchHandler!() + } + } + + public static func stop() { + if let _ = spinnerView { + spinnerView!.stopAnimating() + spinnerView!.removeFromSuperview() + spinnerView = nil + } + } +} diff --git a/WOKA/Helpers/ActivityToast&Indicator/Toast.swift b/WOKA/Helpers/ActivityToast&Indicator/Toast.swift new file mode 100644 index 0000000..a6f38bc --- /dev/null +++ b/WOKA/Helpers/ActivityToast&Indicator/Toast.swift @@ -0,0 +1,8 @@ +// +// Toast.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation diff --git a/WOKA/Helpers/ActivityToast&Indicator/Utilities.swift b/WOKA/Helpers/ActivityToast&Indicator/Utilities.swift new file mode 100644 index 0000000..3d380db --- /dev/null +++ b/WOKA/Helpers/ActivityToast&Indicator/Utilities.swift @@ -0,0 +1,108 @@ +// +// File.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import UIKit + +class Utilities{ + let activityIndicator = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium) + +// static func setupProgressHUD() { +// let loaderColor = UIColor.green +// let backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) +// +// SVProgressHUD.setInfoImage(UIImage(named: "AppLogo")!) +// SVProgressHUD.setBackgroundColor(backgroundColor) +// SVProgressHUD.setForegroundColor(loaderColor) +// // don't let user press anything on screen while SVProgessHUD is active +// SVProgressHUD.setDefaultMaskType(.black) +// } + + static func startProgressHUD(progress: Float? = nil) { + LLSpinner.spin() +// if let progress = progress { +// SVProgressHUD.showProgress(progress) +// } else { +// SVProgressHUD.show() +// } + } + + + static func dismissProgressHUD() { + DispatchQueue.main.async { + LLSpinner.stop() + } + } + + static func stopActivity(view : UIView){ + Utilities().activityIndicator.stopAnimating() + } + + static func alertWithBtn(title : String , msgBody : String, okBtnStr : String?,vc : UIViewController){ + let alert = UIAlertController(title: title, message: msgBody, preferredStyle: .alert) + + let titleAttrString = NSMutableAttributedString(string: title != "" ? (title + "\n") : "", attributes: [NSAttributedString.Key.font: UIFont(name: "Nunito-Medium", size: 20)! as Any]) + alert.setValue(titleAttrString, forKey: "attributedTitle") + + + var messageMutableString = NSMutableAttributedString() + messageMutableString = NSMutableAttributedString(string: msgBody as String, attributes: [NSAttributedString.Key.font:UIFont(name: "Nunito-Regular", size: 18)!]) + messageMutableString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.black, range: NSRange(location:0,length:msgBody.count)) + alert.setValue(messageMutableString, forKey: "attributedMessage") + + let okAction = UIAlertAction(title: okBtnStr ?? "OK", style: .default, handler: nil) + okAction.setValue(UIColor.appColor(.AppBaseBlueColor), forKey: "titleTextColor") + + alert.addAction(okAction) + + vc.present(alert, animated: true, completion: nil) + } + + static func alertWithBtnCompletion(title : String , msgBody : String, okBtnStr : String?,vc : UIViewController ,onCompletion : @escaping (Bool) -> Void){ + let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert) + let okAction = UIAlertAction(title: okBtnStr ?? "OK", style: .default, handler: {_ in + onCompletion(true) + }) + + let messageAttributes = [NSAttributedString.Key.font: UIFont(name: "Nunito-Regular", size: 18)!, NSAttributedString.Key.foregroundColor: UIColor.black] + let messageString = NSAttributedString(string: msgBody, attributes: messageAttributes) + + okAction.setValue(UIColor.appColor(.AppBaseBlueColor), forKey: "titleTextColor") + + alert.addAction(okAction) + alert.setValue(messageString, forKey: "attributedMessage") + + vc.present(alert, animated: true, completion: nil) + } + + static func reportBug(message: String, line: Int = #line, file: String = #file) { + print("Line: \(line): File \(file): \(message)") + //TODO: Send report to ferofly + } + + static func alert(line: Int = #line, file: String = #file, title: String, message: String, viewController: UIViewController, toastTime: TimeInterval = 0, onCompletion: (() -> Void)? = nil) { + print("Line: \(line): File \(file): \(title): \(message)") + + let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) + + if toastTime == 0 { + let action = UIAlertAction(title: "Ok", style: .default) { (action) in + alertVC.dismiss(animated: true, completion: onCompletion) + } + alertVC.addAction(action) + } + + DispatchQueue.main.async { + viewController.present(alertVC, animated: true, completion: nil) + + if toastTime > 0 { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + toastTime) { + alertVC.dismiss(animated: true, completion: onCompletion) + } + } + } + } +} diff --git a/WOKA/Helpers/UIApplication/UIApplication.swift b/WOKA/Helpers/UIApplication/UIApplication.swift new file mode 100644 index 0000000..8e19289 --- /dev/null +++ b/WOKA/Helpers/UIApplication/UIApplication.swift @@ -0,0 +1,22 @@ +// +// File.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import UIKit + +extension UIApplication { + var mainKeyWindow: UIWindow? { + get { + if #available(iOS 13, *) { + return connectedScenes + .flatMap { ($0 as? UIWindowScene)?.windows ?? [] } + .first { $0.isKeyWindow } + } else { + return keyWindow + } + } + } +} diff --git a/WOKA/Network Adapter/APIEndPoints.swift b/WOKA/Network Adapter/APIEndPoints.swift new file mode 100644 index 0000000..e77f3f3 --- /dev/null +++ b/WOKA/Network Adapter/APIEndPoints.swift @@ -0,0 +1,8 @@ +// +// APIEndPoints.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation diff --git a/WOKA/Network Adapter/BaseResponseModel.swift b/WOKA/Network Adapter/BaseResponseModel.swift new file mode 100644 index 0000000..fc967c0 --- /dev/null +++ b/WOKA/Network Adapter/BaseResponseModel.swift @@ -0,0 +1,8 @@ +// +// BaseResponseModel.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation diff --git a/WOKA/Network Adapter/NetworkManager.swift b/WOKA/Network Adapter/NetworkManager.swift new file mode 100644 index 0000000..acd13dc --- /dev/null +++ b/WOKA/Network Adapter/NetworkManager.swift @@ -0,0 +1,173 @@ +// +// NetworkManager.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Alamofire + +class NetworkManager{ + + static let shareInstance = NetworkManager() + + private init() {} + + enum APIError: Error { + case networkError + case noNetwork(message:String) + case invalidURL + case parameterEncodingFailed + case responseValidationFailed + case unknown(message: String) + case custom(message: String) + } + + /// This function will do the network call for HTTPMethod & Encoding is URLEncoding with contentType ["application/json"] + /// + /// + /// - Warning: The returned string is not localized. + /// + /// Usage: + /// + /// Alamofire network call Generic (.get , .post, .put.). + /// + /// - Parameter (header : HTTPHeaders , Params :[String : Any] , URL , Dedocable Generic T Struct. , queue : dispatchQueue decide) + /// - NOTE Oncompletion will be called on main thread so no need to specify the main thread for UI updates + /// - Returns: This function returns a GENERIC response base on the T Model & APIError . + func apiRequest( + url: URLConvertible, + method: HTTPMethod, + parameters: Parameters? = nil, + encoding: ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + queue: DispatchQueue = QueueHelper.background, + completionHandler: @escaping (Result) -> Void + ) { + // Stop monitoring network reachability + NetworkReachibility.shared.stopMonitoring() + + // Execute the request on the specified queue + queue.async { + AF.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers, requestModifier: { $0.timeoutInterval = 30 }) + .validate(statusCode: 200..<300) + .responseDecodable(of: T.self) { response in + switch response.result { + case .success(let value): + // Handle successful response on the main thread + DispatchQueue.main.async { + completionHandler(.success(value)) + } + case .failure(let error): + // Handle failure cases + if let afError = error as? AFError { + switch afError { + case .sessionTaskFailed(let urlError): + if urlError._code == -1020 || urlError._code == -1009 { + // Handle network error on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.noNetwork(message: K.ConstantString.noInternet))) + } + } else { + // Handle other network errors on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.networkError)) + } + } + case .invalidURL: + // Handle invalid URL error on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.invalidURL)) + } + case .parameterEncodingFailed: + // Handle parameter encoding failure on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.parameterEncodingFailed)) + } + case .responseValidationFailed: + // Handle response validation failure on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.responseValidationFailed)) + } + default: + // Handle other network errors on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.networkError)) + } + } + } else { + // Handle unrecognized errors on the main thread + DispatchQueue.main.async { + completionHandler(.failure(.unknown(message: K.ConstantString.unRecognised))) + } + } + } + } + } + } + +} + +// func handleAFError(error: Error)-> Int? { +// if let afError = error as? AFError { +// // This error is of type AFError, and you can access its properties. +// let errorCode = afError.responseCode +// return errorCode +// } else { +// // Handle other types of errors or unsupported cases +// return nil +// } +// } +// +// func uploadFormData( +// url: URLConvertible, +// method: HTTPMethod, +// headers: HTTPHeaders? = nil, +// params : [String : Any]?, +// image : UIImage?, +// formData: [MultipartFormData], +// completionHandler: @escaping (Result) -> Void +// ) { +// +// let imageData = image?.jpegData(compressionQuality: 0.4)! +// let imageKey = "contact_photo" // Change me +// AF.upload(multipartFormData: { multiPart in +// for (key, value) in (params ?? [:]) { +// if let arrayObj = value as? [Any] { +// for index in 0..) in +// switch response.result { +// case .success(let value): +// completionHandler(.success(value)) +// case .failure(let error): +// if let statusCode = response.response?.statusCode { +// switch statusCode { +// case 400..<500: +// // Handle client-side errors (4xx) +// completionHandler(.failure(.custom(message: "Client-side error: \(statusCode)"))) +// case 500..<600: +// // Handle server-side errors (5xx) +// completionHandler(.failure(.custom(message: "Server-side error: \(statusCode)"))) +// default: +// completionHandler(.failure(.unknown)) +// } +// } else { +// completionHandler(.failure(.unknown)) +// } +// } +// } +// } +//} diff --git a/WOKA/Network Adapter/NetworkReachibility.swift b/WOKA/Network Adapter/NetworkReachibility.swift new file mode 100644 index 0000000..8cc7778 --- /dev/null +++ b/WOKA/Network Adapter/NetworkReachibility.swift @@ -0,0 +1,43 @@ +// +// NetworkReachibility.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Alamofire + +class NetworkReachibility{ + + static let shared = NetworkReachibility() + private init(){} + let manager = NetworkReachabilityManager(host: "www.apple.com") + fileprivate var isInternetReachable = false + + func startMonitoring(onCompletion : @escaping (Bool) -> Void) { + manager?.startListening(onQueue: DispatchQueue.main, onUpdatePerforming: { (status) in + switch status { + case .notReachable: + print("network connection status - lost") + self.isInternetReachable = false + onCompletion(false) + case .reachable(.ethernetOrWiFi): + print("network connection status - ethernet/WiFI") + self.isInternetReachable = true + onCompletion(true) + case .reachable(.cellular): + print("network connection status - cellular") + self.isInternetReachable = true + onCompletion(true) + default: + self.isInternetReachable = false + onCompletion(false) + break + } + }) + } + + func stopMonitoring(){ + manager?.stopListening() + } +} diff --git a/WOKA/Network Adapter/QueueHelper.swift b/WOKA/Network Adapter/QueueHelper.swift new file mode 100644 index 0000000..869a83a --- /dev/null +++ b/WOKA/Network Adapter/QueueHelper.swift @@ -0,0 +1,25 @@ +// +// File.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + +import Foundation + +/* + this are the predefined queuehelper + modify as required + */ + +class QueueHelper { + static let queue = DispatchQueue(label: "com.simpliTend") + static let queueHome = DispatchQueue(label: "com.simpliTend.home") + static let group = DispatchGroup() + static let group2 = DispatchGroup() + static let group3 = DispatchGroup() + static let background = DispatchQueue.global(qos: .background) + static let utilityGroup = DispatchQueue.global(qos: .utility) + static let userInteractive = DispatchQueue.global(qos: .userInteractive) + static let semaphore = DispatchSemaphore(value: 1) +} diff --git a/WOKA/Network Adapter/ValueWrapper.swift b/WOKA/Network Adapter/ValueWrapper.swift new file mode 100644 index 0000000..462b414 --- /dev/null +++ b/WOKA/Network Adapter/ValueWrapper.swift @@ -0,0 +1,98 @@ +// +// File.swift +// WOKA +// +// Created by MacBook Pro on 06/05/24. +// + + +import Foundation + +enum ValueWrapper: Codable { + case stringValue(String) + case intValue(Int) + case doubleValue(Double) + case boolValue(Bool) + + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let value = try? container.decode(String.self) { + self = .stringValue(value) + return + } + if let value = try? container.decode(Bool.self) { + self = .boolValue(value) + return + } + if let value = try? container.decode(Double.self) { + self = .doubleValue(value) + return + } + if let value = try? container.decode(Int.self) { + self = .intValue(value) + return + } + + throw DecodingError.typeMismatch(ValueWrapper.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ValueWrapper")) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .stringValue(value): + try container.encode(value) + case let .boolValue(value): + try container.encode(value) + case let .intValue(value): + try container.encode(value) + case let .doubleValue(value): + try container.encode(value) + } + } + + var rawValue: String { + var result: String + switch self { + case let .stringValue(value): + result = value + case let .boolValue(value): + result = String(value) + case let .intValue(value): + result = String(value) + case let .doubleValue(value): + result = String(value) + } + return result + } + + var intValue: Int? { + var result: Int? + switch self { + case let .stringValue(value): + result = Int(value) + case let .intValue(value): + result = value + case let .boolValue(value): + result = value ? 1 : 0 + case let .doubleValue(value): + result = Int(value) + } + return result + } + + var boolValue: Bool? { + var result: Bool? + switch self { + case let .stringValue(value): + result = Bool(value) + case let .boolValue(value): + result = value + case let .intValue(value): + result = Bool(truncating: value as NSNumber) + case let .doubleValue(value): + result = Bool(truncating: value as NSNumber) + } + return result + } +}