From 8a25ca7877df01318fa62df5657ace846c110a2c Mon Sep 17 00:00:00 2001 From: Maksim Ivanov Date: Sun, 9 Apr 2023 04:48:59 +0300 Subject: [PATCH] Release 1.0.0. --- Package.swift | 22 ++--- README.md | 10 ++- Source/Array+Core.swift | 17 ++++ Source/Builder.swift | 23 ++++++ Source/CoreRouter.swift | 11 +++ Source/Date+Core.swift | 22 +++++ Source/Logger/LogLevel.swift | 13 +++ Source/Logger/Logger+Extension.swift | 81 +++++++++++++++++++ Source/Logger/Logger.swift | 11 +++ Source/Logger/LoggerImpl.swift | 42 ++++++++++ Source/Networking/HttpClient.swift | 33 ++++++++ Source/Networking/HttpClientImpl.swift | 53 ++++++++++++ Source/Tagged.swift | 19 +++++ Source/UIKit+Core/Bundle+Core.swift | 15 ++++ Source/UIKit+Core/Notification+Core.swift | 15 ++++ Source/UIKit+Core/UIColor+Core.swift | 29 +++++++ Source/UIKit+Core/UIScrollView+Core.swift | 17 ++++ Source/UIKit+Core/UITableView+Core.swift | 18 +++++ Source/UIKit+Core/UIViewController+Core.swift | 24 ++++++ Source/debug.swift | 14 ++++ Sources/CoreModule/CoreModule.swift | 6 -- Tests/CoreModuleTests/CoreModuleTests.swift | 11 --- 22 files changed, 474 insertions(+), 32 deletions(-) create mode 100644 Source/Array+Core.swift create mode 100644 Source/Builder.swift create mode 100644 Source/CoreRouter.swift create mode 100644 Source/Date+Core.swift create mode 100644 Source/Logger/LogLevel.swift create mode 100644 Source/Logger/Logger+Extension.swift create mode 100644 Source/Logger/Logger.swift create mode 100644 Source/Logger/LoggerImpl.swift create mode 100644 Source/Networking/HttpClient.swift create mode 100644 Source/Networking/HttpClientImpl.swift create mode 100644 Source/Tagged.swift create mode 100644 Source/UIKit+Core/Bundle+Core.swift create mode 100644 Source/UIKit+Core/Notification+Core.swift create mode 100644 Source/UIKit+Core/UIColor+Core.swift create mode 100644 Source/UIKit+Core/UIScrollView+Core.swift create mode 100644 Source/UIKit+Core/UITableView+Core.swift create mode 100644 Source/UIKit+Core/UIViewController+Core.swift create mode 100644 Source/debug.swift delete mode 100644 Sources/CoreModule/CoreModule.swift delete mode 100644 Tests/CoreModuleTests/CoreModuleTests.swift diff --git a/Package.swift b/Package.swift index 8ccc19e..eb6b844 100644 --- a/Package.swift +++ b/Package.swift @@ -1,28 +1,22 @@ -// swift-tools-version: 5.7 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version:5.7 import PackageDescription let package = Package( name: "CoreModule", + platforms: [ + .iOS(.v14) + ], products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "CoreModule", - targets: ["CoreModule"]), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), + targets: ["CoreModule"] + ) ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "CoreModule", - dependencies: []), - .testTarget( - name: "CoreModuleTests", - dependencies: ["CoreModule"]), + path: "Source" + ) ] ) diff --git a/README.md b/README.md index 376f96e..4499c37 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ # CoreModule -A description of this package. +Core module for iOS projects. + +Contents + +* Core protocols +* Logging framework +* Some extensions for basic Swift types +* Some UIKit extensions +* HTTP client diff --git a/Source/Array+Core.swift b/Source/Array+Core.swift new file mode 100644 index 0000000..be21f15 --- /dev/null +++ b/Source/Array+Core.swift @@ -0,0 +1,17 @@ +// +// Array+Core.swift +// ReTodoList +// +// Created by Maksim Ivanov on 21.02.2023. +// + +extension Array { + + public subscript(safeIndex index: Int) -> Element? { + guard index >= 0, index < endIndex else { + return nil + } + + return self[index] + } +} diff --git a/Source/Builder.swift b/Source/Builder.swift new file mode 100644 index 0000000..fa90048 --- /dev/null +++ b/Source/Builder.swift @@ -0,0 +1,23 @@ +// +// Builder.swift +// PersonalDictionary +// +// Created by Maxim Ivanov on 27.10.2022. +// + +import UIKit + +public protocol ViewBuilder { + + func build() -> UIView +} + +public protocol ViewControllerBuilder { + + func build() -> UIViewController +} + +public protocol SearchControllerBuilder { + + func build() -> UISearchController +} diff --git a/Source/CoreRouter.swift b/Source/CoreRouter.swift new file mode 100644 index 0000000..61d0464 --- /dev/null +++ b/Source/CoreRouter.swift @@ -0,0 +1,11 @@ +// +// CoreRouter.swift +// CoreModule +// +// Created by Maxim Ivanov on 16.12.2021. +// + +public protocol CoreRouter { + + func navigate() +} diff --git a/Source/Date+Core.swift b/Source/Date+Core.swift new file mode 100644 index 0000000..35e6922 --- /dev/null +++ b/Source/Date+Core.swift @@ -0,0 +1,22 @@ +// +// Date+Core.swift +// ReTodoList +// +// Created by Maksim Ivanov on 12.08.2022. +// + +import Foundation + +extension Date { + + public var dMMMMyyyy: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "d MMMM yyyy" + + return dateFormatter.string(from: self) + } + + public var integer: Int { + Int(timeIntervalSince1970) + } +} diff --git a/Source/Logger/LogLevel.swift b/Source/Logger/LogLevel.swift new file mode 100644 index 0000000..618c519 --- /dev/null +++ b/Source/Logger/LogLevel.swift @@ -0,0 +1,13 @@ +// +// CoreRouter.swift +// CoreModule +// +// Created by Maxim Ivanov on 16.12.2021. +// + +public enum LogLevel: String { + case debug = "DEBUG" + case info = "INFO" + case warn = "WARN ⚠️" + case error = "ALERT ❌" +} diff --git a/Source/Logger/Logger+Extension.swift b/Source/Logger/Logger+Extension.swift new file mode 100644 index 0000000..b35e96a --- /dev/null +++ b/Source/Logger/Logger+Extension.swift @@ -0,0 +1,81 @@ +// +// CoreRouter.swift +// CoreModule +// +// Created by Maxim Ivanov on 16.12.2021. +// + +import Foundation + +extension Logger { + + public func debug(_ message: String) { + log(message, level: .debug) + } + + public func logWithContext(_ message: String, + level: LogLevel = .info, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard isDevelopment() else { return } + + log(message, level: level, file: file, function: function, line: line) + } + + public func errorWithContext(_ error: Error, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard isDevelopment() else { return } + + log("\(error)", level: .error, file: file, function: function, line: line) + } + + public func logState(actionName: String, + _ state: State, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard isDevelopment() else { return } + let text = "\(actionName) result:\n\(state)" + + log(text, level: .info, file: file, function: function, line: line) + } + + public func logSending(_ object: T, + toModelStream modelStreamName: String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard isDevelopment() else { return } + let text = "Sending \(type(of: object)) = \(object) to the \(modelStreamName) model stream." + + log(text, level: .info, file: file, function: function, line: line) + } + + public func logReceiving(_ object: T, + fromModelStream modelStreamName: String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard isDevelopment() else { return } + let text = "Received \(type(of: object)) = \(object) from the \(modelStreamName) model stream." + + log(text, level: .info, file: file, function: function, line: line) + } + + public func log(installedFeatureName: String) { + debug("The \(installedFeatureName) feature has been installed.") + } + + public func log(dismissedFeatureName: String) { + debug("The \(dismissedFeatureName) feature has been dismissed.") + } + + private func log(_ text: String, level: LogLevel, file: String, function: String, line: Int) { + let description = "\((file as NSString).lastPathComponent) line:\(line) \(function)" + + log("\(text) -> \(description)", level: level) + } +} diff --git a/Source/Logger/Logger.swift b/Source/Logger/Logger.swift new file mode 100644 index 0000000..f90d25c --- /dev/null +++ b/Source/Logger/Logger.swift @@ -0,0 +1,11 @@ +// +// Logger.swift +// CoreModule +// +// Created by Maxim Ivanov on 24.03.2023. +// + +public protocol Logger { + + func log(_ message: String, level: LogLevel) +} diff --git a/Source/Logger/LoggerImpl.swift b/Source/Logger/LoggerImpl.swift new file mode 100644 index 0000000..57f519a --- /dev/null +++ b/Source/Logger/LoggerImpl.swift @@ -0,0 +1,42 @@ +// +// LoggerImpl.swift +// CoreModule +// +// Created by Maxim Ivanov on 24.03.2023. +// + +import os + +public final class LoggerImpl: CoreModule.Logger { + + private let logger: os.Logger + + public init(category: String) { + logger = os.Logger(subsystem: "General", category: category) + } + + public func log(_ message: String, level: LogLevel) { + guard isDevelopment() else { return } + + logger.log(level: level.getOsLogLevel(), "[\(level.rawValue)] \(message)") + } +} + +extension LogLevel { + + func getOsLogLevel() -> OSLogType { + switch self { + case .debug: + return .debug + + case .info: + return .default + + case .warn: + return .error + + case .error: + return .fault + } + } +} diff --git a/Source/Networking/HttpClient.swift b/Source/Networking/HttpClient.swift new file mode 100644 index 0000000..c7f378b --- /dev/null +++ b/Source/Networking/HttpClient.swift @@ -0,0 +1,33 @@ +// +// CoreService.swift +// PersonalDictionary +// +// Created by Maxim Ivanov on 09.10.2021. +// + +import Combine +import Foundation + +public typealias RxHttpResponse = AnyPublisher<(response: HTTPURLResponse, data: Data), Error> + +public struct Http { + public let urlString: String + public let method: String + public let headers: [String: String]? + public let body: Data? + + public init(urlString: String = "", + method: String = "", + headers: [String: String]? = nil, + body: Data? = nil) { + self.urlString = urlString + self.method = method + self.headers = headers + self.body = body + } +} + +public protocol HttpClient { + + func send(_ http: Http) -> RxHttpResponse +} diff --git a/Source/Networking/HttpClientImpl.swift b/Source/Networking/HttpClientImpl.swift new file mode 100644 index 0000000..aaf2fca --- /dev/null +++ b/Source/Networking/HttpClientImpl.swift @@ -0,0 +1,53 @@ +// +// UrlSessionCoreService.swift +// PersonalDictionary +// +// Created by Maxim Ivanov on 09.10.2021. +// + +import Combine +import Foundation + +public class HttpClientImpl: HttpClient { + + private let sessionConfiguration: URLSessionConfiguration + + private lazy var session: URLSession = { + sessionConfiguration.timeoutIntervalForResource = 30.0 + return URLSession(configuration: sessionConfiguration) + }() + + public init(sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default) { + self.sessionConfiguration = sessionConfiguration + } + + public func send(_ http: Http) -> RxHttpResponse { + let urlString = http.urlString + guard let url = URL(string: urlString) else { + return Fail(error: URLError(.badURL)) + .eraseToAnyPublisher() + } + var request = URLRequest(url: url) + + request.httpMethod = http.method + request.httpBody = http.body + + http.headers?.forEach { (key, value) in + request.addValue(value, forHTTPHeaderField: key) + } + + return session.dataTaskPublisher(for: request) + .tryMap { value in + guard let httpURLResponse = value.response as? HTTPURLResponse else { + throw HttpClientError.nonHttpRequest + } + + return (response: httpURLResponse, data: value.data) + } + .eraseToAnyPublisher() + } + + enum HttpClientError: Error { + case nonHttpRequest + } +} diff --git a/Source/Tagged.swift b/Source/Tagged.swift new file mode 100644 index 0000000..489e59d --- /dev/null +++ b/Source/Tagged.swift @@ -0,0 +1,19 @@ +// +// Tagged.swift +// PersonalDictionary +// +// Created by Maxim Ivanov on 05.10.2021. +// + +public struct Tagged: Equatable { + + public let raw: RawValue + + public init(raw: RawValue) { + self.raw = raw + } + + public static func == (_ lhs: Tagged, _ rhs: Tagged) -> Bool { + lhs.raw == rhs.raw + } +} diff --git a/Source/UIKit+Core/Bundle+Core.swift b/Source/UIKit+Core/Bundle+Core.swift new file mode 100644 index 0000000..7a17dad --- /dev/null +++ b/Source/UIKit+Core/Bundle+Core.swift @@ -0,0 +1,15 @@ +// +// Bundle+Core.swift +// CoreModule +// +// Created by Maxim Ivanov on 19.12.2021. +// + +import UIKit + +extension Bundle { + + public func moduleLocalizedString(_ text: String) -> String { + localizedString(forKey: text, value: text, table: nil) + } +} diff --git a/Source/UIKit+Core/Notification+Core.swift b/Source/UIKit+Core/Notification+Core.swift new file mode 100644 index 0000000..67ea715 --- /dev/null +++ b/Source/UIKit+Core/Notification+Core.swift @@ -0,0 +1,15 @@ +// +// Notification+Core.swift +// ReTodoList +// +// Created by Maksim Ivanov on 16.02.2023. +// + +import UIKit + +extension Notification { + + public var keyboardSize: CGSize { + ((userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size) ?? .zero + } +} diff --git a/Source/UIKit+Core/UIColor+Core.swift b/Source/UIKit+Core/UIColor+Core.swift new file mode 100644 index 0000000..281684c --- /dev/null +++ b/Source/UIKit+Core/UIColor+Core.swift @@ -0,0 +1,29 @@ +// +// UIColor+Core.swift +// CoreModule +// +// Created by Maxim Ivanov on 30.03.2022. +// + +import UIKit + +public extension UIColor { + + /// Creates a color object that generates its color data dynamically using the specified colors. For early SDKs creates light color. + /// - Parameters: + /// - light: The color for light mode. + /// - dark: The color for dark mode. + convenience init(light: UIColor, dark: UIColor) { + if #available(iOS 13.0, tvOS 13.0, *) { + self.init { traitCollection in + if traitCollection.userInterfaceStyle == .dark { + return dark + } + return light + } + } + else { + self.init(cgColor: light.cgColor) + } + } +} diff --git a/Source/UIKit+Core/UIScrollView+Core.swift b/Source/UIKit+Core/UIScrollView+Core.swift new file mode 100644 index 0000000..14ffdc4 --- /dev/null +++ b/Source/UIKit+Core/UIScrollView+Core.swift @@ -0,0 +1,17 @@ +// +// UIScrollView+Core.swift +// ReTodoList +// +// Created by Maksim Ivanov on 16.02.2023. +// + +import UIKit + +extension UIScrollView { + + public func scrollToBottom() { + let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.height + self.contentInset.bottom) + + setContentOffset(bottomOffset, animated: true) + } +} diff --git a/Source/UIKit+Core/UITableView+Core.swift b/Source/UIKit+Core/UITableView+Core.swift new file mode 100644 index 0000000..f027b7d --- /dev/null +++ b/Source/UIKit+Core/UITableView+Core.swift @@ -0,0 +1,18 @@ +// +// UITableView+Core.swift +// CoreModule +// +// Created by Maxim Ivanov on 13.01.2022. +// + +import UIKit + +extension UITableView { + + public func isLastVisibleCell(at indexPath: IndexPath) -> Bool { + guard let lastIndexPath = indexPathsForVisibleRows?.last else { + return false + } + return lastIndexPath == indexPath + } +} diff --git a/Source/UIKit+Core/UIViewController+Core.swift b/Source/UIKit+Core/UIViewController+Core.swift new file mode 100644 index 0000000..fc249a7 --- /dev/null +++ b/Source/UIKit+Core/UIViewController+Core.swift @@ -0,0 +1,24 @@ +// +// UIViewController+Core.swift +// PersonalDictionary +// +// Created by Maxim Ivanov on 16.12.2021. +// + +import UIKit + +@nonobjc +extension UIViewController { + + public func add(childViewController: UIViewController) { + view.addSubview(childViewController.view) + addChild(childViewController) + childViewController.didMove(toParent: self) + } + + public func removeFromParentViewController() { + willMove(toParent: nil) + view.removeFromSuperview() + removeFromParent() + } +} diff --git a/Source/debug.swift b/Source/debug.swift new file mode 100644 index 0000000..b687301 --- /dev/null +++ b/Source/debug.swift @@ -0,0 +1,14 @@ +// +// CoreRouter.swift +// CoreModule +// +// Created by Maxim Ivanov on 16.12.2021. +// + +public func isDevelopment() -> Bool { + #if DEBUG + return true + #else + return false + #endif +} diff --git a/Sources/CoreModule/CoreModule.swift b/Sources/CoreModule/CoreModule.swift deleted file mode 100644 index 7a503f5..0000000 --- a/Sources/CoreModule/CoreModule.swift +++ /dev/null @@ -1,6 +0,0 @@ -public struct CoreModule { - public private(set) var text = "Hello, World!" - - public init() { - } -} diff --git a/Tests/CoreModuleTests/CoreModuleTests.swift b/Tests/CoreModuleTests/CoreModuleTests.swift deleted file mode 100644 index 351b7f7..0000000 --- a/Tests/CoreModuleTests/CoreModuleTests.swift +++ /dev/null @@ -1,11 +0,0 @@ -import XCTest -@testable import CoreModule - -final class CoreModuleTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(CoreModule().text, "Hello, World!") - } -}