PassCode controller added
This commit is contained in:
parent
f5ea2895cd
commit
fabd2c3039
|
|
@ -1,6 +1,6 @@
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = "LeadKitAdditions"
|
s.name = "LeadKitAdditions"
|
||||||
s.version = "0.0.3"
|
s.version = "0.0.4"
|
||||||
s.summary = "iOS framework with a bunch of tools for rapid development"
|
s.summary = "iOS framework with a bunch of tools for rapid development"
|
||||||
s.homepage = "https://github.com/NikAshanin/LeadKitAdditions"
|
s.homepage = "https://github.com/NikAshanin/LeadKitAdditions"
|
||||||
s.license = "Apache License, Version 2.0"
|
s.license = "Apache License, Version 2.0"
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,13 @@
|
||||||
EF05EDC81EAF91D500CAE7B6 /* BasePassCodeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDC71EAF91D500CAE7B6 /* BasePassCodeService.swift */; };
|
EF05EDC81EAF91D500CAE7B6 /* BasePassCodeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDC71EAF91D500CAE7B6 /* BasePassCodeService.swift */; };
|
||||||
EF05EDD21EAF9CF100CAE7B6 /* CommonCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = EF05EDD01EAF9CF100CAE7B6 /* CommonCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
EF05EDD21EAF9CF100CAE7B6 /* CommonCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = EF05EDD01EAF9CF100CAE7B6 /* CommonCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
EF05EDDA1EAF9D4A00CAE7B6 /* CommonCrypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF05EDCE1EAF9CF100CAE7B6 /* CommonCrypto.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
EF05EDDA1EAF9D4A00CAE7B6 /* CommonCrypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF05EDCE1EAF9CF100CAE7B6 /* CommonCrypto.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
|
EF05EDE11EAFA74200CAE7B6 /* BasePassCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE01EAFA74200CAE7B6 /* BasePassCodeViewController.swift */; };
|
||||||
|
EF05EDE31EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE21EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift */; };
|
||||||
|
EF05EDE51EAFA80D00CAE7B6 /* PassCodeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE41EAFA80D00CAE7B6 /* PassCodeConfiguration.swift */; };
|
||||||
|
EF05EDE71EAFA87300CAE7B6 /* PassCodeValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE61EAFA87300CAE7B6 /* PassCodeValidationResult.swift */; };
|
||||||
|
EF05EDE91EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDE81EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift */; };
|
||||||
|
EF05EDEB1EAFA8E600CAE7B6 /* PassCodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDEA1EAFA8E600CAE7B6 /* PassCodeError.swift */; };
|
||||||
|
EF05EDED1EAFA96D00CAE7B6 /* PassCodeHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05EDEC1EAFA96D00CAE7B6 /* PassCodeHolder.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
|
@ -46,6 +53,13 @@
|
||||||
EF05EDD61EAF9D2900CAE7B6 /* CommonCrypto.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = CommonCrypto.xcconfig; sourceTree = "<group>"; };
|
EF05EDD61EAF9D2900CAE7B6 /* CommonCrypto.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = CommonCrypto.xcconfig; sourceTree = "<group>"; };
|
||||||
EF05EDD71EAF9D2900CAE7B6 /* iphoneos.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = "<group>"; };
|
EF05EDD71EAF9D2900CAE7B6 /* iphoneos.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = "<group>"; };
|
||||||
EF05EDD81EAF9D2900CAE7B6 /* iphonesimulator.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = "<group>"; };
|
EF05EDD81EAF9D2900CAE7B6 /* iphonesimulator.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = "<group>"; };
|
||||||
|
EF05EDE01EAFA74200CAE7B6 /* BasePassCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeViewController.swift; sourceTree = "<group>"; };
|
||||||
|
EF05EDE21EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePassCodeViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
EF05EDE41EAFA80D00CAE7B6 /* PassCodeConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
EF05EDE61EAFA87300CAE7B6 /* PassCodeValidationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeValidationResult.swift; sourceTree = "<group>"; };
|
||||||
|
EF05EDE81EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeHolderProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
EF05EDEA1EAFA8E600CAE7B6 /* PassCodeError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeError.swift; sourceTree = "<group>"; };
|
||||||
|
EF05EDEC1EAFA96D00CAE7B6 /* PassCodeHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassCodeHolder.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
|
@ -99,6 +113,7 @@
|
||||||
CAE698E51E968820000394B0 /* LeadKitAdditions */ = {
|
CAE698E51E968820000394B0 /* LeadKitAdditions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EF05EDDB1EAFA6FA00CAE7B6 /* Controllers */,
|
||||||
CAE698EE1E968B72000394B0 /* Classes */,
|
CAE698EE1E968B72000394B0 /* Classes */,
|
||||||
CAE699011E9693DE000394B0 /* Enums */,
|
CAE699011E9693DE000394B0 /* Enums */,
|
||||||
CAE698F81E968F56000394B0 /* Extensions */,
|
CAE698F81E968F56000394B0 /* Extensions */,
|
||||||
|
|
@ -159,6 +174,52 @@
|
||||||
path = CommonCrypto;
|
path = CommonCrypto;
|
||||||
sourceTree = "<group>";
|
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>";
|
||||||
|
};
|
||||||
F8A65FEC7C0EB4B93746E50F /* Pods */ = {
|
F8A65FEC7C0EB4B93746E50F /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -352,15 +413,22 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
EF05EDB81EAF704800CAE7B6 /* UserDefaults+UserService.swift in Sources */,
|
EF05EDB81EAF704800CAE7B6 /* UserDefaults+UserService.swift in Sources */,
|
||||||
|
EF05EDE11EAFA74200CAE7B6 /* BasePassCodeViewController.swift in Sources */,
|
||||||
EF05EDC61EAF70EB00CAE7B6 /* TouchIDService.swift in Sources */,
|
EF05EDC61EAF70EB00CAE7B6 /* TouchIDService.swift in Sources */,
|
||||||
|
EF05EDE31EAFA7A600CAE7B6 /* BasePassCodeViewModel.swift in Sources */,
|
||||||
EF05EDC21EAF706200CAE7B6 /* DefaultNetworkService.swift in Sources */,
|
EF05EDC21EAF706200CAE7B6 /* DefaultNetworkService.swift in Sources */,
|
||||||
EF05EDBB1EAF705500CAE7B6 /* ApiError.swift in Sources */,
|
EF05EDBB1EAF705500CAE7B6 /* ApiError.swift in Sources */,
|
||||||
|
EF05EDE91EAFA8A000CAE7B6 /* PassCodeHolderProtocol.swift in Sources */,
|
||||||
|
EF05EDED1EAFA96D00CAE7B6 /* PassCodeHolder.swift in Sources */,
|
||||||
EF05EDB71EAF704800CAE7B6 /* Observable+Extensions.swift in Sources */,
|
EF05EDB71EAF704800CAE7B6 /* Observable+Extensions.swift in Sources */,
|
||||||
EF05EDC01EAF706200CAE7B6 /* ApiResponse.swift in Sources */,
|
EF05EDC01EAF706200CAE7B6 /* ApiResponse.swift in Sources */,
|
||||||
EF05EDBC1EAF705500CAE7B6 /* ConnectionError.swift in Sources */,
|
EF05EDBC1EAF705500CAE7B6 /* ConnectionError.swift in Sources */,
|
||||||
|
EF05EDEB1EAFA8E600CAE7B6 /* PassCodeError.swift in Sources */,
|
||||||
EF05EDB41EAF703A00CAE7B6 /* BaseUserService.swift in Sources */,
|
EF05EDB41EAF703A00CAE7B6 /* BaseUserService.swift in Sources */,
|
||||||
|
EF05EDE51EAFA80D00CAE7B6 /* PassCodeConfiguration.swift in Sources */,
|
||||||
EF05EDC81EAF91D500CAE7B6 /* BasePassCodeService.swift in Sources */,
|
EF05EDC81EAF91D500CAE7B6 /* BasePassCodeService.swift in Sources */,
|
||||||
EF05EDC11EAF706200CAE7B6 /* BaseDateFormatter.swift in Sources */,
|
EF05EDC11EAF706200CAE7B6 /* BaseDateFormatter.swift in Sources */,
|
||||||
|
EF05EDE71EAFA87300CAE7B6 /* PassCodeValidationResult.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
public struct PassCodeConfiguration {
|
||||||
|
|
||||||
|
public var passCodeCharactersNumber: UInt = 4
|
||||||
|
public var maxAttemptsLoginNumber: UInt = 5
|
||||||
|
|
||||||
|
public var shouldResetWhenGoBackground: Bool = true
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
init?(passCodeCharactersNumber: UInt) {
|
||||||
|
guard passCodeCharactersNumber > 0 else {
|
||||||
|
assertionFailure("passCodeCharactersNumber must be greater then 0")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.passCodeCharactersNumber = passCodeCharactersNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var defaultConfiguration: PassCodeConfiguration {
|
||||||
|
let passCodeConfiguration = PassCodeConfiguration()
|
||||||
|
return passCodeConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
public enum PassCodeError: Error {
|
||||||
|
case codesNotMatch
|
||||||
|
case wrongCode
|
||||||
|
case tooMuchAttempts
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
extension PassCodeHolderProtocol {
|
||||||
|
|
||||||
|
public var passCodeHolderCreate: PassCodeHolderCreate? {
|
||||||
|
return self as? PassCodeHolderCreate
|
||||||
|
}
|
||||||
|
|
||||||
|
public var passCodeHolderEnter: PassCodeHolderEnter? {
|
||||||
|
return self as? PassCodeHolderEnter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PassCodeHolderCreate: PassCodeHolderProtocol {
|
||||||
|
|
||||||
|
public let type: PassCodeControllerType = .create
|
||||||
|
|
||||||
|
private var firstPassCode: String?
|
||||||
|
private var secondPassCode: String?
|
||||||
|
|
||||||
|
public var enterStep: PassCodeEnterStep {
|
||||||
|
if firstPassCode == nil {
|
||||||
|
return .first
|
||||||
|
} else {
|
||||||
|
return .second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var shouldValidate: Bool {
|
||||||
|
return firstPassCode != nil && secondPassCode != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public var passCode: String? {
|
||||||
|
guard let firstPassCode = firstPassCode, let secondPassCode = secondPassCode, firstPassCode == secondPassCode else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstPassCode
|
||||||
|
}
|
||||||
|
|
||||||
|
public func add(passCode: String) {
|
||||||
|
switch enterStep {
|
||||||
|
case .first:
|
||||||
|
firstPassCode = passCode
|
||||||
|
case .second:
|
||||||
|
secondPassCode = passCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func validate() -> PassCodeValidationResult {
|
||||||
|
if let passCode = passCode {
|
||||||
|
return .valid(passCode)
|
||||||
|
} else {
|
||||||
|
return .inValid(.codesNotMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
firstPassCode = nil
|
||||||
|
secondPassCode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PassCodeHolderEnter: PassCodeHolderProtocol {
|
||||||
|
|
||||||
|
public let type: PassCodeControllerType = .enter
|
||||||
|
public let enterStep: PassCodeEnterStep = .first
|
||||||
|
|
||||||
|
public var shouldValidate: Bool {
|
||||||
|
return passCode != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public var passCode: String?
|
||||||
|
|
||||||
|
public func add(passCode: String) {
|
||||||
|
self.passCode = passCode
|
||||||
|
}
|
||||||
|
|
||||||
|
public func validate() -> PassCodeValidationResult {
|
||||||
|
if let passCode = passCode {
|
||||||
|
return .valid(passCode)
|
||||||
|
} else {
|
||||||
|
return .inValid(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
passCode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
public enum PassCodeEnterStep {
|
||||||
|
case first
|
||||||
|
case second
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol PassCodeHolderProtocol {
|
||||||
|
|
||||||
|
var type: PassCodeControllerType { get }
|
||||||
|
var enterStep: PassCodeEnterStep { get }
|
||||||
|
|
||||||
|
func add(passCode: String)
|
||||||
|
func reset()
|
||||||
|
|
||||||
|
var shouldValidate: Bool { get }
|
||||||
|
var passCode: String? { get }
|
||||||
|
|
||||||
|
func validate() -> PassCodeValidationResult
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PassCodeHolderBuilder {
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
public static func build(with type: PassCodeControllerType) -> PassCodeHolderProtocol {
|
||||||
|
switch type {
|
||||||
|
case .create:
|
||||||
|
return PassCodeHolderCreate()
|
||||||
|
case .enter:
|
||||||
|
return PassCodeHolderEnter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
public enum PassCodeValidationResult {
|
||||||
|
|
||||||
|
case valid(String)
|
||||||
|
case inValid(PassCodeError?)
|
||||||
|
|
||||||
|
public var isValid: Bool {
|
||||||
|
switch self {
|
||||||
|
case .valid:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var passCode: String? {
|
||||||
|
switch self {
|
||||||
|
case let .valid(passCode):
|
||||||
|
return passCode
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var error: PassCodeError? {
|
||||||
|
switch self {
|
||||||
|
case let .inValid(error):
|
||||||
|
return error
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import LeadKit
|
||||||
|
|
||||||
|
public enum PinImageType {
|
||||||
|
case entered
|
||||||
|
case clear
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PassCodeControllerType {
|
||||||
|
case create
|
||||||
|
case enter
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PassCodeControllerState {
|
||||||
|
case enter
|
||||||
|
case repeatEnter
|
||||||
|
}
|
||||||
|
|
||||||
|
open class BasePassCodeViewController: UIViewController {
|
||||||
|
|
||||||
|
public var viewModel: BasePassCodeViewModel!
|
||||||
|
|
||||||
|
// MARK: - IBOutlets
|
||||||
|
|
||||||
|
@IBOutlet public weak var titleLabel: UILabel?
|
||||||
|
@IBOutlet public weak var errorLabel: UILabel?
|
||||||
|
@IBOutlet public weak var dotStackView: UIStackView!
|
||||||
|
|
||||||
|
let disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
fileprivate lazy var fakeTextField: UITextField = {
|
||||||
|
let fakeTextField = UITextField()
|
||||||
|
fakeTextField.isSecureTextEntry = true
|
||||||
|
fakeTextField.keyboardType = .numberPad
|
||||||
|
fakeTextField.isHidden = true
|
||||||
|
self.view.addSubview(fakeTextField)
|
||||||
|
return fakeTextField
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Life circle
|
||||||
|
|
||||||
|
override open func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
initialLoadView()
|
||||||
|
initialDotNumberConfiguration()
|
||||||
|
enebleKeyboard()
|
||||||
|
configureBackgroundNotifications()
|
||||||
|
showTouchIdIfNeeded(with: touchIdHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private functions
|
||||||
|
|
||||||
|
private func configureBackgroundNotifications() {
|
||||||
|
guard viewModel.passCodeConfiguration.shouldResetWhenGoBackground else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCenter.default.rx.notification(.UIApplicationWillResignActive)
|
||||||
|
.subscribe(onNext: { [weak self] _ in
|
||||||
|
self?.resetUI()
|
||||||
|
})
|
||||||
|
.addDisposableTo(disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func enebleKeyboard() {
|
||||||
|
fakeTextField.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialDotNumberConfiguration() {
|
||||||
|
dotStackView.arrangedSubviews.forEach { dotStackView.removeArrangedSubview($0) }
|
||||||
|
|
||||||
|
for _ in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
|
||||||
|
let dotImageView = UIImageView()
|
||||||
|
dotImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
dotImageView.widthAnchor.constraint(equalTo: dotImageView.heightAnchor, multiplier: 1)
|
||||||
|
dotImageView.contentMode = .scaleAspectFit
|
||||||
|
dotStackView.addArrangedSubview(dotImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetDotsUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func resetDotsUI() {
|
||||||
|
fakeTextField.text = nil
|
||||||
|
dotStackView.arrangedSubviews
|
||||||
|
.flatMap { $0 as? UIImageView }
|
||||||
|
.forEach { $0.image = self.imageFor(type: .clear) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setState(_ state: PinImageType, at index: Int) {
|
||||||
|
guard dotStackView.arrangedSubviews.count > index,
|
||||||
|
let imageView = dotStackView.arrangedSubviews[index] as? UIImageView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imageView.image = imageFor(type: state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func setStates(for passCodeText: String) {
|
||||||
|
var statesArray: [PinImageType] = []
|
||||||
|
|
||||||
|
for characterIndex in 0..<viewModel.passCodeConfiguration.passCodeCharactersNumber {
|
||||||
|
let state: PinImageType = Int(characterIndex) <= passCodeText.characters.count - 1 ? .entered : .clear
|
||||||
|
statesArray.append(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
statesArray.enumerated().forEach {
|
||||||
|
self.setState($0.element, at: $0.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func showTouchIdIfNeeded(with description: String) {
|
||||||
|
guard viewModel.isTouchIdEnabled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.touchIdService?.authenticateByTouchId(description: description) { [weak self] isSuccess in
|
||||||
|
if isSuccess {
|
||||||
|
self?.viewModel.authSucceed(.touchId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func resetUI() {
|
||||||
|
resetDotsUI()
|
||||||
|
viewModel.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - HAVE TO OVERRIDE
|
||||||
|
|
||||||
|
open var touchIdHint: String {
|
||||||
|
assertionFailure("You should override this var")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// override to change Images
|
||||||
|
func imageFor(type: PinImageType) -> UIImage {
|
||||||
|
assertionFailure("Don't use it directly. Override it!")
|
||||||
|
return UIImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// override to change error text
|
||||||
|
func errorDescription(for error: PassCodeError) -> String {
|
||||||
|
assertionFailure("Don't use it directly. Override it!")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// override to change action title text
|
||||||
|
func actionTitle(for passCodeControllerState: PassCodeControllerState) -> String {
|
||||||
|
assertionFailure("Don't use it directly. Override it!")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Functions that can you can override to castomise your controller
|
||||||
|
|
||||||
|
open func showError(for error: PassCodeError) {
|
||||||
|
errorLabel?.text = errorDescription(for: error)
|
||||||
|
errorLabel?.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
open func hideError() {
|
||||||
|
errorLabel?.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// override to change UI for state
|
||||||
|
open func configureUI(for passCodeControllerState: PassCodeControllerState) {
|
||||||
|
resetDotsUI()
|
||||||
|
titleLabel?.text = actionTitle(for: passCodeControllerState)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to implement all functions of ConfigurableController protocol to give ability to override them.
|
||||||
|
extension BasePassCodeViewController: ConfigurableController {
|
||||||
|
|
||||||
|
open func bindViews() {
|
||||||
|
fakeTextField.rx.text.asDriver()
|
||||||
|
.do(onNext: { [weak self] text in
|
||||||
|
self?.setStates(for: text ?? "")
|
||||||
|
self?.hideError()
|
||||||
|
})
|
||||||
|
.drive(viewModel.passCodeText)
|
||||||
|
.addDisposableTo(disposeBag)
|
||||||
|
|
||||||
|
viewModel.validationResult
|
||||||
|
.drive(onNext: { [weak self] validationResult in
|
||||||
|
guard let validationResult = validationResult else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if validationResult.isValid {
|
||||||
|
self?.hideError()
|
||||||
|
} else if let pasCodeError = validationResult.error {
|
||||||
|
self?.showError(for: pasCodeError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addDisposableTo(disposeBag)
|
||||||
|
|
||||||
|
viewModel.passCodeControllerState
|
||||||
|
.drive(onNext: { [weak self] controllerState in
|
||||||
|
self?.configureUI(for: controllerState)
|
||||||
|
})
|
||||||
|
.addDisposableTo(disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
open func addViews() {}
|
||||||
|
|
||||||
|
open func setAppearance() {}
|
||||||
|
|
||||||
|
open func configureBarButtons() {}
|
||||||
|
|
||||||
|
open func localize() {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
import LeadKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
public enum PassCodeAuthType {
|
||||||
|
case passCode(String)
|
||||||
|
case touchId
|
||||||
|
}
|
||||||
|
|
||||||
|
open class BasePassCodeViewModel: BaseViewModel {
|
||||||
|
|
||||||
|
public let controllerType: PassCodeControllerType
|
||||||
|
|
||||||
|
public let disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
public let touchIdService: TouchIDService?
|
||||||
|
public let passCodeConfiguration: PassCodeConfiguration
|
||||||
|
|
||||||
|
fileprivate let validationResultHolder = Variable<PassCodeValidationResult?>(nil)
|
||||||
|
var validationResult: Driver<PassCodeValidationResult?> {
|
||||||
|
return validationResultHolder.asDriver()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate let passCodeControllerStateHolder = Variable<PassCodeControllerState>(.enter)
|
||||||
|
public var passCodeControllerState: Driver<PassCodeControllerState> {
|
||||||
|
return passCodeControllerStateHolder.asDriver()
|
||||||
|
}
|
||||||
|
|
||||||
|
public let passCodeText = Variable<String?>(nil)
|
||||||
|
|
||||||
|
fileprivate var attemptsNumber = 0
|
||||||
|
|
||||||
|
fileprivate lazy var passCodeHolder: PassCodeHolderProtocol = {
|
||||||
|
return PassCodeHolderBuilder.build(with: self.controllerType)
|
||||||
|
}()
|
||||||
|
|
||||||
|
init(controllerType: PassCodeControllerType,
|
||||||
|
passCodeConfiguration: PassCodeConfiguration,
|
||||||
|
touchIdService: TouchIDService? = nil) {
|
||||||
|
|
||||||
|
self.controllerType = controllerType
|
||||||
|
self.passCodeConfiguration = passCodeConfiguration
|
||||||
|
self.touchIdService = touchIdService
|
||||||
|
|
||||||
|
bindViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bindViewModel() {
|
||||||
|
passCodeText.asDriver()
|
||||||
|
.distinctUntilChanged { $0 == $1 }
|
||||||
|
.drive(onNext: { [weak self] passCode in
|
||||||
|
if let passCode = passCode,
|
||||||
|
passCode.characters.count == Int(self?.passCodeConfiguration.passCodeCharactersNumber ?? 0) {
|
||||||
|
self?.set(passCode: passCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addDisposableTo(disposeBag)
|
||||||
|
|
||||||
|
validationResultHolder.asDriver()
|
||||||
|
.drive(onNext: { [weak self] validationResult in
|
||||||
|
if validationResult?.isValid ?? false, let passCode = validationResult?.passCode {
|
||||||
|
self?.authSucceed(.passCode(passCode))
|
||||||
|
} else {
|
||||||
|
self?.passCodeControllerStateHolder.value = .enter
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addDisposableTo(disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
passCodeText.value = nil
|
||||||
|
validationResultHolder.value = nil
|
||||||
|
passCodeControllerStateHolder.value = .enter
|
||||||
|
attemptsNumber = 0
|
||||||
|
passCodeHolder.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - HAVE TO OVERRIDE
|
||||||
|
|
||||||
|
open func isEnteredPassCodeValid(_ passCode: String) -> Bool {
|
||||||
|
assertionFailure("Don't use it directly. Override it!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
open func authSucceed(_ type: PassCodeAuthType) {
|
||||||
|
assertionFailure("Don't use it directly. Override it!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Functions that can you can override to use TouchId
|
||||||
|
|
||||||
|
open var isTouchIdEnabled: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
open func activateTouchIdForUser() {
|
||||||
|
assertionFailure("Don't use it directly. Override it!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BasePassCodeViewModel {
|
||||||
|
|
||||||
|
fileprivate func set(passCode: String) {
|
||||||
|
passCodeHolder.add(passCode: passCode)
|
||||||
|
validateIfNeeded()
|
||||||
|
|
||||||
|
if shouldUpdateControllerState {
|
||||||
|
switch passCodeHolder.enterStep {
|
||||||
|
case .first:
|
||||||
|
passCodeControllerStateHolder.value = .enter
|
||||||
|
case .second:
|
||||||
|
passCodeControllerStateHolder.value = .repeatEnter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var shouldUpdateControllerState: Bool {
|
||||||
|
return !passCodeHolder.shouldValidate ||
|
||||||
|
!(validationResultHolder.value?.isValid ?? true) ||
|
||||||
|
validationResultHolder.value?.error == .tooMuchAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateIfNeeded() {
|
||||||
|
guard passCodeHolder.shouldValidate else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var validationResult = passCodeHolder.validate()
|
||||||
|
|
||||||
|
if passCodeHolder.type == .enter {
|
||||||
|
attemptsNumber += 1
|
||||||
|
|
||||||
|
if let passCode = validationResult.passCode, !isEnteredPassCodeValid(passCode) {
|
||||||
|
validationResult = .inValid(.wrongCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validationResult.isValid && attemptsNumber == Int(passCodeConfiguration.maxAttemptsLoginNumber)) ||
|
||||||
|
attemptsNumber > Int(passCodeConfiguration.maxAttemptsLoginNumber) {
|
||||||
|
validationResult = .inValid(.tooMuchAttempts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validationResult.isValid {
|
||||||
|
passCodeHolder.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
validationResultHolder.value = validationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue