commit
3b77b4c0fc
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = "LeadKitAdditions"
|
||||
s.version = "0.0.3"
|
||||
s.version = "0.0.4"
|
||||
s.summary = "iOS framework with a bunch of tools for rapid development"
|
||||
s.homepage = "https://github.com/NikAshanin/LeadKitAdditions"
|
||||
s.license = "Apache License, Version 2.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// Copyright (c) 2017 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/UIKit.h>
|
||||
|
||||
//! Project version number for CommonCrypto.
|
||||
FOUNDATION_EXPORT double CommonCryptoVersionNumber;
|
||||
|
||||
//! Project version string for CommonCrypto.
|
||||
FOUNDATION_EXPORT const unsigned char CommonCryptoVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <CommonCrypto/PublicHeader.h>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Copyright (c) 2017 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.
|
||||
//
|
||||
|
||||
MODULEMAP_FILE[sdk=iphoneos*] = $(SRCROOT)/CommonCrypto/iphoneos.modulemap
|
||||
MODULEMAP_FILE[sdk=iphonesimulator*] = $(SRCROOT)/CommonCrypto/iphonesimulator.modulemap
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
module CommonCrypto [system] {
|
||||
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonCrypto.h"
|
||||
export *
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
module CommonCrypto [system] {
|
||||
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"
|
||||
export *
|
||||
}
|
||||
|
|
@ -9,14 +9,28 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
7A94B4A5956B82BE1CEBA873 /* Pods_LeadKitAdditions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B7F57C5E5275C4D8DC71992 /* Pods_LeadKitAdditions.framework */; };
|
||||
CAE698E81E968820000394B0 /* LeadKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE698E61E968820000394B0 /* LeadKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
CAE698F11E968BA6000394B0 /* BaseDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE698EF1E968BA6000394B0 /* BaseDateFormatter.swift */; };
|
||||
CAE698F61E968E3D000394B0 /* BaseUserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE698F41E968E3D000394B0 /* BaseUserService.swift */; };
|
||||
CAE698FB1E968F78000394B0 /* UserDefaults+UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE698F91E968F78000394B0 /* UserDefaults+UserService.swift */; };
|
||||
CAE698FF1E969352000394B0 /* ApiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE698FD1E969352000394B0 /* ApiResponse.swift */; };
|
||||
CAE699041E9693F4000394B0 /* ConnectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE699021E9693F4000394B0 /* ConnectionError.swift */; };
|
||||
CAE699081E96956D000394B0 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE699061E96956D000394B0 /* ApiError.swift */; };
|
||||
CAE6990E1E969C66000394B0 /* Observable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE6990C1E969C66000394B0 /* Observable+Extensions.swift */; };
|
||||
CAE699121E969D2B000394B0 /* DefaultNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE699101E969D2B000394B0 /* DefaultNetworkService.swift */; };
|
||||
EF05EDB41EAF703A00CAE7B6 /* BaseUserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDB31EAF703A00CAE7B6 /* BaseUserService.swift */; };
|
||||
EF05EDB71EAF704800CAE7B6 /* Observable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDB51EAF704800CAE7B6 /* Observable+Extensions.swift */; };
|
||||
EF05EDB81EAF704800CAE7B6 /* UserDefaults+UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDB61EAF704800CAE7B6 /* UserDefaults+UserService.swift */; };
|
||||
EF05EDBB1EAF705500CAE7B6 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDB91EAF705500CAE7B6 /* ApiError.swift */; };
|
||||
EF05EDBC1EAF705500CAE7B6 /* ConnectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDBA1EAF705500CAE7B6 /* ConnectionError.swift */; };
|
||||
EF05EDC01EAF706200CAE7B6 /* ApiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDBD1EAF706200CAE7B6 /* ApiResponse.swift */; };
|
||||
EF05EDC11EAF706200CAE7B6 /* BaseDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDBE1EAF706200CAE7B6 /* BaseDateFormatter.swift */; };
|
||||
EF05EDC61EAF70EB00CAE7B6 /* TouchIDService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDC51EAF70EB00CAE7B6 /* TouchIDService.swift */; };
|
||||
EF05EDC81EAF91D500CAE7B6 /* BasePassCodeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDC71EAF91D500CAE7B6 /* BasePassCodeService.swift */; };
|
||||
EF05EDD21EAF9CF100CAE7B6 /* CommonCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = EF05EDD01EAF9CF100CAE7B6 /* CommonCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
EF05EDDA1EAF9D4A00CAE7B6 /* CommonCrypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF05EDCE1EAF9CF100CAE7B6 /* CommonCrypto.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
EF05EDE11EAFA74200CAE7B6 /* BasePassCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE01EAFA74200CAE7B6 /* BasePassCodeViewController.swift */; };
|
||||
EF05EDE31EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE21EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift */; };
|
||||
EF05EDE51EAFA80D00CAE7B6 /* PassCodeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE41EAFA80D00CAE7B6 /* PassCodeConfiguration.swift */; };
|
||||
EF05EDE71EAFA87300CAE7B6 /* PassCodeValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE61EAFA87300CAE7B6 /* PassCodeValidationResult.swift */; };
|
||||
EF05EDE91EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE81EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift */; };
|
||||
EF05EDEB1EAFA8E600CAE7B6 /* PassCodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDEA1EAFA8E600CAE7B6 /* PassCodeError.swift */; };
|
||||
EF05EDED1EAFA96D00CAE7B6 /* PassCodeHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDEC1EAFA96D00CAE7B6 /* PassCodeHolder.swift */; };
|
||||
EF05EDF61EB0D4C300CAE7B6 /* BarButtonLoadingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDF51EB0D4C300CAE7B6 /* BarButtonLoadingService.swift */; };
|
||||
EF05EDF81EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDF71EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift */; };
|
||||
EF05EDFC1EB0D77400CAE7B6 /* DefaultNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDFA1EB0D77400CAE7B6 /* DefaultNetworkService.swift */; };
|
||||
EF05EDFD1EB0D77400CAE7B6 /* SimpleNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDFB1EB0D77400CAE7B6 /* SimpleNetworkService.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
|
@ -26,14 +40,32 @@
|
|||
CAE698E31E968820000394B0 /* LeadKitAdditions.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LeadKitAdditions.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CAE698E61E968820000394B0 /* LeadKitAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LeadKitAdditions.h; sourceTree = "<group>"; };
|
||||
CAE698E71E968820000394B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
CAE698EF1E968BA6000394B0 /* BaseDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseDateFormatter.swift; path = Classes/BaseDateFormatter.swift; sourceTree = "<group>"; };
|
||||
CAE698F41E968E3D000394B0 /* BaseUserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseUserService.swift; path = Services/BaseUserService.swift; sourceTree = "<group>"; };
|
||||
CAE698F91E968F78000394B0 /* UserDefaults+UserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UserDefaults+UserService.swift"; path = "Extensions/UserDefaults+UserService.swift"; sourceTree = "<group>"; };
|
||||
CAE698FD1E969352000394B0 /* ApiResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApiResponse.swift; path = Classes/ApiResponse.swift; sourceTree = "<group>"; };
|
||||
CAE699021E9693F4000394B0 /* ConnectionError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConnectionError.swift; path = Enums/ConnectionError.swift; sourceTree = "<group>"; };
|
||||
CAE699061E96956D000394B0 /* ApiError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApiError.swift; path = Enums/ApiError.swift; sourceTree = "<group>"; };
|
||||
CAE6990C1E969C66000394B0 /* Observable+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Observable+Extensions.swift"; path = "Extensions/Observable+Extensions.swift"; sourceTree = "<group>"; };
|
||||
CAE699101E969D2B000394B0 /* DefaultNetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DefaultNetworkService.swift; path = Classes/DefaultNetworkService.swift; sourceTree = "<group>"; };
|
||||
EF05EDB31EAF703A00CAE7B6 /* BaseUserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUserService.swift; sourceTree = "<group>"; };
|
||||
EF05EDB51EAF704800CAE7B6 /* Observable+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Extensions.swift"; sourceTree = "<group>"; };
|
||||
EF05EDB61EAF704800CAE7B6 /* UserDefaults+UserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+UserService.swift"; sourceTree = "<group>"; };
|
||||
EF05EDB91EAF705500CAE7B6 /* ApiError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = "<group>"; };
|
||||
EF05EDBA1EAF705500CAE7B6 /* ConnectionError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionError.swift; sourceTree = "<group>"; };
|
||||
EF05EDBD1EAF706200CAE7B6 /* ApiResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiResponse.swift; sourceTree = "<group>"; };
|
||||
EF05EDBE1EAF706200CAE7B6 /* BaseDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseDateFormatter.swift; sourceTree = "<group>"; };
|
||||
EF05EDC51EAF70EB00CAE7B6 /* TouchIDService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchIDService.swift; sourceTree = "<group>"; };
|
||||
EF05EDC71EAF91D500CAE7B6 /* BasePassCodeService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeService.swift; sourceTree = "<group>"; };
|
||||
EF05EDCE1EAF9CF100CAE7B6 /* CommonCrypto.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CommonCrypto.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EF05EDD01EAF9CF100CAE7B6 /* CommonCrypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommonCrypto.h; sourceTree = "<group>"; };
|
||||
EF05EDD11EAF9CF100CAE7B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
EF05EDD61EAF9D2900CAE7B6 /* CommonCrypto.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = CommonCrypto.xcconfig; sourceTree = "<group>"; };
|
||||
EF05EDD71EAF9D2900CAE7B6 /* iphoneos.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = "<group>"; };
|
||||
EF05EDD81EAF9D2900CAE7B6 /* iphonesimulator.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = "<group>"; };
|
||||
EF05EDE01EAFA74200CAE7B6 /* BasePassCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeViewController.swift; sourceTree = "<group>"; };
|
||||
EF05EDE21EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeViewModel.swift; sourceTree = "<group>"; };
|
||||
EF05EDE41EAFA80D00CAE7B6 /* PassCodeConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeConfiguration.swift; sourceTree = "<group>"; };
|
||||
EF05EDE61EAFA87300CAE7B6 /* PassCodeValidationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeValidationResult.swift; sourceTree = "<group>"; };
|
||||
EF05EDE81EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeHolderProtocol.swift; sourceTree = "<group>"; };
|
||||
EF05EDEA1EAFA8E600CAE7B6 /* PassCodeError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeError.swift; sourceTree = "<group>"; };
|
||||
EF05EDEC1EAFA96D00CAE7B6 /* PassCodeHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeHolder.swift; sourceTree = "<group>"; };
|
||||
EF05EDF51EB0D4C300CAE7B6 /* BarButtonLoadingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButtonLoadingService.swift; sourceTree = "<group>"; };
|
||||
EF05EDF71EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Extensions.swift"; sourceTree = "<group>"; };
|
||||
EF05EDFA1EB0D77400CAE7B6 /* DefaultNetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultNetworkService.swift; sourceTree = "<group>"; };
|
||||
EF05EDFB1EB0D77400CAE7B6 /* SimpleNetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleNetworkService.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -41,10 +73,18 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EF05EDDA1EAF9D4A00CAE7B6 /* CommonCrypto.framework in Frameworks */,
|
||||
7A94B4A5956B82BE1CEBA873 /* Pods_LeadKitAdditions.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EF05EDCA1EAF9CF100CAE7B6 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
|
@ -60,6 +100,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
CAE698E51E968820000394B0 /* LeadKitAdditions */,
|
||||
EF05EDCF1EAF9CF100CAE7B6 /* CommonCrypto */,
|
||||
CAE698E41E968820000394B0 /* Products */,
|
||||
F8A65FEC7C0EB4B93746E50F /* Pods */,
|
||||
A3117951840B8B7D2E7A8A80 /* Frameworks */,
|
||||
|
|
@ -70,6 +111,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
CAE698E31E968820000394B0 /* LeadKitAdditions.framework */,
|
||||
EF05EDCE1EAF9CF100CAE7B6 /* CommonCrypto.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -77,12 +119,13 @@
|
|||
CAE698E51E968820000394B0 /* LeadKitAdditions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDDB1EAFA6FA00CAE7B6 /* Controllers */,
|
||||
CAE698EE1E968B72000394B0 /* Classes */,
|
||||
CAE699011E9693DE000394B0 /* Enums */,
|
||||
CAE698F81E968F56000394B0 /* Extensions */,
|
||||
CAE698F31E968E28000394B0 /* Services */,
|
||||
CAE698EE1E968B72000394B0 /* Classes */,
|
||||
CAE698E61E968820000394B0 /* LeadKitAdditions.h */,
|
||||
CAE698E71E968820000394B0 /* Info.plist */,
|
||||
CAE698E61E968820000394B0 /* LeadKitAdditions.h */,
|
||||
CAE698F31E968E28000394B0 /* Services */,
|
||||
);
|
||||
path = LeadKitAdditions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -90,37 +133,108 @@
|
|||
CAE698EE1E968B72000394B0 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CAE698EF1E968BA6000394B0 /* BaseDateFormatter.swift */,
|
||||
CAE698FD1E969352000394B0 /* ApiResponse.swift */,
|
||||
CAE699101E969D2B000394B0 /* DefaultNetworkService.swift */,
|
||||
EF05EDBD1EAF706200CAE7B6 /* ApiResponse.swift */,
|
||||
EF05EDBE1EAF706200CAE7B6 /* BaseDateFormatter.swift */,
|
||||
);
|
||||
name = Classes;
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CAE698F31E968E28000394B0 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CAE698F41E968E3D000394B0 /* BaseUserService.swift */,
|
||||
EF05EDF91EB0D75A00CAE7B6 /* Network */,
|
||||
EF05EDF51EB0D4C300CAE7B6 /* BarButtonLoadingService.swift */,
|
||||
EF05EDC71EAF91D500CAE7B6 /* BasePassCodeService.swift */,
|
||||
EF05EDB31EAF703A00CAE7B6 /* BaseUserService.swift */,
|
||||
EF05EDC51EAF70EB00CAE7B6 /* TouchIDService.swift */,
|
||||
);
|
||||
name = Services;
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CAE698F81E968F56000394B0 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CAE698F91E968F78000394B0 /* UserDefaults+UserService.swift */,
|
||||
CAE6990C1E969C66000394B0 /* Observable+Extensions.swift */,
|
||||
EF05EDB51EAF704800CAE7B6 /* Observable+Extensions.swift */,
|
||||
EF05EDF71EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift */,
|
||||
EF05EDB61EAF704800CAE7B6 /* UserDefaults+UserService.swift */,
|
||||
);
|
||||
name = Extensions;
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CAE699011E9693DE000394B0 /* Enums */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CAE699021E9693F4000394B0 /* ConnectionError.swift */,
|
||||
CAE699061E96956D000394B0 /* ApiError.swift */,
|
||||
EF05EDB91EAF705500CAE7B6 /* ApiError.swift */,
|
||||
EF05EDBA1EAF705500CAE7B6 /* ConnectionError.swift */,
|
||||
);
|
||||
name = Enums;
|
||||
path = Enums;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDCF1EAF9CF100CAE7B6 /* CommonCrypto */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDD01EAF9CF100CAE7B6 /* CommonCrypto.h */,
|
||||
EF05EDD61EAF9D2900CAE7B6 /* CommonCrypto.xcconfig */,
|
||||
EF05EDD11EAF9CF100CAE7B6 /* Info.plist */,
|
||||
EF05EDD71EAF9D2900CAE7B6 /* iphoneos.modulemap */,
|
||||
EF05EDD81EAF9D2900CAE7B6 /* iphonesimulator.modulemap */,
|
||||
);
|
||||
path = CommonCrypto;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDDB1EAFA6FA00CAE7B6 /* Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDDC1EAFA72600CAE7B6 /* PassCode */,
|
||||
);
|
||||
path = Controllers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDDC1EAFA72600CAE7B6 /* PassCode */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDDD1EAFA72600CAE7B6 /* Model */,
|
||||
EF05EDDE1EAFA72600CAE7B6 /* View */,
|
||||
EF05EDDF1EAFA72600CAE7B6 /* ViewModel */,
|
||||
);
|
||||
path = PassCode;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDDD1EAFA72600CAE7B6 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDE41EAFA80D00CAE7B6 /* PassCodeConfiguration.swift */,
|
||||
EF05EDEA1EAFA8E600CAE7B6 /* PassCodeError.swift */,
|
||||
EF05EDEC1EAFA96D00CAE7B6 /* PassCodeHolder.swift */,
|
||||
EF05EDE81EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift */,
|
||||
EF05EDE61EAFA87300CAE7B6 /* PassCodeValidationResult.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDDE1EAFA72600CAE7B6 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDE01EAFA74200CAE7B6 /* BasePassCodeViewController.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDDF1EAFA72600CAE7B6 /* ViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDE21EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EF05EDF91EB0D75A00CAE7B6 /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EF05EDFA1EB0D77400CAE7B6 /* DefaultNetworkService.swift */,
|
||||
EF05EDFB1EB0D77400CAE7B6 /* SimpleNetworkService.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F8A65FEC7C0EB4B93746E50F /* Pods */ = {
|
||||
|
|
@ -143,6 +257,14 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EF05EDCB1EAF9CF100CAE7B6 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EF05EDD21EAF9CF100CAE7B6 /* CommonCrypto.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -168,6 +290,24 @@
|
|||
productReference = CAE698E31E968820000394B0 /* LeadKitAdditions.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
EF05EDCD1EAF9CF100CAE7B6 /* CommonCrypto */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = EF05EDD31EAF9CF100CAE7B6 /* Build configuration list for PBXNativeTarget "CommonCrypto" */;
|
||||
buildPhases = (
|
||||
EF05EDC91EAF9CF100CAE7B6 /* Sources */,
|
||||
EF05EDCA1EAF9CF100CAE7B6 /* Frameworks */,
|
||||
EF05EDCB1EAF9CF100CAE7B6 /* Headers */,
|
||||
EF05EDCC1EAF9CF100CAE7B6 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = CommonCrypto;
|
||||
productName = CommonCrypto;
|
||||
productReference = EF05EDCE1EAF9CF100CAE7B6 /* CommonCrypto.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
|
@ -182,6 +322,10 @@
|
|||
LastSwiftMigration = 0830;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
EF05EDCD1EAF9CF100CAE7B6 = {
|
||||
CreatedOnToolsVersion = 8.3.1;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CAE698DD1E968820000394B0 /* Build configuration list for PBXProject "LeadKitAdditions" */;
|
||||
|
|
@ -197,6 +341,7 @@
|
|||
projectRoot = "";
|
||||
targets = (
|
||||
CAE698E21E968820000394B0 /* LeadKitAdditions */,
|
||||
EF05EDCD1EAF9CF100CAE7B6 /* CommonCrypto */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
|
@ -209,6 +354,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EF05EDCC1EAF9CF100CAE7B6 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
|
|
@ -267,7 +419,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
|
@ -277,14 +429,33 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CAE698FB1E968F78000394B0 /* UserDefaults+UserService.swift in Sources */,
|
||||
CAE698FF1E969352000394B0 /* ApiResponse.swift in Sources */,
|
||||
CAE698F11E968BA6000394B0 /* BaseDateFormatter.swift in Sources */,
|
||||
CAE699041E9693F4000394B0 /* ConnectionError.swift in Sources */,
|
||||
CAE6990E1E969C66000394B0 /* Observable+Extensions.swift in Sources */,
|
||||
CAE698F61E968E3D000394B0 /* BaseUserService.swift in Sources */,
|
||||
CAE699121E969D2B000394B0 /* DefaultNetworkService.swift in Sources */,
|
||||
CAE699081E96956D000394B0 /* ApiError.swift in Sources */,
|
||||
EF05EDB81EAF704800CAE7B6 /* UserDefaults+UserService.swift in Sources */,
|
||||
EF05EDE11EAFA74200CAE7B6 /* BasePassCodeViewController.swift in Sources */,
|
||||
EF05EDC61EAF70EB00CAE7B6 /* TouchIDService.swift in Sources */,
|
||||
EF05EDE31EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift in Sources */,
|
||||
EF05EDBB1EAF705500CAE7B6 /* ApiError.swift in Sources */,
|
||||
EF05EDE91EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift in Sources */,
|
||||
EF05EDFD1EB0D77400CAE7B6 /* SimpleNetworkService.swift in Sources */,
|
||||
EF05EDF81EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift in Sources */,
|
||||
EF05EDED1EAFA96D00CAE7B6 /* PassCodeHolder.swift in Sources */,
|
||||
EF05EDF61EB0D4C300CAE7B6 /* BarButtonLoadingService.swift in Sources */,
|
||||
EF05EDB71EAF704800CAE7B6 /* Observable+Extensions.swift in Sources */,
|
||||
EF05EDC01EAF706200CAE7B6 /* ApiResponse.swift in Sources */,
|
||||
EF05EDBC1EAF705500CAE7B6 /* ConnectionError.swift in Sources */,
|
||||
EF05EDEB1EAFA8E600CAE7B6 /* PassCodeError.swift in Sources */,
|
||||
EF05EDB41EAF703A00CAE7B6 /* BaseUserService.swift in Sources */,
|
||||
EF05EDE51EAFA80D00CAE7B6 /* PassCodeConfiguration.swift in Sources */,
|
||||
EF05EDFC1EB0D77400CAE7B6 /* DefaultNetworkService.swift in Sources */,
|
||||
EF05EDC81EAF91D500CAE7B6 /* BasePassCodeService.swift in Sources */,
|
||||
EF05EDC11EAF706200CAE7B6 /* BaseDateFormatter.swift in Sources */,
|
||||
EF05EDE71EAFA87300CAE7B6 /* PassCodeValidationResult.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
EF05EDC91EAF9CF100CAE7B6 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -441,6 +612,52 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
EF05EDD41EAF9CF100CAE7B6 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = CommonCrypto/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
"MODULEMAP_FILE[sdk=iphoneos*]" = /Users/alexeygerasimov/Documents/dev/LeadKitAdditions/LeadKitAdditions/CommonCrypto/iphoneos.modulemap;
|
||||
"MODULEMAP_FILE[sdk=iphonesimulator*]" = /Users/alexeygerasimov/Documents/dev/LeadKitAdditions/LeadKitAdditions/CommonCrypto/iphonesimulator.modulemap;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.touchin.CommonCrypto;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
EF05EDD51EAF9CF100CAE7B6 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = CommonCrypto/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
"MODULEMAP_FILE[sdk=iphoneos*]" = /Users/alexeygerasimov/Documents/dev/LeadKitAdditions/LeadKitAdditions/CommonCrypto/iphoneos.modulemap;
|
||||
"MODULEMAP_FILE[sdk=iphonesimulator*]" = /Users/alexeygerasimov/Documents/dev/LeadKitAdditions/LeadKitAdditions/CommonCrypto/iphonesimulator.modulemap;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.touchin.CommonCrypto;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
|
@ -462,6 +679,14 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
EF05EDD31EAF9CF100CAE7B6 /* Build configuration list for PBXNativeTarget "CommonCrypto" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
EF05EDD41EAF9CF100CAE7B6 /* Debug */,
|
||||
EF05EDD51EAF9CF100CAE7B6 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CAE698DA1E968820000394B0 /* Project object */;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright (c) 2017 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.
|
||||
//
|
||||
|
||||
public struct PassCodeConfiguration {
|
||||
|
||||
public var passCodeCharactersNumber: UInt = 4
|
||||
public var maxAttemptsLoginNumber: UInt = 5
|
||||
|
||||
public var shouldResetWhenGoBackground: Bool = true
|
||||
|
||||
private init() {}
|
||||
|
||||
init?(passCodeCharactersNumber: UInt) {
|
||||
guard passCodeCharactersNumber > 0 else {
|
||||
assertionFailure("passCodeCharactersNumber must be greater then 0")
|
||||
return nil
|
||||
}
|
||||
self.passCodeCharactersNumber = passCodeCharactersNumber
|
||||
}
|
||||
|
||||
public static var defaultConfiguration: PassCodeConfiguration {
|
||||
return PassCodeConfiguration()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright (c) 2017 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.
|
||||
//
|
||||
|
||||
public enum PassCodeError: Error {
|
||||
case codesNotMatch
|
||||
case wrongCode
|
||||
case tooManyAttempts
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 RxSwift
|
||||
import RxCocoa
|
||||
|
||||
extension PassCodeHolderProtocol {
|
||||
|
||||
public var passCodeHolderCreate: PassCodeHolderCreate? {
|
||||
return self as? PassCodeHolderCreate
|
||||
}
|
||||
|
||||
public var passCodeHolderEnter: PassCodeHolderEnter? {
|
||||
return self as? PassCodeHolderEnter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class PassCodeHolderCreate: PassCodeHolderProtocol {
|
||||
|
||||
public let type: PassCodeControllerType = .create
|
||||
|
||||
private var firstPassCode: String?
|
||||
private var secondPassCode: String?
|
||||
|
||||
public var enterStep: PassCodeEnterStep {
|
||||
if firstPassCode == nil {
|
||||
return .first
|
||||
} else {
|
||||
return .second
|
||||
}
|
||||
}
|
||||
|
||||
public var shouldValidate: Bool {
|
||||
return firstPassCode != nil && secondPassCode != nil
|
||||
}
|
||||
|
||||
public var passCode: String? {
|
||||
guard let firstPassCode = firstPassCode, let secondPassCode = secondPassCode, firstPassCode == secondPassCode else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return firstPassCode
|
||||
}
|
||||
|
||||
public func add(passCode: String) {
|
||||
switch enterStep {
|
||||
case .first:
|
||||
firstPassCode = passCode
|
||||
case .second:
|
||||
secondPassCode = passCode
|
||||
}
|
||||
}
|
||||
|
||||
public func validate() -> PassCodeValidationResult {
|
||||
if let passCode = passCode {
|
||||
return .valid(passCode)
|
||||
} else {
|
||||
return .inValid(.codesNotMatch)
|
||||
}
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
firstPassCode = nil
|
||||
secondPassCode = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class PassCodeHolderEnter: PassCodeHolderProtocol {
|
||||
|
||||
public let type: PassCodeControllerType = .enter
|
||||
public let enterStep: PassCodeEnterStep = .first
|
||||
|
||||
public var shouldValidate: Bool {
|
||||
return passCode != nil
|
||||
}
|
||||
|
||||
public var passCode: String?
|
||||
|
||||
public func add(passCode: String) {
|
||||
self.passCode = passCode
|
||||
}
|
||||
|
||||
public func validate() -> PassCodeValidationResult {
|
||||
if let passCode = passCode {
|
||||
return .valid(passCode)
|
||||
} else {
|
||||
return .inValid(nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
passCode = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Copyright (c) 2017 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.
|
||||
//
|
||||
|
||||
public enum PassCodeEnterStep {
|
||||
case first
|
||||
case second
|
||||
}
|
||||
|
||||
public protocol PassCodeHolderProtocol {
|
||||
|
||||
var type: PassCodeControllerType { get }
|
||||
var enterStep: PassCodeEnterStep { get }
|
||||
|
||||
func add(passCode: String)
|
||||
func reset()
|
||||
|
||||
var shouldValidate: Bool { get }
|
||||
var passCode: String? { get }
|
||||
|
||||
func validate() -> PassCodeValidationResult
|
||||
|
||||
}
|
||||
|
||||
public class PassCodeHolderBuilder {
|
||||
|
||||
private init() {}
|
||||
|
||||
public static func build(with type: PassCodeControllerType) -> PassCodeHolderProtocol {
|
||||
switch type {
|
||||
case .create:
|
||||
return PassCodeHolderCreate()
|
||||
case .enter:
|
||||
return PassCodeHolderEnter()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// Copyright (c) 2017 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.
|
||||
//
|
||||
|
||||
public enum PassCodeValidationResult {
|
||||
|
||||
case valid(String)
|
||||
case inValid(PassCodeError?)
|
||||
|
||||
public var isValid: Bool {
|
||||
switch self {
|
||||
case .valid:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var passCode: String? {
|
||||
switch self {
|
||||
case let .valid(passCode):
|
||||
return passCode
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var error: PassCodeError? {
|
||||
switch self {
|
||||
case let .inValid(error):
|
||||
return error
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 RxSwift
|
||||
import RxCocoa
|
||||
import LeadKit
|
||||
|
||||
public enum PinImageType {
|
||||
case entered
|
||||
case clear
|
||||
}
|
||||
|
||||
public enum PassCodeControllerType {
|
||||
case create
|
||||
case enter
|
||||
}
|
||||
|
||||
public enum PassCodeControllerState {
|
||||
case enter
|
||||
case repeatEnter
|
||||
}
|
||||
|
||||
open class BasePassCodeViewController: UIViewController {
|
||||
|
||||
public var viewModel: BasePassCodeViewModel!
|
||||
|
||||
// MARK: - IBOutlets
|
||||
|
||||
@IBOutlet public weak var titleLabel: UILabel?
|
||||
@IBOutlet public weak var errorLabel: UILabel?
|
||||
@IBOutlet public weak var dotStackView: UIStackView!
|
||||
|
||||
let disposeBag = DisposeBag()
|
||||
|
||||
fileprivate lazy var fakeTextField: UITextField = {
|
||||
let fakeTextField = UITextField()
|
||||
fakeTextField.isSecureTextEntry = true
|
||||
fakeTextField.keyboardType = .numberPad
|
||||
fakeTextField.isHidden = true
|
||||
self.view.addSubview(fakeTextField)
|
||||
return fakeTextField
|
||||
}()
|
||||
|
||||
// MARK: - Life circle
|
||||
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
initialLoadView()
|
||||
initialDotNumberConfiguration()
|
||||
enebleKeyboard()
|
||||
configureBackgroundNotifications()
|
||||
showTouchIdIfNeeded(with: touchIdHint)
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
private func configureBackgroundNotifications() {
|
||||
guard viewModel.passCodeConfiguration.shouldResetWhenGoBackground else {
|
||||
return
|
||||
}
|
||||
|
||||
NotificationCenter.default.rx.notification(.UIApplicationWillResignActive)
|
||||
.subscribe(onNext: { [weak self] _ in
|
||||
self?.resetUI()
|
||||
})
|
||||
.addDisposableTo(disposeBag)
|
||||
}
|
||||
|
||||
private func enebleKeyboard() {
|
||||
fakeTextField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
private func initialDotNumberConfiguration() {
|
||||
dotStackView.arrangedSubviews.forEach { dotStackView.removeArrangedSubview($0) }
|
||||
|
||||
for _ in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
|
||||
let dotImageView = UIImageView()
|
||||
dotImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
dotImageView.widthAnchor.constraint(equalTo: dotImageView.heightAnchor, multiplier: 1)
|
||||
dotImageView.contentMode = .scaleAspectFit
|
||||
dotStackView.addArrangedSubview(dotImageView)
|
||||
}
|
||||
|
||||
resetDotsUI()
|
||||
}
|
||||
|
||||
fileprivate func resetDotsUI() {
|
||||
fakeTextField.text = nil
|
||||
dotStackView.arrangedSubviews
|
||||
.flatMap { $0 as? UIImageView }
|
||||
.forEach { $0.image = self.imageFor(type: .clear) }
|
||||
}
|
||||
|
||||
private func setState(_ state: PinImageType, at index: Int) {
|
||||
guard dotStackView.arrangedSubviews.count > index,
|
||||
let imageView = dotStackView.arrangedSubviews[index] as? UIImageView else {
|
||||
return
|
||||
}
|
||||
|
||||
imageView.image = imageFor(type: state)
|
||||
}
|
||||
|
||||
fileprivate func setStates(for passCodeText: String) {
|
||||
var statesArray: [PinImageType] = []
|
||||
|
||||
for characterIndex in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
|
||||
let state: PinImageType = Int(characterIndex) <= passCodeText.characters.count - 1 ? .entered : .clear
|
||||
statesArray.append(state)
|
||||
}
|
||||
|
||||
statesArray.enumerated().forEach {
|
||||
self.setState($0.element, at: $0.offset)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func showTouchIdIfNeeded(with description: String) {
|
||||
guard viewModel.isTouchIdEnabled else {
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.touchIdService?.authenticateByTouchId(description: description) { [weak self] isSuccess in
|
||||
if isSuccess {
|
||||
self?.viewModel.authSucceed(.touchId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func resetUI() {
|
||||
resetDotsUI()
|
||||
viewModel.reset()
|
||||
}
|
||||
|
||||
// MARK: - HAVE TO OVERRIDE
|
||||
|
||||
open var touchIdHint: String {
|
||||
assertionFailure("You should override this var: touchIdHint")
|
||||
return ""
|
||||
}
|
||||
|
||||
// override to change Images
|
||||
func imageFor(type: PinImageType) -> UIImage {
|
||||
assertionFailure("You should override this method: imageFor(type: PinImageType)")
|
||||
return UIImage()
|
||||
}
|
||||
|
||||
// override to change error text
|
||||
func errorDescription(for error: PassCodeError) -> String {
|
||||
assertionFailure("You should override this method: errorDescription(for error: PassCodeError)")
|
||||
return ""
|
||||
}
|
||||
|
||||
// override to change action title text
|
||||
func actionTitle(for passCodeControllerState: PassCodeControllerState) -> String {
|
||||
assertionFailure("You should override this method: actionTitle(for passCodeControllerState: PassCodeControllerState)")
|
||||
return ""
|
||||
}
|
||||
|
||||
// MARK: - Functions that can you can override to castomise your controller
|
||||
|
||||
open func showError(for error: PassCodeError) {
|
||||
errorLabel?.text = errorDescription(for: error)
|
||||
errorLabel?.isHidden = false
|
||||
}
|
||||
|
||||
open func hideError() {
|
||||
errorLabel?.isHidden = true
|
||||
}
|
||||
|
||||
// override to change UI for state
|
||||
open func configureUI(for passCodeControllerState: PassCodeControllerState) {
|
||||
resetDotsUI()
|
||||
titleLabel?.text = actionTitle(for: passCodeControllerState)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We need to implement all functions of ConfigurableController protocol to give ability to override them.
|
||||
extension BasePassCodeViewController: ConfigurableController {
|
||||
|
||||
open func bindViews() {
|
||||
fakeTextField.rx.text.asDriver()
|
||||
.do(onNext: { [weak self] text in
|
||||
self?.setStates(for: text ?? "")
|
||||
self?.hideError()
|
||||
})
|
||||
.drive(viewModel.passCodeText)
|
||||
.addDisposableTo(disposeBag)
|
||||
|
||||
viewModel.validationResult
|
||||
.drive(onNext: { [weak self] validationResult in
|
||||
guard let validationResult = validationResult else {
|
||||
return
|
||||
}
|
||||
|
||||
if validationResult.isValid {
|
||||
self?.hideError()
|
||||
} else if let pasCodeError = validationResult.error {
|
||||
self?.showError(for: pasCodeError)
|
||||
}
|
||||
})
|
||||
.addDisposableTo(disposeBag)
|
||||
|
||||
viewModel.passCodeControllerState
|
||||
.drive(onNext: { [weak self] controllerState in
|
||||
self?.configureUI(for: controllerState)
|
||||
})
|
||||
.addDisposableTo(disposeBag)
|
||||
}
|
||||
|
||||
open func addViews() {}
|
||||
|
||||
open func setAppearance() {}
|
||||
|
||||
open func configureBarButtons() {}
|
||||
|
||||
open func localize() {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 LeadKit
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
public enum PassCodeAuthType {
|
||||
case passCode(String)
|
||||
case touchId
|
||||
}
|
||||
|
||||
open class BasePassCodeViewModel: BaseViewModel {
|
||||
|
||||
public let controllerType: PassCodeControllerType
|
||||
|
||||
public let disposeBag = DisposeBag()
|
||||
|
||||
public let touchIdService: TouchIDService?
|
||||
public let passCodeConfiguration: PassCodeConfiguration
|
||||
|
||||
fileprivate let validationResultHolder = Variable<PassCodeValidationResult?>(nil)
|
||||
var validationResult: Driver<PassCodeValidationResult?> {
|
||||
return validationResultHolder.asDriver()
|
||||
}
|
||||
|
||||
fileprivate let passCodeControllerStateHolder = Variable<PassCodeControllerState>(.enter)
|
||||
public var passCodeControllerState: Driver<PassCodeControllerState> {
|
||||
return passCodeControllerStateHolder.asDriver()
|
||||
}
|
||||
|
||||
public let passCodeText = Variable<String?>(nil)
|
||||
|
||||
fileprivate var attemptsNumber = 0
|
||||
|
||||
fileprivate lazy var passCodeHolder: PassCodeHolderProtocol = {
|
||||
return PassCodeHolderBuilder.build(with: self.controllerType)
|
||||
}()
|
||||
|
||||
init(controllerType: PassCodeControllerType,
|
||||
passCodeConfiguration: PassCodeConfiguration,
|
||||
touchIdService: TouchIDService? = nil) {
|
||||
|
||||
self.controllerType = controllerType
|
||||
self.passCodeConfiguration = passCodeConfiguration
|
||||
self.touchIdService = touchIdService
|
||||
|
||||
bindViewModel()
|
||||
}
|
||||
|
||||
private func bindViewModel() {
|
||||
passCodeText.asDriver()
|
||||
.distinctUntilChanged { $0 == $1 }
|
||||
.drive(onNext: { [weak self] passCode in
|
||||
if let passCode = passCode,
|
||||
passCode.characters.count == Int(self?.passCodeConfiguration.passCodeCharactersNumber ?? 0) {
|
||||
self?.set(passCode: passCode)
|
||||
}
|
||||
})
|
||||
.addDisposableTo(disposeBag)
|
||||
|
||||
validationResultHolder.asDriver()
|
||||
.drive(onNext: { [weak self] validationResult in
|
||||
if validationResult?.isValid ?? false, let passCode = validationResult?.passCode {
|
||||
self?.authSucceed(.passCode(passCode))
|
||||
} else {
|
||||
self?.passCodeControllerStateHolder.value = .enter
|
||||
}
|
||||
})
|
||||
.addDisposableTo(disposeBag)
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
passCodeText.value = nil
|
||||
validationResultHolder.value = nil
|
||||
passCodeControllerStateHolder.value = .enter
|
||||
attemptsNumber = 0
|
||||
passCodeHolder.reset()
|
||||
}
|
||||
|
||||
// MARK: - HAVE TO OVERRIDE
|
||||
|
||||
open func isEnteredPassCodeValid(_ passCode: String) -> Bool {
|
||||
assertionFailure("You should override this method: isEnteredPassCodeValid(_ passCode: String)")
|
||||
return false
|
||||
}
|
||||
|
||||
open func authSucceed(_ type: PassCodeAuthType) {
|
||||
assertionFailure("You should override this method: authSucceed(_ type: PassCodeAuthType)")
|
||||
}
|
||||
|
||||
// MARK: - Functions that can you can override to use TouchId
|
||||
|
||||
open var isTouchIdEnabled: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
open func activateTouchIdForUser() {
|
||||
assertionFailure("You should override this method: activateTouchIdForUser()")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BasePassCodeViewModel {
|
||||
|
||||
fileprivate func set(passCode: String) {
|
||||
passCodeHolder.add(passCode: passCode)
|
||||
validateIfNeeded()
|
||||
|
||||
if shouldUpdateControllerState {
|
||||
switch passCodeHolder.enterStep {
|
||||
case .first:
|
||||
passCodeControllerStateHolder.value = .enter
|
||||
case .second:
|
||||
passCodeControllerStateHolder.value = .repeatEnter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var shouldUpdateControllerState: Bool {
|
||||
return !passCodeHolder.shouldValidate ||
|
||||
!(validationResultHolder.value?.isValid ?? true) ||
|
||||
validationResultHolder.value?.error == .tooManyAttempts
|
||||
}
|
||||
|
||||
private func validateIfNeeded() {
|
||||
guard passCodeHolder.shouldValidate else {
|
||||
return
|
||||
}
|
||||
|
||||
var validationResult = passCodeHolder.validate()
|
||||
|
||||
if passCodeHolder.type == .enter {
|
||||
attemptsNumber += 1
|
||||
|
||||
if let passCode = validationResult.passCode, !isEnteredPassCodeValid(passCode) {
|
||||
validationResult = .inValid(.wrongCode)
|
||||
}
|
||||
|
||||
if (!validationResult.isValid && attemptsNumber == Int(passCodeConfiguration.maxAttemptsLoginNumber)) ||
|
||||
attemptsNumber > Int(passCodeConfiguration.maxAttemptsLoginNumber) {
|
||||
validationResult = .inValid(.tooManyAttempts)
|
||||
}
|
||||
}
|
||||
|
||||
if !validationResult.isValid {
|
||||
passCodeHolder.reset()
|
||||
}
|
||||
|
||||
validationResultHolder.value = validationResult
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// Copyright (c) 2017 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
|
||||
|
||||
extension UIBarButtonItem {
|
||||
|
||||
public static var activityIndicator: (barButton: UIBarButtonItem, activityIndicator: UIActivityIndicatorView) {
|
||||
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .white)
|
||||
let indicatorBar = UIBarButtonItem(customView: indicatorView)
|
||||
return (indicatorBar, indicatorView)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 RxSwift
|
||||
import RxCocoa
|
||||
|
||||
public enum BarButtonLoadingServiceSide {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public class BarButtonLoadingService {
|
||||
|
||||
fileprivate weak var navigationItem: UINavigationItem?
|
||||
fileprivate var savedBarButton: UIBarButtonItem?
|
||||
private let side: BarButtonLoadingServiceSide
|
||||
|
||||
private var barButtonItem: UIBarButtonItem? {
|
||||
get {
|
||||
switch side {
|
||||
case .left:
|
||||
return navigationItem?.leftBarButtonItem
|
||||
case .right:
|
||||
return navigationItem?.rightBarButtonItem
|
||||
}
|
||||
}
|
||||
set {
|
||||
switch side {
|
||||
case .left:
|
||||
navigationItem?.leftBarButtonItem = newValue
|
||||
case .right:
|
||||
navigationItem?.rightBarButtonItem = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(navigationItem: UINavigationItem, side: BarButtonLoadingServiceSide) {
|
||||
self.navigationItem = navigationItem
|
||||
self.side = side
|
||||
}
|
||||
|
||||
fileprivate func setBarButton(waiting: Bool = false) {
|
||||
if waiting {
|
||||
savedBarButton = barButtonItem
|
||||
|
||||
let activityIndicatorItem = UIBarButtonItem.activityIndicator
|
||||
barButtonItem = activityIndicatorItem.barButton
|
||||
activityIndicatorItem.activityIndicator.startAnimating()
|
||||
} else {
|
||||
barButtonItem = savedBarButton
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
public func changeLoadingUI(using loadingService: BarButtonLoadingService) -> Observable<Observable.E> {
|
||||
return observeOn(MainScheduler.instance)
|
||||
.do(onSubscribe: {
|
||||
loadingService.setBarButton(waiting: true)
|
||||
}, onDispose: {
|
||||
loadingService.setBarButton(waiting: false)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 KeychainAccess
|
||||
import CocoaLumberjack
|
||||
import CommonCrypto
|
||||
|
||||
open class BasePassCodeService {
|
||||
|
||||
open class var keychainService: String {
|
||||
return Bundle.main.bundleIdentifier ?? ""
|
||||
}
|
||||
|
||||
public init() {
|
||||
let isInitialLoad = UserDefaults.standard.bool(forKey: Keys.isInitialLoad)
|
||||
if isInitialLoad {
|
||||
UserDefaults.standard.set(false, forKey: Keys.isInitialLoad)
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private stuff
|
||||
|
||||
fileprivate lazy var keychain: Keychain = {
|
||||
return Keychain(service: keychainService)
|
||||
.synchronizable(false)
|
||||
}()
|
||||
|
||||
fileprivate var passCodeHash: String? {
|
||||
return keychain[Keys.passCodeHash]
|
||||
}
|
||||
|
||||
fileprivate enum Keys {
|
||||
static let passCodeHash = "passCodeHash"
|
||||
static let isTouchIdEnabled = "isTouchIdEnabled"
|
||||
static let isInitialLoad = "isInitialLoad"
|
||||
}
|
||||
|
||||
fileprivate enum Values {
|
||||
static let touchIdEnabled = "touchIdEnabled"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BasePassCodeService {
|
||||
|
||||
public var isPassCodeSaved: Bool {
|
||||
return keychain[Keys.passCodeHash] != nil
|
||||
}
|
||||
|
||||
public var isTouchIdEnabled: Bool {
|
||||
get {
|
||||
return keychain[Keys.isTouchIdEnabled] == Values.touchIdEnabled
|
||||
}
|
||||
set {
|
||||
keychain[Keys.isTouchIdEnabled] = newValue ? Values.touchIdEnabled : nil
|
||||
}
|
||||
}
|
||||
|
||||
public func save(passCode: String?) {
|
||||
if let passCode = passCode {
|
||||
keychain[Keys.passCodeHash] = sha256(passCode)
|
||||
} else {
|
||||
keychain[Keys.passCodeHash] = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func check(passCode: String) -> Bool {
|
||||
return sha256(passCode) == passCodeHash
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
save(passCode: nil)
|
||||
isTouchIdEnabled = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension BasePassCodeService {
|
||||
|
||||
func sha256(_ str: String) -> String? {
|
||||
guard let data = str.data(using: String.Encoding.utf8), let shaData = sha256(data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let rc = shaData.base64EncodedString(options: [])
|
||||
return rc
|
||||
}
|
||||
|
||||
func sha256(_ data: Data) -> Data? {
|
||||
guard let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH)) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
CC_SHA256((data as NSData).bytes, CC_LONG(data.count), res.mutableBytes.assumingMemoryBound(to: UInt8.self))
|
||||
return res as Data
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,14 +28,16 @@ import RxSwift
|
|||
import RxCocoa
|
||||
import RxAlamofire
|
||||
|
||||
public let defaultTimeoutInterval = 20.0
|
||||
|
||||
open class DefaultNetworkService: NetworkService {
|
||||
|
||||
static let retryLimit = 3
|
||||
|
||||
open class func baseUrl() -> String {
|
||||
fatalError("base url should be overrided")
|
||||
open class var baseUrl: String {
|
||||
fatalError("You should override this var: baseUrl")
|
||||
}
|
||||
|
||||
open class var defaultTimeoutInterval: TimeInterval {
|
||||
fatalError("You should override this var: defaultTimeoutInterval")
|
||||
}
|
||||
|
||||
public override init(sessionManager: SessionManager) {
|
||||
|
|
@ -46,22 +48,22 @@ open class DefaultNetworkService: NetworkService {
|
|||
|
||||
// MARK: - Default Values
|
||||
|
||||
open class func serverTrustPolicies() -> [String: ServerTrustPolicy] {
|
||||
open class var serverTrustPolicies: [String: ServerTrustPolicy] {
|
||||
return [
|
||||
DefaultNetworkService.baseUrl(): .disableEvaluation
|
||||
DefaultNetworkService.baseUrl: .disableEvaluation
|
||||
]
|
||||
}
|
||||
|
||||
open class func configuration() -> URLSessionConfiguration {
|
||||
open class var configuration: URLSessionConfiguration {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.timeoutIntervalForRequest = defaultTimeoutInterval
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
open class func sessionManager() -> SessionManager {
|
||||
let sessionManager = SessionManager(configuration: configuration(),
|
||||
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies()))
|
||||
open class var sessionManager: SessionManager {
|
||||
let sessionManager = SessionManager(configuration: configuration,
|
||||
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
|
||||
return sessionManager
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +73,7 @@ extension ApiRequestParameters {
|
|||
|
||||
init(url: String, parameters: [String: Any] = [:]) {
|
||||
|
||||
self.init(url: DefaultNetworkService.baseUrl() + url,
|
||||
self.init(url: DefaultNetworkService.baseUrl + url,
|
||||
method: .post,
|
||||
parameters: parameters,
|
||||
encoding: JSONEncoding.default,
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 LeadKit
|
||||
import Alamofire
|
||||
import ObjectMapper
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
import RxAlamofire
|
||||
|
||||
open class SimpleNetworkService: DefaultNetworkService {
|
||||
|
||||
// Singleton
|
||||
static let shared = SimpleNetworkService()
|
||||
|
||||
public convenience init() {
|
||||
self.init(sessionManager: SimpleNetworkService.sessionManager)
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
open func request<T: ImmutableMappable>(with parameters: ApiRequestParameters) -> Observable<T> {
|
||||
let apiResponseRequest = rxRequest(with: parameters) as Observable<(response: HTTPURLResponse, model: ApiResponse)>
|
||||
|
||||
return apiResponseRequest
|
||||
.handleConnectionErrors()
|
||||
.map {
|
||||
if $0.model.errorCode == 0 {
|
||||
return try T(JSON: try cast($0.model.result) as [String: Any])
|
||||
} else {
|
||||
throw ApiError(apiResponse: $0.model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func requestForResult(with parameters: ApiRequestParameters) -> Observable<Bool> {
|
||||
let apiResponseRequest = rxRequest(with: parameters) as Observable<(response: HTTPURLResponse, model: ApiResponse)>
|
||||
|
||||
return apiResponseRequest
|
||||
.handleConnectionErrors()
|
||||
.map {
|
||||
if $0.model.errorCode == 0,
|
||||
let result = $0.model.result as? Bool {
|
||||
return result
|
||||
} else {
|
||||
throw ApiError(apiResponse: $0.model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// Copyright (c) 2017 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 LocalAuthentication
|
||||
|
||||
public typealias TouchIDServiceAuthHandler = (Bool) -> Void
|
||||
|
||||
public class TouchIDService {
|
||||
|
||||
private lazy var laContext: LAContext = {
|
||||
return LAContext()
|
||||
}()
|
||||
|
||||
public var canAuthenticateByTouchId: Bool {
|
||||
return laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
|
||||
}
|
||||
|
||||
public func authenticateByTouchId(description: String, authHandler: @escaping TouchIDServiceAuthHandler) {
|
||||
laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
|
||||
localizedReason: description) { success, _ in
|
||||
|
||||
authHandler(success)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ target 'LeadKitAdditions' do
|
|||
|
||||
pod 'LeadKit', :git => 'https://github.com/TouchInstinct/LeadKit.git', :branch => 'fix/sharedApplication', :commit => 'fd0eb18b8a6680ff16bbb1668d1ae0d29f29fad7'
|
||||
pod 'TableKit'
|
||||
pod 'KeychainAccess'
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ PODS:
|
|||
- CocoaLumberjack/Default (3.1.0)
|
||||
- CocoaLumberjack/Swift (3.1.0):
|
||||
- CocoaLumberjack/Default
|
||||
- KeychainAccess (3.0.2)
|
||||
- LeadKit (0.4.6):
|
||||
- CocoaLumberjack/Swift (~> 3.1.0)
|
||||
- ObjectMapper (~> 2.1)
|
||||
|
|
@ -23,6 +24,7 @@ PODS:
|
|||
- Toast-Swift (2.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- KeychainAccess
|
||||
- LeadKit (from `https://github.com/TouchInstinct/LeadKit.git`, commit `fd0eb18b8a6680ff16bbb1668d1ae0d29f29fad7`, branch `fix/sharedApplication`)
|
||||
- TableKit
|
||||
|
||||
|
|
@ -40,6 +42,7 @@ CHECKOUT OPTIONS:
|
|||
SPEC CHECKSUMS:
|
||||
Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d
|
||||
CocoaLumberjack: 8311463ddf9ee86a06ef92a071dd656c89244500
|
||||
KeychainAccess: a986406022dfc7c634c691ad3bec670cc6a32002
|
||||
LeadKit: d688a8bef79de7bbd83d553da3cb6c5292d48f2d
|
||||
ObjectMapper: fb30f71e08470d1e5a20b199fafe1246281db898
|
||||
RxAlamofire: 0b1fa48f545fffe7f7a28af2086bcaa3b5946cc9
|
||||
|
|
@ -48,6 +51,6 @@ SPEC CHECKSUMS:
|
|||
TableKit: 02e041b443f75fa3e9f1ee6024d4b256305bd904
|
||||
Toast-Swift: 5b2f8f720f7e78e48511f693df1f9c9a6e38a25a
|
||||
|
||||
PODFILE CHECKSUM: ce4fe179e6470b751617d19baacbce25502a7002
|
||||
PODFILE CHECKSUM: f6bfca600b479c264f39b85593af2629edff3515
|
||||
|
||||
COCOAPODS: 1.2.0
|
||||
COCOAPODS: 1.2.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue