Compare commits

..

1 Commits

Author SHA1 Message Date
Igor Kislyuk 81a4f4c4e8 Add. Version 0.0.14.1 2017-06-06 16:30:21 +03:00
58 changed files with 1478 additions and 2411 deletions

3
.gitignore vendored
View File

@ -63,6 +63,3 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
# Touch Instinct custom
Downloads/

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "build-scripts"]
path = build-scripts
url = git@github.com:TouchInstinct/BuildScripts.git

View File

@ -1 +0,0 @@
5.0

View File

@ -1,144 +0,0 @@
# Changelog
### 0.4.0
- **Update**: update libraries to build with Xcode 13.1
### 0.3.15
- **Update**: update LeadKit dependency
### 0.3.14
- **Update**: remove Carthage binary dependencies, update build scripts.
### 0.3.13
- **[Breaking change]**: `LabelTableViewCell` moved to `LeadKit`.
- **[Breaking change]**: Removed `PinLayout` dependency.
### 0.3.12
- **Update**: Swift 5 support
### 0.3.11
- **Add**: Carthage support.
- **[Breaking change]**: Remove `BaseDateFormatter`. Use `NumberFormattingService` from LeadKit instead.
### 0.3.10
- **[Breaking change]**: Remove `isTouchIdSupported` and `isFaceIdSupported`.
- **Add** BiometryType for BiometricsService: `touchID`, `faceID`, `none`.
### 0.3.9
- **Add**: `allowableReuseDuration` variable to `BiometricsService` which is a wrapper for touchIDAuthenticationAllowableReuseDuration.
- **Add**: individual `isTouchIdSupported` variable to `BiometricsService` to indicate if TouchId can be used.
- **Add**: `clear` function to `BiometricsService` so the context can be refreshed.
- **Update**: Documentations.
### 0.3.8
- **Fixed**: `SwiftValidator` fork moved to TouchInstinct repo, the used one is removed. Version number is downgraded to `4.0.2` to avoid collision when the original pod will update.
- **Fixed**: Extensions target building. Removed all PassCode logic, SeparatorTableViewCell and PinLayoutTableViewCell from extensions target.
### 0.3.7
- **Fixed**: `PassCodeError.tooManyAttempts` logic in `.create` and `.change` `PassCodeOperationType`
### 0.3.6
- **Update**: PassCodeError, now emmit `tooManyAttempts` in any operation (*create* / *change* / *enter*) type.
- **Update**: Rename `PassCodeControllerType` to `PassCodeOperationType`.
- **Update**: `showBiometricsRequestIfNeeded` method become public.
### 0.3.5
- **Update**: Migrate to Swift 4.2 & Xcode 10. Update dependencies.
### 0.3.4
- **Add**: `isFaceIdSupported` variable to `BiometricsService` to distinguish FaceID from TouchID.
### 0.3.3
- **Add**: Public `init` to `BiometricsService`
### 0.3.2
- **Add**: functions to `BasePassCodeViewController` to make `fakeTextField` become and resign first responder
### 0.3.1
- **Add**: `PinLayoutTableViewCell` and `SeparatorTableViewCell` to `Core-iOS-Extension`.
### 0.3.0
- **Remove**: `ApiResponse` class
- **Remove**: Occurrences of `ObjectMapper` in `BaseDateFormatter`
- **Remove**: `ApiError` enum and `ApiErrorProtocol`
- **Remove**: `Observable` extension related to error handling
- **Remove**: `ApiNetworkService`
- **Remove**: `DefaultNetworkService` and its extensions
### 0.2.9
- **Add**: `evaluatedPolicyDomainState` to `BiometricsService`.
### 0.2.8
- **Update**: `validationResult` of `BasePassCodeViewModel` renamed to `validationResultDriver` and becomes public.
- **Remove**: `MaskFieldTextProxy`.
- **Remove**: `InputMask` dependency
- **Replace**: `IDZSwiftCommonCrypto` with `CryptoSwift`
### 0.2.7
- **Fix**: Build with new LeadKit 0.7.9
### 0.2.6
- **Update**: `DefaultNetworkService` supported `NetworkServiceConfiguration`
### 0.2.5
- **Add**: Methods to notify when biometrics auth begins and ends.
### 0.2.4
- **Add**: `PinLayout` dependency.
- **Add**: `PinLayoutTableViewCell`, `SeparatorTableViewCell` and `LabelTableViewCell` powered by PinLayout.
- **Add**: `LabelCellViewModel` default view model for label cell.
- **Add**: Playground to project.
### 0.2.3
- **Update**: Xcode 9.3 migration.
### 0.2.2
- **Add**: `PassCodeDelayedDescription` to schedule error messages
### 0.2.1
- **Fixed**: BasePassCodeViewController doesn't draw last dot filled
## 0.2.0
- **Updated**: LeadKit to `0.7.x` version
- **Removed**: `CellField*` and `FormField*` protocols and classes.
- **Add**: `BaseTextFieldViewEvents` and `BaseTextFieldViewModelEvents` with default offline and online validation.
- **Deprecated**: `DefaultNetworkService`.
### 0.1.5
- **Update**: Passcode private configuration
### 0.1.4
- **Update**: Refactor PassCode
### 0.1.3
- **Update**: Typical api response keys naming
### 0.1.2
- **Update**: Access modifiers of `ValidationService`
### 0.1.1
- **Add**: `acceptableStatusCodes` property in `DefaultNetworkService`.
- **Add**: `retry(retryLimit:canRetryClosure:)` to `Observable` extension.
- **Removed**: `retryWithinErrors` method from `Observable` extension.
- **Update**: LeadKit to `0.6.x` version
- **Removed**: `ConnectionError`. (Replaced by `LeadKit.RequestError`)
- **Removed**: `handleConnectionErrors` from Observable+Extensions
## 0.1.0
- **Add**: support for Swift 3.2 / 4

View File

@ -1,5 +0,0 @@
github "krzyzanowskim/CryptoSwift"
github "TouchInstinct/LeadKit"
github "petropavel13/SwiftValidator"
github "kishikawakatsumi/KeychainAccess"
github "layoutBox/PinLayout"

View File

@ -1,12 +0,0 @@
github "Alamofire/Alamofire" "4.9.1"
github "ReactiveX/RxSwift" "4.5.0"
github "RxSwiftCommunity/RxAlamofire" "4.5.0"
github "SnapKit/SnapKit" "4.2.0"
github "TouchInstinct/LeadKit" "0.9.29"
github "TouchInstinct/TableKit" "2.10008.1"
github "krzyzanowskim/CryptoSwift" "1.3.0"
github "malcommac/SwiftDate" "6.1.0"
github "petropavel13/SwiftValidator" "be856202cd70e6549ebc8c9d07ec47bbff784b07"
github "pronebird/UIScrollView-InfiniteScroll" "1.1.0"
github "kishikawakatsumi/KeychainAccess" "v3.2.0"
github "layoutBox/PinLayout" "1.8.6"

201
LICENSE
View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,23 +1,15 @@
Pod::Spec.new do |s|
s.name = "LeadKitAdditions"
s.version = "0.4.0"
s.version = "0.0.14.1"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKitAdditions"
s.license = "Apache License, Version 2.0"
s.author = "Touch Instinct"
s.platform = :ios, "10.0"
s.platform = :ios, "9.0"
s.source = { :git => "https://github.com/TouchInstinct/LeadKitAdditions.git", :tag => s.version }
s.source_files = "LeadKitAdditions/LeadKitAdditions/**/*.swift"
s.subspec 'Core' do |ss|
ss.ios.deployment_target = '10.0'
ss.source_files = "Sources/**/*.swift"
ss.dependency "LeadKit", '~> 1.7.0'
ss.dependency "KeychainAccess", '~> 4.2.0'
ss.dependency "CryptoSwift", '~> 1.4.0'
ss.dependency "SwiftValidator", '4.0.2'
end
s.default_subspec = 'Core'
s.dependency "LeadKit", '~> 0.4.6'
s.dependency "KeychainAccess", '3.0.2'
s.dependency "IDZSwiftCommonCrypto", '0.9.1'
end

View File

@ -1,637 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
14E3AD49502B3D81264560DB /* Pods_LeadKitAdditions_LeadKitAdditions_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3EDEF7F6C22BB04CBB754C14 /* Pods_LeadKitAdditions_LeadKitAdditions_iOS.framework */; };
67779CBC206986390098F024 /* BaseTextFieldViewEvents+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67779CBB206986390098F024 /* BaseTextFieldViewEvents+Extensions.swift */; };
678D26AA206935B900B05B93 /* BiometricsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678D26A9206935B900B05B93 /* BiometricsService.swift */; };
67B4E6F6206945DD00E233EA /* OnlineValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B4E6F5206945DC00E233EA /* OnlineValidationResult.swift */; };
67B4E6F9206945F900E233EA /* OnlineValidationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B4E6F8206945F900E233EA /* OnlineValidationState.swift */; };
67B4E6FB20694A4200E233EA /* BaseTextFieldViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B4E6F1206945D200E233EA /* BaseTextFieldViewModel.swift */; };
67D6041821627600002DAF5B /* LegacyConfigurableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D6041721627600002DAF5B /* LegacyConfigurableController.swift */; };
786CBB2F220DD7A70017587F /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB22220DD7A70017587F /* RxSwift.framework */; };
786CBB30220DD7A70017587F /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB23220DD7A70017587F /* Alamofire.framework */; };
786CBB31220DD7A70017587F /* TableKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB24220DD7A70017587F /* TableKit.framework */; };
786CBB32220DD7A70017587F /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB25220DD7A70017587F /* LeadKit.framework */; };
786CBB33220DD7A70017587F /* SwiftValidator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB26220DD7A70017587F /* SwiftValidator.framework */; };
786CBB34220DD7A70017587F /* UIScrollView_InfiniteScroll.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB27220DD7A70017587F /* UIScrollView_InfiniteScroll.framework */; };
786CBB36220DD7A70017587F /* RxAlamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB29220DD7A70017587F /* RxAlamofire.framework */; };
786CBB37220DD7A70017587F /* SwiftDate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB2A220DD7A70017587F /* SwiftDate.framework */; };
786CBB38220DD7A70017587F /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB2B220DD7A70017587F /* RxCocoa.framework */; };
786CBB39220DD7A70017587F /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB2C220DD7A70017587F /* CryptoSwift.framework */; };
786CBB3A220DD7A70017587F /* KeychainAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 786CBB2D220DD7A70017587F /* KeychainAccess.framework */; };
A6CFB8D91F5024A500A42CC2 /* Error+NetworkingExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CFB8D81F5024A500A42CC2 /* Error+NetworkingExtensions.swift */; };
CAE698E81E968820000394B0 /* LeadKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE698E61E968820000394B0 /* LeadKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
ED0C34071F2906EC00FAE9FD /* LoadingBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33D51F2906EC00FAE9FD /* LoadingBarButton.swift */; };
ED0C34091F2906EC00FAE9FD /* PassCodeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33D91F2906EC00FAE9FD /* PassCodeConfiguration.swift */; };
ED0C340B1F2906EC00FAE9FD /* PassCodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33DA1F2906EC00FAE9FD /* PassCodeError.swift */; };
ED0C340D1F2906EC00FAE9FD /* PassCodeHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33DB1F2906EC00FAE9FD /* PassCodeHolder.swift */; };
ED0C340F1F2906EC00FAE9FD /* PassCodeHolderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33DC1F2906EC00FAE9FD /* PassCodeHolderProtocol.swift */; };
ED0C34111F2906EC00FAE9FD /* PassCodeValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33DD1F2906EC00FAE9FD /* PassCodeValidationResult.swift */; };
ED0C34131F2906EC00FAE9FD /* BasePassCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33DF1F2906EC00FAE9FD /* BasePassCodeViewController.swift */; };
ED0C34151F2906EC00FAE9FD /* BasePassCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33E11F2906EC00FAE9FD /* BasePassCodeViewModel.swift */; };
ED0C341F1F2906EC00FAE9FD /* UIBarButtonItem+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33E81F2906EC00FAE9FD /* UIBarButtonItem+Extensions.swift */; };
ED0C34211F2906EC00FAE9FD /* UserDefaults+UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33E91F2906EC00FAE9FD /* UserDefaults+UserService.swift */; };
ED0C342D1F2906EC00FAE9FD /* BasePassCodeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33F11F2906EC00FAE9FD /* BasePassCodeService.swift */; };
ED0C342F1F2906EC00FAE9FD /* BaseUserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33F21F2906EC00FAE9FD /* BaseUserService.swift */; };
ED0C343F1F2906EC00FAE9FD /* ValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33FC1F2906EC00FAE9FD /* ValidationError.swift */; };
ED0C34411F2906EC00FAE9FD /* ValidationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33FD1F2906EC00FAE9FD /* ValidationItem.swift */; };
ED0C34431F2906EC00FAE9FD /* ValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0C33FE1F2906EC00FAE9FD /* ValidationService.swift */; };
EF5A43B1206E7A67003CED07 /* PassCodeDelayedDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF5A43B0206E7A67003CED07 /* PassCodeDelayedDescription.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1AC235099AA10D83D608A253 /* Pods-LeadKitAdditions-LeadKitAdditions iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKitAdditions-LeadKitAdditions iOS.release.xcconfig"; path = "Target Support Files/Pods-LeadKitAdditions-LeadKitAdditions iOS/Pods-LeadKitAdditions-LeadKitAdditions iOS.release.xcconfig"; sourceTree = "<group>"; };
3EDEF7F6C22BB04CBB754C14 /* Pods_LeadKitAdditions_LeadKitAdditions_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKitAdditions_LeadKitAdditions_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
67528BCE206E3CC6009F2525 /* iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = iOS.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
67779CBB206986390098F024 /* BaseTextFieldViewEvents+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseTextFieldViewEvents+Extensions.swift"; sourceTree = "<group>"; };
678D26A9206935B900B05B93 /* BiometricsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricsService.swift; sourceTree = "<group>"; };
67B4E6F1206945D200E233EA /* BaseTextFieldViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTextFieldViewModel.swift; sourceTree = "<group>"; };
67B4E6F5206945DC00E233EA /* OnlineValidationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnlineValidationResult.swift; sourceTree = "<group>"; };
67B4E6F8206945F900E233EA /* OnlineValidationState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnlineValidationState.swift; sourceTree = "<group>"; };
67D6041721627600002DAF5B /* LegacyConfigurableController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyConfigurableController.swift; sourceTree = "<group>"; };
6FEE03727D0B955F00DE8429 /* Pods-LeadKitAdditions-LeadKitAdditions iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKitAdditions-LeadKitAdditions iOS.debug.xcconfig"; path = "Target Support Files/Pods-LeadKitAdditions-LeadKitAdditions iOS/Pods-LeadKitAdditions-LeadKitAdditions iOS.debug.xcconfig"; sourceTree = "<group>"; };
786CBB22220DD7A70017587F /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = "<group>"; };
786CBB23220DD7A70017587F /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
786CBB24220DD7A70017587F /* TableKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TableKit.framework; path = Carthage/Build/iOS/TableKit.framework; sourceTree = "<group>"; };
786CBB25220DD7A70017587F /* LeadKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LeadKit.framework; path = Carthage/Build/iOS/LeadKit.framework; sourceTree = "<group>"; };
786CBB26220DD7A70017587F /* SwiftValidator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftValidator.framework; path = Carthage/Build/iOS/SwiftValidator.framework; sourceTree = "<group>"; };
786CBB27220DD7A70017587F /* UIScrollView_InfiniteScroll.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIScrollView_InfiniteScroll.framework; path = Carthage/Build/iOS/UIScrollView_InfiniteScroll.framework; sourceTree = "<group>"; };
786CBB29220DD7A70017587F /* RxAlamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxAlamofire.framework; path = Carthage/Build/iOS/RxAlamofire.framework; sourceTree = "<group>"; };
786CBB2A220DD7A70017587F /* SwiftDate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftDate.framework; path = Carthage/Build/iOS/SwiftDate.framework; sourceTree = "<group>"; };
786CBB2B220DD7A70017587F /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/iOS/RxCocoa.framework; sourceTree = "<group>"; };
786CBB2C220DD7A70017587F /* CryptoSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoSwift.framework; path = Carthage/Build/iOS/CryptoSwift.framework; sourceTree = "<group>"; };
786CBB2D220DD7A70017587F /* KeychainAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainAccess.framework; path = Carthage/Build/iOS/KeychainAccess.framework; sourceTree = "<group>"; };
786CBB2E220DD7A70017587F /* PinLayout.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PinLayout.framework; path = Carthage/Build/iOS/PinLayout.framework; sourceTree = "<group>"; };
A6CFB8D81F5024A500A42CC2 /* Error+NetworkingExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Error+NetworkingExtensions.swift"; sourceTree = "<group>"; };
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>"; };
ED0C33D51F2906EC00FAE9FD /* LoadingBarButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingBarButton.swift; sourceTree = "<group>"; };
ED0C33D91F2906EC00FAE9FD /* PassCodeConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeConfiguration.swift; sourceTree = "<group>"; };
ED0C33DA1F2906EC00FAE9FD /* PassCodeError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeError.swift; sourceTree = "<group>"; };
ED0C33DB1F2906EC00FAE9FD /* PassCodeHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeHolder.swift; sourceTree = "<group>"; };
ED0C33DC1F2906EC00FAE9FD /* PassCodeHolderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeHolderProtocol.swift; sourceTree = "<group>"; };
ED0C33DD1F2906EC00FAE9FD /* PassCodeValidationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeValidationResult.swift; sourceTree = "<group>"; };
ED0C33DF1F2906EC00FAE9FD /* BasePassCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeViewController.swift; sourceTree = "<group>"; };
ED0C33E11F2906EC00FAE9FD /* BasePassCodeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeViewModel.swift; sourceTree = "<group>"; };
ED0C33E81F2906EC00FAE9FD /* UIBarButtonItem+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Extensions.swift"; sourceTree = "<group>"; };
ED0C33E91F2906EC00FAE9FD /* UserDefaults+UserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+UserService.swift"; sourceTree = "<group>"; };
ED0C33F11F2906EC00FAE9FD /* BasePassCodeService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeService.swift; sourceTree = "<group>"; };
ED0C33F21F2906EC00FAE9FD /* BaseUserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUserService.swift; sourceTree = "<group>"; };
ED0C33FC1F2906EC00FAE9FD /* ValidationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationError.swift; sourceTree = "<group>"; };
ED0C33FD1F2906EC00FAE9FD /* ValidationItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationItem.swift; sourceTree = "<group>"; };
ED0C33FE1F2906EC00FAE9FD /* ValidationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationService.swift; sourceTree = "<group>"; };
EF5A43B0206E7A67003CED07 /* PassCodeDelayedDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassCodeDelayedDescription.swift; sourceTree = "<group>"; };
EFBD55781EBB893F0062AA63 /* Info-iOS-Extensions.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS-Extensions.plist"; sourceTree = "<group>"; };
EFBD55791EBB893F0062AA63 /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
CAE698DF1E968820000394B0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
786CBB2F220DD7A70017587F /* RxSwift.framework in Frameworks */,
786CBB30220DD7A70017587F /* Alamofire.framework in Frameworks */,
786CBB31220DD7A70017587F /* TableKit.framework in Frameworks */,
786CBB32220DD7A70017587F /* LeadKit.framework in Frameworks */,
786CBB33220DD7A70017587F /* SwiftValidator.framework in Frameworks */,
786CBB34220DD7A70017587F /* UIScrollView_InfiniteScroll.framework in Frameworks */,
786CBB36220DD7A70017587F /* RxAlamofire.framework in Frameworks */,
786CBB37220DD7A70017587F /* SwiftDate.framework in Frameworks */,
786CBB38220DD7A70017587F /* RxCocoa.framework in Frameworks */,
786CBB39220DD7A70017587F /* CryptoSwift.framework in Frameworks */,
786CBB3A220DD7A70017587F /* KeychainAccess.framework in Frameworks */,
14E3AD49502B3D81264560DB /* Pods_LeadKitAdditions_LeadKitAdditions_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
67B4E6F0206945D200E233EA /* BaseTextFieldViewModel */ = {
isa = PBXGroup;
children = (
67B4E6F1206945D200E233EA /* BaseTextFieldViewModel.swift */,
);
path = BaseTextFieldViewModel;
sourceTree = "<group>";
};
67B4E6F4206945DC00E233EA /* ValidationService */ = {
isa = PBXGroup;
children = (
67B4E6F5206945DC00E233EA /* OnlineValidationResult.swift */,
);
path = ValidationService;
sourceTree = "<group>";
};
A3117951840B8B7D2E7A8A80 /* Frameworks */ = {
isa = PBXGroup;
children = (
786CBB23220DD7A70017587F /* Alamofire.framework */,
786CBB2C220DD7A70017587F /* CryptoSwift.framework */,
786CBB2D220DD7A70017587F /* KeychainAccess.framework */,
786CBB25220DD7A70017587F /* LeadKit.framework */,
786CBB2E220DD7A70017587F /* PinLayout.framework */,
786CBB29220DD7A70017587F /* RxAlamofire.framework */,
786CBB2B220DD7A70017587F /* RxCocoa.framework */,
786CBB22220DD7A70017587F /* RxSwift.framework */,
786CBB2A220DD7A70017587F /* SwiftDate.framework */,
786CBB26220DD7A70017587F /* SwiftValidator.framework */,
786CBB24220DD7A70017587F /* TableKit.framework */,
786CBB27220DD7A70017587F /* UIScrollView_InfiniteScroll.framework */,
3EDEF7F6C22BB04CBB754C14 /* Pods_LeadKitAdditions_LeadKitAdditions_iOS.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
CAE698D91E968820000394B0 = {
isa = PBXGroup;
children = (
A3117951840B8B7D2E7A8A80 /* Frameworks */,
CAE698E41E968820000394B0 /* Products */,
CAE698E51E968820000394B0 /* Sources */,
67528BCE206E3CC6009F2525 /* iOS.playground */,
FBC34E01DAB566B85ED77BAD /* Pods */,
);
sourceTree = "<group>";
};
CAE698E41E968820000394B0 /* Products */ = {
isa = PBXGroup;
children = (
CAE698E31E968820000394B0 /* LeadKitAdditions.framework */,
);
name = Products;
sourceTree = "<group>";
};
CAE698E51E968820000394B0 /* Sources */ = {
isa = PBXGroup;
children = (
ED0C33D21F2906EC00FAE9FD /* Classes */,
ED0C33D61F2906EC00FAE9FD /* Controllers */,
ED0C33E61F2906EC00FAE9FD /* Extensions */,
ED0C33EA1F2906EC00FAE9FD /* Protocols */,
ED0C33F01F2906EC00FAE9FD /* Services */,
EFBD55781EBB893F0062AA63 /* Info-iOS-Extensions.plist */,
EFBD55791EBB893F0062AA63 /* Info-iOS.plist */,
CAE698E61E968820000394B0 /* LeadKitAdditions.h */,
);
path = Sources;
sourceTree = "<group>";
};
ED0C33D21F2906EC00FAE9FD /* Classes */ = {
isa = PBXGroup;
children = (
67B4E6F0206945D200E233EA /* BaseTextFieldViewModel */,
ED0C33D51F2906EC00FAE9FD /* LoadingBarButton.swift */,
);
path = Classes;
sourceTree = "<group>";
};
ED0C33D61F2906EC00FAE9FD /* Controllers */ = {
isa = PBXGroup;
children = (
ED0C33D71F2906EC00FAE9FD /* PassCode */,
);
path = Controllers;
sourceTree = "<group>";
};
ED0C33D71F2906EC00FAE9FD /* PassCode */ = {
isa = PBXGroup;
children = (
ED0C33D81F2906EC00FAE9FD /* Model */,
ED0C33DE1F2906EC00FAE9FD /* View */,
ED0C33E01F2906EC00FAE9FD /* ViewModel */,
);
path = PassCode;
sourceTree = "<group>";
};
ED0C33D81F2906EC00FAE9FD /* Model */ = {
isa = PBXGroup;
children = (
ED0C33D91F2906EC00FAE9FD /* PassCodeConfiguration.swift */,
EF5A43B0206E7A67003CED07 /* PassCodeDelayedDescription.swift */,
ED0C33DA1F2906EC00FAE9FD /* PassCodeError.swift */,
ED0C33DB1F2906EC00FAE9FD /* PassCodeHolder.swift */,
ED0C33DC1F2906EC00FAE9FD /* PassCodeHolderProtocol.swift */,
ED0C33DD1F2906EC00FAE9FD /* PassCodeValidationResult.swift */,
);
path = Model;
sourceTree = "<group>";
};
ED0C33DE1F2906EC00FAE9FD /* View */ = {
isa = PBXGroup;
children = (
ED0C33DF1F2906EC00FAE9FD /* BasePassCodeViewController.swift */,
);
path = View;
sourceTree = "<group>";
};
ED0C33E01F2906EC00FAE9FD /* ViewModel */ = {
isa = PBXGroup;
children = (
ED0C33E11F2906EC00FAE9FD /* BasePassCodeViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
ED0C33E61F2906EC00FAE9FD /* Extensions */ = {
isa = PBXGroup;
children = (
ED0C33E81F2906EC00FAE9FD /* UIBarButtonItem+Extensions.swift */,
ED0C33E91F2906EC00FAE9FD /* UserDefaults+UserService.swift */,
A6CFB8D81F5024A500A42CC2 /* Error+NetworkingExtensions.swift */,
67779CBB206986390098F024 /* BaseTextFieldViewEvents+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
ED0C33EA1F2906EC00FAE9FD /* Protocols */ = {
isa = PBXGroup;
children = (
67B4E6F4206945DC00E233EA /* ValidationService */,
67D6041721627600002DAF5B /* LegacyConfigurableController.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
ED0C33F01F2906EC00FAE9FD /* Services */ = {
isa = PBXGroup;
children = (
ED0C33FB1F2906EC00FAE9FD /* ValidationService */,
ED0C33F11F2906EC00FAE9FD /* BasePassCodeService.swift */,
ED0C33F21F2906EC00FAE9FD /* BaseUserService.swift */,
678D26A9206935B900B05B93 /* BiometricsService.swift */,
);
path = Services;
sourceTree = "<group>";
};
ED0C33FB1F2906EC00FAE9FD /* ValidationService */ = {
isa = PBXGroup;
children = (
67B4E6F8206945F900E233EA /* OnlineValidationState.swift */,
ED0C33FC1F2906EC00FAE9FD /* ValidationError.swift */,
ED0C33FD1F2906EC00FAE9FD /* ValidationItem.swift */,
ED0C33FE1F2906EC00FAE9FD /* ValidationService.swift */,
);
path = ValidationService;
sourceTree = "<group>";
};
FBC34E01DAB566B85ED77BAD /* Pods */ = {
isa = PBXGroup;
children = (
6FEE03727D0B955F00DE8429 /* Pods-LeadKitAdditions-LeadKitAdditions iOS.debug.xcconfig */,
1AC235099AA10D83D608A253 /* Pods-LeadKitAdditions-LeadKitAdditions iOS.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
CAE698E01E968820000394B0 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
CAE698E81E968820000394B0 /* LeadKitAdditions.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
CAE698E21E968820000394B0 /* LeadKitAdditions iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = CAE698EB1E968820000394B0 /* Build configuration list for PBXNativeTarget "LeadKitAdditions iOS" */;
buildPhases = (
DA96E76177E2633117FCBE55 /* [CP] Check Pods Manifest.lock */,
CAE698DE1E968820000394B0 /* Sources */,
CAE698DF1E968820000394B0 /* Frameworks */,
CAE698E01E968820000394B0 /* Headers */,
CAE698E11E968820000394B0 /* Resources */,
CAE6990A1E969A7A000394B0 /* Swiftlint */,
);
buildRules = (
);
dependencies = (
);
name = "LeadKitAdditions iOS";
productName = LeadKitAdditions;
productReference = CAE698E31E968820000394B0 /* LeadKitAdditions.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
CAE698DA1E968820000394B0 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = TouchInstinct;
TargetAttributes = {
CAE698E21E968820000394B0 = {
CreatedOnToolsVersion = 8.3;
LastSwiftMigration = 1020;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = CAE698DD1E968820000394B0 /* Build configuration list for PBXProject "LeadKitAdditions" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = CAE698D91E968820000394B0;
productRefGroup = CAE698E41E968820000394B0 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
CAE698E21E968820000394B0 /* LeadKitAdditions iOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
CAE698E11E968820000394B0 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
CAE6990A1E969A7A000394B0 /* Swiftlint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = Swiftlint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = ". run_swiftlint.sh\n";
};
DA96E76177E2633117FCBE55 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-LeadKitAdditions-LeadKitAdditions iOS-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
CAE698DE1E968820000394B0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
67D6041821627600002DAF5B /* LegacyConfigurableController.swift in Sources */,
67B4E6FB20694A4200E233EA /* BaseTextFieldViewModel.swift in Sources */,
ED0C340F1F2906EC00FAE9FD /* PassCodeHolderProtocol.swift in Sources */,
ED0C343F1F2906EC00FAE9FD /* ValidationError.swift in Sources */,
ED0C342F1F2906EC00FAE9FD /* BaseUserService.swift in Sources */,
678D26AA206935B900B05B93 /* BiometricsService.swift in Sources */,
ED0C34411F2906EC00FAE9FD /* ValidationItem.swift in Sources */,
ED0C341F1F2906EC00FAE9FD /* UIBarButtonItem+Extensions.swift in Sources */,
ED0C34091F2906EC00FAE9FD /* PassCodeConfiguration.swift in Sources */,
EF5A43B1206E7A67003CED07 /* PassCodeDelayedDescription.swift in Sources */,
ED0C34131F2906EC00FAE9FD /* BasePassCodeViewController.swift in Sources */,
ED0C342D1F2906EC00FAE9FD /* BasePassCodeService.swift in Sources */,
67B4E6F9206945F900E233EA /* OnlineValidationState.swift in Sources */,
67779CBC206986390098F024 /* BaseTextFieldViewEvents+Extensions.swift in Sources */,
ED0C340D1F2906EC00FAE9FD /* PassCodeHolder.swift in Sources */,
A6CFB8D91F5024A500A42CC2 /* Error+NetworkingExtensions.swift in Sources */,
ED0C34071F2906EC00FAE9FD /* LoadingBarButton.swift in Sources */,
ED0C34211F2906EC00FAE9FD /* UserDefaults+UserService.swift in Sources */,
ED0C34111F2906EC00FAE9FD /* PassCodeValidationResult.swift in Sources */,
ED0C34431F2906EC00FAE9FD /* ValidationService.swift in Sources */,
ED0C340B1F2906EC00FAE9FD /* PassCodeError.swift in Sources */,
67B4E6F6206945DD00E233EA /* OnlineValidationResult.swift in Sources */,
ED0C34151F2906EC00FAE9FD /* BasePassCodeViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
CAE698E91E968820000394B0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
CAE698EA1E968820000394B0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
CAE698EC1E968820000394B0 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6FEE03727D0B955F00DE8429 /* Pods-LeadKitAdditions-LeadKitAdditions iOS.debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "touchinstinct.LeadKitAdditions-iOS";
PRODUCT_NAME = LeadKitAdditions;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
CAE698ED1E968820000394B0 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1AC235099AA10D83D608A253 /* Pods-LeadKitAdditions-LeadKitAdditions iOS.release.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "touchinstinct.LeadKitAdditions-iOS";
PRODUCT_NAME = LeadKitAdditions;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
CAE698DD1E968820000394B0 /* Build configuration list for PBXProject "LeadKitAdditions" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CAE698E91E968820000394B0 /* Debug */,
CAE698EA1E968820000394B0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CAE698EB1E968820000394B0 /* Build configuration list for PBXNativeTarget "LeadKitAdditions iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CAE698EC1E968820000394B0 /* Debug */,
CAE698ED1E968820000394B0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = CAE698DA1E968820000394B0 /* Project object */;
}

View File

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CAE698E21E968820000394B0"
BuildableName = "LeadKitAdditions.framework"
BlueprintName = "LeadKitAdditions iOS"
ReferencedContainer = "container:LeadKitAdditions.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CAE698E21E968820000394B0"
BuildableName = "LeadKitAdditions.framework"
BlueprintName = "LeadKitAdditions iOS"
ReferencedContainer = "container:LeadKitAdditions.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CAE698E21E968820000394B0"
BuildableName = "LeadKitAdditions.framework"
BlueprintName = "LeadKitAdditions iOS"
ReferencedContainer = "container:LeadKitAdditions.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,8 +0,0 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,21 @@
disabled_rules:
- variable_name
excluded:
- Carthage
- Pods
line_length: 128
type_body_length:
- 500 # warning
- 700 # error
file_length:
warning: 500
error: 1200
warning_threshold: 1
custom_rules:
uiwebview_disabled:
included: ".*.swift"
name: "UIWebView Usage Disabled"
regex: "(UIWebView)"
message: "Do not use UIWebView. Use WKWebView Instead. https://developer.apple.com/reference/uikit/uiwebview"
severity: error

View File

@ -0,0 +1,4 @@
exclude:
- 'Pods'
- 'Carthage'
- 'RxAlamofire'

View File

@ -0,0 +1,568 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* 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, ); }; };
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 */; };
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 */; };
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 /* ApiNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDFB1EB0D77400CAE7B6 /* ApiNetworkService.swift */; };
EF05EE021EB206C000CAE7B6 /* LoadingBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EE011EB206C000CAE7B6 /* LoadingBarButton.swift */; };
EF05EE041EB21A2D00CAE7B6 /* ApiErrorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EE031EB21A2D00CAE7B6 /* ApiErrorProtocol.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
53E343F9E8F0A27C6114C160 /* Pods-LeadKitAdditions.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKitAdditions.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LeadKitAdditions/Pods-LeadKitAdditions.debug.xcconfig"; sourceTree = "<group>"; };
7B7F57C5E5275C4D8DC71992 /* Pods_LeadKitAdditions.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKitAdditions.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8A48492D1BE72C6D161A6E8E /* Pods-LeadKitAdditions.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKitAdditions.release.xcconfig"; path = "Pods/Target Support Files/Pods-LeadKitAdditions/Pods-LeadKitAdditions.release.xcconfig"; sourceTree = "<group>"; };
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>"; };
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>"; };
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>"; };
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 /* ApiNetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiNetworkService.swift; sourceTree = "<group>"; };
EF05EE011EB206C000CAE7B6 /* LoadingBarButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingBarButton.swift; sourceTree = "<group>"; };
EF05EE031EB21A2D00CAE7B6 /* ApiErrorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiErrorProtocol.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
CAE698DF1E968820000394B0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7A94B4A5956B82BE1CEBA873 /* Pods_LeadKitAdditions.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
A3117951840B8B7D2E7A8A80 /* Frameworks */ = {
isa = PBXGroup;
children = (
7B7F57C5E5275C4D8DC71992 /* Pods_LeadKitAdditions.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
CAE698D91E968820000394B0 = {
isa = PBXGroup;
children = (
CAE698E51E968820000394B0 /* LeadKitAdditions */,
CAE698E41E968820000394B0 /* Products */,
F8A65FEC7C0EB4B93746E50F /* Pods */,
A3117951840B8B7D2E7A8A80 /* Frameworks */,
);
sourceTree = "<group>";
};
CAE698E41E968820000394B0 /* Products */ = {
isa = PBXGroup;
children = (
CAE698E31E968820000394B0 /* LeadKitAdditions.framework */,
);
name = Products;
sourceTree = "<group>";
};
CAE698E51E968820000394B0 /* LeadKitAdditions */ = {
isa = PBXGroup;
children = (
EF05EDDB1EAFA6FA00CAE7B6 /* Controllers */,
CAE698EE1E968B72000394B0 /* Classes */,
CAE699011E9693DE000394B0 /* Enums */,
CAE698F81E968F56000394B0 /* Extensions */,
CAE698E71E968820000394B0 /* Info.plist */,
CAE698E61E968820000394B0 /* LeadKitAdditions.h */,
CAE698F31E968E28000394B0 /* Services */,
);
path = LeadKitAdditions;
sourceTree = "<group>";
};
CAE698EE1E968B72000394B0 /* Classes */ = {
isa = PBXGroup;
children = (
EF05EDBD1EAF706200CAE7B6 /* ApiResponse.swift */,
EF05EDBE1EAF706200CAE7B6 /* BaseDateFormatter.swift */,
EF05EE011EB206C000CAE7B6 /* LoadingBarButton.swift */,
);
path = Classes;
sourceTree = "<group>";
};
CAE698F31E968E28000394B0 /* Services */ = {
isa = PBXGroup;
children = (
EF05EDF91EB0D75A00CAE7B6 /* Network */,
EF05EDC71EAF91D500CAE7B6 /* BasePassCodeService.swift */,
EF05EDB31EAF703A00CAE7B6 /* BaseUserService.swift */,
EF05EDC51EAF70EB00CAE7B6 /* TouchIDService.swift */,
);
path = Services;
sourceTree = "<group>";
};
CAE698F81E968F56000394B0 /* Extensions */ = {
isa = PBXGroup;
children = (
EF05EDB51EAF704800CAE7B6 /* Observable+Extensions.swift */,
EF05EDF71EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift */,
EF05EDB61EAF704800CAE7B6 /* UserDefaults+UserService.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
CAE699011E9693DE000394B0 /* Enums */ = {
isa = PBXGroup;
children = (
EF05EDB91EAF705500CAE7B6 /* ApiError.swift */,
EF05EE031EB21A2D00CAE7B6 /* ApiErrorProtocol.swift */,
EF05EDBA1EAF705500CAE7B6 /* ConnectionError.swift */,
);
path = Enums;
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 = (
EF05EDFB1EB0D77400CAE7B6 /* ApiNetworkService.swift */,
EF05EDFA1EB0D77400CAE7B6 /* DefaultNetworkService.swift */,
);
path = Network;
sourceTree = "<group>";
};
F8A65FEC7C0EB4B93746E50F /* Pods */ = {
isa = PBXGroup;
children = (
53E343F9E8F0A27C6114C160 /* Pods-LeadKitAdditions.debug.xcconfig */,
8A48492D1BE72C6D161A6E8E /* Pods-LeadKitAdditions.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
CAE698E01E968820000394B0 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
CAE698E81E968820000394B0 /* LeadKitAdditions.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
CAE698E21E968820000394B0 /* LeadKitAdditions */ = {
isa = PBXNativeTarget;
buildConfigurationList = CAE698EB1E968820000394B0 /* Build configuration list for PBXNativeTarget "LeadKitAdditions" */;
buildPhases = (
E8E82E34792B38EF225575D7 /* [CP] Check Pods Manifest.lock */,
CAE698DE1E968820000394B0 /* Sources */,
CAE698DF1E968820000394B0 /* Frameworks */,
CAE698E01E968820000394B0 /* Headers */,
CAE698E11E968820000394B0 /* Resources */,
94F6E1BA5AD68C6E2F10062B /* [CP] Copy Pods Resources */,
CAE6990A1E969A7A000394B0 /* Swiftlint */,
CAE6990B1E969A8D000394B0 /* Tailor */,
);
buildRules = (
);
dependencies = (
);
name = LeadKitAdditions;
productName = LeadKitAdditions;
productReference = CAE698E31E968820000394B0 /* LeadKitAdditions.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
CAE698DA1E968820000394B0 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = TouchInstinct;
TargetAttributes = {
CAE698E21E968820000394B0 = {
CreatedOnToolsVersion = 8.3;
LastSwiftMigration = 0830;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = CAE698DD1E968820000394B0 /* Build configuration list for PBXProject "LeadKitAdditions" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = CAE698D91E968820000394B0;
productRefGroup = CAE698E41E968820000394B0 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
CAE698E21E968820000394B0 /* LeadKitAdditions */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
CAE698E11E968820000394B0 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
94F6E1BA5AD68C6E2F10062B /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LeadKitAdditions/Pods-LeadKitAdditions-resources.sh\"\n";
showEnvVarsInLog = 0;
};
CAE6990A1E969A7A000394B0 /* Swiftlint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = Swiftlint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
};
CAE6990B1E969A8D000394B0 /* Tailor */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = Tailor;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if hash tailor 2>/dev/null; then\n tailor\nelse\n echo \"warning: Please install Tailor from https://tailor.sh\"\nfi";
};
E8E82E34792B38EF225575D7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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 */
/* Begin PBXSourcesBuildPhase section */
CAE698DE1E968820000394B0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EF05EE041EB21A2D00CAE7B6 /* ApiErrorProtocol.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 /* ApiNetworkService.swift in Sources */,
EF05EDF81EB0D5A600CAE7B6 /* UIBarButtonItem+Extensions.swift in Sources */,
EF05EDED1EAFA96D00CAE7B6 /* PassCodeHolder.swift in Sources */,
EF05EDB71EAF704800CAE7B6 /* Observable+Extensions.swift in Sources */,
EF05EDC01EAF706200CAE7B6 /* ApiResponse.swift in Sources */,
EF05EDBC1EAF705500CAE7B6 /* ConnectionError.swift in Sources */,
EF05EE021EB206C000CAE7B6 /* LoadingBarButton.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;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
CAE698E91E968820000394B0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
CAE698EA1E968820000394B0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
CAE698EC1E968820000394B0 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 53E343F9E8F0A27C6114C160 /* Pods-LeadKitAdditions.debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = LeadKitAdditions/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = touchinstinct.LeadKitAdditions;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
CAE698ED1E968820000394B0 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8A48492D1BE72C6D161A6E8E /* Pods-LeadKitAdditions.release.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = LeadKitAdditions/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = touchinstinct.LeadKitAdditions;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
CAE698DD1E968820000394B0 /* Build configuration list for PBXProject "LeadKitAdditions" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CAE698E91E968820000394B0 /* Debug */,
CAE698EA1E968820000394B0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CAE698EB1E968820000394B0 /* Build configuration list for PBXNativeTarget "LeadKitAdditions" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CAE698EC1E968820000394B0 /* Debug */,
CAE698ED1E968820000394B0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = CAE698DA1E968820000394B0 /* Project object */;
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,19 +20,25 @@
// THE SOFTWARE.
//
import RxSwift
import ObjectMapper
public extension BaseTextFieldViewEvents {
public class ApiResponse: ApiResponseProtocol, ImmutableMappable {
/// Convenience init with UITextField as textChangedDriver.
///
/// - Parameter textField: UITextField to use for text events.
convenience init(textField: UITextField) {
let textChangedDriver = textField.rx
.text
.asDriver()
.distinctUntilChanged { $0 == $1 }
public let result: Any?
public let errorCode: Int
public let errorMessage: String?
self.init(textChangedDriver: textChangedDriver)
public required init(map: Map) throws {
result = try? map.value("result")
errorCode = try map.value("error_code")
errorMessage = try? map.value("error_message")
}
}
public protocol ApiResponseProtocol: ImmutableMappable {
var errorCode: Int { get }
var errorMessage: String? { get }
}

View File

@ -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 Foundation
import ObjectMapper
open class BaseDateFormatter {
private static let apiDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
private static let apiDateWithoutTimeFormat = "yyyy-MM-dd'T'Z"
private static let hourAndMinuteDateTimeFormat = "HH:mm"
private static let dayAndMonthDateTimeFormat = "dd MMM"
private static let dayMonthYearDateTimeFormat = "dd.MM.yyyy"
private static let apiFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = apiDateTimeFormat
return dateFormatter
}()
private static let apiDateWithoutTimeFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = apiDateWithoutTimeFormat
return dateFormatter
}()
private static let hourAndMinuteFormatter: DateFormatter = {
let dateFormater = DateFormatter()
dateFormater.dateFormat = hourAndMinuteDateTimeFormat
return dateFormater
}()
private static let dayAndMonthFormatter: DateFormatter = {
let dateFormater = DateFormatter()
dateFormater.dateFormat = dayAndMonthDateTimeFormat
return dateFormater
}()
private static let dayMonthYearFormatter: DateFormatter = {
let dateFormater = DateFormatter()
dateFormater.dateFormat = dayMonthYearDateTimeFormat
return dateFormater
}()
// MARK: Public interface
open class var usedLocale: Locale {
return .current
}
public static func backendDate(fromStrDate strDate: String) -> Date? {
apiFormatter.locale = usedLocale
return apiFormatter.date(from: strDate)
}
public static func backendStrDate(withDate date: Date) -> String {
apiFormatter.locale = usedLocale
return apiFormatter.string(from: date)
}
public static func backendDateWithoutTime(withDate date: Date) -> String {
apiDateWithoutTimeFormatter.locale = usedLocale
return apiDateWithoutTimeFormatter.string(from: date)
}
public static func hourAndMinuteStrDate(withDate date: Date) -> String {
hourAndMinuteFormatter.locale = usedLocale
return hourAndMinuteFormatter.string(from: date)
}
public static func dayAndMonthStrDate(withDate date: Date) -> String {
hourAndMinuteFormatter.locale = usedLocale
return dayAndMonthFormatter.string(from: date)
}
public static func dayMonthYearStrDate(withDate date: Date) -> String {
hourAndMinuteFormatter.locale = usedLocale
return dayMonthYearFormatter.string(from: date)
}
// MARK: - Transformers
public static var transformFromStringToDate: TransformOf<Date, String> {
return TransformOf<Date, String>(fromJSON: { (stringValue) -> Date? in
if let stringValue = stringValue {
return backendDate(fromStrDate: stringValue)
} else {
return nil
}
}, toJSON: { (dateValue) -> String? in
if let dateValue = dateValue {
return backendStrDate(withDate: dateValue)
} else {
return nil
}
})
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -24,13 +24,11 @@ import UIKit
import RxSwift
import RxCocoa
/// Side to which activity indicator applied
public enum LoadingBarButtonSide {
case left
case right
}
/// Workaround with navigationBarButton, that can change state (UI) into activity indicator
public class LoadingBarButton {
fileprivate weak var navigationItem: UINavigationItem?
@ -42,7 +40,6 @@ public class LoadingBarButton {
switch side {
case .left:
return navigationItem?.leftBarButtonItem
case .right:
return navigationItem?.rightBarButtonItem
}
@ -51,20 +48,12 @@ public class LoadingBarButton {
switch side {
case .left:
navigationItem?.leftBarButtonItem = newValue
case .right:
navigationItem?.rightBarButtonItem = newValue
}
}
}
/**
Create an instance of LoadingBarButton
- Parameters:
- navigationItem: item to which apply changes
- side: side where navigationItem would be placed
*/
public init(navigationItem: UINavigationItem, side: LoadingBarButtonSide) {
self.navigationItem = navigationItem
self.side = side
@ -73,32 +62,25 @@ public class LoadingBarButton {
fileprivate func setState(waiting: Bool = false) {
if waiting {
let activityIndicatorItem = UIBarButtonItem.activityIndicator
let activityIndicatorItem = UIBarButtonItem.activityIndicator
barButtonItem = activityIndicatorItem.barButton
activityIndicatorItem.activityIndicator.startAnimating()
} else {
barButtonItem = initialBarButton
}
}
}
public extension Observable {
extension Observable {
/**
Reactive extension for LoadingBarButton
Apply transformations on subscribe and on dispose events
- Parameters:
- barButton: LoadingBarButton instance to which transformations would applied
- Returns:
- observable, that handles LoadingBarButton behaviour
*/
func changeLoadingUI(using barButton: LoadingBarButton) -> Observable<Observable.Element> {
observe(on: MainScheduler.instance)
public func changeLoadingUI(using barButton: LoadingBarButton) -> Observable<Observable.E> {
return observeOn(MainScheduler.instance)
.do(onSubscribe: {
barButton.setState(waiting: true)
}, onDispose: {
barButton.setState(waiting: false)
})
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,26 +20,25 @@
// THE SOFTWARE.
//
/// Configuration container for BasePassCodeViewController
public struct PassCodeConfiguration {
/// Pass code length
public let passCodeLength: Int
public var passCodeCharactersNumber: UInt = 4
public var maxAttemptsLoginNumber: UInt = 5
/// Incorrect pass code attempts count
public let maxAttemptsNumber: Int
public var shouldResetWhenGoBackground: Bool = true
/// Clear input progress when application goes to background
public let shouldResetWhenGoBackground: Bool
private init() {}
public init(passCodeLength: Int = 4, maxAttemptsNumber: Int = 5, shouldResetWhenGoBackground: Bool = true) {
self.passCodeLength = passCodeLength
self.maxAttemptsNumber = maxAttemptsNumber
self.shouldResetWhenGoBackground = shouldResetWhenGoBackground
init?(passCodeCharactersNumber: UInt) {
guard passCodeCharactersNumber > 0 else {
assertionFailure("passCodeCharactersNumber must be greater then 0")
return nil
}
self.passCodeCharactersNumber = passCodeCharactersNumber
}
/// Returns configuration with default values
public static var defaultConfiguration: PassCodeConfiguration {
PassCodeConfiguration()
return PassCodeConfiguration()
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,16 +20,8 @@
// THE SOFTWARE.
//
/// Enum that describes current state of online validation.
///
/// - initial: Nothing did happen.
/// - processing: Processing validation.
/// - valid: Got a valid result.
/// - invalid: Got an invalid result.
public enum OnlineValidationState {
case initial
case processing
case valid
case invalid(error: Error?)
public enum PassCodeError: Error {
case codesNotMatch
case wrongCode
case tooManyAttempts
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -23,25 +23,24 @@
import RxSwift
import RxCocoa
public extension PassCodeHolderProtocol {
extension PassCodeHolderProtocol {
var passCodeHolderCreate: PassCodeHolderCreate? {
self as? PassCodeHolderCreate
public var passCodeHolderCreate: PassCodeHolderCreate? {
return self as? PassCodeHolderCreate
}
var passCodeHolderEnter: PassCodeHolderEnter? {
self as? PassCodeHolderEnter
public var passCodeHolderEnter: PassCodeHolderEnter? {
return self as? PassCodeHolderEnter
}
var passCodeHolderChange: PassCodeHolderChange? {
self as? PassCodeHolderChange
public var passCodeHolderChange: PassCodeHolderChange? {
return self as? PassCodeHolderChange
}
}
/// Holds information about pass codes during pass code creation process
public class PassCodeHolderCreate: PassCodeHolderProtocol {
public let type: PassCodeOperationType = .create
public let type: PassCodeControllerType = .create
private var firstPassCode: String?
private var secondPassCode: String?
@ -55,7 +54,7 @@ public class PassCodeHolderCreate: PassCodeHolderProtocol {
}
public var shouldValidate: Bool {
firstPassCode != nil && secondPassCode != nil
return firstPassCode != nil && secondPassCode != nil
}
public var passCode: String? {
@ -70,10 +69,8 @@ public class PassCodeHolderCreate: PassCodeHolderProtocol {
switch enterStep {
case .enter:
firstPassCode = passCode
case .repeatEnter:
secondPassCode = passCode
default:
break
}
@ -83,24 +80,24 @@ public class PassCodeHolderCreate: PassCodeHolderProtocol {
if let passCode = passCode {
return .valid(passCode)
} else {
return .invalid(.codesNotMatch)
return .inValid(.codesNotMatch)
}
}
public func reset() {
firstPassCode = nil
firstPassCode = nil
secondPassCode = nil
}
}
/// Holds information about pass code during pass code entering process
public class PassCodeHolderEnter: PassCodeHolderProtocol {
public let type: PassCodeOperationType = .enter
public let type: PassCodeControllerType = .enter
public let enterStep: PassCodeControllerState = .enter
public var shouldValidate: Bool {
passCode != nil
return passCode != nil
}
public var passCode: String?
@ -113,19 +110,19 @@ public class PassCodeHolderEnter: PassCodeHolderProtocol {
if let passCode = passCode {
return .valid(passCode)
} else {
return .invalid(nil)
return .inValid(nil)
}
}
public func reset() {
passCode = nil
}
}
/// Holds information about pass codes during pass code changing process
public class PassCodeHolderChange: PassCodeHolderProtocol {
public let type: PassCodeOperationType = .change
public let type: PassCodeControllerType = .change
private var oldPassCode: String?
private var newFirstPassCode: String?
@ -153,15 +150,12 @@ public class PassCodeHolderChange: PassCodeHolderProtocol {
public var passCode: String? {
switch (oldPassCode, newFirstPassCode, newSecondPassCode) {
case let (oldPassCode?, nil, nil):
case (let oldPassCode?, nil, nil):
return oldPassCode
case (_, _?, nil):
return nil
case let (_, newFirstPassCode?, newSecondPassCode?) where newFirstPassCode == newSecondPassCode:
case (_, let newFirstPassCode?, let newSecondPassCode?) where newFirstPassCode == newSecondPassCode:
return newFirstPassCode
default:
return nil
}
@ -181,7 +175,7 @@ public class PassCodeHolderChange: PassCodeHolderProtocol {
if let passCode = passCode {
return .valid(passCode)
} else {
return .invalid(enterStep == .newEnter ? nil : .codesNotMatch)
return .inValid(enterStep == .newEnter ? nil : .codesNotMatch)
}
}
@ -193,4 +187,5 @@ public class PassCodeHolderChange: PassCodeHolderProtocol {
newSecondPassCode = nil
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,49 +20,34 @@
// THE SOFTWARE.
//
/// Holds information about enter type (create, change, etc), step
/// Also describes interface to manipulate with entered pass code
public protocol PassCodeHolderProtocol {
/// Type of operation with pass code
var type: PassCodeOperationType { get }
/// Operation step
var type: PassCodeControllerType { get }
var enterStep: PassCodeControllerState { get }
/// Add pass code for current step
func add(passCode: String)
/// Reset all progress
func reset()
/// Should been pass code validated
var shouldValidate: Bool { get }
/// Current pass code
var passCode: String? { get }
/// Returns passCode or error if pass code is invalid
func validate() -> PassCodeValidationResult
}
public class PassCodeHolderBuilder {
private init() {}
/**
Creates holder by type (create, change, etc)
- parameter type: type of pass code controller
- returns: pass code information holder, specific by type
*/
public static func build(with type: PassCodeOperationType) -> PassCodeHolderProtocol {
public static func build(with type: PassCodeControllerType) -> PassCodeHolderProtocol {
switch type {
case .create:
return PassCodeHolderCreate()
case .enter:
return PassCodeHolderEnter()
case .change:
return PassCodeHolderChange()
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,41 +20,36 @@
// THE SOFTWARE.
//
/// Result of pass code validation
public enum PassCodeValidationResult {
case valid(String)
case invalid(PassCodeError?)
}
case inValid(PassCodeError?)
public extension PassCodeValidationResult {
var isValid: Bool {
public var isValid: Bool {
switch self {
case .valid:
return true
case .invalid:
default:
return false
}
}
var passCode: String? {
public var passCode: String? {
switch self {
case let .valid(passCode):
return passCode
case .invalid:
default:
return nil
}
}
var error: PassCodeError? {
public var error: PassCodeError? {
switch self {
case let .invalid(error):
case let .inValid(error):
return error
case .valid:
default:
return nil
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -25,20 +25,17 @@ import RxSwift
import RxCocoa
import LeadKit
/// Describes pin image
public enum PinImageType {
case entered
case clear
}
/// Pass code operation type
public enum PassCodeOperationType {
public enum PassCodeControllerType {
case create
case enter
case change
}
/// Pass code operation state
public enum PassCodeControllerState {
case enter
case repeatEnter
@ -46,21 +43,19 @@ public enum PassCodeControllerState {
case newEnter
}
/// Base view controller that operates with pass code
open class BasePassCodeViewController: UIViewController, LegacyConfigurableController {
open class BasePassCodeViewController: UIViewController {
public var viewModel: BasePassCodeViewModel! // swiftlint:disable:this implicitly_unwrapped_optional
public var viewModel: BasePassCodeViewModel!
// MARK: - IBOutlets
@IBOutlet private weak var titleLabel: UILabel?
@IBOutlet private weak var errorLabel: UILabel?
@IBOutlet private weak var dotStackView: UIStackView!
@IBOutlet public weak var titleLabel: UILabel?
@IBOutlet public weak var errorLabel: UILabel?
@IBOutlet public weak var dotStackView: UIStackView!
public let disposeBag = DisposeBag()
private var delayedErrorDescriptions: Disposable?
private lazy var fakeTextField: UITextField = {
fileprivate lazy var fakeTextField: UITextField = {
let fakeTextField = UITextField()
fakeTextField.isSecureTextEntry = true
fakeTextField.keyboardType = .numberPad
@ -77,20 +72,9 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
initialLoadView()
initialDotNumberConfiguration()
enebleKeyboard()
configureBackgroundNotifications()
showBiometricsRequestIfNeeded()
}
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fakeTextField.becomeFirstResponder()
}
override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
fakeTextField.resignFirstResponder()
showTouchIdIfNeeded(with: touchIdHint)
}
// MARK: - Private functions
@ -100,17 +84,21 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
return
}
NotificationCenter.default.rx.notification(UIApplication.willResignActiveNotification)
NotificationCenter.default.rx.notification(.UIApplicationWillResignActive)
.subscribe(onNext: { [weak self] _ in
self?.resetUI()
})
.disposed(by: disposeBag)
.addDisposableTo(disposeBag)
}
private func enebleKeyboard() {
fakeTextField.becomeFirstResponder()
}
private func initialDotNumberConfiguration() {
dotStackView.arrangedSubviews.forEach { dotStackView.removeArrangedSubview($0) }
for _ in 0 ..< viewModel.passCodeConfiguration.passCodeLength {
for _ in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
let dotImageView = UIImageView()
dotImageView.translatesAutoresizingMaskIntoConstraints = false
dotImageView.widthAnchor.constraint(equalTo: dotImageView.heightAnchor, multiplier: 1)
@ -121,10 +109,10 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
resetDotsUI()
}
private func resetDotsUI() {
fileprivate func resetDotsUI() {
fakeTextField.text = nil
dotStackView.arrangedSubviews
.compactMap { $0 as? UIImageView }
.flatMap { $0 as? UIImageView }
.forEach { $0.image = self.imageFor(type: .clear) }
}
@ -137,11 +125,11 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
imageView.image = imageFor(type: state)
}
private func setStates(for passCodeText: String) {
fileprivate func setStates(for passCodeText: String) {
var statesArray: [PinImageType] = []
for characterIndex in 0..<viewModel.passCodeConfiguration.passCodeLength {
let state: PinImageType = Int(characterIndex) <= passCodeText.count - 1 ? .entered : .clear
for characterIndex in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
let state: PinImageType = Int(characterIndex) <= passCodeText.characters.count - 1 ? .entered : .clear
statesArray.append(state)
}
@ -150,110 +138,69 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
}
}
private func resetUI() {
fileprivate func showTouchIdIfNeeded(with description: String) {
guard viewModel.isTouchIdEnabled && viewModel.controllerType == .enter 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
/// Returns prompt that appears on touch id system alert
open var biometricsAuthorizationHint: String {
assertionFailure("You should override this \(#function)")
open var touchIdHint: String {
assertionFailure("You should override this var: touchIdHint")
return ""
}
/// Returns prompt that appears on touch id system alert
open var biometricsFallbackButtonTitle: String? {
assertionFailure("You should override this \(#function)")
return nil
}
/// Returns prompt that appears on touch id system alert
open var biometricsCancelButtonTitle: String? {
assertionFailure("You should override this \(#function)")
return nil
}
/// Override to point certain images
// override to change Images
open func imageFor(type: PinImageType) -> UIImage {
assertionFailure("You should override this method: imageFor(type: PinImageType)")
return UIImage()
}
/// Override to change error description
open func errorDescription(for error: PassCodeError) -> [PassCodeDelayedDescription] {
// override to change error text
open func errorDescription(for error: PassCodeError) -> String {
assertionFailure("You should override this method: errorDescription(for error: PassCodeError)")
return []
return ""
}
/// Override to change action title text
open func actionTitle(for passCodeControllerState: PassCodeControllerState) -> NSAttributedString {
// override to change action title text
open func actionTitle(for passCodeControllerState: PassCodeControllerState) -> String {
assertionFailure("You should override this method: actionTitle(for passCodeControllerState: PassCodeControllerState)")
return NSAttributedString(string: "")
return ""
}
// MARK: - Functions that you can override to customize your controller
// MARK: - Functions that can you can override to castomise your controller
/// Call to show error
open func showError(for error: PassCodeError) {
let descriptionsObservables = errorDescription(for: error)
.sorted { $0.delay < $1.delay }
.map { [weak self] delayedDescription in
Observable<Int>
.interval(.seconds(Int(delayedDescription.delay)),
scheduler: MainScheduler.instance)
.take(1)
.do(onNext: { _ in
self?.errorLabel?.attributedText = delayedDescription.description
})
}
delayedErrorDescriptions?.dispose()
errorLabel?.attributedText = nil
errorLabel?.text = errorDescription(for: error)
errorLabel?.isHidden = false
delayedErrorDescriptions = Observable
.merge(descriptionsObservables)
.subscribe()
}
/// Call to disappear error label
open func hideError() {
errorLabel?.isHidden = true
}
/// Override to change UI for state
// override to change UI for state
open func configureUI(for passCodeControllerState: PassCodeControllerState) {
resetDotsUI()
titleLabel?.attributedText = actionTitle(for: passCodeControllerState)
titleLabel?.text = actionTitle(for: passCodeControllerState)
}
// MARK: - Public functions
}
/// Make fakeTextField become first responder
public func enableInput() {
fakeTextField.becomeFirstResponder()
}
/// Make fakeTextField resign first responder
public func disableInput() {
fakeTextField.resignFirstResponder()
}
/// Show biometrics system UI if applicable
public func showBiometricsRequestIfNeeded() {
guard viewModel.isBiometricsEnabled && viewModel.operationType == .enter else {
return
}
viewModel.authenticateUsingBiometrics(with: biometricsAuthorizationHint,
fallback: biometricsFallbackButtonTitle,
cancel: biometricsCancelButtonTitle)
}
// MARK: - ConfigurableController
// 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()
@ -261,13 +208,10 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
self?.setStates(for: text ?? "")
self?.hideError()
})
.delay(.milliseconds(100)) // time to draw dots
.drive(onNext: { [weak self] text in
self?.viewModel.setPassCodeText(text)
})
.disposed(by: disposeBag)
.drive(viewModel.passCodeText)
.addDisposableTo(disposeBag)
viewModel.validationResultDriver
viewModel.validationResult
.drive(onNext: { [weak self] validationResult in
guard let validationResult = validationResult else {
return
@ -275,29 +219,28 @@ open class BasePassCodeViewController: UIViewController, LegacyConfigurableContr
if validationResult.isValid {
self?.hideError()
} else if let passCodeError = validationResult.error {
self?.showError(for: passCodeError)
} else if let pasCodeError = validationResult.error {
self?.showError(for: pasCodeError)
}
})
.disposed(by: disposeBag)
.addDisposableTo(disposeBag)
viewModel.passCodeControllerStateDriver
viewModel.passCodeControllerState
.drive(onNext: { [weak self] controllerState in
self?.configureUI(for: controllerState)
})
.disposed(by: disposeBag)
.addDisposableTo(disposeBag)
}
open func addViews() {}
open func configureAppearance() {}
open func setAppearance() {}
open func configureBarButtons() {}
open func localize() {}
}
// MARK: - UITextFieldDelegate
}
extension BasePassCodeViewController: UITextFieldDelegate {
@ -308,4 +251,5 @@ extension BasePassCodeViewController: UITextFieldDelegate {
let invalid = CharacterSet(charactersIn: "0123456789").inverted
return string.rangeOfCharacter(from: invalid, options: [], range: string.startIndex..<string.endIndex) == nil
}
}

View File

@ -0,0 +1,182 @@
//
// 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)
}()
public 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
guard let sSelf = self else {
return
}
if sSelf.passCodeHolder.type == .change {
if validationResult?.isValid ?? false,
sSelf.passCodeHolder.enterStep == .repeatEnter,
let passCode = validationResult?.passCode {
sSelf.authSucceed(.passCode(passCode))
} else {
sSelf.passCodeControllerStateHolder.value = sSelf.passCodeHolder.enterStep
}
} else {
if validationResult?.isValid ?? false, let passCode = validationResult?.passCode {
sSelf.authSucceed(.passCode(passCode))
} else {
sSelf.passCodeControllerStateHolder.value = sSelf.passCodeHolder.enterStep
}
}
})
.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 {
passCodeControllerStateHolder.value = passCodeHolder.enterStep
}
}
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 || (passCodeHolder.type == .change && passCodeHolder.enterStep == .newEnter) {
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
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,26 +20,32 @@
// THE SOFTWARE.
//
/// Describes error, which may occur during pass code entering
public enum PassCodeError: Error {
/// Different codes
case codesNotMatch
import Foundation
/// Value is remaining attemps
case wrongCode(attemptsRemaining: Int)
public enum ApiError: Error {
case error(code: Int, message: String)
case none
/// Attempts limit reached (for create, change or enter)
case tooManyAttempts(type: PassCodeOperationType)
}
public extension PassCodeError {
var isTooManyAttempts: Bool {
switch self {
case .tooManyAttempts:
return true
extension ApiError: LocalizedError {
default:
return false
public init(apiResponse: ApiResponseProtocol) {
if apiResponse.errorCode != 0, let message = apiResponse.errorMessage {
self = ApiError.error(code: apiResponse.errorCode, message: message)
} else {
self = ApiError.none
}
}
public var errorDescription: String? {
switch self {
case .error(_, let message):
return message
case .none:
return nil
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -20,16 +20,18 @@
// THE SOFTWARE.
//
import Foundation
public protocol ApiErrorProtocol: RawRepresentable {}
/// Describes attributed string and time interval that message should be displayed after
public struct PassCodeDelayedDescription {
extension Error {
let delay: TimeInterval
let description: NSAttributedString
public init(description: NSAttributedString, delay: TimeInterval = 0) {
self.description = description
self.delay = delay
public func isApiError<T: ApiErrorProtocol>(_ apiErrorType: T) -> Bool where T.RawValue == Int {
if let error = self as? ApiError,
case let .error(code: code, message: _) = error,
code == apiErrorType.rawValue {
return true
} else {
return false
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -22,12 +22,8 @@
import Foundation
/// Prototol with two fields that describes result of online validation.
public protocol OnlineValidateable {
public enum ConnectionError: LocalizedError {
/// Contains true if online validation did passed.
var isValid: Bool { get }
case noConnection
/// May contain an error if validation did failed.
var error: Error? { get }
}

View File

@ -0,0 +1,92 @@
//
// 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 Alamofire
import CocoaLumberjack
public typealias VoidBlock = () -> Void
public extension Observable {
public func handleConnectionErrors() -> Observable<Observable.E> {
return observeOn(CurrentThreadScheduler.instance)
// handle no internet connection
.do(onError: { error in
if let urlError = error as? URLError,
urlError.code == .notConnectedToInternet ||
urlError.code == .timedOut {
DDLogError("Error: No Connection")
throw ConnectionError.noConnection
}
})
// handle unacceptable http status code like "500 Internal Server Error" and others
.do(onError: { error in
if let afError = error as? AFError,
case let .responseValidationFailed(reason: reason) = afError,
case let .unacceptableStatusCode(code: statusCode) = reason {
DDLogError("Error: Unacceptable HTTP Status Code - \(statusCode)")
throw ConnectionError.noConnection
}
})
}
public func retryWithinErrors(_ errorTypes: [Error.Type] = [ConnectionError.self],
retryLimit: Int = DefaultNetworkService.retryLimit)
-> Observable<Observable.E> {
return observeOn(CurrentThreadScheduler.instance)
.retryWhen { errors -> Observable<Observable.E> in
return errors.flatMapWithIndex { e, a -> Observable<Observable.E> in
let canRetry = errorTypes.contains { type(of: e) == $0 }
return (canRetry && a < retryLimit - 1) ? self : .error(e)
}
}
}
public func handleApiError<T: ApiErrorProtocol>(_ apiErrorType: T,
handler: @escaping () -> Void) -> Observable<Observable.E>
where T.RawValue == Int {
return observeOn(CurrentThreadScheduler.instance)
.do(onError: { error in
if error.isApiError(apiErrorType) {
handler()
}
})
}
public func changeLoadingBehaviour(isLoading: PublishSubject<Bool>) -> Observable<Observable.E> {
return observeOn(CurrentThreadScheduler.instance)
.do(onNext: { _ in
isLoading.onNext(false)
}, onError: { _ in
isLoading.onNext(false)
}, onSubscribe: { _ in
isLoading.onNext(true)
})
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -22,12 +22,12 @@
import UIKit
public extension UIBarButtonItem {
extension UIBarButtonItem {
/// Creates activity indicator view and bar button item (based on activity indicator)
static var activityIndicator: (barButton: UIBarButtonItem, activityIndicator: UIActivityIndicatorView) {
let indicatorView = UIActivityIndicatorView(style: .white)
public static var activityIndicator: (barButton: UIBarButtonItem, activityIndicator: UIActivityIndicatorView) {
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .white)
let indicatorBar = UIBarButtonItem(customView: indicatorView)
return (indicatorBar, indicatorView)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -22,30 +22,29 @@
import Foundation
private enum Keys {
fileprivate enum Keys {
static let sessionId = "sessionId"
static let userLogin = "userLogin"
}
public extension UserDefaults {
/// Default place to store session id
var sessionId: String? {
public var sessionId: String? {
get {
string(forKey: Keys.sessionId)
return string(forKey: Keys.sessionId)
}
set {
set(newValue, forKey: Keys.sessionId)
}
}
/// Default place to store userLogin
var userLogin: String? {
public var userLogin: String? {
get {
string(forKey: Keys.userLogin)
return string(forKey: Keys.userLogin)
}
set {
set(newValue, forKey: Keys.userLogin)
}
}
}

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>0.0.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -21,25 +21,13 @@
//
import KeychainAccess
import CryptoSwift
import CocoaLumberjack
import IDZSwiftCommonCrypto
private enum Keys {
static let passCodeHash = "passCodeHashKey"
static let isBiometricsEnabled = "isBiometricsEnabledKey"
static let isInitialLoad = "isInitialLoadKey"
}
private enum Values {
static let biometricsEnabled = "biometricsEnabled"
static let initialLoad = "initialLoad"
}
/// Represents base pass code service which encapsulates pass code storing
open class BasePassCodeService {
/// Override to set specific keychain service name
open class var keychainServiceString: String {
Bundle.main.bundleIdentifier ?? ""
open class var keychainService: String {
return Bundle.main.bundleIdentifier ?? ""
}
public init() {
@ -52,47 +40,69 @@ open class BasePassCodeService {
// MARK: - Private stuff
private lazy var keychain = Keychain(service: BasePassCodeService.keychainServiceString).synchronizable(false)
fileprivate lazy var keychain: Keychain = {
return Keychain(service: keychainService)
.synchronizable(false)
}()
private var passCodeHash: String? {
keychain[Keys.passCodeHash]
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"
static let initialLoad = "initialLoad"
}
}
public extension BasePassCodeService {
extension BasePassCodeService {
/// Indicates is pass code already saved on this device
var isPassCodeSaved: Bool {
keychain[Keys.passCodeHash] != nil
public var isPassCodeSaved: Bool {
return keychain[Keys.passCodeHash] != nil
}
/// Possibility to authenticate via biometrics. TouchID or FaceID
var isBiometricsAuthorizationEnabled: Bool {
public var isTouchIdEnabled: Bool {
get {
keychain[Keys.isBiometricsEnabled] == Values.biometricsEnabled
return keychain[Keys.isTouchIdEnabled] == Values.touchIdEnabled
}
set {
keychain[Keys.isBiometricsEnabled] = newValue ? Values.biometricsEnabled : nil
keychain[Keys.isTouchIdEnabled] = newValue ? Values.touchIdEnabled : nil
}
}
/// Saves new pass code
func save(passCode: String?) {
public func save(passCode: String?) {
if let passCode = passCode {
keychain[Keys.passCodeHash] = passCode.sha256()
keychain[Keys.passCodeHash] = sha256(passCode)
} else {
keychain[Keys.passCodeHash] = nil
}
}
/// Check if pass code is correct
func check(passCode: String) -> Bool {
passCode.sha256() == passCodeHash
public func check(passCode: String) -> Bool {
return sha256(passCode) == passCodeHash
}
/// Reset pass code settings
func reset() {
public func reset() {
save(passCode: nil)
isBiometricsAuthorizationEnabled = false
isTouchIdEnabled = false
}
}
private extension BasePassCodeService {
func sha256(_ str: String) -> String? {
guard let digests = Digest(algorithm: .sha256).update(string: str)?.final() else {
return nil
}
return hexString(fromArray: digests)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Touch Instinct
// 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
@ -21,15 +21,14 @@
//
import RxSwift
import LeadKit
/// Represents service that store basic user information
open class BaseUserService {
public init() {
// Can be overrided
}
/// Returns user login
open var userLogin: String {
guard let defaultsLogin = UserDefaults.standard.userLogin else {
assertionFailure("userLogin is nil. Use isLoggedIn before read userLogin")
@ -39,7 +38,6 @@ open class BaseUserService {
return defaultsLogin
}
/// Returns session id
open var sessionId: String {
guard let defaultsSessionId = UserDefaults.standard.sessionId else {
assertionFailure("sessionId is nil. Use isLoggedIn before read sessionId")
@ -48,14 +46,13 @@ open class BaseUserService {
return defaultsSessionId
}
/// Indicates if user is logged in
open var isLoggedIn: Bool {
UserDefaults.standard.sessionId != nil
return UserDefaults.standard.sessionId != nil
}
/// Reset user information
open class func clearData() {
UserDefaults.standard.sessionId = nil
UserDefaults.standard.userLogin = nil
}
}

View File

@ -0,0 +1,59 @@
//
// 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
open class ApiNetworkService: DefaultNetworkService {
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)
}
}
}
}

View File

@ -0,0 +1,67 @@
//
// 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 Alamofire
import LeadKit
import ObjectMapper
import RxSwift
open class DefaultNetworkService: NetworkService {
static let retryLimit = 3
open class var baseUrl: String {
fatalError("You should override this var: baseUrl")
}
open class var defaultTimeoutInterval: TimeInterval {
return 20.0
}
public override init(sessionManager: SessionManager) {
super.init(sessionManager: sessionManager)
bindActivityIndicator()
}
// MARK: - Default Values
open class var serverTrustPolicies: [String: ServerTrustPolicy] {
return [
baseUrl: .disableEvaluation
]
}
open class var configuration: URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = defaultTimeoutInterval
return configuration
}
open class var sessionManager: SessionManager {
let sessionManager = SessionManager(configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
return sessionManager
}
}

View File

@ -0,0 +1,47 @@
//
// 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 init() {}
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)
}
}
}

15
LeadKitAdditions/Podfile Normal file
View File

@ -0,0 +1,15 @@
source "https://github.com/CocoaPods/Specs.git"
source "https://github.com/TouchInstinct/Podspecs.git"
platform :ios, '9.0'
target 'LeadKitAdditions' do
use_frameworks!
pod 'LeadKit', :git => 'https://github.com/TouchInstinct/LeadKit.git', :branch => 'fix/sharedApplication', :commit => 'fd0eb18b8a6680ff16bbb1668d1ae0d29f29fad7'
pod 'KeychainAccess'
pod 'IDZSwiftCommonCrypto'
end
# If you have slow HDD
ENV['COCOAPODS_DISABLE_STATS'] = "true"

View File

@ -0,0 +1,56 @@
PODS:
- Alamofire (4.4.0)
- CocoaLumberjack/Default (3.1.0)
- CocoaLumberjack/Swift (3.1.0):
- CocoaLumberjack/Default
- IDZSwiftCommonCrypto (0.9.1)
- KeychainAccess (3.0.2)
- LeadKit (0.4.6):
- CocoaLumberjack/Swift (~> 3.1.0)
- ObjectMapper (~> 2.1)
- RxAlamofire (= 3.0.0)
- RxCocoa (= 3.2.0)
- RxSwift (= 3.2.0)
- Toast-Swift (~> 2.0.0)
- ObjectMapper (2.2.5)
- RxAlamofire (3.0.0):
- RxAlamofire/Core (= 3.0.0)
- RxAlamofire/Core (3.0.0):
- Alamofire (~> 4.0)
- RxSwift (~> 3.0)
- RxCocoa (3.2.0):
- RxSwift (~> 3.1)
- RxSwift (3.2.0)
- Toast-Swift (2.0.0)
DEPENDENCIES:
- IDZSwiftCommonCrypto
- KeychainAccess
- LeadKit (from `https://github.com/TouchInstinct/LeadKit.git`, commit `fd0eb18b8a6680ff16bbb1668d1ae0d29f29fad7`, branch `fix/sharedApplication`)
EXTERNAL SOURCES:
LeadKit:
:branch: fix/sharedApplication
:commit: fd0eb18b8a6680ff16bbb1668d1ae0d29f29fad7
:git: https://github.com/TouchInstinct/LeadKit.git
CHECKOUT OPTIONS:
LeadKit:
:commit: fd0eb18b8a6680ff16bbb1668d1ae0d29f29fad7
:git: https://github.com/TouchInstinct/LeadKit.git
SPEC CHECKSUMS:
Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d
CocoaLumberjack: 8311463ddf9ee86a06ef92a071dd656c89244500
IDZSwiftCommonCrypto: c44fe5c0219a219846b56b4c148615dd06e58591
KeychainAccess: a986406022dfc7c634c691ad3bec670cc6a32002
LeadKit: d688a8bef79de7bbd83d553da3cb6c5292d48f2d
ObjectMapper: fb30f71e08470d1e5a20b199fafe1246281db898
RxAlamofire: 0b1fa48f545fffe7f7a28af2086bcaa3b5946cc9
RxCocoa: ccdf43101a70407097a29082f648ba1676075b30
RxSwift: 46574f70d416b7923c237195939cc488a7fbf3a0
Toast-Swift: 5b2f8f720f7e78e48511f693df1f9c9a6e38a25a
PODFILE CHECKSUM: 8e8ba1566ac9d3fe5b93325ab2faa9c76da42cd5
COCOAPODS: 1.2.1

22
Podfile
View File

@ -1,22 +0,0 @@
source 'https://cdn.cocoapods.org/'
source "https://github.com/TouchInstinct/Podspecs.git"
abstract_target 'LeadKitAdditions' do
pod "KeychainAccess", '~> 4.2.0'
pod "CryptoSwift", "~> 1.4.0"
pod "SwiftValidator", '4.0.2'
pod "SwiftLint", '~> 0.45.0'
pod "PinLayout", '~> 1.6'
inhibit_all_warnings!
use_frameworks!
target 'LeadKitAdditions iOS' do
platform :ios, '10.0'
pod 'LeadKit', '~> 1.7.0'
end
end
# If you have slow HDD
ENV['COCOAPODS_DISABLE_STATS'] = "true"

View File

@ -1,81 +0,0 @@
PODS:
- Alamofire (5.5.0)
- CryptoSwift (1.4.2)
- KeychainAccess (4.2.2)
- LeadKit (1.7.0):
- LeadKit/Core (= 1.7.0)
- LeadKit/Core (1.7.0):
- RxAlamofire (~> 6.1)
- RxCocoa (~> 6.2)
- RxSwift (~> 6.2)
- SnapKit (~> 5.0.1)
- SwiftDate (~> 6)
- TableKit (~> 2.11)
- UIScrollView-InfiniteScroll (~> 1.1.0)
- PinLayout (1.8.6)
- RxAlamofire (6.1.1):
- RxAlamofire/Core (= 6.1.1)
- RxAlamofire/Core (6.1.1):
- Alamofire (~> 5.4)
- RxSwift (~> 6.0)
- RxCocoa (6.2.0):
- RxRelay (= 6.2.0)
- RxSwift (= 6.2.0)
- RxRelay (6.2.0):
- RxSwift (= 6.2.0)
- RxSwift (6.2.0)
- SnapKit (5.0.1)
- SwiftDate (6.3.1)
- SwiftLint (0.45.1)
- SwiftValidator (4.0.2)
- TableKit (2.11.0)
- UIScrollView-InfiniteScroll (1.1.0)
DEPENDENCIES:
- CryptoSwift (~> 1.4.0)
- KeychainAccess (~> 4.2.0)
- LeadKit (~> 1.7.0)
- PinLayout (~> 1.6)
- SwiftLint (~> 0.45.0)
- SwiftValidator (= 4.0.2)
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- PinLayout
https://github.com/TouchInstinct/Podspecs.git:
- LeadKit
- SwiftValidator
trunk:
- Alamofire
- CryptoSwift
- KeychainAccess
- RxAlamofire
- RxCocoa
- RxRelay
- RxSwift
- SnapKit
- SwiftDate
- SwiftLint
- TableKit
- UIScrollView-InfiniteScroll
SPEC CHECKSUMS:
Alamofire: 1c4fb5369c3fe93d2857c780d8bbe09f06f97e7c
CryptoSwift: a532e74ed010f8c95f611d00b8bbae42e9fe7c17
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
LeadKit: e5765a6edac2813042304e940fd7a43b37be80ca
PinLayout: fe2a2432d6982588e208572005c941aeeae417ab
RxAlamofire: beb75a1c452d0de225651db4903f5d29d034a620
RxCocoa: 4baf94bb35f2c0ab31bc0cb9f1900155f646ba42
RxRelay: e72dbfd157807478401ef1982e1c61c945c94b2f
RxSwift: d356ab7bee873611322f134c5f9ef379fa183d8f
SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
SwiftLint: 06ac37e4d38c7068e0935bb30cda95f093bec761
SwiftValidator: 8517a67bceebd7286cb487da259db7dd44c5fc74
TableKit: 8a1a267d841a2da638ea09bdb0db33aedb459fa8
UIScrollView-InfiniteScroll: 3ef456bcbe759c19f510a383cff96e6647c98c98
PODFILE CHECKSUM: 1d0f91b670998d56b8cf9bc24d5b265a89e8409b
COCOAPODS: 1.10.1

View File

@ -1 +0,0 @@
# LeadKitAdditions

View File

@ -1,164 +0,0 @@
//
// Copyright (c) 2018 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
import SwiftValidator
/// Base implementation of TextFieldViewEvents.
open class BaseTextFieldViewEvents: TextFieldViewEvents {
public let textChangedDriver: Driver<String?>
/// Memberwise initializer.
///
/// - Parameter textChangedDriver: Driver that emits text changes from a view.
public init(textChangedDriver: Driver<String?>) {
self.textChangedDriver = textChangedDriver
}
}
/// Base implementation of text field view model events.
open class BaseTextFieldViewModelEvents: TextFieldViewModelEvents {
public let setTextDriver: Driver<String?>
public let changeValidationStateDriver: Driver<ValidationItemState>
public let changeOnlineValidationStateDriver: Driver<OnlineValidationState>
/// Memberwise initializer.
///
/// - Parameters:
/// - setTextDriver: Driver that emit text that should be set inside a view.
/// - changeValidationStateDriver: Driver that emit validation state changes events.
/// - changeOnlineValidationStateDriver: Driver that emit online validation state changes events.
public init(setTextDriver: Driver<String?>,
changeValidationStateDriver: Driver<ValidationItemState>,
changeOnlineValidationStateDriver: Driver<OnlineValidationState>) {
self.setTextDriver = setTextDriver
self.changeValidationStateDriver = changeValidationStateDriver
self.changeOnlineValidationStateDriver = changeOnlineValidationStateDriver
}
}
public extension BaseTextFieldViewModelEvents {
/// Method that binds text driver to validation service via validation rules.
///
/// - Parameters:
/// - textDriver: Driver that emits text changes.
/// - rules: Rules to validate for.
/// - validationService: Validation service to register in.
/// - Returns: Driver that emit validation state changes.
static func offlineValidationDriver(with textDriver: Driver<String?>,
using rules: [Rule] = [RequiredRule()],
in validationService: ValidationService) -> Driver<ValidationItemState> {
let validationItem = ValidationItem(rules: rules, textDriver: textDriver)
validationService.register(item: validationItem)
let validationStateDriver = validationItem
.validationStateObservable
.asDriver(onErrorJustReturn: .initial)
return validationStateDriver
}
typealias OnlineValidationClosure = (String) -> Single<OnlineValidateable>
/// Method that binds text driver to validation chain (offline validation -> online validation)
/// and returns online validation state driver.
///
/// - Parameters:
/// - textDriver: Driver that emits text changes.
/// - offlineRules: Rules to validate before online validation will be requested.
/// - validationClosure: Closure that will be called for online validation request.
/// - Returns: Driver that emit online validation state changes.
static func onlineValidationDriver(with textDriver: Driver<String?>,
using offlineRules: [Rule] = [],
validationClosure: @escaping OnlineValidationClosure) -> Driver<OnlineValidationState> {
textDriver.flatMap { string -> Driver<OnlineValidationState> in
guard let nonEmptyString = string, !nonEmptyString.isEmpty else {
return .just(.initial)
}
let passedRules = offlineRules
.allSatisfy { $0.validate(nonEmptyString) }
guard passedRules else {
return .just(.initial)
}
let validationDriver = validationClosure(nonEmptyString).map { result -> OnlineValidationState in
if result.isValid {
return .valid
} else {
return .invalid(error: result.error)
}
}
.asDriver(onErrorJustReturn: .initial)
return Driver.merge([.just(.initial), .just(.processing), validationDriver])
}
}
/// Convenience initializer with offline and online validation.
///
/// - Parameters:
/// - binding: Data model field binding.
/// - rules: Rules to validate before online validation will be requested.
/// - validationService: Validation service to register in.
/// - onlineValidationClosure: Closure that will be called for online validation request.
/// - onlineValidationThrottle: Throttling duration for each text change.
convenience init<T>(binding: DataModelFieldBinding<T>,
rules: [Rule] = [RequiredRule()],
validationService: ValidationService,
onlineValidationClosure: OnlineValidationClosure? = nil,
onlineValidationThrottle: RxTimeInterval = .milliseconds(500)) {
let dataModelFieldDriver = binding.fieldDriver
let offlineValidationDriver = BaseTextFieldViewModelEvents.offlineValidationDriver(with: dataModelFieldDriver,
using: rules,
in: validationService)
let onlineValidationDriver: Driver<OnlineValidationState>
if let onlineValidationClosure = onlineValidationClosure {
let throttledTextDriver = dataModelFieldDriver.throttle(onlineValidationThrottle)
onlineValidationDriver = BaseTextFieldViewModelEvents
.onlineValidationDriver(with: throttledTextDriver,
using: rules,
validationClosure: onlineValidationClosure)
} else {
onlineValidationDriver = .just(.initial)
}
self.init(setTextDriver: dataModelFieldDriver,
changeValidationStateDriver: offlineValidationDriver,
changeOnlineValidationStateDriver: onlineValidationDriver)
}
}

View File

@ -1,235 +0,0 @@
//
// Copyright (c) 2018 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
/// Describes types of authentication
public enum PassCodeAuthType {
case passCode(String)
case touchId
}
/// Base view model for passCodeViewController
open class BasePassCodeViewModel {
public let operationType: PassCodeOperationType
public let disposeBag = DisposeBag()
/// Service that can answer if user is authorized by biometrics
public let biometricsService = BiometricsService()
/// Contains configuration for pass code operations
public let passCodeConfiguration: PassCodeConfiguration
private let validationResultHolder = BehaviorRelay<PassCodeValidationResult?>(value: nil)
public var validationResultDriver: Driver<PassCodeValidationResult?> {
validationResultHolder.asDriver()
}
private let passCodeControllerStateVariable = BehaviorRelay<PassCodeControllerState>(value: .enter)
public var passCodeControllerStateDriver: Driver<PassCodeControllerState> {
passCodeControllerStateVariable.asDriver()
}
private let passCodeText = BehaviorRelay<String?>(value: nil)
private var attemptsNumber = 0
private lazy var passCodeHolder: PassCodeHolderProtocol = PassCodeHolderBuilder.build(with: self.operationType)
public init(operationType: PassCodeOperationType, passCodeConfiguration: PassCodeConfiguration) {
self.operationType = operationType
self.passCodeConfiguration = passCodeConfiguration
bindViewModel()
}
private func bindViewModel() {
passCodeText.asDriver()
.distinctUntilChanged { $0 == $1 }
.drive(onNext: { [weak self] passCode in
if let passCode = passCode,
passCode.count == Int(self?.passCodeConfiguration.passCodeLength ?? 0) {
self?.set(passCode: passCode)
}
})
.disposed(by: disposeBag)
validationResultHolder.asObservable()
.bind(to: validationResultBinder)
.disposed(by: disposeBag)
}
// MARK: - Public
public var passCodeTextValue: String? {
passCodeText.value
}
public func setPassCodeText(_ value: String?) {
passCodeText.accept(value)
}
public func reset() {
passCodeText.accept(nil)
validationResultHolder.accept(nil)
passCodeControllerStateVariable.accept(operationType == .change ? .oldEnter : .enter)
attemptsNumber = 0
passCodeHolder.reset()
}
public func authenticateUsingBiometrics(with description: String, fallback: String?, cancel: String?) {
biometricsAuthBegins()
biometricsService.authenticateWithBiometrics(with: description,
fallback: fallback,
cancel: cancel) { [weak self] success, error in
self?.biometricsAuthEnds()
if success {
self?.authSucceed(.touchId)
} else {
self?.authFailed(with: error)
}
}
}
// MARK: - HAVE TO OVERRIDE
/// Override to check if entered pass code is equal to stored
open func isEnteredPassCodeValid(_ passCode: String) -> Bool {
assertionFailure("You should override this method: isEnteredPassCodeValid(_ passCode: String)")
return false
}
/// Method is called after successful authentication
open func authSucceed(_ type: PassCodeAuthType) {
assertionFailure("You should override this method: authSucceed(_ type: PassCodeAuthType)")
}
/// Called when authentication failed
open func authFailed(with: Error?) {
assertionFailure("You should override this method: authFailed(with: Error)")
}
// MARK: - Biometrics
/// Posibility to use biometrics for authentication
open var isBiometricsEnabled: Bool {
false
}
/// Notify about activation for biometrics. Remember to save user choice
open func activateBiometricsForUser() {
assertionFailure("You should override this method: activateBiometricsForUser()")
}
/// Notify before system alert with biometrics
open func biometricsAuthBegins() {}
/// Notify after system alert with biometrics
open func biometricsAuthEnds() {}
}
private extension BasePassCodeViewModel {
var validationResultBinder: Binder<PassCodeValidationResult?> {
Binder(self) { model, validationResult in
let isValid = validationResult?.isValid ?? false
let passCode = validationResult?.passCode
if model.passCodeHolder.type == .change {
if isValid, model.passCodeHolder.enterStep == .repeatEnter, let passCode = passCode {
model.authSucceed(.passCode(passCode))
} else {
model.passCodeControllerStateVariable.accept(model.passCodeHolder.enterStep)
}
} else {
if isValid, let passCode = passCode {
model.authSucceed(.passCode(passCode))
} else {
model.passCodeControllerStateVariable.accept(model.passCodeHolder.enterStep)
}
}
}
}
}
extension BasePassCodeViewModel {
private func set(passCode: String) {
passCodeHolder.add(passCode: passCode)
validateIfNeeded()
if shouldUpdateControllerState {
passCodeControllerStateVariable.accept(passCodeHolder.enterStep)
}
}
private var shouldUpdateControllerState: Bool {
!passCodeHolder.shouldValidate ||
!(validationResultHolder.value?.isValid ?? true) ||
validationResultHolder.value?.error?.isTooManyAttempts ?? false
}
private func validateIfNeeded() {
guard passCodeHolder.shouldValidate else {
return
}
switch passCodeHolder.type {
case .create where passCodeHolder.enterStep == .repeatEnter:
attemptsNumber += 1
case .change where passCodeHolder.enterStep == .repeatEnter:
attemptsNumber += 1
case .enter:
attemptsNumber += 1
default:
break
}
var validationResult = passCodeHolder.validate()
// if entered (in .enter mode) code is invalid -> .wrongCode
if passCodeHolder.type == .enter,
let passCode = validationResult.passCode,
!isEnteredPassCodeValid(passCode) {
let remainingAttemptsCount = passCodeConfiguration.maxAttemptsNumber - attemptsNumber
validationResult = .invalid(.wrongCode(attemptsRemaining: remainingAttemptsCount))
}
// if entered code (in any mode) is mismatched too many times -> .tooManyAttempts
if (!validationResult.isValid && attemptsNumber == passCodeConfiguration.maxAttemptsNumber) ||
attemptsNumber > passCodeConfiguration.maxAttemptsNumber {
validationResult = .invalid(.tooManyAttempts(type: operationType))
}
if !validationResult.isValid {
passCodeHolder.reset()
}
validationResultHolder.accept(validationResult)
}
}

View File

@ -1,20 +0,0 @@
import Foundation
import Alamofire
/// Extension for network Error classification
public extension Error {
/// Returns: true if error is connection error
var isConnectionError: Bool {
guard let urlError = self as? URLError else {
return false
}
return urlError.code == .notConnectedToInternet || urlError.code == .timedOut
}
/// Returns: true if server json response is not valid
var isResponseSerializationError: Bool {
(self as? AFError)?.isResponseSerializationError ?? false
}
}

View File

@ -1,24 +0,0 @@
<?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>0.3.13</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -1,51 +0,0 @@
//
// Copyright (c) 2018 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
public protocol LegacyConfigurableController: InitializableView {
associatedtype ViewModelT
var viewModel: ViewModelT! { get } // swiftlint:disable:this implicitly_unwrapped_optional
func configureBarButtons()
func initialLoadView()
}
public extension LegacyConfigurableController where Self: UIViewController {
func initializeView() {
assertionFailure("Use \(String(describing: initialLoadView)) for UIViewController instead!")
}
/// Method that should be called in viewDidLoad method of UIViewController.
func initialLoadView() {
addViews()
configureLayout()
configureAppearance()
configureBarButtons()
localize()
bindViews()
}
}

View File

@ -1,122 +0,0 @@
//
// Copyright (c) 2018 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 BiometricsAuthHandler = (Bool, Error?) -> Void
/// Service that provide access to authentication via biometric
public final class BiometricsService {
public enum BiometryType {
case faceID
case touchID
case none
}
private lazy var laContext = LAContext()
/// If the user unlocks the device using biometrics within the specified time interval,
/// then authentication for the receiver succeeds automatically, without prompting the user for biometrics.
/// Works only after device unlock event, no other apps authentications counts.
public var allowableReuseDuration: TimeInterval? {
didSet {
guard let duration = allowableReuseDuration else {
return
}
if #available(iOS 9.0, *) {
laContext.touchIDAuthenticationAllowableReuseDuration = duration
}
}
}
/// Returns BiometryType supporting by device: TouchID, FaceID or none
public var biometryType: BiometryType {
let canEvaluate = canAuthenticateWithBiometrics
if #available(iOS 11.0, *) {
switch laContext.biometryType {
case .touchID:
return .touchID
case .faceID:
return .faceID
case .none:
return .none
@unknown default:
return .none
}
}
return canEvaluate ? .touchID : .none
}
/// Returns current domain state
public var evaluatedPolicyDomainState: Data? {
// We need to call canEvaluatePolicy function for evaluatedPolicyDomainState to be updated
_ = canAuthenticateWithBiometrics
return laContext.evaluatedPolicyDomainState
}
/// Indicates is it possible to authenticate on this device via any biometric.
/// Returns false if not enrolled or lockedout.
public var canAuthenticateWithBiometrics: Bool {
laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
/**
Initiates system biometrics authentication process.
Once evaluated, will return success until the context is deallocated.
Call "clear" to use a new context instance.
- parameters:
- description: prompt on the system alert
- fallback: alternative action button title on system alert
- cancel: cancel button title on the system alert
- authHandler: callback, with parameter, indicates if user authenticate successfuly
*/
public func authenticateWithBiometrics(with description: String,
fallback fallbackTitle: String?,
cancel cancelTitle: String?,
authHandler: @escaping BiometricsAuthHandler) {
if #available(iOS 10.0, *) {
laContext.localizedCancelTitle = cancelTitle
}
laContext.localizedFallbackTitle = fallbackTitle
laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: description) { success, error in
authHandler(success, error)
}
}
/// Replace old instance of the context with the new one
public func clear() {
laContext = LAContext()
}
public init() {}
}

View File

@ -1,14 +0,0 @@
import SwiftValidator
public struct ValidationError: Error {
public let failedRule: Rule
public let errorMessage: String?
public let errorHint: String?
public init(failedRule: Rule, errorMessage: String?, errorHint: String? = nil) {
self.failedRule = failedRule
self.errorMessage = errorMessage
self.errorHint = errorHint
}
}

View File

@ -1,102 +0,0 @@
import SwiftValidator
import RxSwift
import RxCocoa
public enum ValidationItemState {
case initial
case correction(ValidationError)
case error(ValidationError)
case valid
}
public extension ValidationItemState {
var isInitial: Bool {
switch self {
case .initial:
return true
default:
return false
}
}
var isValid: Bool {
switch self {
case .valid:
return true
default:
return false
}
}
}
public final class ValidationItem {
private let disposeBag = DisposeBag()
private let validationStateHolder = BehaviorRelay<ValidationItemState>(value: .initial)
public var validationState: ValidationItemState {
validationStateHolder.value
}
public var validationStateObservable: Observable<ValidationItemState> {
validationStateHolder.asObservable()
}
private let text = BehaviorRelay<String?>(value: nil)
private(set) var rules: [Rule] = []
public init(rules: [Rule], textDriver: Driver<String?>) {
self.rules = rules
bindText(textDriver: textDriver)
}
private func bindText(textDriver: Driver<String?>) {
textDriver
.drive(text)
.disposed(by: disposeBag)
textDriver.asObservable()
.withLatestFrom(validationStateHolder.asObservable()) { (text: $0, validationState: $1) }
.filter { !$0.validationState.isInitial }
.map { $0.text }
.subscribe(onNext: { [weak self] text in
self?.validate(text: text)
})
.disposed(by: disposeBag)
}
public func manualValidate() -> Bool {
validate(text: text.value, isManual: true)
return validationStateHolder.value.isValid
}
private func validate(text: String?, isManual: Bool = false) {
let error = rules.filter {
!$0.validate(text ?? "")
}
.map { rule -> ValidationError in
ValidationError(failedRule: rule, errorMessage: rule.errorMessage())
}
.first
if let validationError = error {
switch validationStateHolder.value {
case .error where !isManual,
.correction where !isManual,
.valid where !isManual:
validationStateHolder.accept(.correction(validationError))
default:
validationStateHolder.accept(.error(validationError))
}
} else {
validationStateHolder.accept(.valid)
}
}
}

View File

@ -1,109 +0,0 @@
import SwiftValidator
import RxCocoa
import RxSwift
private enum ValidationServiceStateReactType {
case none
case all
case each
}
public enum ValidationServiceState {
case initial
case valid
case invalid
}
public extension ValidationServiceState {
var isValid: Bool {
self == .valid
}
}
public final class ValidationService {
private var disposeBag = DisposeBag()
private(set) var validationItems: [ValidationItem] = []
private let stateHolder = BehaviorRelay<ValidationServiceState>(value: .initial)
public var state: ValidationServiceState {
stateHolder.value
}
public var stateObservable: Observable<ValidationServiceState> {
stateHolder.asObservable()
}
private var validationStateReactType: ValidationServiceStateReactType = .none
public init() {
// just to be accessible
}
public func register(item: ValidationItem) {
register(items: [item])
}
public func register(items: [ValidationItem]) {
validationItems += items
bindItems()
}
public func unregisterAll() {
validationItems.removeAll()
bindItems()
}
public func unregister(item: ValidationItem) {
unregister(items: [item])
}
public func unregister(items: [ValidationItem]) {
items.forEach { item in
if let removeIndex = validationItems.firstIndex(where: { $0 === item }) {
validationItems.remove(at: removeIndex)
}
}
bindItems()
}
public func validate() -> Bool {
validationStateReactType = .all
let isValid = validationItems.allSatisfy { $0.manualValidate() }
validationStateReactType = .each
return isValid
}
private func bindItems() {
disposeBag = DisposeBag()
let allValidationStateObservables = validationItems.map { $0.validationStateObservable }
let zipStates = Observable
.zip(allValidationStateObservables) { $0 }
.filter { [weak self] _ in
self?.validationStateReactType == .all
}
let combineLatestStates = Observable
.combineLatest(allValidationStateObservables) { $0 }
.filter { [weak self] _ in
self?.validationStateReactType == .each
}
let stateObservables = [zipStates, combineLatestStates]
stateObservables.forEach { observable in
observable
.map { states -> Bool in
states.allSatisfy { $0.isValid }
}
.map { $0 ? ValidationServiceState.valid : .invalid }
.bind(to: stateHolder)
.disposed(by: disposeBag)
}
}
}

@ -1 +0,0 @@
Subproject commit 2799169e2103c034b0e1be221860a23292766f36

View File

@ -1,4 +0,0 @@
import LeadKit
import LeadKitAdditions
import PlaygroundSupport

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios' executeOnSourceChanges='false'>
<timeline fileName='timeline.xctimeline'/>
</playground>

View File

@ -1,14 +0,0 @@
#!/bin/sh
readonly CONFIG_PATH=${PROJECT_DIR}/build-scripts/xcode/.swiftlint.yml
readonly SWIFTLINT_VERSION=0.45.1
readonly SWIFTLINT_PORTABLE_FILENAME=portable_swiftlint.zip
readonly SWIFTLINT_PORTABLE_URL=https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/${SWIFTLINT_PORTABLE_FILENAME}
. build-scripts/xcode/aux_scripts/download_file.sh ${SWIFTLINT_PORTABLE_FILENAME} ${SWIFTLINT_PORTABLE_URL} Downloads --remove-cached
cd Downloads && unzip -o ${SWIFTLINT_PORTABLE_FILENAME}
${PROJECT_DIR}/Downloads/swiftlint autocorrect --path ${PROJECT_DIR}/Sources --config ${CONFIG_PATH} && ${PROJECT_DIR}/Downloads/swiftlint --path ${PROJECT_DIR}/Sources --config ${CONFIG_PATH}