diff --git a/EasyCode.podspec b/EasyCode.podspec index 6171bf4..eb18dc9 100644 --- a/EasyCode.podspec +++ b/EasyCode.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'EasyCode' s.license = 'MIT' - s.version = '1.4.0' + s.version = '1.4.1' s.summary = 'EasyCode' s.homepage = 'https://github.com/Salmik/EaseCode' s.authors = { 'Salmik' => 'salmik94@gmail.com' } diff --git a/EasyCode.xcodeproj/project.pbxproj b/EasyCode.xcodeproj/project.pbxproj index e5b92c9..0f15b33 100644 --- a/EasyCode.xcodeproj/project.pbxproj +++ b/EasyCode.xcodeproj/project.pbxproj @@ -10,12 +10,10 @@ BB0A70632CBF822500084289 /* RSAWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0A70622CBF822500084289 /* RSAWorker.swift */; }; BB3B162E2CEC95AA0049F5F2 /* ZipManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B162D2CEC95AA0049F5F2 /* ZipManager.swift */; }; BB3B16312CEC96980049F5F2 /* DependencyInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16302CEC96980049F5F2 /* DependencyInjector.swift */; }; - BB3B16332CEC96B30049F5F2 /* WeakBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16322CEC96B30049F5F2 /* WeakBox.swift */; }; BB3B16352CEC96C30049F5F2 /* DependencyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16342CEC96C30049F5F2 /* DependencyError.swift */; }; BB3B16392CEC96FA0049F5F2 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16382CEC96FA0049F5F2 /* Inject.swift */; }; BB3B163B2CEC97290049F5F2 /* WeakInject.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B163A2CEC97290049F5F2 /* WeakInject.swift */; }; BB3B163D2CEC97470049F5F2 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B163C2CEC97470049F5F2 /* Provider.swift */; }; - BB3B16412CEC97730049F5F2 /* WeakProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16402CEC97730049F5F2 /* WeakProvider.swift */; }; BB3B16442CEC97A20049F5F2 /* KeychainWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16432CEC97A20049F5F2 /* KeychainWrapper.swift */; }; BB3B16462CEC97EF0049F5F2 /* SecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16452CEC97EF0049F5F2 /* SecureField.swift */; }; BB3B16492CECAB160049F5F2 /* PublishedProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3B16482CECAB160049F5F2 /* PublishedProperty.swift */; }; @@ -32,6 +30,8 @@ BB3C2F072C6A0DE300149867 /* Bundle+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C2F062C6A0DE300149867 /* Bundle+extension.swift */; }; BB3C2F092C6A0E2800149867 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C2F082C6A0E2800149867 /* NetworkManager.swift */; }; BB3C2F0B2C6A10F800149867 /* ServiceLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C2F0A2C6A10F800149867 /* ServiceLocator.swift */; }; + BB3C76C82CFCDFDF005217AF /* View+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C76C72CFCDFDF005217AF /* View+extension.swift */; }; + BB3C76CA2CFD502D005217AF /* UIApplication+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C76C92CFD502D005217AF /* UIApplication+extension.swift */; }; BB8A14152C36649C00F18CE8 /* UnmanagedWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8A14142C36649C00F18CE8 /* UnmanagedWrapper.swift */; }; BBAFFBC52C340D12005703B0 /* EasyCode.docc in Sources */ = {isa = PBXBuildFile; fileRef = BBAFFBC42C340D12005703B0 /* EasyCode.docc */; }; BBAFFBCB2C340D12005703B0 /* EasyCode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBAFFBC02C340D12005703B0 /* EasyCode.framework */; }; @@ -99,6 +99,8 @@ BBCC22D52C35208F00CC19C9 /* BiometricIDAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCC22D42C35208F00CC19C9 /* BiometricIDAuth.swift */; }; BBCC22D72C35246200CC19C9 /* NLLanguage+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCC22D62C35246200CC19C9 /* NLLanguage+extension.swift */; }; BBCC22D92C352ADF00CC19C9 /* MailConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCC22D82C352ADF00CC19C9 /* MailConfiguration.swift */; }; + BBE498CA2D2D06930085233A /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE498C92D2D06930085233A /* BackgroundTaskManager.swift */; }; + BBEB8E3A2D2CFB6B00988E03 /* UnownedInject.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEB8E392D2CFB6B00988E03 /* UnownedInject.swift */; }; BBEE8D752C6B679C00133020 /* PaddedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEE8D742C6B679C00133020 /* PaddedLabel.swift */; }; BBEE8D772C6B6BB500133020 /* TipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEE8D762C6B6BB500133020 /* TipViewController.swift */; }; /* End PBXBuildFile section */ @@ -117,12 +119,10 @@ BB0A70622CBF822500084289 /* RSAWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSAWorker.swift; sourceTree = ""; }; BB3B162D2CEC95AA0049F5F2 /* ZipManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipManager.swift; sourceTree = ""; }; BB3B16302CEC96980049F5F2 /* DependencyInjector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyInjector.swift; sourceTree = ""; }; - BB3B16322CEC96B30049F5F2 /* WeakBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakBox.swift; sourceTree = ""; }; BB3B16342CEC96C30049F5F2 /* DependencyError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyError.swift; sourceTree = ""; }; BB3B16382CEC96FA0049F5F2 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = ""; }; BB3B163A2CEC97290049F5F2 /* WeakInject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakInject.swift; sourceTree = ""; }; BB3B163C2CEC97470049F5F2 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; - BB3B16402CEC97730049F5F2 /* WeakProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakProvider.swift; sourceTree = ""; }; BB3B16432CEC97A20049F5F2 /* KeychainWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainWrapper.swift; sourceTree = ""; }; BB3B16452CEC97EF0049F5F2 /* SecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureField.swift; sourceTree = ""; }; BB3B16482CECAB160049F5F2 /* PublishedProperty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedProperty.swift; sourceTree = ""; }; @@ -139,6 +139,8 @@ BB3C2F062C6A0DE300149867 /* Bundle+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+extension.swift"; sourceTree = ""; }; BB3C2F082C6A0E2800149867 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; BB3C2F0A2C6A10F800149867 /* ServiceLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLocator.swift; sourceTree = ""; }; + BB3C76C72CFCDFDF005217AF /* View+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+extension.swift"; sourceTree = ""; }; + BB3C76C92CFD502D005217AF /* UIApplication+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+extension.swift"; sourceTree = ""; }; BB8A14142C36649C00F18CE8 /* UnmanagedWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnmanagedWrapper.swift; sourceTree = ""; }; BBAFFBC02C340D12005703B0 /* EasyCode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = EasyCode.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BBAFFBC32C340D12005703B0 /* EasyCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EasyCode.h; sourceTree = ""; }; @@ -207,6 +209,8 @@ BBCC22D42C35208F00CC19C9 /* BiometricIDAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricIDAuth.swift; sourceTree = ""; }; BBCC22D62C35246200CC19C9 /* NLLanguage+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NLLanguage+extension.swift"; sourceTree = ""; }; BBCC22D82C352ADF00CC19C9 /* MailConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailConfiguration.swift; sourceTree = ""; }; + BBE498C92D2D06930085233A /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = ""; }; + BBEB8E392D2CFB6B00988E03 /* UnownedInject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnownedInject.swift; sourceTree = ""; }; BBEE8D742C6B679C00133020 /* PaddedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddedLabel.swift; sourceTree = ""; }; BBEE8D762C6B6BB500133020 /* TipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -231,33 +235,16 @@ /* Begin PBXGroup section */ BB3B162F2CEC96910049F5F2 /* DI */ = { - isa = PBXGroup; - children = ( - BB3B16372CEC96E50049F5F2 /* Provider */, - BB3B16362CEC96DD0049F5F2 /* Inject */, - BB3B16302CEC96980049F5F2 /* DependencyInjector.swift */, - BB3B16342CEC96C30049F5F2 /* DependencyError.swift */, - BB3B16322CEC96B30049F5F2 /* WeakBox.swift */, - ); - path = DI; - sourceTree = ""; - }; - BB3B16362CEC96DD0049F5F2 /* Inject */ = { isa = PBXGroup; children = ( BB3B16382CEC96FA0049F5F2 /* Inject.swift */, BB3B163A2CEC97290049F5F2 /* WeakInject.swift */, - ); - path = Inject; - sourceTree = ""; - }; - BB3B16372CEC96E50049F5F2 /* Provider */ = { - isa = PBXGroup; - children = ( + BBEB8E392D2CFB6B00988E03 /* UnownedInject.swift */, BB3B163C2CEC97470049F5F2 /* Provider.swift */, - BB3B16402CEC97730049F5F2 /* WeakProvider.swift */, + BB3B16302CEC96980049F5F2 /* DependencyInjector.swift */, + BB3B16342CEC96C30049F5F2 /* DependencyError.swift */, ); - path = Provider; + path = DI; sourceTree = ""; }; BB3B16422CEC979D0049F5F2 /* Keychain */ = { @@ -382,6 +369,7 @@ BBCC22D02C3513CD00CC19C9 /* TemporaryImageCache.swift */, BBAFFBE12C340EB6005703B0 /* ThreadSafe.swift */, BBEE8D762C6B6BB500133020 /* TipViewController.swift */, + BB3C76C92CFD502D005217AF /* UIApplication+extension.swift */, BBAFFC272C342862005703B0 /* UICollectionView+extension.swift */, BBAFFBE92C34194A005703B0 /* UIColor+extension.swift */, BBAFFC1F2C34278A005703B0 /* UIImage+extension.swift */, @@ -393,12 +381,14 @@ BBAFFC072C34218B005703B0 /* URL+extension.swift */, BB3B164F2CECB0B60049F5F2 /* UserDefault.swift */, BBAFFC2D2C342A0D005703B0 /* UserDefaultsStore.swift */, + BB3C76C72CFCDFDF005217AF /* View+extension.swift */, BB3B162D2CEC95AA0049F5F2 /* ZipManager.swift */, BBBCE97D2CFCBCE10061DB04 /* BottomSheet */, BB3B16472CECAB050049F5F2 /* Combine */, BB3B162F2CEC96910049F5F2 /* DI */, BB3B16422CEC979D0049F5F2 /* Keychain */, BB3C2EF92C6A0D3B00149867 /* Network Layer */, + BBE498C92D2D06930085233A /* BackgroundTaskManager.swift */, ); path = Source; sourceTree = ""; @@ -566,27 +556,28 @@ BBAFFBF02C341B35005703B0 /* Dictionary+extension.swift in Sources */, BB3C2EFD2C6A0D6C00149867 /* EndPointProtocol.swift in Sources */, BBAFFBF82C341D33005703B0 /* UIView+constraints.swift in Sources */, + BBEB8E3A2D2CFB6B00988E03 /* UnownedInject.swift in Sources */, BBAFFBFC2C341DCB005703B0 /* KeychainService.swift in Sources */, BBAFFBE62C341881005703B0 /* NotificationManager.swift in Sources */, BBAFFC0E2C34225C005703B0 /* JSONWorker.swift in Sources */, BBAFFBC52C340D12005703B0 /* EasyCode.docc in Sources */, BB3C2F032C6A0DA800149867 /* NetworkError.swift in Sources */, BBBCE97F2CFCBCF10061DB04 /* DrawerLineView.swift in Sources */, - BB3B16332CEC96B30049F5F2 /* WeakBox.swift in Sources */, BBAFFBE42C340EE7005703B0 /* CoreDataManager.swift in Sources */, BB3B162E2CEC95AA0049F5F2 /* ZipManager.swift in Sources */, BBAFFC0C2C34220F005703B0 /* MeasureTest.swift in Sources */, BBAFFC102C342274005703B0 /* DispatchWorker.swift in Sources */, - BB3B16412CEC97730049F5F2 /* WeakProvider.swift in Sources */, BBEE8D752C6B679C00133020 /* PaddedLabel.swift in Sources */, BBAFFBDE2C340E20005703B0 /* PerformManager.swift in Sources */, BBBD51F02C36A1E500557CD3 /* AVCaptureDevice+extension.swift in Sources */, BBAFFC242C342840005703B0 /* DynamicHeightCollectionView.swift in Sources */, + BB3C76C82CFCDFDF005217AF /* View+extension.swift in Sources */, BBAFFC1C2C342689005703B0 /* KeyboardNotificationProtocol.swift in Sources */, BB3C2F092C6A0E2800149867 /* NetworkManager.swift in Sources */, BBAFFC0A2C3421EF005703B0 /* Logger.swift in Sources */, BBAFFBE22C340EB6005703B0 /* ThreadSafe.swift in Sources */, BBAFFC162C342349005703B0 /* HostingView.swift in Sources */, + BBE498CA2D2D06930085233A /* BackgroundTaskManager.swift in Sources */, BBEE8D772C6B6BB500133020 /* TipViewController.swift in Sources */, BBBCE96E2CFCB7A10061DB04 /* LoggerTableViewController.swift in Sources */, BBAFFBDC2C340DF2005703B0 /* BinaryFloatingPoint+extension.swift in Sources */, @@ -600,6 +591,7 @@ BBAFFBEE2C341A98005703B0 /* Array+extension.swift in Sources */, BB3B16312CEC96980049F5F2 /* DependencyInjector.swift in Sources */, BBAFFC182C342430005703B0 /* UIViewController+extension.swift in Sources */, + BB3C76CA2CFD502D005217AF /* UIApplication+extension.swift in Sources */, BBBCE97C2CFCBAE70061DB04 /* Typealiases.swift in Sources */, BBBD51EA2C36803600557CD3 /* ContactsWorker.swift in Sources */, BBCC22D92C352ADF00CC19C9 /* MailConfiguration.swift in Sources */, diff --git a/EasyCode/Source/BackgroundTaskManager.swift b/EasyCode/Source/BackgroundTaskManager.swift new file mode 100644 index 0000000..ccb35e3 --- /dev/null +++ b/EasyCode/Source/BackgroundTaskManager.swift @@ -0,0 +1,67 @@ +// +// BackgroundTaskManager.swift +// EasyCode +// +// Created by Zhanibek Lukpanov on 07.01.2025. +// + +import UIKit + +public class BackgroundTaskManager { + + private var taskID: UIBackgroundTaskIdentifier = .invalid + private weak var timeoutTimer: Timer? + private let timeLimit: TimeInterval + + public init(timeLimit: TimeInterval = 28.0) { + self.timeLimit = timeLimit + } + + deinit { endTaskIfNeeded() } + + public func beginTask( + backgroundQueue: DispatchQueue = .global(qos: .background), + autoEndTask: Bool = true, + onBegan: (() -> Void)? = nil, + onExpiration: (() -> Void)? = nil + ) { + guard taskID == .invalid else { return } + + taskID = UIApplication.shared.beginBackgroundTask { [weak self] in + onExpiration?() + self?.endTaskIfNeeded() + } + + guard taskID != .invalid else { return } + + startTimeoutTimer() + + backgroundQueue.async { [weak self] in + onBegan?() + if autoEndTask { + self?.endTaskIfNeeded() + } + } + } + + public func endTaskIfNeeded() { + guard taskID != .invalid else { return } + + UIApplication.shared.endBackgroundTask(taskID) + taskID = .invalid + + timeoutTimer?.invalidate() + timeoutTimer = nil + } + + private func startTimeoutTimer() { + timeoutTimer?.invalidate() + + let timer = Timer.scheduledTimer(withTimeInterval: timeLimit, repeats: false) { [weak self] _ in + self?.endTaskIfNeeded() + } + RunLoop.current.add(timer, forMode: .common) + + timeoutTimer = timer + } +} diff --git a/EasyCode/Source/BottomSheet/DrawerLineView.swift b/EasyCode/Source/BottomSheet/DrawerLineView.swift index d1f3e7e..4cec8f1 100644 --- a/EasyCode/Source/BottomSheet/DrawerLineView.swift +++ b/EasyCode/Source/BottomSheet/DrawerLineView.swift @@ -20,7 +20,7 @@ class DrawerLineView: UIView { } private func setLayoutConstraints() { - lineView.center(to: self) + lineView.constraintToCenter(of: self) lineView.constraintSize(to: .init(width: 40, height: 5)) } diff --git a/EasyCode/Source/DI/DependencyInjector.swift b/EasyCode/Source/DI/DependencyInjector.swift index 8cacad1..68b067e 100644 --- a/EasyCode/Source/DI/DependencyInjector.swift +++ b/EasyCode/Source/DI/DependencyInjector.swift @@ -7,60 +7,53 @@ import Foundation +private protocol OptionalType { + static var wrappedType: Any.Type { get } +} + public class DependencyInjector { - private var strongDependencies: [String: Any] = [:] - private var weakDependencies: [String: WeakBox] = [:] + private var dependencies: [String: Any] = [:] private let queue = DispatchQueue(label: "DependencyContainer.Queue", attributes: .concurrent) + public init() {} + public func resolve() throws -> T { - let key = String(describing: T.self) - return try queue.sync { - if let dependency = strongDependencies[key] as? T { - return dependency - } else { - throw DependencyError.providerNotFound(type: T.self) - } + let baseType: Any.Type + if let optionalMeta = T.self as? OptionalType.Type { + baseType = optionalMeta.wrappedType + } else { + baseType = T.self } - } - public func weakResolve() throws -> T { - let key = String(describing: T.self) + let key = String(describing: baseType) + return try queue.sync { - if let weakBox = weakDependencies[key], let dependency = weakBox.value as? T { - return dependency - } else { + guard let dependency = dependencies[key], let typed = dependency as? T else { throw DependencyError.providerNotFound(type: T.self) } - } - } - public func register(dependency: T) { - let key = String(describing: T.self) - queue.async(flags: .barrier) { - self.strongDependencies[key] = dependency + return typed } } - public func registerWeak(dependency: T) { + public func register(dependency: T) { let key = String(describing: T.self) queue.async(flags: .barrier) { - self.weakDependencies[key] = WeakBox(dependency) + self.dependencies[key] = dependency } } public func unregister(type: T.Type) { let key = String(describing: T.self) queue.async(flags: .barrier) { - self.strongDependencies.removeValue(forKey: key) - self.weakDependencies.removeValue(forKey: key) + self.dependencies.removeValue(forKey: key) } } public func unregisterAll() { queue.async(flags: .barrier) { - self.strongDependencies.removeAll() - self.weakDependencies.removeAll() + self.dependencies.removeAll() } } } @@ -68,3 +61,7 @@ public class DependencyInjector { extension DependencyInjector { public static let shared = DependencyInjector() } + +extension Optional: OptionalType { + static var wrappedType: Any.Type { Wrapped.self } +} diff --git a/EasyCode/Source/DI/Inject/Inject.swift b/EasyCode/Source/DI/Inject.swift similarity index 100% rename from EasyCode/Source/DI/Inject/Inject.swift rename to EasyCode/Source/DI/Inject.swift diff --git a/EasyCode/Source/DI/Inject/WeakInject.swift b/EasyCode/Source/DI/Inject/WeakInject.swift deleted file mode 100644 index cbfe83e..0000000 --- a/EasyCode/Source/DI/Inject/WeakInject.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// WeakInject.swift -// EasyCode -// -// Created by Zhanibek Lukpanov on 19.11.2024. -// - -import Foundation - -@propertyWrapper -public struct WeakInject { - - private var value: T? - - public init(container: DependencyInjector = DependencyInjector.shared) { - if let resolvedDependency: T = try? container.weakResolve() { - self.value = resolvedDependency - } else { - self.value = nil - } - } - - public var wrappedValue: T? { value } -} diff --git a/EasyCode/Source/DI/Provider/Provider.swift b/EasyCode/Source/DI/Provider.swift similarity index 100% rename from EasyCode/Source/DI/Provider/Provider.swift rename to EasyCode/Source/DI/Provider.swift diff --git a/EasyCode/Source/DI/Provider/WeakProvider.swift b/EasyCode/Source/DI/Provider/WeakProvider.swift deleted file mode 100644 index b6d1170..0000000 --- a/EasyCode/Source/DI/Provider/WeakProvider.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// WeakProvider.swift -// EasyCode -// -// Created by Zhanibek Lukpanov on 19.11.2024. -// - -import Foundation - -@propertyWrapper -public struct WeakProvider { - - public var wrappedValue: T - - public init(wrappedValue: T, container: DependencyInjector = DependencyInjector.shared) { - self.wrappedValue = wrappedValue - container.registerWeak(dependency: wrappedValue) - } -} diff --git a/EasyCode/Source/DI/UnownedInject.swift b/EasyCode/Source/DI/UnownedInject.swift new file mode 100644 index 0000000..7978d02 --- /dev/null +++ b/EasyCode/Source/DI/UnownedInject.swift @@ -0,0 +1,24 @@ +// +// UnownedInject.swift +// EasyCode +// +// Created by Zhanibek Lukpanov on 07.01.2025. +// + +import Foundation + +@propertyWrapper +public struct UnownedInject { + + private unowned var value: T + + public init(container: DependencyInjector = .shared) { + do { + value = try container.resolve() + } catch { + fatalError(error.localizedDescription) + } + } + + public var wrappedValue: T { value } +} diff --git a/EasyCode/Source/DI/WeakBox.swift b/EasyCode/Source/DI/WeakBox.swift deleted file mode 100644 index c363ab9..0000000 --- a/EasyCode/Source/DI/WeakBox.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// WeakBox.swift -// EasyCode -// -// Created by Zhanibek Lukpanov on 19.11.2024. -// - -import Foundation - -class WeakBox { - - weak var value: AnyObject? - - init(_ value: AnyObject) { - self.value = value - } -} diff --git a/EasyCode/Source/DI/WeakInject.swift b/EasyCode/Source/DI/WeakInject.swift new file mode 100644 index 0000000..1cfe785 --- /dev/null +++ b/EasyCode/Source/DI/WeakInject.swift @@ -0,0 +1,20 @@ +// +// WeakInject.swift +// EasyCode +// +// Created by Zhanibek Lukpanov on 19.11.2024. +// + +import Foundation + +@propertyWrapper +public struct WeakInject { + + private weak var value: T? + + public init(container: DependencyInjector = .shared) { + value = try? container.resolve() + } + + public var wrappedValue: T? { value } +} diff --git a/EasyCode/Source/Data+extension.swift b/EasyCode/Source/Data+extension.swift index 64c079c..8b9f293 100644 --- a/EasyCode/Source/Data+extension.swift +++ b/EasyCode/Source/Data+extension.swift @@ -39,6 +39,26 @@ public extension Data { } } + var json: String? { + if let jsonObject = try? JSONSerialization.jsonObject(with: self), + let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]) { + return String(data: data, encoding: .utf8) + } + return String(data: self, encoding: .utf8) + } + + var printableJsonString: String? { + guard var json else { return nil } + + let specials = [("\\/", "/"), ("\\t", "\t"), ("\\n", "\n"), ("\\r", "\r"), ("\\\"", "\""), ("\\\'", "\'")] + for special in specials { + json = json.replacingOccurrences(of: special.0, with: special.1, options: .literal) + } + json = json.replacingOccurrences(of: "\" : ", with: "\": ", options: .literal) + + return json + } + /// Determines the MIME type of the data based on its initial byte. var mimeType: String { var buffer = UInt8(0) @@ -52,6 +72,17 @@ public extension Data { case 0x25: return "application/pdf" case 0xD0: return "application/vnd" case 0x46: return "text/plain" + case 0x00: + if count >= 12 { + let subdata = self.subdata(in: 4..<12) + let headerString = String(data: subdata, encoding: .ascii) + if headerString?.hasPrefix("ftyp") == true { + return "video/mp4" + } else if headerString?.hasPrefix("M4V") == true { + return "video/x-m4v" + } + } + return "application/octet-stream" default: return "application/octet-stream" } } diff --git a/EasyCode/Source/DeviceInformation.swift b/EasyCode/Source/DeviceInformation.swift index e0db240..996ed61 100644 --- a/EasyCode/Source/DeviceInformation.swift +++ b/EasyCode/Source/DeviceInformation.swift @@ -54,6 +54,9 @@ public extension DeviceInformation { /// The preferred language of the device. var preferredLanguage: String { Locale.preferredLanguages.first ?? "Unknown" } + /// Is device enabled power mode + var isLowPowerModeEnabled: Bool { ProcessInfo.processInfo.isLowPowerModeEnabled } + // swiftlint:disable control_statement /// Retrieves the IP address of the device for the preferred network interfaces. /// diff --git a/EasyCode/Source/JailbreakDetection.swift b/EasyCode/Source/JailbreakDetection.swift index 3d65d66..1340472 100644 --- a/EasyCode/Source/JailbreakDetection.swift +++ b/EasyCode/Source/JailbreakDetection.swift @@ -31,7 +31,7 @@ public class JailbreakDetection: JailbreakDetectionService { public var isJailbrokenDevice: Bool { // Check for simulator - #if arch(i386) || arch(x86_64) + #if targetEnvironment(simulator) return false #endif diff --git a/EasyCode/Source/Network Layer/EndPointProtocol.swift b/EasyCode/Source/Network Layer/EndPointProtocol.swift index 6fa82a3..69d09c6 100644 --- a/EasyCode/Source/Network Layer/EndPointProtocol.swift +++ b/EasyCode/Source/Network Layer/EndPointProtocol.swift @@ -55,7 +55,10 @@ public extension EndPointProtocol { /// Creates a `URLRequest` object based on the properties defined in the endpoint. /// - Returns: A configured `URLRequest` object, or `nil` if the URL is invalid. func makeRequest() -> URLRequest? { - guard let url = URL(string: baseURL + path) else { return nil } + guard let url = URL(string: baseURL + path) else { + Logger.print("Invalid URL: \(baseURL + path)") + return nil + } var request = URLRequest(url: url) request.httpMethod = httpMethod.rawValue diff --git a/EasyCode/Source/NotificationManager.swift b/EasyCode/Source/NotificationManager.swift index 10682cd..12aa826 100644 --- a/EasyCode/Source/NotificationManager.swift +++ b/EasyCode/Source/NotificationManager.swift @@ -84,7 +84,7 @@ public class NotificationManager { ) { NotificationCenter.default.post( name: notification.name, - object: object as AnyObject, + object: object, userInfo: userInfo ) } diff --git a/EasyCode/Source/PasswordValidator.swift b/EasyCode/Source/PasswordValidator.swift index 1b85dd8..d3f6c73 100644 --- a/EasyCode/Source/PasswordValidator.swift +++ b/EasyCode/Source/PasswordValidator.swift @@ -206,6 +206,14 @@ public class PasswordValidator { } return results } + + public func isValid(password: String) -> Bool { + var results: [ValidationRule: Bool] = [:] + for strategy in strategies { + results[strategy.rule] = strategy.isValid(password: password) + } + return !results.compactMap { $0.value }.contains(false) + } } /// Factory class for creating password validation strategies based on rules. diff --git a/EasyCode/Source/PerformManager.swift b/EasyCode/Source/PerformManager.swift index 1cc19ed..176178f 100644 --- a/EasyCode/Source/PerformManager.swift +++ b/EasyCode/Source/PerformManager.swift @@ -70,8 +70,8 @@ public class PerformManager { /// print("All tasks completed concurrently") /// } /// ``` - public func performTasks(inSequence: Bool = false, completion: (() -> Void)? = nil) { - executionTask = Task(priority: .userInitiated) { @MainActor in + public func performTasks(inSequence: Bool = false, queue: DispatchQueue = .main, completion: (() -> Void)? = nil) { + executionTask = Task(priority: .userInitiated) { if inSequence { for task in tasks { await task() @@ -86,7 +86,9 @@ public class PerformManager { await group.waitForAll() } } - completion?() + queue.async { + completion?() + } } } } diff --git a/EasyCode/Source/UIApplication+extension.swift b/EasyCode/Source/UIApplication+extension.swift new file mode 100644 index 0000000..0b3ccc3 --- /dev/null +++ b/EasyCode/Source/UIApplication+extension.swift @@ -0,0 +1,34 @@ +// +// UIApplication+extension.swift +// EasyCode +// +// Created by Zhanibek Lukpanov on 02.12.2024. +// + +import UIKit + +public extension UIApplication { + + var firstActiveScene: UIWindowScene? { + guard let connectedScene = connectedScenes.first(where: { $0.activationState == .foregroundActive }), + let scene = connectedScene as? UIWindowScene else { + return nil + } + + return scene + } + + var firstActiveWindow: UIWindow? { + guard let scene = firstActiveScene, let window = scene.windows.first else { return nil } + return window + } + + func openAppSettings() { + guard let url = URL(string: UIApplication.openSettingsURLString), canOpenURL(url) else { return } + open(url) + } + + func dismissKeyboard() { + sendAction(#selector(UIApplication.resignFirstResponder), to: nil, from: nil, for: nil) + } +} diff --git a/EasyCode/Source/UIViewController+extension.swift b/EasyCode/Source/UIViewController+extension.swift index e90747e..ede6c3d 100644 --- a/EasyCode/Source/UIViewController+extension.swift +++ b/EasyCode/Source/UIViewController+extension.swift @@ -348,27 +348,6 @@ public extension UIViewController { } } - private struct AssociatedKeys { - static var bottomSheetTransitioningDelegate = "bottomSheetTransitioningDelegate" - } - - private var bottomSheetTransitioningDelegate: BottomSheetTransitioningDelegate? { - get { - return objc_getAssociatedObject( - self, - &AssociatedKeys.bottomSheetTransitioningDelegate - ) as? BottomSheetTransitioningDelegate - } - set { - objc_setAssociatedObject( - self, - &AssociatedKeys.bottomSheetTransitioningDelegate, - newValue, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) - } - } - func presentBottomSheet( _ viewControllerToPresent: UIViewController, height: CGFloat? = nil, @@ -378,7 +357,6 @@ public extension UIViewController { let transitioningDelegate = BottomSheetTransitioningDelegate(height: height) viewControllerToPresent.modalPresentationStyle = .custom viewControllerToPresent.transitioningDelegate = transitioningDelegate - viewControllerToPresent.bottomSheetTransitioningDelegate = transitioningDelegate present(viewControllerToPresent, animated: animated, completion: completion) } } diff --git a/EasyCode/Source/UserDefault.swift b/EasyCode/Source/UserDefault.swift index 67efd7d..a218b1e 100644 --- a/EasyCode/Source/UserDefault.swift +++ b/EasyCode/Source/UserDefault.swift @@ -8,15 +8,15 @@ import Foundation @propertyWrapper -struct UserDefault { +public struct UserDefault { - let key: String + public let key: String - init(_ key: String) { + public init(_ key: String) { self.key = key } - var wrappedValue: V? { + public var wrappedValue: V? { get { UserDefaults.standard.object(forKey: key) as? V } set { if let newValue { diff --git a/EasyCode/Source/View+extension.swift b/EasyCode/Source/View+extension.swift new file mode 100644 index 0000000..6dd3293 --- /dev/null +++ b/EasyCode/Source/View+extension.swift @@ -0,0 +1,148 @@ +// +// View+extension.swift +// EasyCode +// +// Created by Zhanibek Lukpanov on 01.12.2024. +// + +import SwiftUI +import Charts + +public protocol ChartDataPoint: Identifiable { + var xValue: String { get } + var yValue: Double { get } + var xAxisLabel: String? { get } + var yAxisLabel: String? { get } +} + +public struct DataPoint: ChartDataPoint { + public let id = UUID() + public let xValue: String + public let yValue: Double + public var xAxisLabel: String? + public var yAxisLabel: String? +} + +public struct RangeDataPoint: Identifiable { + public let id = UUID() + public let label: String + public let minValue: Double + public let maxValue: Double +} + +@available(iOS 16.0, *) +public extension View { + + var uiView: UIView { HostingView(rootView: self) } + + func createBarChart(dataPoints: [DataType], title: String? = nil) -> some View { + VStack { + if let title { + Text(title) + .font(.headline) + .padding(.bottom, 12) + } + + Chart(dataPoints) { dataPoint in + BarMark( + x: .value(dataPoint.xAxisLabel ?? "X", dataPoint.xValue), + y: .value(dataPoint.yAxisLabel ?? "Y", dataPoint.yValue) + ) + } + .padding() + } + } + + func createLineChart(dataPoints: [DataType], title: String? = nil) -> some View { + VStack { + if let title { + Text(title) + .font(.headline) + .padding(.bottom, 12) + } + + Chart(dataPoints) { dataPoint in + LineMark( + x: .value(dataPoint.xAxisLabel ?? "X", dataPoint.xValue), + y: .value(dataPoint.yAxisLabel ?? "Y", dataPoint.yValue) + ) + } + .padding() + } + } + + func createAreaChart(dataPoints: [DataType], title: String? = nil) -> some View { + VStack { + if let title { + Text(title) + .font(.headline) + .padding(.bottom, 12) + } + + Chart(dataPoints) { dataPoint in + AreaMark( + x: .value(dataPoint.xAxisLabel ?? "X", dataPoint.xValue), + y: .value(dataPoint.yAxisLabel ?? "Y", dataPoint.yValue) + ) + } + .padding() + } + } + + func createPointChart(dataPoints: [DataType], title: String? = nil) -> some View { + VStack { + if let title { + Text(title) + .font(.headline) + .padding(.bottom, 12) + } + + Chart(dataPoints) { dataPoint in + PointMark( + x: .value(dataPoint.xAxisLabel ?? "X", dataPoint.xValue), + y: .value(dataPoint.yAxisLabel ?? "Y", dataPoint.yValue) + ) + } + .padding() + } + } + + func createRangeAreaChart(rangeDataPoints: [RangeDataPoint], title: String? = nil) -> some View { + VStack { + if let title { + Text(title) + .font(.headline) + .padding(.bottom, 12) + } + + Chart(rangeDataPoints) { dataPoint in + AreaMark( + x: .value("Label", dataPoint.label), + yStart: .value("Min Value", dataPoint.minValue), + yEnd: .value("Max Value", dataPoint.maxValue) + ) + } + .padding() + } + } + + @available(iOS 17.0, *) + func createPieChart(dataPoints: [DataType], title: String? = nil) -> some View { + VStack { + if let title { + Text(title) + .font(.headline) + .padding(.bottom, 12) + } + + Chart(dataPoints) { dataPoint in + SectorMark( + angle: .value(dataPoint.xAxisLabel ?? "Angle", dataPoint.xValue), + innerRadius: .ratio(0.5), + outerRadius: .ratio(1.0) + ) + } + .padding() + } + } +} diff --git a/README.md b/README.md index 29412b7..fb14af3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### Cocoapods -[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate RBKLiveness into your Xcode project using CocoaPods, specify it in your `Podfile`: +[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate EasyCode into your Xcode project using CocoaPods, specify it in your `Podfile`: For iOS Swift projects: