diff --git a/CHANGELOG.md b/CHANGELOG.md index 9516a9d3..587d7294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +### 1.52.0 + +- **Added**: `TIApplication` module with core dependencies of main application and its extension targets +- **Added**: `DefaultHomogeneousItemsCollectionView` default collection view implementation with configurable identical-type cells +- **Update**: Changed implementation of `AppInstallLifetimeSingleValueStorage`. Now it uses `SingleValueStorage` to be able to migrate stored UserDefaults values +- **Added**: `UserLocationFetcher.OnLocationFetchFailureCallback` and `ItemDistanceTo` in `TIMapUtils` +- **Added**: Tap handler closure to `DefaultConfigurableStatefulButton.ViewModel` + + ### 1.51.0 - **Added**: `BaseModalViewController` implementing `PanModalPresentable` with additional functionality diff --git a/Makefile b/Makefile index f9807505..fb8080a9 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ export SRCROOT := $(shell pwd) -push_to_podspecs: TISwiftUtils.target TIFoundationUtils.target TICoreGraphicsUtils.target TIKeychainUtils.target TIUIKitCore.target TIUIElements.target TIWebView.target TIBottomSheet.target TISwiftUICore.target TITableKitUtils.target TIDeeplink.target TIDeveloperUtils.target TILogging.target TINetworking.target TIMoyaNetworking.target TINetworkingCache.target TIMapUtils.target TIAppleMapUtils.target TIGoogleMapUtils.target TIPagination.target TIAuth.target TIEcommerce.target TITextProcessing.target +push_to_podspecs: TISwiftUtils.target TIFoundationUtils.target TICoreGraphicsUtils.target TIKeychainUtils.target TIUIKitCore.target TIUIElements.target TIWebView.target TIBottomSheet.target TISwiftUICore.target TITableKitUtils.target TIDeeplink.target TIDeveloperUtils.target TILogging.target TINetworking.target TIMoyaNetworking.target TINetworkingCache.target TIMapUtils.target TIAppleMapUtils.target TIGoogleMapUtils.target TIPagination.target TIAuth.target TIEcommerce.target TITextProcessing.target TIApplication.target $(call clean) TISwiftUtils.target: MODULE_NAME="TISwiftUtils" ./project-scripts/push_to_podspecs.sh touch TISwiftUtils.target -TIFoundationUtils.target: TISwiftUtils.target +TIFoundationUtils.target: TISwiftUtils.target TILogging.target MODULE_NAME="TIFoundationUtils" ./project-scripts/push_to_podspecs.sh touch TIFoundationUtils.target @@ -99,5 +99,9 @@ TITextProcessing.target: MODULE_NAME="TITextProcessing" ./project-scripts/push_to_podspecs.sh touch TITextProcessing.target +TIApplication.target: TIFoundationUtils.target TILogging.target + MODULE_NAME="TIApplication" ./project-scripts/push_to_podspecs.sh + touch TIApplication.target + clean: rm *.target diff --git a/Package.swift b/Package.swift index feca2418..dbd1be06 100644 --- a/Package.swift +++ b/Package.swift @@ -11,13 +11,19 @@ let package = Package( ], products: [ + // MARK: - Application + + .library(name: "TIApplication", targets: ["TIApplication"]), + // MARK: - UIKit + .library(name: "TIUIKitCore", targets: ["TIUIKitCore"]), .library(name: "TIUIElements", targets: ["TIUIElements"]), .library(name: "TIWebView", targets: ["TIWebView"]), .library(name: "TIBottomSheet", targets: ["TIBottomSheet"]), // MARK: - SwiftUI + .library(name: "TISwiftUICore", targets: ["TISwiftUICore"]), // MARK: - Utils @@ -41,6 +47,7 @@ let package = Package( .library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]), // MARK: - Elements + .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]), .library(name: "TITransitions", targets: ["TITransitions"]), .library(name: "TIPagination", targets: ["TIPagination"]), @@ -60,7 +67,15 @@ let package = Package( ], targets: [ + // MARK: - Application architecture + + .target(name: "TIApplication", + dependencies: ["TILogging", "TIFoundationUtils", "KeychainAccess"], + path: "TIApplication/Sources", + plugins: [.plugin(name: "TISwiftLintPlugin")]), + // MARK: - UIKit + .target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"), .target(name: "TIUIElements", @@ -77,6 +92,7 @@ let package = Package( plugins: [.plugin(name: "TISwiftLintPlugin")]), // MARK: - SwiftUI + .target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"), @@ -140,6 +156,7 @@ let package = Package( plugins: [.plugin(name: "TISwiftLintPlugin")]), // MARK: - Elements + .target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"), .target(name: "TITransitions", path: "TITransitions/Sources"), .target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"), diff --git a/TIAppleMapUtils/TIAppleMapUtils.podspec b/TIAppleMapUtils/TIAppleMapUtils.podspec index 1319b561..3db0176f 100644 --- a/TIAppleMapUtils/TIAppleMapUtils.podspec +++ b/TIAppleMapUtils/TIAppleMapUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIAppleMapUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIApplication/Sources/CoreDependencies.swift b/TIApplication/Sources/CoreDependencies.swift new file mode 100644 index 00000000..bfadd75c --- /dev/null +++ b/TIApplication/Sources/CoreDependencies.swift @@ -0,0 +1,105 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TIFoundationUtils +import KeychainAccess +import TILogging +import UIKit + +public struct CoreDependencies { + public var dateFormattersResusePool = DateFormattersReusePool() + public var iso8601DateFormattersReusePool = ISO8601DateFormattersReusePool() + public var jsonCodingConfigurator: JsonCodingConfigurator + public var jsonKeyValueDecoder: JSONKeyValueDecoder + public var jsonKeyValueEncoder: JSONKeyValueEncoder + + public var device: UIDevice = .current + + public var bundle: Bundle = .main + public var fileManager: FileManager = .default + + public var logger: DefaultOSLogErrorLogger + + public var keychain: Keychain + public var defaults: UserDefaults + + public var appGroupDefaults: UserDefaults? + public var appGroupKeychain: Keychain? + + public var appGroupCacheDirectory: URL? + + public var networkCallbackQueue: DispatchQueue + + public init(bundleIdentifierPrefix: String, + appIdentifier: String, + customAppGroupIdentifier: String? = nil) { + + jsonCodingConfigurator = JsonCodingConfigurator(dateFormattersReusePool: dateFormattersResusePool, + iso8601DateFormattersReusePool: iso8601DateFormattersReusePool) + + jsonKeyValueDecoder = JSONKeyValueDecoder(jsonDecoder: jsonCodingConfigurator.jsonDecoder) + jsonKeyValueEncoder = JSONKeyValueEncoder(jsonEncoder: jsonCodingConfigurator.jsonEncoder) + + let bundleIdentifier = bundleIdentifierPrefix + "." + appIdentifier + + logger = DefaultOSLogErrorLogger(subsystem: bundleIdentifier, category: "general") + + keychain = Keychain(service: bundleIdentifier) + defaults = .standard + + let appGroupIdentifier = customAppGroupIdentifier ?? "group." + bundleIdentifierPrefix + + if let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) { + var appGroupCacheURL: URL + + if #available(iOS 16.0, *) { + appGroupCacheURL = containerURL.appending(path: "Caches", directoryHint: .isDirectory) + } else { + appGroupCacheURL = containerURL.appendingPathComponent("Caches", isDirectory: true) + } + + var resourceValues = URLResourceValues() + resourceValues.isExcludedFromBackup = true + + do { + try fileManager.createDirectory(at: appGroupCacheURL, + withIntermediateDirectories: true) + + try appGroupCacheURL.setResourceValues(resourceValues) + + appGroupCacheDirectory = appGroupCacheURL + } catch { + logger.log(error: error, file: #file, line: #line) + } + + appGroupDefaults = UserDefaults(suiteName: appGroupIdentifier) + appGroupKeychain = Keychain(service: bundleIdentifierPrefix, accessGroup: appGroupIdentifier) + } else { + appGroupCacheDirectory = nil + appGroupDefaults = nil + appGroupKeychain = nil + } + + networkCallbackQueue = DispatchQueue(label: bundleIdentifier + ".network-callback-queue", attributes: .concurrent) + } +} diff --git a/TIAuth/Sources/TokenStorage/AuthSettingsStorage.swift b/TIApplication/Sources/TargetDependencies.swift similarity index 83% rename from TIAuth/Sources/TokenStorage/AuthSettingsStorage.swift rename to TIApplication/Sources/TargetDependencies.swift index f5f626e6..c01b0919 100644 --- a/TIAuth/Sources/TokenStorage/AuthSettingsStorage.swift +++ b/TIApplication/Sources/TargetDependencies.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Touch Instinct +// Copyright (c) 2023 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -20,9 +20,7 @@ // THE SOFTWARE. // -import Foundation - -public protocol AuthSettingsStorage: AnyObject { - /// Should be true by default (on app first run) - var shouldResetStoredAuthData: Bool { get set } +public protocol TargetDependencies { + static func assemble() -> Self + static func assembleForPreview() -> Self } diff --git a/TIApplication/TIApplication.podspec b/TIApplication/TIApplication.podspec new file mode 100644 index 00000000..2a313988 --- /dev/null +++ b/TIApplication/TIApplication.podspec @@ -0,0 +1,25 @@ +Pod::Spec.new do |s| + s.name = 'TIApplication' + s.version = '1.52.0' + s.summary = 'Application architecture.' + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.7'] + + sources = 'Sources/**/*' + if ENV["DEVELOPMENT_INSTALL"] # installing using :path => + s.source_files = sources + s.exclude_files = s.name + '.app' + else + s.source_files = s.name + '/' + sources + s.exclude_files = s.name + '/*.app' + end + + s.dependency 'TIFoundationUtils', s.version.to_s + s.dependency 'TILogging', s.version.to_s + s.dependency 'KeychainAccess', "~> 4.2" +end diff --git a/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift index 9b23e6bd..1e7aebe8 100644 --- a/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift +++ b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenKeyStorage.swift @@ -40,8 +40,8 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), localAuthContext: LAContext = Defaults.reusableLAContext, - settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), - encryptedTokenKeyStorageKey: StorageKey = Defaults.encryptedTokenKeyStorageKey) { + encryptedTokenKeyStorageKey: StorageKey = Defaults.encryptedTokenKeyStorageKey, + appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) { let getValueClosure: GetValueClosure = { keychain, storageKey in do { @@ -64,9 +64,9 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage } super.init(keychain: keychain.authenticationContext(localAuthContext), - settingsStorage: settingsStorage, storageKey: encryptedTokenKeyStorageKey, getValueClosure: getValueClosure, - storeValueClosure: storeValueClosure) + storeValueClosure: storeValueClosure, + appFirstRunCheckStorage: appFirstRunCheckStorage) } } diff --git a/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift index 95a90e16..c3eb9393 100644 --- a/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift +++ b/TIAuth/Sources/TokenStorage/DefaultEncryptedTokenStorage.swift @@ -32,8 +32,8 @@ open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage = Defaults.encryptedTokenStorageKey) { + encryptedTokenStorageKey: StorageKey = Defaults.encryptedTokenStorageKey, + appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) { let getValueClosure: GetValueClosure = { keychain, storageKey in do { @@ -60,9 +60,9 @@ open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage { - .init(rawValue: "shouldResetFingerprints") + public static var shouldResetAuthDataKey: StorageKey { + .init(rawValue: "shouldResetAuthData") } } - public override init(defaultsStorage: UserDefaults = .standard, - storageKey: StorageKey = Defaults.shouldResetFingerprintsKey) { + public override init(defaults: UserDefaults = .standard, + storageKey: StorageKey = Defaults.shouldResetAuthDataKey) { - super.init(defaultsStorage: defaultsStorage, storageKey: storageKey) + super.init(defaults: defaults, storageKey: storageKey) } } diff --git a/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift b/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift index bf41c001..b9f7e709 100644 --- a/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift +++ b/TIAuth/Sources/TokenStorage/SingleValueAuthKeychainStorage.swift @@ -25,51 +25,29 @@ import KeychainAccess import Foundation import TIKeychainUtils -open class SingleValueAuthKeychainStorage: BaseSingleValueKeychainStorage { +open class SingleValueAuthKeychainStorage: AppInstallLifetimeSingleValueStorage< +BaseSingleValueKeychainStorage, +BoolValueDefaultsStorage> { + public typealias GetValueClosure = BaseSingleValueKeychainStorage.GetValueClosure + public typealias StoreValueClosure = BaseSingleValueKeychainStorage.StoreValueClosure + open class Defaults { public static var keychainServiceIdentifier: String { Bundle.main.bundleIdentifier ?? "ru.touchin.TIAuth" } } - public let settingsStorage: AuthSettingsStorage - public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), - settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), storageKey: StorageKey, getValueClosure: @escaping GetValueClosure, - storeValueClosure: @escaping StoreValueClosure) { + storeValueClosure: @escaping StoreValueClosure, + appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) { - self.settingsStorage = settingsStorage + let keychainStorage = BaseSingleValueKeychainStorage(keychain: keychain, + storageKey: storageKey, + getValueClosure: getValueClosure, + storeValueClosure: storeValueClosure) - super.init(keychain: keychain, - storageKey: storageKey, - getValueClosure: getValueClosure, - storeValueClosure: storeValueClosure) - } - - // MARK: - SingleValueStorage - - open override func hasStoredValue() -> Bool { - !settingsStorage.shouldResetStoredAuthData && super.hasStoredValue() - } - - open override func getValue() -> Result { - guard !settingsStorage.shouldResetStoredAuthData else { - let result: Result - - do { - try storage.remove(storageKey.rawValue) - settingsStorage.shouldResetStoredAuthData = false - - result = .failure(.valueNotFound) - } catch { - result = .failure(.unableToWriteData(underlyingError: error)) - } - - return result - } - - return super.getValue() + super.init(storage: keychainStorage, appFirstRunCheckStorage: appFirstRunCheckStorage) } } diff --git a/TIAuth/TIAuth.podspec b/TIAuth/TIAuth.podspec index 231c1c91..9d9a9172 100644 --- a/TIAuth/TIAuth.podspec +++ b/TIAuth/TIAuth.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIAuth' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Login, registration, confirmation and other related actions' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIBottomSheet/TIBottomSheet.podspec b/TIBottomSheet/TIBottomSheet.podspec index bfaeb5a1..7b071f12 100644 --- a/TIBottomSheet/TIBottomSheet.podspec +++ b/TIBottomSheet/TIBottomSheet.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIBottomSheet' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Base models for creating bottom sheet view controllers' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'castlele' => 'nikita.semenov@touchin.ru', 'petropavel13' => 'ivan.smolin@touchin.ru'} diff --git a/TICoreGraphicsUtils/TICoreGraphicsUtils.podspec b/TICoreGraphicsUtils/TICoreGraphicsUtils.podspec index e36a6951..79ba72cd 100644 --- a/TICoreGraphicsUtils/TICoreGraphicsUtils.podspec +++ b/TICoreGraphicsUtils/TICoreGraphicsUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TICoreGraphicsUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'CoreGraphics drawing helpers' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIDeeplink/TIDeeplink.podspec b/TIDeeplink/TIDeeplink.podspec index f018e444..825e29ec 100644 --- a/TIDeeplink/TIDeeplink.podspec +++ b/TIDeeplink/TIDeeplink.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIDeeplink' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Deeplink service API' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', 'castlele' => 'nikita.semenov@touchin.ru' } diff --git a/TIDeveloperUtils/TIDeveloperUtils.podspec b/TIDeveloperUtils/TIDeveloperUtils.podspec index 00cb5431..c70ba1fb 100644 --- a/TIDeveloperUtils/TIDeveloperUtils.podspec +++ b/TIDeveloperUtils/TIDeveloperUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIDeveloperUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Universal web view API' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', 'castlele' => 'nikita.semenov@touchin.ru' } diff --git a/TIEcommerce/TIEcommerce.podspec b/TIEcommerce/TIEcommerce.podspec index c3b053a7..2108e49a 100644 --- a/TIEcommerce/TIEcommerce.podspec +++ b/TIEcommerce/TIEcommerce.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIEcommerce' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Cart, products, promocodes, bonuses and other related actions' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/BoolValueDefaultsStorage.swift b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/BoolValueDefaultsStorage.swift new file mode 100644 index 00000000..8c989ba0 --- /dev/null +++ b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/BoolValueDefaultsStorage.swift @@ -0,0 +1,32 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class BoolValueDefaultsStorage: BaseSingleValueDefaultsStorage { + public init(defaults: UserDefaults, storageKey: StorageKey) { + super.init(defaults: defaults, + storageKey: storageKey, + getValueClosure: { .success($0.bool(forKey: $1.rawValue)) }, + storeValueClosure: { .success($0.set($1, forKey: $2.rawValue)) }) + } +} diff --git a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/MigratableStorage/MigratingSingleValueStorage.swift b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/MigratableStorage/MigratingSingleValueStorage.swift index 11728a8a..9fe8125c 100644 --- a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/MigratableStorage/MigratingSingleValueStorage.swift +++ b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/MigratableStorage/MigratingSingleValueStorage.swift @@ -52,9 +52,7 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType, public func store(value: ValueType) -> Result { targetStorage.store(value: value) .flatMap { - if case let .failure(error) = sourceStorage.deleteValue() { - logErrorIfNeeded(error, file: #file, line: #line) - } + deleteSourceValue() return .success(()) } @@ -63,9 +61,7 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType, public func getValue() -> Result { targetStorage.getValue() .flatMap { - if case let .failure(error) = sourceStorage.deleteValue() { - logErrorIfNeeded(error, file: #file, line: #line) - } + deleteSourceValue() return .success($0) } @@ -73,7 +69,11 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType, sourceStorage.getValue() .flatMap { value in store(value: value) - .map { _ in value } + .map { _ in + deleteSourceValue() + + return value + } } } } @@ -107,6 +107,12 @@ where SourceStorage.ErrorType == TargetStorage.ErrorType, errorLogger.log(error: error, file: file, line: line) } + + private func deleteSourceValue() { + if case let .failure(error) = sourceStorage.deleteValue() { + logErrorIfNeeded(error, file: #file, line: #line) + } + } } // MARK: - Factory methods diff --git a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Wrappers/AppInstallLifetimeSingleValueStorage.swift b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Wrappers/AppInstallLifetimeSingleValueStorage.swift index 0ea24379..f829bc57 100644 --- a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Wrappers/AppInstallLifetimeSingleValueStorage.swift +++ b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Wrappers/AppInstallLifetimeSingleValueStorage.swift @@ -20,51 +20,52 @@ // THE SOFTWARE. // -open class AppInstallLifetimeSingleValueStorage: SingleValueStorage -where Storage.ErrorType == StorageError { +public typealias DefaultAppFirstRunCheckStorage = BoolValueDefaultsStorage - public let appReinstallChecker: AppReinstallChecker +open class AppInstallLifetimeSingleValueStorage: SingleValueStorage +where Storage.ErrorType == StorageError, + AppFirstRunCheckStorage.ErrorType == Storage.ErrorType, + AppFirstRunCheckStorage.ValueType == Bool { + + public let appFirstRunCheckStorage: AppFirstRunCheckStorage public let wrappedStorage: Storage public init(storage: Storage, - appReinstallChecker: AppReinstallChecker) { + appFirstRunCheckStorage: AppFirstRunCheckStorage) { self.wrappedStorage = storage - self.appReinstallChecker = appReinstallChecker + self.appFirstRunCheckStorage = appFirstRunCheckStorage } // MARK: - SingleValueStorage open func hasStoredValue() -> Bool { - if appReinstallChecker.isAppFirstRun { - return false - } + let hasStoredValueResult = appFirstRunCheckStorage.getValue() + .flatMap { .success($0 ? false : wrappedStorage.hasStoredValue()) } - return wrappedStorage.hasStoredValue() + return (try? hasStoredValueResult.get()) ?? false } open func getValue() -> Result { - guard appReinstallChecker.isAppFirstRun else { - return wrappedStorage.getValue() - } + appFirstRunCheckStorage.getValue() + .flatMap { + guard $0 else { + return wrappedStorage.getValue() + } - let result = wrappedStorage.deleteValue() - - if case .success = result { - appReinstallChecker.isAppFirstRun = false - } - - return result.flatMap { .failure(.valueNotFound) } + return wrappedStorage.deleteValue() + .flatMap { + appFirstRunCheckStorage.store(value: false) + } + .flatMap { .failure(.valueNotFound) } + } } open func store(value: Storage.ValueType) -> Result { - let result = wrappedStorage.store(value: value) - - if case .success = result { - appReinstallChecker.isAppFirstRun = false - } - - return result + wrappedStorage.store(value: value) + .flatMap { + appFirstRunCheckStorage.store(value: false) + } } public func deleteValue() -> Result { @@ -73,10 +74,12 @@ where Storage.ErrorType == StorageError { } public extension SingleValueStorage { - func appInstallLifetimeStorage(reinstallChecker: AppReinstallChecker) -> AppInstallLifetimeSingleValueStorage - where Self.ErrorType == ErrorType { + func appInstallLifetimeStorage(appFirstRunCheckStorage: FirstRunCheckStorage) -> AppInstallLifetimeSingleValueStorage + where Self.ErrorType == ErrorType, + FirstRunCheckStorage.ErrorType == ErrorType, + FirstRunCheckStorage.ValueType == Bool { AppInstallLifetimeSingleValueStorage(storage: self, - appReinstallChecker: reinstallChecker) + appFirstRunCheckStorage: appFirstRunCheckStorage) } } diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index 98183c7d..61495197 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for Foundation framework classes.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIGoogleMapUtils/TIGoogleMapUtils.podspec b/TIGoogleMapUtils/TIGoogleMapUtils.podspec index f799cd0f..fcf470ec 100644 --- a/TIGoogleMapUtils/TIGoogleMapUtils.podspec +++ b/TIGoogleMapUtils/TIGoogleMapUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIGoogleMapUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec index d22109cf..66842ac8 100644 --- a/TIKeychainUtils/TIKeychainUtils.podspec +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIKeychainUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for Keychain classes.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TILogging/TILogging.podspec b/TILogging/TILogging.podspec index 03ac88c1..7780280d 100644 --- a/TILogging/TILogging.podspec +++ b/TILogging/TILogging.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TILogging' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Logging for TI libraries.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIMapUtils/Sources/Helpers/TIMapLogger.swift b/TIMapUtils/Sources/Helpers/TIMapLogger.swift new file mode 100644 index 00000000..66e07511 --- /dev/null +++ b/TIMapUtils/Sources/Helpers/TIMapLogger.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TILogging +import os + +public final class TIMapLogger: DefaultOSLogErrorLogger { + public init(category: String) { + super.init(log: OSLog(subsystem: "TIMapUtils", category: category)) + } +} diff --git a/TIMapUtils/Sources/Helpers/UserLocationFetcher.swift b/TIMapUtils/Sources/Helpers/UserLocationFetcher.swift index b39ac634..1f33bc33 100644 --- a/TIMapUtils/Sources/Helpers/UserLocationFetcher.swift +++ b/TIMapUtils/Sources/Helpers/UserLocationFetcher.swift @@ -35,10 +35,12 @@ open class UserLocationFetcher: NSObject, CLLocationManagerDelegate { case fullAccuracyDenied } - public typealias LocationCallback = (CLLocation) -> Void public typealias OnAuthSuccessCallback = (CLLocationManager) -> Void public typealias OnAuthFailureCallback = (Failure) -> Void + public typealias OnLocationCallback = (CLLocation) -> Void + public typealias OnLocationFetchFailureCallback = (Error) -> Void + public var locationManager: CLLocationManager public var accuracyRequest: AccuracyRequest @@ -81,21 +83,26 @@ open class UserLocationFetcher: NSObject, CLLocationManagerDelegate { public var authSuccessCallback: OnAuthSuccessCallback public var authFailureCallback: OnAuthFailureCallback? - public var locationCallback: LocationCallback? + public var onLocationCallback: OnLocationCallback? + public var onLocationFetchFailureCallback: OnLocationFetchFailureCallback? - public var logger: ErrorLogger = DefaultOSLogErrorLogger(subsystem: "TIMapUtils", category: "UserLocationFetcher") + public var logger: ErrorLogger = TIMapLogger(category: "UserLocationFetcher") public init(locationManager: CLLocationManager = CLLocationManager(), accuracyRequest: AccuracyRequest = .default, onSuccess: @escaping OnAuthSuccessCallback = { $0.requestLocation() }, onFailure: OnAuthFailureCallback? = nil, - locationCallback: LocationCallback? = nil) { + onLocationFetchFailureCallback: OnLocationFetchFailureCallback? = nil, + onLocationCallback: OnLocationCallback? = nil) { self.locationManager = locationManager self.accuracyRequest = accuracyRequest + self.authSuccessCallback = onSuccess self.authFailureCallback = onFailure - self.locationCallback = locationCallback + + self.onLocationCallback = onLocationCallback + self.onLocationFetchFailureCallback = onLocationFetchFailureCallback super.init() } @@ -174,14 +181,16 @@ open class UserLocationFetcher: NSObject, CLLocationManagerDelegate { } open func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let locationCallback else { + guard let onLocationCallback else { return } - locations.forEach(locationCallback) + locations.forEach(onLocationCallback) } open func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + onLocationFetchFailureCallback?(error) + logger.log(error: error, file: #file, line: #line) } } diff --git a/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift b/TIMapUtils/Sources/MapItem/ItemDistanceTo.swift similarity index 55% rename from TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift rename to TIMapUtils/Sources/MapItem/ItemDistanceTo.swift index a12f2a56..ed9f6c39 100644 --- a/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift +++ b/TIMapUtils/Sources/MapItem/ItemDistanceTo.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Touch Instinct +// Copyright (c) 2023 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -20,33 +20,27 @@ // THE SOFTWARE. // -import TIFoundationUtils -import Foundation +import CoreLocation -open class DefaultAuthSettingsStorage: AuthSettingsStorage { - public enum Defaults { - public static var shouldResetAuthDataKey: StorageKey { - .init(rawValue: "shouldResetAuthData") - } +public struct ItemDistanceTo { + public let item: T + public let distance: CLLocationDistance + + public init(item: T, distance: CLLocationDistance) { + self.item = item + self.distance = distance } - private let reinstallChecker: AppReinstallChecker + public init(item: T, + distanceTo location: CLLocation) where T: MapLocatable, T.Position == CLLocationCoordinate2D { - // MARK: - PinCodeSettingsStorage + if let itemPosition = item.position { + let itemLocation = CLLocation(latitude: itemPosition.latitude, + longitude: itemPosition.longitude) - open var shouldResetStoredAuthData: Bool { - get { - reinstallChecker.isAppFirstRun + self.init(item: item, distance: itemLocation.distance(from: location)) + } else { + self.init(item: item, distance: .infinity) } - set { - reinstallChecker.isAppFirstRun = newValue - } - } - - public init(defaultsStorage: UserDefaults = .standard, - storageKey: StorageKey = Defaults.shouldResetAuthDataKey) { - - self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage, - storageKey: storageKey) } } diff --git a/TIMapUtils/TIMapUtils.podspec b/TIMapUtils/TIMapUtils.podspec index 6c0a706a..74c3ff47 100644 --- a/TIMapUtils/TIMapUtils.podspec +++ b/TIMapUtils/TIMapUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIMapUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for map objects clustering and interacting.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIMoyaNetworking/TIMoyaNetworking.podspec b/TIMoyaNetworking/TIMoyaNetworking.podspec index 0ef8a3b3..1de69f72 100644 --- a/TIMoyaNetworking/TIMoyaNetworking.podspec +++ b/TIMoyaNetworking/TIMoyaNetworking.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIMoyaNetworking' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Moya + Swagger network service.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TINetworking/Sources/RequestPreprocessors/DefaultSecuritySchemesRequestPreprocessor.swift b/TINetworking/Sources/RequestPreprocessors/DefaultSecuritySchemesRequestPreprocessor.swift index 58d6ad85..d485cb85 100644 --- a/TINetworking/Sources/RequestPreprocessors/DefaultSecuritySchemesRequestPreprocessor.swift +++ b/TINetworking/Sources/RequestPreprocessors/DefaultSecuritySchemesRequestPreprocessor.swift @@ -86,6 +86,8 @@ open class DefaultEndpointSecurityPreprocessor: EndpointRequestPreprocessor { return Cancellables.nonCancellable() } + let remainingSchemes = schemes.dropFirst() + let preprocessorsGroup: [KeyValueTuple] = schemeGroup.compactMap { guard let preprocessor = schemePreprocessors[$0.key] else { return nil @@ -98,7 +100,7 @@ open class DefaultEndpointSecurityPreprocessor: EndpointRequestPreprocessor { // unable to satisfy group requirement // try next scheme group return preprocess(request: request, - using: schemes.dropFirst(), + using: remainingSchemes, schemePreprocessors: schemePreprocessors, completion: completion) } @@ -112,13 +114,13 @@ open class DefaultEndpointSecurityPreprocessor: EndpointRequestPreprocessor { completion(.success(modifiedRequest)) case let .failure(error): - guard !schemes.isEmpty else { + guard !remainingSchemes.isEmpty else { completion(.failure(error)) return } preprocess(request: request, - using: schemes.dropFirst(), + using: remainingSchemes, schemePreprocessors: schemePreprocessors, completion: completion) .add(to: scope) diff --git a/TINetworking/TINetworking.podspec b/TINetworking/TINetworking.podspec index 5e0bba93..bc32133c 100644 --- a/TINetworking/TINetworking.podspec +++ b/TINetworking/TINetworking.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TINetworking' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Swagger-frendly networking layer helpers.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TINetworkingCache/Sources/EndpointCacheService.swift b/TINetworkingCache/Sources/EndpointCacheService.swift index 7cc2d4c9..f69a4773 100644 --- a/TINetworkingCache/Sources/EndpointCacheService.swift +++ b/TINetworkingCache/Sources/EndpointCacheService.swift @@ -34,7 +34,8 @@ public struct EndpointCacheService: SingleValueStorage { public init(serializedRequest: SerializedRequest, cacheLifetime: Expiry, - jsonCodingConfigurator: JsonCodingConfigurator) throws { + jsonCodingConfigurator: JsonCodingConfigurator, + appGroupDirectory: URL? = nil) throws { self.serializedRequest = serializedRequest @@ -47,7 +48,8 @@ public struct EndpointCacheService: SingleValueStorage { } let diskConfig = DiskConfig(name: nameWithoutLeadingSlash, - expiry: cacheLifetime) + expiry: cacheLifetime, + directory: appGroupDirectory) let memoryConfig = MemoryConfig(expiry: cacheLifetime, countLimit: 0, totalCostLimit: 0) diff --git a/TINetworkingCache/TINetworkingCache.podspec b/TINetworkingCache/TINetworkingCache.podspec index 0e947440..49928a9d 100644 --- a/TINetworkingCache/TINetworkingCache.podspec +++ b/TINetworkingCache/TINetworkingCache.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TINetworkingCache' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Caching results of EndpointRequests.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIPagination/TIPagination.podspec b/TIPagination/TIPagination.podspec index af15b71b..4b5dffee 100644 --- a/TIPagination/TIPagination.podspec +++ b/TIPagination/TIPagination.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIPagination' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Generic pagination component.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TISwiftUICore/TISwiftUICore.podspec b/TISwiftUICore/TISwiftUICore.podspec index e8f73430..c527a695 100644 --- a/TISwiftUICore/TISwiftUICore.podspec +++ b/TISwiftUICore/TISwiftUICore.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TISwiftUICore' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Core UI elements: protocols, views and helpers.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index 1451f5f8..0ba317d3 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Bunch of useful helpers for Swift development.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index a31eaac8..fd0e1cd3 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for TableKit classes.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TITextProcessing/TITextProcessing.podspec b/TITextProcessing/TITextProcessing.podspec index 8813a18e..939f0d8c 100644 --- a/TITextProcessing/TITextProcessing.podspec +++ b/TITextProcessing/TITextProcessing.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TITextProcessing' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'A text processing service helping to get a text mask and a placeholder from incoming regex.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/TIUIElements/Sources/Appearance/UIView+ViewLayout.swift b/TIUIElements/Sources/Appearance/UIView+ViewLayout.swift index 4a7118a3..94904d82 100644 --- a/TIUIElements/Sources/Appearance/UIView+ViewLayout.swift +++ b/TIUIElements/Sources/Appearance/UIView+ViewLayout.swift @@ -53,7 +53,7 @@ extension UIView { public var centerOffset: UIOffset public var insets: UIEdgeInsets - public init(insets: UIEdgeInsets = .nan, + public init(insets: UIEdgeInsets = .zero, size: CGSize = .infinity, centerOffset: UIOffset = .nan) { @@ -73,7 +73,7 @@ extension UIView { open class BaseSpecedWrappedLayout: BaseWrappedLayout { public var spacing: CGFloat - public init(insets: UIEdgeInsets = .nan, + public init(insets: UIEdgeInsets = .zero, size: CGSize = .infinity, centerOffset: UIOffset = .nan, spacing: CGFloat = .zero) { @@ -98,7 +98,7 @@ extension UIView { public var distribution: UIStackView.Distribution public var alignment: UIStackView.Alignment - public init(insets: UIEdgeInsets = .nan, + public init(insets: UIEdgeInsets = .zero, size: CGSize = .infinity, centerOffset: UIOffset = .nan, spacing: CGFloat = .zero, diff --git a/TIUIElements/Sources/Views/BaseInitializeableViews/BaseInitializeableStackView.swift b/TIUIElements/Sources/Views/BaseInitializeableViews/BaseInitializeableStackView.swift new file mode 100644 index 00000000..d58e4170 --- /dev/null +++ b/TIUIElements/Sources/Views/BaseInitializeableViews/BaseInitializeableStackView.swift @@ -0,0 +1,70 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TIUIKitCore +import UIKit + +open class BaseInitializeableStackView: UIStackView, InitializableViewProtocol { + public var callbacks: [ViewCallbacks] = [] + + public override init(frame: CGRect) { + super.init(frame: frame) + + initializeView() + } + + public required init(coder: NSCoder) { + super.init(coder: coder) + + initializeView() + } + + open override func layoutSubviews() { + super.layoutSubviews() + + for callback in callbacks { + callback.onDidLayoutSubviews() + } + } + + // MARK: - InitializableViewProtocol + + open func addViews() { + // override in subclass + } + + open func configureLayout() { + // override in subclass + } + + open func bindViews() { + // override in subclass + } + + open func configureAppearance() { + // override in subclass + } + + open func localize() { + // override in subclass + } +} diff --git a/TIUIElements/Sources/Views/BaseStackView/BaseStackView.swift b/TIUIElements/Sources/Views/BaseStackView/BaseStackView.swift index 36c51f2d..faf1930e 100644 --- a/TIUIElements/Sources/Views/BaseStackView/BaseStackView.swift +++ b/TIUIElements/Sources/Views/BaseStackView/BaseStackView.swift @@ -23,7 +23,7 @@ import UIKit import TIUIKitCore -open class BaseStackView: UIStackView { +open class BaseStackView: BaseInitializeableStackView { public var customArrangedSubviews: [View] = [] @available(*, unavailable, message: "Use strongly-typed version of this function") @@ -56,6 +56,8 @@ open class BaseStackView: UIStackView { customArrangedSubviews.remove(at: indexOfView) super.removeArrangedSubview(view) + + view.removeFromSuperview() } open func replaceArrangedSubviews(_ newViews: [View]) { @@ -73,8 +75,10 @@ open class BaseStackView: UIStackView { super.insertArrangedSubview(view, at: stackIndex) } +} - open func configureUIStackView(appearance: BaseWrappedAppearance) { +public extension UIStackView { + func configureUIStackView(appearance: BaseWrappedAppearance) { configureUIView(appearance: appearance) axis = appearance.layout.axis @@ -91,7 +95,7 @@ extension BaseStackView: ConfigurableView where View: ConfigurableView { } extension BaseStackView: AppearanceConfigurable where View: AppearanceConfigurable { - public final class Appearance: UIView.BaseWrappedAppearance, WrappedViewAppearance { + public final class Appearance: BaseWrappedAppearance, WrappedViewAppearance { public static var defaultAppearance: Self { Self() @@ -99,7 +103,7 @@ extension BaseStackView: AppearanceConfigurable where View: AppearanceConfigurab public var arrangedSubviewsAppearance: View.Appearance - public init(layout: UIView.DefaultStackLayout = .defaultLayout, + public init(layout: DefaultStackLayout = .defaultLayout, backgroundColor: UIColor = .clear, border: UIViewBorder = .init(), shadow: UIViewShadow? = nil, diff --git a/TIUIElements/Sources/Views/DefaultHomogeneousItemsCollectionView/DefaultCollectionViewModel.swift b/TIUIElements/Sources/Views/DefaultHomogeneousItemsCollectionView/DefaultCollectionViewModel.swift new file mode 100644 index 00000000..f44ff1ca --- /dev/null +++ b/TIUIElements/Sources/Views/DefaultHomogeneousItemsCollectionView/DefaultCollectionViewModel.swift @@ -0,0 +1,32 @@ +import TISwiftUtils +import Foundation + +open class DefaultCollectionViewModel { + public typealias SelectItemHandlerPayload = (itemViewModel: ItemViewModel, indexPath: IndexPath) + + public var items: [ItemViewModel] + public var selectItemHandler: ParameterClosure? + + public init(items: [ItemViewModel], + selectItemHandler: ParameterClosure? = nil) { + + self.items = items + self.selectItemHandler = selectItemHandler + } + + open func numberOfSections() -> Int { + 1 + } + + open func numberOfItems(inSection section: Int) -> Int { + items.count + } + + open func itemViewModel(at indexPath: IndexPath) -> ItemViewModel { + items[indexPath.row] + } + + open func didSelectItem(at indexPath: IndexPath) { + selectItemHandler?(SelectItemHandlerPayload(itemViewModel(at: indexPath), indexPath)) + } +} diff --git a/TIUIElements/Sources/Views/DefaultHomogeneousItemsCollectionView/DefaultHomogeneousItemsCollectionView.swift b/TIUIElements/Sources/Views/DefaultHomogeneousItemsCollectionView/DefaultHomogeneousItemsCollectionView.swift new file mode 100644 index 00000000..18d495af --- /dev/null +++ b/TIUIElements/Sources/Views/DefaultHomogeneousItemsCollectionView/DefaultHomogeneousItemsCollectionView.swift @@ -0,0 +1,111 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit +import TIUIKitCore + +open class DefaultHomogeneousItemsCollectionView: + BaseInitializeableCollectionView, + UICollectionViewDataSource, + ConfigurableView, + AppearanceConfigurable + where CellContentView.Appearance: WrappedViewAppearance { + + typealias CellType = CellContentView.InCollectionCell + + public private(set) var viewModel: DefaultCollectionViewModel? + + private var appearance: Appearance = .defaultAppearance + + // MARK: - BaseInitializeableCollectionView + + override open func bindViews() { + super.bindViews() + + register(CellType.self, forCellWithReuseIdentifier: String(describing: CellType.self)) + + dataSource = self + } + + override open func configureLayout() { + super.configureLayout() + + collectionViewLayout = createLayout() + } + + override open func configureAppearance() { + super.configureAppearance() + + isScrollEnabled = false + } + + // MARK: - UICollectionViewDataSource + + open func numberOfSections(in collectionView: UICollectionView) -> Int { + viewModel?.numberOfSections() ?? .zero + } + + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + viewModel?.numberOfItems(inSection: section) ?? .zero + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CellType.self), for: indexPath) + + guard let viewModel else { + return cell + } + + switch cell { + case let configureableCell as CellType: + configureableCell.configure(with: viewModel.itemViewModel(at: indexPath)) + configureableCell.configure(appearance: appearance) + + default: + break + } + + return cell + } + + // MARK: - ConfigurableView + + open func configure(with viewModel: DefaultCollectionViewModel) { + self.viewModel = viewModel + + reloadData() + } + + // MARK: - AppearanceConfigurable + + open func configure(appearance: DefaultWrappedViewHolderAppearance) { + configureUIView(appearance: appearance) + + self.appearance = appearance + } + + // MARK: - Subclass override + + open func createLayout() -> UICollectionViewLayout { + collectionViewLayout + } +} diff --git a/TIUIElements/Sources/Views/ListItemView/BaseListItemView.swift b/TIUIElements/Sources/Views/ListItemView/BaseListItemView.swift index f1c9bbf7..91bcead5 100644 --- a/TIUIElements/Sources/Views/ListItemView/BaseListItemView.swift +++ b/TIUIElements/Sources/Views/ListItemView/BaseListItemView.swift @@ -33,13 +33,17 @@ open class BaseListItemView CGFloat { - let leadingToSuperviewContraint: NSLayoutConstraint - let leadingViewLeftInset: CGFloat - - if isLeadingViewHidden { + switch (isLeadingViewHidden, isMiddleViewHidded) { + case (true, true): leadingViewConstraints.deactivate() - middleViewLeadingToSuperViewConstraint.isActive = true - leadingToSuperviewContraint = middleViewLeadingToSuperViewConstraint - leadingViewLeftInset = middleViewLayout.insets.left - } else { - let middleViewLeadingConstant = leadingViewLayout.insets.add(\.left, - to: \.right, - of: middleViewLayout.insets, - onNan: .zero) + return UIEdgeInsets.nan.right - middleViewLeadingConstraint.setActiveConstantOrDeactivate(constant: middleViewLeadingConstant) - leadingViewConstraints.edgeConstraints.leadingConstraint.isActive = true - middleViewConstraints.edgeConstraints.leadingConstraint.isActive = true - middleViewLeadingToSuperViewConstraint.isActive = false + case (false, false): + leadingViewConstraints.edgeConstraints.trailingConstraint.isActive = false + leadingViewConstraints.edgeConstraints.trailingConstraint = middleViewLeadingToLeadingViewConstraint leadingViewConstraints.update(from: leadingViewLayout) - leadingToSuperviewContraint = leadingViewConstraints.edgeConstraints.leadingConstraint - leadingViewLeftInset = leadingViewLayout.insets.left + return middleViewLayout.insets.add(\.left, + to: \.right, + of: leadingViewLayout.insets) + + case (true, false): + leadingViewConstraints.deactivate() + + return middleViewLayout.insets.left + + case (false, true): + leadingViewConstraints.edgeConstraints.trailingConstraint.isActive = false + + defer { + leadingViewConstraints.update(from: leadingViewLayout) + } + + if isTrailingViewHidden { + leadingViewConstraints.edgeConstraints.trailingConstraint = leadingViewTrailingToSuperviewConstraint + + return leadingViewLayout.insets.right + } else { + leadingViewConstraints.edgeConstraints.trailingConstraint = leadingToTrailingViewConstraint + + return leadingViewLayout.insets.add(\.right, + to: \.right, + of: trailingViewLayout.insets) + } + } + } + + private func updateMiddleConstraints(middleViewLayout: WrappedViewLayout, + leadingViewTrailingSpacing: CGFloat, + trailingViewLeadingSpacing: CGFloat) { + + guard !isMiddleViewHidded else { + middleViewConstraints.deactivate() + return } - leadingToSuperviewContraint.constant = leadingViewLeftInset.isFinite ? leadingViewLeftInset : .zero - leadingToSuperviewContraint.isActive = true - } + middleViewConstraints.edgeConstraints.leadingConstraint.isActive = false - private func update(middleViewLayout: WrappedViewLayout) { - middleViewConstraints.update(from: middleViewLayout) - } + if isLeadingViewHidden { + middleViewConstraints.edgeConstraints.leadingConstraint = middleViewLeadingToSuperViewConstraint + } else { + middleViewConstraints.edgeConstraints.leadingConstraint = middleViewLeadingToLeadingViewConstraint + } - private func update(trailingViewLayout: WrappedViewLayout, - middleViewLayout: WrappedViewLayout) { - - let trailingToSuperviewConstraint: NSLayoutConstraint - let trailingViewRightInset: CGFloat + middleViewConstraints.edgeConstraints.trailingConstraint.isActive = false if isTrailingViewHidden { - trailingViewConstraints.deactivate() - middleViewTrailingToSuperViewConstraint.isActive = true - - trailingToSuperviewConstraint = middleViewTrailingToSuperViewConstraint - trailingViewRightInset = middleViewLayout.insets.right + middleViewConstraints.edgeConstraints.trailingConstraint = middleViewTrailingToSuperViewConstraint } else { - let trailingViewLeadingToMiddleConstant = middleViewLayout.insets.add(\.right, - to: \.left, - of: trailingViewLayout.insets, - onNan: .zero) + middleViewConstraints.edgeConstraints.trailingConstraint = middleToTrailingViewConstraint + } - trailingViewLeadingToMiddleConstraint.setActiveConstantOrDeactivate(constant: trailingViewLeadingToMiddleConstant) - trailingViewLeadingToMiddleConstraint.isActive = true - middleViewTrailingToSuperViewConstraint.isActive = false + middleViewConstraints.update(from: middleViewLayout) + + middleViewConstraints.edgeConstraints.leadingConstraint + .setActiveConstantOrDeactivate(constant: leadingViewTrailingSpacing) + + middleViewConstraints.edgeConstraints.trailingConstraint + .setActiveConstantOrDeactivate(constant: trailingViewLeadingSpacing) + } + + private func updateTrailingConstraints(trailingViewLayout: WrappedViewLayout, + middleViewLayout: WrappedViewLayout, + leadingViewLayout: WrappedViewLayout) -> CGFloat { + + switch (isMiddleViewHidded, isTrailingViewHidden) { + case (true, true): + trailingViewConstraints.deactivate() + + return UIEdgeInsets.nan.left + + case (true, false): + trailingViewConstraints.edgeConstraints.leadingConstraint.isActive = false + + defer { + trailingViewConstraints.update(from: trailingViewLayout) + } + + if isLeadingViewHidden { + trailingViewConstraints.edgeConstraints.leadingConstraint = trailingViewLeadingToSuperviewConstraint + + return trailingViewLayout.insets.left + } else { + trailingViewConstraints.edgeConstraints.leadingConstraint = middleToTrailingViewConstraint + + return trailingViewLayout.insets.add(\.left, + to: \.right, + of: middleViewLayout.insets) + } + + case (false, true): + trailingViewConstraints.deactivate() + + return middleViewLayout.insets.right + + case (false, false): + trailingViewConstraints.edgeConstraints.leadingConstraint.isActive = false + trailingViewConstraints.edgeConstraints.leadingConstraint = middleToTrailingViewConstraint trailingViewConstraints.update(from: trailingViewLayout) - trailingToSuperviewConstraint = trailingViewConstraints.edgeConstraints.trailingConstraint - trailingViewRightInset = trailingViewLayout.insets.right + return trailingViewLayout.insets.add(\.right, + to: \.left, + of: middleViewLayout.insets) } - - trailingToSuperviewConstraint.constant = trailingViewRightInset.isFinite ? -trailingViewRightInset : .zero - trailingToSuperviewConstraint.isActive = true } } diff --git a/TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderImageView.swift b/TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderImageView.swift index a0d174b3..cca26289 100644 --- a/TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderImageView.swift +++ b/TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderImageView.swift @@ -52,7 +52,7 @@ open class BasePlaceholderImageView: UIImageView, } public lazy var placeholderConstraints: SubviewConstraints = { - configureWrappedViewLayout() + createSubviewConstraints() }() open override var image: UIImage? { diff --git a/TIFoundationUtils/DataStorage/Sources/AppReinstallChecker.swift b/TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/ClosureTarget.swift similarity index 62% rename from TIFoundationUtils/DataStorage/Sources/AppReinstallChecker.swift rename to TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/ClosureTarget.swift index 8fc4c023..2d3f1c61 100644 --- a/TIFoundationUtils/DataStorage/Sources/AppReinstallChecker.swift +++ b/TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/ClosureTarget.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Touch Instinct +// Copyright (c) 2023 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -21,28 +21,17 @@ // import Foundation +import TISwiftUtils -open class AppReinstallChecker { - private let defaultsStorage: UserDefaults - private let storageKey: String +@MainActor +public final class ClosureTarget: NSObject { + public var handler: UIVoidClosure? - open var isAppFirstRun: Bool { - get { - guard defaultsStorage.object(forKey: storageKey) != nil else { - return true - } - - return defaultsStorage.bool(forKey: storageKey) - } - set { - defaultsStorage.set(newValue, forKey: storageKey) - } + public init(handler: UIVoidClosure?) { + self.handler = handler } - public init(defaultsStorage: UserDefaults = .standard, - storageKey: StorageKey) { - - self.defaultsStorage = defaultsStorage - self.storageKey = storageKey.rawValue + @objc public func onAction() { + self.handler?() } } diff --git a/TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/DefaultConfigurableStatefulButton.swift b/TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/DefaultConfigurableStatefulButton.swift index 96342b9e..60c4b597 100644 --- a/TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/DefaultConfigurableStatefulButton.swift +++ b/TIUIElements/Sources/Views/StatefulButton/DefaultConfigurableStatefulButton/DefaultConfigurableStatefulButton.swift @@ -22,6 +22,7 @@ import TIUIKitCore import UIKit +import TISwiftUtils public final class DefaultConfigurableStatefulButton: StatefulButton, ConfigurableView, AppearanceConfigurable { @@ -32,6 +33,12 @@ public final class DefaultConfigurableStatefulButton: StatefulButton, Configurab public func configure(with viewModel: ViewModel) { viewModel.willReuse(view: self) + let previousTarget = target(forAction: #selector(ClosureTarget.onAction), withSender: nil) + + removeTarget(previousTarget, + action: #selector(ClosureTarget.onAction), + for: .touchUpInside) + stateViewModelMap = viewModel.stateViewModelMap for (state, viewModel) in viewModel.stateViewModelMap { @@ -44,6 +51,10 @@ public final class DefaultConfigurableStatefulButton: StatefulButton, Configurab configureStatefulButton(appearance: appearance) + addTarget(viewModel.closureTarget, + action: #selector(ClosureTarget.onAction), + for: .touchUpInside) + viewModel.didCompleteConfiguration(of: self) } @@ -55,8 +66,8 @@ public final class DefaultConfigurableStatefulButton: StatefulButton, Configurab } } -extension DefaultConfigurableStatefulButton { - public final class ViewModel: DefaultUIViewPresenter { +public extension DefaultConfigurableStatefulButton { + final class ViewModel: DefaultUIViewPresenter { public var stateViewModelMap: [State: BaseButtonViewModel] public var currentState: State { didSet { @@ -64,11 +75,22 @@ extension DefaultConfigurableStatefulButton { } } + var closureTarget: ClosureTarget + + public var tapHandler: UIVoidClosure? { + didSet { + closureTarget.handler = tapHandler + } + } + public init(stateViewModelMap: [State: BaseButtonViewModel], - currentState: State) { + currentState: State, + tapHander: UIVoidClosure?) { self.stateViewModelMap = stateViewModelMap self.currentState = currentState + self.tapHandler = tapHander + self.closureTarget = ClosureTarget(handler: tapHander) } } } diff --git a/TIUIElements/Sources/Views/StatefulButton/StatefulButton+Appearance.swift b/TIUIElements/Sources/Views/StatefulButton/StatefulButton+Appearance.swift index d8c100f2..8d19521f 100644 --- a/TIUIElements/Sources/Views/StatefulButton/StatefulButton+Appearance.swift +++ b/TIUIElements/Sources/Views/StatefulButton/StatefulButton+Appearance.swift @@ -30,16 +30,14 @@ extension StatefulButton { open class BaseAppearance: UIView.BaseAppearance { - public enum Defaults { - public static var stateAppearances: StateAppearances { - [ - .normal: UIButton.DefaultStateAppearance.defaultAppearance, - .highlighted: UIButton.DefaultStateAppearance.defaultAppearance, - .selected: UIButton.DefaultStateAppearance.defaultAppearance, - .disabled: UIButton.DefaultStateAppearance.defaultAppearance, - .loading: UIButton.DefaultStateAppearance.defaultAppearance - ] - } + public static var defaultStateAppearances: StateAppearances { + [ + .normal: UIButton.DefaultStateAppearance.defaultAppearance, + .highlighted: UIButton.DefaultStateAppearance.defaultAppearance, + .selected: UIButton.DefaultStateAppearance.defaultAppearance, + .disabled: UIButton.DefaultStateAppearance.defaultAppearance, + .loading: UIButton.DefaultStateAppearance.defaultAppearance + ] } public var stateAppearances: StateAppearances @@ -48,7 +46,7 @@ extension StatefulButton { backgroundColor: UIColor = .clear, border: UIViewBorder = .init(), shadow: UIViewShadow? = nil, - stateAppearances: StateAppearances = Defaults.stateAppearances) { + stateAppearances: StateAppearances = defaultStateAppearances) { self.stateAppearances = stateAppearances @@ -58,7 +56,6 @@ extension StatefulButton { shadow: shadow) } - public func set(appearanceBuilder: (StateAppearance) -> Void, for states: [State]) { for state in states { stateAppearances[state] = DefaultStateAppearance.make(builder: appearanceBuilder) @@ -66,14 +63,16 @@ extension StatefulButton { } } - open class BasePositionAppearance: BaseAppearance { + open class BasePositionAppearance: + BaseAppearance { + public var activityIndicatorPosition: ActivityIndicatorPosition public init(layout: Layout = .defaultLayout, backgroundColor: UIColor = .clear, border: UIViewBorder = .init(), shadow: UIViewShadow? = nil, - stateAppearances: StateAppearances = Defaults.stateAppearances, + stateAppearances: StateAppearances = defaultStateAppearances, activityIndicatorPosition: ActivityIndicatorPosition = .center) { self.activityIndicatorPosition = activityIndicatorPosition @@ -86,7 +85,8 @@ extension StatefulButton { } } - public final class DefaultPositionAppearance: BasePositionAppearance, WrappedViewAppearance { + public final class DefaultPositionAppearance: BasePositionAppearance, + WrappedViewAppearance { public static var defaultAppearance: Self { Self() } @@ -102,7 +102,7 @@ extension StatefulButton { backgroundColor: UIColor = .clear, border: UIViewBorder = .init(), shadow: UIViewShadow? = nil, - stateAppearances: StateAppearances = Defaults.stateAppearances, + stateAppearances: StateAppearances = defaultStateAppearances, activityIndicatorPlacement: ActivityIndicatorPlacement = .center) { self.activityIndicatorPlacement = activityIndicatorPlacement @@ -116,7 +116,8 @@ extension StatefulButton { } @available(iOS 15.0, *) - public final class DefaultPlacementAppearance: BasePlacementAppearance, WrappedViewAppearance { + public final class DefaultPlacementAppearance: BasePlacementAppearance, + WrappedViewAppearance { public static var defaultAppearance: Self { Self() } @@ -139,6 +140,19 @@ extension StatefulButton { public func configureStatefulButton(appearance: BasePositionAppearance) { configureBaseStatefulButton(appearance: appearance) + let baseOnStateChanged = onStateChanged + + onStateChanged = { [weak self] in + baseOnStateChanged?($0) + + self?.configureStatefulButtonActivityIndicator(appearance: appearance) + } + + onStateChanged?(state) + } + + private func configureStatefulButtonActivityIndicator(appearance: BasePositionAppearance) { switch appearance.activityIndicatorPosition { case .beforeTitle: activityIndicatorShouldCenterInView = false @@ -150,7 +164,7 @@ extension StatefulButton { case .center: activityIndicatorShouldCenterInView = true - + super.setImage(.init(), for: .loading) } @@ -174,6 +188,19 @@ extension StatefulButton { public func configureStatefulButton(appearance: BasePlacementAppearance) { configureBaseStatefulButton(appearance: appearance) + let baseOnStateChanged = onStateChanged + + onStateChanged = { [weak self] in + baseOnStateChanged?($0) + + self?.configureActivityIndicator(appearance: appearance) + } + + onStateChanged?(state) + } + + @available(iOS 15.0, *) + private func configureActivityIndicator(appearance: BasePlacementAppearance) { var config = configuration ?? .plain() switch appearance.activityIndicatorPlacement { @@ -187,6 +214,7 @@ extension StatefulButton { config.imagePlacement = imagePlacement config.imagePadding = padding + case .center: activityIndicatorShouldCenterInView = true diff --git a/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift b/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift index 1ab5eeb5..eb354409 100644 --- a/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift +++ b/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift @@ -48,8 +48,19 @@ open class StatefulButton: BaseInitializableButton { public typealias StateEventPropagations = [State: Bool] - public var isLoading = false { - didSet { + private var backedIsLoading = false + + public var isLoading: Bool { + get { + backedIsLoading + } + set { + guard backedIsLoading != newValue else { + return + } + + backedIsLoading = newValue + if isLoading { if activityIndicator == nil { configureDefaultActivityIndicator() @@ -75,7 +86,7 @@ open class StatefulButton: BaseInitializableButton { activityIndicator?.removeFromSuperview() } didSet { - if let activityIndicator = activityIndicator { + if let activityIndicator { addSubview(activityIndicator) } } @@ -128,19 +139,46 @@ open class StatefulButton: BaseInitializableButton { } override open var isEnabled: Bool { - didSet { + get { + super.isEnabled + } + set { + guard isEnabled != newValue else { + return + } + + super.isEnabled = newValue + updateAppearance() } } override open var isHighlighted: Bool { - didSet { + get { + super.isHighlighted + } + set { + guard isHighlighted != newValue else { + return + } + + super.isHighlighted = newValue + updateAppearance() } } open override var isSelected: Bool { - didSet { + get { + super.isSelected + } + set { + guard isSelected != newValue else { + return + } + + super.isSelected = newValue + updateAppearance() } } @@ -178,7 +216,7 @@ open class StatefulButton: BaseInitializableButton { open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let pointInsideView = self.point(inside: point, with: event) - if !isEnabled && pointInsideView, event?.allTouches?.isEmpty == false { + if !isEnabled && pointInsideView, !(event?.allTouches?.isEmpty ?? true) { onDisabledStateTapHandler?() } diff --git a/TIUIElements/Sources/Views/TitleSubtitleView/DefaultTitleSubtitleView.swift b/TIUIElements/Sources/Views/TitleSubtitleView/DefaultTitleSubtitleView.swift index 4dc0c7e4..db52897d 100644 --- a/TIUIElements/Sources/Views/TitleSubtitleView/DefaultTitleSubtitleView.swift +++ b/TIUIElements/Sources/Views/TitleSubtitleView/DefaultTitleSubtitleView.swift @@ -30,6 +30,8 @@ public final class DefaultTitleSubtitleView: BaseInitializableView, private let titleLabel = UILabel() private let subtitleLabel = UILabel() + private var appearance: Appearance = .defaultAppearance + public var titleLableBottomConstraint: NSLayoutConstraint? public var spacingConstraint: NSLayoutConstraint? @@ -58,7 +60,7 @@ public final class DefaultTitleSubtitleView: BaseInitializableView, subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), spacingConstraint, subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor), - subtitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor), + subtitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor) ].compactMap { $0 }) } @@ -66,7 +68,10 @@ public final class DefaultTitleSubtitleView: BaseInitializableView, public func configure(with viewModel: DefaultTitleSubtitleViewModel) { titleLabel.text = viewModel.title + titleLabel.configureUILabel(appearance: appearance.titleAppearance) + subtitleLabel.text = viewModel.subtitle + subtitleLabel.configureUILabel(appearance: appearance.subtitleAppearance) setSubtitle(hidden: viewModel.isSubtitleHidden) } @@ -82,6 +87,8 @@ public final class DefaultTitleSubtitleView: BaseInitializableView, // MARK: - AppearanceConfigurable public func configure(appearance: Appearance) { + self.appearance = appearance + configureUIView(appearance: appearance) titleLabel.configureUILabel(appearance: appearance.titleAppearance) subtitleLabel.configureUILabel(appearance: appearance.subtitleAppearance) diff --git a/TIUIElements/Sources/Wrappers/Constraints/CenterConstraints.swift b/TIUIElements/Sources/Wrappers/Constraints/CenterConstraints.swift index 5f55c07d..e25334d4 100644 --- a/TIUIElements/Sources/Wrappers/Constraints/CenterConstraints.swift +++ b/TIUIElements/Sources/Wrappers/Constraints/CenterConstraints.swift @@ -42,8 +42,11 @@ public struct CenterConstraints: ConstraintsSet { self.centerYConstraint = centerYConstraint } - public func update(offset: UIOffset) { + @discardableResult + public func update(offset: UIOffset) -> Self { centerXConstraint.setActiveConstantOrDeactivate(constant: offset.horizontal) centerYConstraint.setActiveConstantOrDeactivate(constant: offset.vertical) + + return self } } diff --git a/TIUIElements/Sources/Wrappers/Constraints/EdgeConstraints.swift b/TIUIElements/Sources/Wrappers/Constraints/EdgeConstraints.swift index 06afa29a..a832c9ea 100644 --- a/TIUIElements/Sources/Wrappers/Constraints/EdgeConstraints.swift +++ b/TIUIElements/Sources/Wrappers/Constraints/EdgeConstraints.swift @@ -64,10 +64,13 @@ public struct EdgeConstraints: ConstraintsSet { self.bottomConstraint = bottomConstraint } - public func update(from insets: UIEdgeInsets) { + @discardableResult + public func update(from insets: UIEdgeInsets) -> Self { leadingConstraint.setActiveConstantOrDeactivate(constant: insets.left) trailingConstraint.setActiveConstantOrDeactivate(constant: -insets.right) topConstraint.setActiveConstantOrDeactivate(constant: insets.top) bottomConstraint.setActiveConstantOrDeactivate(constant: -insets.bottom) + + return self } } diff --git a/TIUIElements/Sources/Wrappers/Constraints/SizeConstraints.swift b/TIUIElements/Sources/Wrappers/Constraints/SizeConstraints.swift index 178b42d4..a677adc9 100644 --- a/TIUIElements/Sources/Wrappers/Constraints/SizeConstraints.swift +++ b/TIUIElements/Sources/Wrappers/Constraints/SizeConstraints.swift @@ -42,8 +42,11 @@ public struct SizeConstraints: ConstraintsSet { self.heightConstraint = heightConstraint } - public func update(from size: CGSize) { + @discardableResult + public func update(from size: CGSize) -> Self { widthConstraint.setActiveConstantOrDeactivate(constant: size.width) heightConstraint.setActiveConstantOrDeactivate(constant: size.height) + + return self } } diff --git a/TIUIElements/Sources/Wrappers/Containers/ContainerCollectionViewCell.swift b/TIUIElements/Sources/Wrappers/Containers/ContainerCollectionViewCell.swift index 85b484fb..be863bc1 100644 --- a/TIUIElements/Sources/Wrappers/Containers/ContainerCollectionViewCell.swift +++ b/TIUIElements/Sources/Wrappers/Containers/ContainerCollectionViewCell.swift @@ -50,7 +50,7 @@ open class ContainerCollectionViewCell: UICollectionViewCell, Init } private lazy var subviewContraints: SubviewConstraints = { - configureWrappedViewLayout() + createSubviewConstraints() }() // MARK: - Initialization @@ -86,7 +86,7 @@ open class ContainerCollectionViewCell: UICollectionViewCell, Init } open func configureLayout() { - subviewContraints.edgeConstraints.activate() + update(subviewConstraints: subviewContraints) } open func configureAppearance() { @@ -101,3 +101,13 @@ open class ContainerCollectionViewCell: UICollectionViewCell, Init View() } } + +// MARK: - AppearanceConfigurable + +extension ContainerCollectionViewCell: AppearanceConfigurable where View: AppearanceConfigurable, + View.Appearance: WrappedViewAppearance { + + public func configure(appearance: DefaultWrappedViewHolderAppearance) { + configureWrappedViewHolder(appearance: appearance) + } +} diff --git a/TIUIElements/Sources/Wrappers/Containers/ContainerScrollView.swift b/TIUIElements/Sources/Wrappers/Containers/ContainerScrollView.swift index 29e353ee..3a5199c0 100644 --- a/TIUIElements/Sources/Wrappers/Containers/ContainerScrollView.swift +++ b/TIUIElements/Sources/Wrappers/Containers/ContainerScrollView.swift @@ -48,7 +48,7 @@ open class ContainerScrollView: BaseInitializeableScrollVie } private lazy var subviewContraints: SubviewConstraints = { - configureWrappedViewLayout() + createSubviewConstraints() }() open func createView() -> ContentView { @@ -62,6 +62,12 @@ open class ContainerScrollView: BaseInitializeableScrollVie addSubview(wrappedView) } + + open override func configureLayout() { + super.configureLayout() + + update(subviewConstraints: subviewContraints) + } } extension ContainerScrollView: AppearanceConfigurable where View: AppearanceConfigurable, diff --git a/TIUIElements/Sources/Wrappers/Containers/ContainerTableViewCell.swift b/TIUIElements/Sources/Wrappers/Containers/ContainerTableViewCell.swift index 233911c2..18ba6704 100644 --- a/TIUIElements/Sources/Wrappers/Containers/ContainerTableViewCell.swift +++ b/TIUIElements/Sources/Wrappers/Containers/ContainerTableViewCell.swift @@ -48,7 +48,7 @@ open class ContainerTableViewCell: BaseInitializableCell, WrappedV } private lazy var subviewContraints: SubviewConstraints = { - configureWrappedViewLayout() + createSubviewConstraints() }() // MARK: - InitializableView @@ -62,7 +62,7 @@ open class ContainerTableViewCell: BaseInitializableCell, WrappedV open override func configureLayout() { super.configureLayout() - subviewContraints.edgeConstraints.activate() + update(subviewConstraints: subviewContraints) } open func createView() -> View { diff --git a/TIUIElements/Sources/Wrappers/Containers/ContainerView.swift b/TIUIElements/Sources/Wrappers/Containers/ContainerView.swift index 272ad2e4..99dd05b8 100644 --- a/TIUIElements/Sources/Wrappers/Containers/ContainerView.swift +++ b/TIUIElements/Sources/Wrappers/Containers/ContainerView.swift @@ -46,7 +46,7 @@ open class ContainerView: BaseInitializableView, WrappedViewHolder } private lazy var subviewContraints: SubviewConstraints = { - configureWrappedViewLayout() + createSubviewConstraints() }() // MARK: - InitializableView @@ -60,7 +60,7 @@ open class ContainerView: BaseInitializableView, WrappedViewHolder public override func configureLayout() { super.configureLayout() - subviewContraints.edgeConstraints.activate() + update(subviewConstraints: subviewContraints) } } diff --git a/TIUIElements/Sources/Wrappers/Containers/ReusableCollectionContainerView.swift b/TIUIElements/Sources/Wrappers/Containers/ReusableCollectionContainerView.swift index af087046..8b57ba93 100644 --- a/TIUIElements/Sources/Wrappers/Containers/ReusableCollectionContainerView.swift +++ b/TIUIElements/Sources/Wrappers/Containers/ReusableCollectionContainerView.swift @@ -51,7 +51,7 @@ open class ReusableCollectionContainerView: UICollectionReusableVi } private lazy var subviewContraints: SubviewConstraints = { - configureWrappedViewLayout() + createSubviewConstraints() }() // MARK: - Initialization diff --git a/TIUIElements/Sources/Wrappers/Protocols/WrappedViewHolder.swift b/TIUIElements/Sources/Wrappers/Protocols/WrappedViewHolder.swift index 01ce6d97..cd5d151b 100644 --- a/TIUIElements/Sources/Wrappers/Protocols/WrappedViewHolder.swift +++ b/TIUIElements/Sources/Wrappers/Protocols/WrappedViewHolder.swift @@ -52,13 +52,10 @@ public extension WrappedViewHolder { centerYConstraint: wrappedView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)) } - func configureWrappedViewLayout() -> SubviewConstraints { + func createSubviewConstraints() -> SubviewConstraints { wrappedView.translatesAutoresizingMaskIntoConstraints = false - let contentEdgeConstraints = wrappedViewEdgeConstraints() - contentEdgeConstraints.activate() - - return SubviewConstraints(edgeConstraints: contentEdgeConstraints, + return SubviewConstraints(edgeConstraints: wrappedViewEdgeConstraints(), centerConstraints: wrappedViewCenterConstraints(), sizeConstraints: wrappedViewSizeConstraints()) } diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 979a0bfc..db7a7ee6 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Bunch of useful protocols and views.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', 'castlele' => 'nikita.semenov@touchin.ru' } diff --git a/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift b/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift index d2737a5e..ff0db7eb 100644 --- a/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift +++ b/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift @@ -154,7 +154,7 @@ open class BaseTextAttributes { open func configure(button: UIButton, with string: String? = nil, for state: UIControl.State = .normal) { if #available(iOS 15, *) { - var configuration = button.configuration ?? UIButton.Configuration.plain() + var configuration = button.configuration ?? .plain() if let title = string { button.setTitle(nil, for: state) diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index 1e6dc19f..186f3483 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Core UI elements: protocols, views and helpers.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', 'castlele' => 'nikita.semenov@touchin.ru' } diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec index 7ef71d3e..2e4837e1 100644 --- a/TIWebView/TIWebView.podspec +++ b/TIWebView/TIWebView.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIWebView' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Universal web view API' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', 'castlele' => 'nikita.semenov@touchin.ru' } diff --git a/TIYandexMapUtils/TIYandexMapUtils.podspec b/TIYandexMapUtils/TIYandexMapUtils.podspec index 5bd52978..1c5facd3 100644 --- a/TIYandexMapUtils/TIYandexMapUtils.podspec +++ b/TIYandexMapUtils/TIYandexMapUtils.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| s.name = 'TIYandexMapUtils' - s.version = '1.51.0' + s.version = '1.52.0' s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.' - s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s } diff --git a/project-scripts/ordered_modules_list.txt b/project-scripts/ordered_modules_list.txt index 70c9d452..96e06f7e 100644 --- a/project-scripts/ordered_modules_list.txt +++ b/project-scripts/ordered_modules_list.txt @@ -2,6 +2,7 @@ TILogging TISwiftUtils TIPagination TIFoundationUtils +TIApplication TICoreGraphicsUtils TIKeychainUtils TIUIKitCore diff --git a/project-scripts/push_to_podspecs.sh b/project-scripts/push_to_podspecs.sh index 0c3a95d1..cd69b204 100755 --- a/project-scripts/push_to_podspecs.sh +++ b/project-scripts/push_to_podspecs.sh @@ -16,16 +16,16 @@ # SRCROOT=`pwd` ./project-scripts/push_to_podspecs.sh # -GIT_REPO_PATH="git.svc.touchin.ru/TouchInstinct/Podspecs" +GIT_REPO_PATH="https://git.svc.touchin.ru/TouchInstinct/Podspecs" if [ -z "${MODULE_NAME}" ]; then for module_name in $(cat ${SRCROOT}/project-scripts/ordered_modules_list.txt); do - bundle exec pod repo push "git@${GIT_REPO_PATH}.git" ${SRCROOT}/${module_name}/${module_name}.podspec "$@" --allow-warnings + bundle exec pod repo push "${GIT_REPO_PATH}" ${SRCROOT}/${module_name}/${module_name}.podspec "$@" --allow-warnings if [ $? -ne 0 ]; then exit $? fi done else - bundle exec pod repo push "git@${GIT_REPO_PATH}.git" ${SRCROOT}/${MODULE_NAME}/${MODULE_NAME}.podspec "$@" --allow-warnings + bundle exec pod repo push "${GIT_REPO_PATH}" ${SRCROOT}/${MODULE_NAME}/${MODULE_NAME}.podspec "$@" --allow-warnings fi