TF-643 Login module [WIP]

This commit is contained in:
Zbigniew Cisiński 2023-11-11 13:58:40 +01:00
parent c6117ce7ff
commit 2a28439b88
13 changed files with 493 additions and 590 deletions

View File

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
C2000F4D217C87970020CA0B /* LoginCoordinatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2000F4C217C87970020CA0B /* LoginCoordinatorDelegate.swift */; };
C200E49E1FB3911B00D7C748 /* Storage.h in Headers */ = {isa = PBXBuildFile; fileRef = C200E49C1FB3911B00D7C748 /* Storage.h */; settings = {ATTRIBUTES = (Public, ); }; };
C200E4A11FB3911B00D7C748 /* Storage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C200E49A1FB3911B00D7C748 /* Storage.framework */; };
C200E4A21FB3911B00D7C748 /* Storage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C200E49A1FB3911B00D7C748 /* Storage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -82,17 +81,13 @@
C2147CC8205D787A0001D011 /* CodeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CC3205D78790001D011 /* CodeStorage.swift */; };
C2147CC9205D787A0001D011 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CC4205D78790001D011 /* Extensions.swift */; };
C2147CCA205D787A0001D011 /* BiometricAuthDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CC5205D78790001D011 /* BiometricAuthDelegate.swift */; };
C2147CD0205D7B520001D011 /* PINPadViewControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CCD205D7B510001D011 /* PINPadViewControllerProtocol.swift */; };
C2147CD1205D7B520001D011 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CCE205D7B510001D011 /* LoginViewController.swift */; };
C2147CD9205D7B630001D011 /* PINPadVerifyPINDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CD4205D7B620001D011 /* PINPadVerifyPINDataModel.swift */; };
C2147CDA205D7B630001D011 /* PINPadCodeDots.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CD5205D7B630001D011 /* PINPadCodeDots.swift */; };
C2147CDB205D7B630001D011 /* PINPadDataModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CD6205D7B630001D011 /* PINPadDataModelProtocol.swift */; };
C2147CDD205D7B630001D011 /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CD8205D7B630001D011 /* LoginPresenter.swift */; };
C2147CE3205D7B790001D011 /* PINPadScreenData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CDF205D7B780001D011 /* PINPadScreenData.swift */; };
C2147CE4205D7B790001D011 /* PINPadCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CE0205D7B780001D011 /* PINPadCircleView.swift */; };
C2147CE5205D7B790001D011 /* PINPadKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CE1205D7B780001D011 /* PINPadKey.swift */; };
C2147CE6205D7B790001D011 /* PINPadKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CE2205D7B780001D011 /* PINPadKeyboard.swift */; };
C2147CF2205D8C6C0001D011 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CEF205D8C6B0001D011 /* LoginViewModel.swift */; };
C2147CF2205D8C6C0001D011 /* LoginModuleInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2147CEF205D8C6B0001D011 /* LoginModuleInteractor.swift */; };
C2153BC21FE9ABA6005FD297 /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2153BBE1FE9ABA6005FD297 /* CameraViewController.swift */; };
C2153BC31FE9ABA6005FD297 /* CameraViewModelProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2153BBF1FE9ABA6005FD297 /* CameraViewModelProtocols.swift */; };
C2153BC51FE9B2BF005FD297 /* CameraActiveArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2153BC41FE9B2BF005FD297 /* CameraActiveArea.swift */; };
@ -722,6 +717,7 @@
C2C93FA12603F87100E10981 /* MainContainerDecoratedVerticalContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C93FA02603F87100E10981 /* MainContainerDecoratedVerticalContainer.swift */; };
C2C93FBD2604005E00E10981 /* MainContainerToggleWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C93FBC2604005E00E10981 /* MainContainerToggleWithLabel.swift */; };
C2CA2B7D258699DA009FB700 /* FirstCodeAddedStatsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CA2B7C258699DA009FB700 /* FirstCodeAddedStatsController.swift */; };
C2CACFBE2AFEC905001E0F8E /* LoginType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CACFBD2AFEC905001E0F8E /* LoginType.swift */; };
C2CB5D3A25DB1C9300A362A9 /* Orientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CB5D3925DB1C9300A362A9 /* Orientation.swift */; };
C2CC0A3A29D0580B00677A7B /* ExternalImportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CC0A3929D0580B00677A7B /* ExternalImportViewController.swift */; };
C2CC0A3C29D05DC900677A7B /* ExternalImportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CC0A3B29D05DC900677A7B /* ExternalImportPresenter.swift */; };
@ -1303,7 +1299,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
C2000F4C217C87970020CA0B /* LoginCoordinatorDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinatorDelegate.swift; sourceTree = "<group>"; };
C200E49A1FB3911B00D7C748 /* Storage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Storage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C200E49C1FB3911B00D7C748 /* Storage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Storage.h; sourceTree = "<group>"; };
C200E49D1FB3911B00D7C748 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -1396,19 +1391,15 @@
C2147CC3205D78790001D011 /* CodeStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeStorage.swift; sourceTree = "<group>"; };
C2147CC4205D78790001D011 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
C2147CC5205D78790001D011 /* BiometricAuthDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricAuthDelegate.swift; sourceTree = "<group>"; };
C2147CCD205D7B510001D011 /* PINPadViewControllerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadViewControllerProtocol.swift; sourceTree = "<group>"; };
C2147CCE205D7B510001D011 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
C2147CD4205D7B620001D011 /* PINPadVerifyPINDataModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadVerifyPINDataModel.swift; sourceTree = "<group>"; };
C2147CD5205D7B630001D011 /* PINPadCodeDots.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadCodeDots.swift; sourceTree = "<group>"; };
C2147CD6205D7B630001D011 /* PINPadDataModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadDataModelProtocol.swift; sourceTree = "<group>"; };
C2147CD8205D7B630001D011 /* LoginPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = "<group>"; };
C2147CDF205D7B780001D011 /* PINPadScreenData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadScreenData.swift; sourceTree = "<group>"; };
C2147CE0205D7B780001D011 /* PINPadCircleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadCircleView.swift; sourceTree = "<group>"; };
C2147CE1205D7B780001D011 /* PINPadKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadKey.swift; sourceTree = "<group>"; };
C2147CE2205D7B780001D011 /* PINPadKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PINPadKeyboard.swift; sourceTree = "<group>"; };
C2147CE8205D7FC90001D011 /* Security.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Security.swift; sourceTree = "<group>"; };
C2147CE9205D7FC90001D011 /* SecurityProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurityProtocol.swift; sourceTree = "<group>"; };
C2147CEF205D8C6B0001D011 /* LoginViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
C2147CEF205D8C6B0001D011 /* LoginModuleInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginModuleInteractor.swift; sourceTree = "<group>"; };
C2153BBE1FE9ABA6005FD297 /* CameraViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = "<group>"; };
C2153BBF1FE9ABA6005FD297 /* CameraViewModelProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewModelProtocols.swift; sourceTree = "<group>"; };
C2153BC41FE9B2BF005FD297 /* CameraActiveArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraActiveArea.swift; sourceTree = "<group>"; };
@ -2110,6 +2101,7 @@
C2C93FA02603F87100E10981 /* MainContainerDecoratedVerticalContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContainerDecoratedVerticalContainer.swift; sourceTree = "<group>"; };
C2C93FBC2604005E00E10981 /* MainContainerToggleWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContainerToggleWithLabel.swift; sourceTree = "<group>"; };
C2CA2B7C258699DA009FB700 /* FirstCodeAddedStatsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstCodeAddedStatsController.swift; sourceTree = "<group>"; };
C2CACFBD2AFEC905001E0F8E /* LoginType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginType.swift; sourceTree = "<group>"; };
C2CB5D3925DB1C9300A362A9 /* Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Orientation.swift; sourceTree = "<group>"; };
C2CC0A3929D0580B00677A7B /* ExternalImportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalImportViewController.swift; sourceTree = "<group>"; };
C2CC0A3B29D05DC900677A7B /* ExternalImportPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalImportPresenter.swift; sourceTree = "<group>"; };
@ -2570,6 +2562,7 @@
C20629F92AF98611003BFC21 /* Interactor */ = {
isa = PBXGroup;
children = (
C2147CEF205D8C6B0001D011 /* LoginModuleInteractor.swift */,
);
path = Interactor;
sourceTree = "<group>";
@ -3112,7 +3105,6 @@
C2147CCC205D7B430001D011 /* PINPad */ = {
isa = PBXGroup;
children = (
C2AB92C027664A6200698382 /* DEPRECATED */,
C2147CDE205D7B6C0001D011 /* KeyboardSubviews */,
C2AB92BE2766342900698382 /* PINKeyboardViewController.swift */,
C2AB92C127664B9200698382 /* PINKeyboardPresenter.swift */,
@ -3143,12 +3135,11 @@
C2147CEE205D8C620001D011 /* Login */ = {
isa = PBXGroup;
children = (
C2CACFBC2AFEC8EB001E0F8E /* Models */,
C20629FA2AF98617003BFC21 /* Flow */,
C20629F92AF98611003BFC21 /* Interactor */,
C20629F82AF9860A003BFC21 /* Presenter */,
C20629F72AF98373003BFC21 /* View */,
C2147CEF205D8C6B0001D011 /* LoginViewModel.swift */,
C2000F4C217C87970020CA0B /* LoginCoordinatorDelegate.swift */,
);
path = Login;
sourceTree = "<group>";
@ -5314,17 +5305,6 @@
path = Flow;
sourceTree = "<group>";
};
C2AB92C027664A6200698382 /* DEPRECATED */ = {
isa = PBXGroup;
children = (
C2147CD6205D7B630001D011 /* PINPadDataModelProtocol.swift */,
C2147CD4205D7B620001D011 /* PINPadVerifyPINDataModel.swift */,
C2147CCD205D7B510001D011 /* PINPadViewControllerProtocol.swift */,
C2147CDF205D7B780001D011 /* PINPadScreenData.swift */,
);
path = DEPRECATED;
sourceTree = "<group>";
};
C2AB92C3276651C000698382 /* ExporterPIN */ = {
isa = PBXGroup;
children = (
@ -6150,6 +6130,14 @@
path = Modules;
sourceTree = "<group>";
};
C2CACFBC2AFEC8EB001E0F8E /* Models */ = {
isa = PBXGroup;
children = (
C2CACFBD2AFEC905001E0F8E /* LoginType.swift */,
);
path = Models;
sourceTree = "<group>";
};
C2CB5D1125DB1B7E00A362A9 /* External */ = {
isa = PBXGroup;
children = (
@ -7782,7 +7770,6 @@
C2F4D9392A8D33FA00608F63 /* AddServiceAdvancedWarningView.swift in Sources */,
C26764B02872483900D468B2 /* ComposeServiceCategorySelectionPresenter.swift in Sources */,
C28633AD1FF57C8600C8F4B4 /* Types.swift in Sources */,
C2000F4D217C87970020CA0B /* LoginCoordinatorDelegate.swift in Sources */,
C2ABA1CC24B24B7F008426D1 /* TokensViewController+ContextMenu.swift in Sources */,
C2A70682276D390500885D79 /* NewPINPresenter.swift in Sources */,
C2E948AD23B666BD003BDCC1 /* UnderscoredInput.swift in Sources */,
@ -7895,7 +7882,7 @@
C25B221627C181690052AAC4 /* ComposeServiceInputCellKind.swift in Sources */,
C2A7946027F9071F00E5C641 /* BrowserExtensionServicePresenter.swift in Sources */,
C267315A299AC9C40031DE09 /* LargeNavigationController.swift in Sources */,
C2147CF2205D8C6C0001D011 /* LoginViewModel.swift in Sources */,
C2147CF2205D8C6C0001D011 /* LoginModuleInteractor.swift in Sources */,
C2AB92E127668DE700698382 /* ShareActivityController.swift in Sources */,
C2AC22062766152C00271402 /* ExporterMainScreenViewController.swift in Sources */,
C23FD50A2814635400E4E9C5 /* ComposeServiceWebExtensionFlowController.swift in Sources */,
@ -7970,7 +7957,6 @@
C2FC422123AA902700320ABE /* PresentableType.swift in Sources */,
C21A9E9C27CC0F6200FE4CC4 /* ComposeServiceAlgorithmViewController.swift in Sources */,
C20E2DFE2A7C4DE000DB64BB /* AddingServiceMainFlowController.swift in Sources */,
C2147CD9205D7B630001D011 /* PINPadVerifyPINDataModel.swift in Sources */,
C2CC80D827B99B0600294CF2 /* ComposeServicePresenter.swift in Sources */,
C2727D55278207D000B8FA03 /* TrashViewEmptyScreen.swift in Sources */,
C23B899D27C2B4950028FE34 /* ComposeServiceBadgeColorView.swift in Sources */,
@ -8123,7 +8109,6 @@
C2625F8E28BB858600D84C5C /* AboutPresenter.swift in Sources */,
C207324929F724AB00FB294E /* TokensAdditionalInfo.swift in Sources */,
C2EC83DE21E69B4800DF5C49 /* ViewTheme.swift in Sources */,
C2147CD0205D7B520001D011 /* PINPadViewControllerProtocol.swift in Sources */,
C240484C276522320076376E /* WidgetWarningFlowController.swift in Sources */,
C2625F8C28BB853D00D84C5C /* AboutViewController.swift in Sources */,
C2B5BF572946169D00E5092D /* TokensLinkAction.swift in Sources */,
@ -8144,7 +8129,6 @@
C2BD85DC26402A910087D087 /* IntroductionContainerView.swift in Sources */,
C2DDEB25289F3D5200011712 /* TokensViewEmptyListScreen.swift in Sources */,
C26D4F542A856F590025E5BA /* AddingServiceTokenPresenter.swift in Sources */,
C2147CDB205D7B630001D011 /* PINPadDataModelProtocol.swift in Sources */,
C2C02244297F224700D596A8 /* MainModuleInteractor.swift in Sources */,
C24590FA280F54400079C21C /* SelectServiceModels.swift in Sources */,
C274BDC42769425900EE28BC /* AppSecurityPresenter+Menu.swift in Sources */,
@ -8232,7 +8216,6 @@
C2EA567128569ADD00026BFE /* AskForAuthFlowController.swift in Sources */,
C2AE5F542ADC1A0D00AED670 /* PINType+.swift in Sources */,
C20E2E0B2A7C52AA00DB64BB /* AddingServiceMainPresenter.swift in Sources */,
C2147CE3205D7B790001D011 /* PINPadScreenData.swift in Sources */,
C2A74FF52610DDF5007E5AB3 /* Array+.swift in Sources */,
C2CC0A3C29D05DC900677A7B /* ExternalImportPresenter.swift in Sources */,
C2FF3EEC27B1CF0100642FA4 /* IconSelectorModels.swift in Sources */,
@ -8286,6 +8269,7 @@
C29626B8236F80A200C133BC /* Animate.swift in Sources */,
C21A9E8427CBF75E00FE4CC4 /* ComposeServiceNumberOfDigitsPresenter.swift in Sources */,
C2830D7E293D4A6900B3DDAF /* TokensViewController+UICollectionViewDelegate.swift in Sources */,
C2CACFBE2AFEC905001E0F8E /* LoginType.swift in Sources */,
C2B077C2292A920B0074517F /* UploadLogsFailurePresenter.swift in Sources */,
C20E578F292976DA002B215B /* UUIDInputField.swift in Sources */,
C21851422A1AC77900A8352A /* TokensHOTPCompactCell.swift in Sources */,

View File

@ -28,7 +28,7 @@ extension Array {
}
extension Array where Element == Int {
func concateToPostionString() -> String {
func concateToPositionString() -> String {
self.map { String($0) }.reduce("", +)
}
}

View File

@ -21,11 +21,13 @@ import UIKit
import Data
protocol LoginFlowControllerParent: AnyObject {
func closeLogin()
func loginClose()
func loginLoggedIn()
}
protocol LoginFlowControlling: AnyObject {
func toClose()
func toLoggedIn()
func toAppReset()
}
@ -35,6 +37,7 @@ final class LoginFlowController: FlowController {
static func insertAsChild(
into viewController: UIViewController,
parent: LoginFlowControllerParent,
loginType: LoginType,
animated: Bool
) {
// let view = LoginViewController()
@ -68,7 +71,11 @@ final class LoginFlowController: FlowController {
extension LoginFlowController: LoginFlowControlling {
func toClose() {
parent?.closeLogin()
parent?.loginClose()
}
func toLoggedIn() {
parent?.loginLoggedIn()
}
func toAppReset() {
@ -105,17 +112,3 @@ extension LoginFlowController: LoginFlowControlling {
viewController.present(vc, animated: true, completion: nil)
}
}
// let appLockStateInteractor = AppLockStateInteractor(mainRepository: MainRepositoryImpl.shared)
// let viewModel = LoginViewModel(
// security: security,
// resetApp: { [weak self] in self?.presentResetAppViewController() },
// leftButtonDescription: leftButtonDescription,
// appLockStateInteractor: appLockStateInteractor
// )
// viewModel.coordinatorDelegate = self
//
// let login = LoginViewController()
// login.viewModel = viewModel
//
// rootViewController.present(login, immediately: showImmediately, animationType: .alpha)

View File

@ -0,0 +1,186 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import UIKit
import Common
import Data
protocol LoginCoordinatorDelegate: AnyObject {
func authorized()
func cancelled()
}
protocol LoginModuleInteracting: AnyObject {
var isLocked: Bool { get }
var isAfterWrongPIN: Bool { get }
var lockTime: Int? { get }
func checkState()
func checkBio()
var updateState: Callback? { get set }
var correctPIN: Callback? { get set }
var codeLength: Int { get }
var hasInput: Bool { get }
var inputCount: Int { get }
func reset()
func addNumber(_ number: Int)
func deleteNumber()
}
final class LoginModuleInteractor {
var updateState: Callback?
var correctPIN: Callback?
private var numbers: [Int] = []
private(set) var isLocked = false
private(set) var isAfterWrongPIN = false
private let textChangeTime: Int = 3
private let timer = CountdownTimer()
// private let appLockStateInteractor: AppLockStateInteracting
fileprivate let security: SecurityProtocol
init(
security: SecurityProtocol,
resetApp: Callback? = nil,
leftButtonDescription: String? = nil,
appLockStateInteractor: AppLockStateInteracting
) {
self.security = security
// self.appLockStateInteractor = appLockStateInteractor
timer.timerFinished = { [weak self] in
DispatchQueue.main.async {
self?.timerFinished()
}
}
}
func checkState() {
isLocked = !security.canAuthorize
}
func checkBio() {
guard security.canAuthorize && UIApplication.shared.applicationState != .background else { return }
security.authenticateUsingBioAuthIfPossible(reason: T.Security.confirmYouAreDeviceOwner)
}
}
extension LoginModuleInteractor: LoginModuleInteracting {
var lockTime: Int? {
nil //appLockStateInteractor.appLockRemainingSeconds
}
var codeLength: Int {
0 // interactor -> security.currentCodeType.intValue
}
var hasInput: Bool {
!numbers.isEmpty
}
var inputCount: Int {
numbers.count
}
func reset() {
clear()
}
func addNumber(_ number: Int) {
numbers.append(number)
if numbers.count == codeLength {
verify()
}
}
func deleteNumber() {
_ = numbers.popLast()
}
}
private extension LoginModuleInteractor {
func clear() {
numbers = []
}
func verify() {
let code = PIN.create(with: numbers)
let codeIsCorrect = security.verifyPIN(code)
if codeIsCorrect {
security.authSuccessfully()
correctPIN?()
} else {
clear()
isAfterWrongPIN = true
timer.start(with: textChangeTime)
updateState?()
security.authFailed()
}
}
func lock() {
clear()
isAfterWrongPIN = false
isLocked = true
updateState?()
}
func unlock() {
clear()
isAfterWrongPIN = false
isLocked = false
updateState?()
}
func timerFinished() {
isAfterWrongPIN = false
guard !isLocked else { return }
updateState?()
}
}
//extension LoginViewModel: SecurityDelegate {
// func securityBioAuthSuccess() {
// security.authSuccessfully()
// delegate?.userWasAuthenticated()
// }
//
// func securityBioAuthFailure() {
// // use code instead. Do nothing
// }
//
// func securityLockUI() {
// delegate?.lockUI()
// }
//
// func securityUnlockUI() {
// delegate?.unlockUI()
// }
//
// func retryBioAuthIfNecessary() {
// guard security.canAuthorize else { return }
// bioAuth()
// }
//}

View File

@ -1,118 +0,0 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import UIKit
import Common
import Data
final class LoginViewModel {
weak var delegate: LoginViewModelDelegate?
weak var coordinatorDelegate: LoginCoordinatorDelegate?
let codeViewModel: PINPadViewModel
fileprivate let security: SecurityProtocol
init(
security: SecurityProtocol,
resetApp: Callback? = nil,
leftButtonDescription: String? = nil,
appLockStateInteractor: AppLockStateInteracting
) {
self.security = security
let codeDataModel = PINPadVerifyPINDataModel(verifyType: .login, leftButtonDescription: leftButtonDescription)
codeDataModel.setCodeLenght(security.currentCodeType.intValue)
codeViewModel = PINPadViewModel(dataModel: codeDataModel, appLockStateInteractor: appLockStateInteractor)
codeViewModel.resetAction = resetApp
security.delegate = self
codeDataModel.checkCode = { [weak self] in self?.checkCode(numbers: $0) ?? false }
codeViewModel.cancel = { [weak self] in self?.cancel() }
}
func viewWillAppear() {
if !security.canAuthorize {
delegate?.lockUI()
} else {
delegate?.unlockUI()
}
}
func viewDidAppear() {
guard security.canAuthorize && UIApplication.shared.applicationState != .background else { return }
bioAuth()
}
private func cancel() {
coordinatorDelegate?.cancelled()
}
private func checkCode(numbers: [Int]) -> Bool {
let code = PIN.create(with: numbers)
let codeIsCorrect = security.verifyPIN(code)
if codeIsCorrect {
security.authSuccessfully()
delegate?.userWasAuthenticated()
} else {
security.authFailed()
}
return codeIsCorrect
}
func userWasAuthenticated() {
coordinatorDelegate?.authorized()
}
fileprivate func bioAuth() {
security.authenticateUsingBioAuthIfPossible(reason: T.Security.confirmYouAreDeviceOwner)
}
func lock() {
codeViewModel.lock()
}
func unlock() {
codeViewModel.unlock()
}
}
extension LoginViewModel: SecurityDelegate {
func securityBioAuthSuccess() {
security.authSuccessfully()
delegate?.userWasAuthenticated()
}
func securityBioAuthFailure() {
// use code instead. Do nothing
}
func securityLockUI() {
delegate?.lockUI()
}
func securityUnlockUI() {
delegate?.unlockUI()
}
func retryBioAuthIfNecessary() {
guard security.canAuthorize else { return }
bioAuth()
}
}

View File

@ -19,7 +19,7 @@
import Foundation
protocol LoginCoordinatorDelegate: AnyObject {
func authorized()
func cancelled()
enum LoginType {
case login
case verify
}

View File

@ -30,145 +30,130 @@ final class LoginPresenter {
private let twoMinutes = 120
private let minute = 60
private var numbers: [Int] = []
private var isLocked = false
private let dataModel: PINPadDataModelProtocol
private let timer = CountdownTimer()
private let appLockStateInteractor: AppLockStateInteracting
private let textChangeTime: Int = 3
init(dataModel: PINPadDataModelProtocol, appLockStateInteractor: AppLockStateInteracting) {
self.dataModel = dataModel
self.appLockStateInteractor = appLockStateInteractor
dataModel.invalidInput = { [weak self] in self?.invalidInput() }
timer.timerFinished = { [weak self] in
DispatchQueue.main.async {
self?.prepareScreenWithNormalData()
VoiceOver.say(dataModel.screenTitle)
}
private var screenTitle: String {
switch loginType {
case .verify: T.Security.enterCurrentPin
case .login: T.Security.enterPin
}
}
// MARK: PINPadViewControllerProtocol implementation
func leftButtonPressed() {
cancel?()
private var leftButtonTitle: String? {
switch loginType {
case .login: nil
case .verify: T.Commons.cancel
}
}
func deleteButtonPressed() {
deleteNumber()
private var showReset: Bool {
switch loginType {
case .login: true
case .verify: false
}
}
func numberButtonPressed(number: Int) {
insertNumber(number)
private var lockTimeMessage: String {
if let lockTime = interactor.lockTime {
if lockTime < twoMinutes {
return T.Security.tooManyAttemptsError2
}
return T.Security.tooManyAttemptsTryAgainAfter("\(lockTime / minute)")
}
return T.Security.tooManyAttemptsError
}
private let loginType: LoginType
private let flowController: LoginFlowControlling
private let interactor: LoginModuleInteracting
init(loginType: LoginType, flowController: LoginFlowControlling, interactor: LoginModuleInteracting) {
self.loginType = loginType
self.flowController = flowController
self.interactor = interactor
interactor.updateState = { [weak self] in
self?.updateState()
}
interactor.correctPIN = { [weak self] in
self?.flowController.toLoggedIn()
}
}
// MARK: - User actions
func onClose() {
flowController.toClose()
}
func onDelete() {
interactor.deleteNumber()
updateState()
}
func onReset() {
interactor.reset()
updateState()
flowController.toAppReset()
}
func onNumberInput(_ number: Int) {
interactor.addNumber(number)
updateState()
}
// MARK: - VC Flow
func viewDidLoad() {
delegate?.setDots(number: dataModel.codeLength)
view?.setDots(number: interactor.codeLength)
}
func viewWillAppear() {
guard !isLocked else { return }
prepareInitialState()
func viewWillAppear() {
interactor.checkState()
updateState()
}
func lock() {
isLocked = true
let lockTimeMessage: String = {
if let lockTime = appLockStateInteractor.appLockRemainingSeconds {
if lockTime < twoMinutes {
return T.Security.tooManyAttemptsError2
}
return T.Security.tooManyAttemptsTryAgainAfter("\(lockTime / minute)")
}
return T.Security.tooManyAttemptsError
}()
numbers = []
delegate?.emptyDots()
delegate?.lock(withMessage: lockTimeMessage)
delegate?.hideLeftButton()
delegate?.hideRightButton()
}
func unlock() {
isLocked = false
delegate?.unlock()
prepareInitialState()
}
func reset() {
resetAction?()
}
// MARK: - Private methods
private func prepareInitialState() {
numbers = []
delegate?.emptyDots()
delegate?.hideLeftButton()
delegate?.hideRightButton()
if resetAction != nil {
delegate?.showReset()
}
prepareScreenWithNormalData()
}
private func deleteNumber() {
_ = numbers.popLast()
delegate?.fillDots(count: numbers.count)
if numbers.isEmpty {
delegate?.hideRightButton()
}
}
private func insertNumber(_ number: Int) {
if numbers.isEmpty {
delegate?.showDeleteButton()
}
numbers.append(number)
delegate?.fillDots(count: numbers.count)
if numbers.count == dataModel.codeLength {
dataModel.PINGathered(numbers: self.numbers)
}
}
private func invalidInput() {
guard !isLocked else { return }
numbers = []
delegate?.hideRightButton()
delegate?.emptyDots()
delegate?.shakeDots()
prepareScreenWithError()
}
private func prepareScreenWithNormalData() {
guard !isLocked else { return }
let screenData = PINPadScreenData(
screenTitle: dataModel.screenTitle,
buttonTitle: dataModel.leftButton,
titleType: .normal
)
delegate?.prepareScreen(withScreenData: screenData)
}
private func prepareScreenWithError() {
let screenData = PINPadScreenData(
screenTitle: T.Security.incorrectPIN,
buttonTitle: dataModel.leftButton,
titleType: .error
)
VoiceOver.say(T.Security.incorrectPIN)
delegate?.prepareScreen(withScreenData: screenData)
timer.start(with: textChangeTime)
func viewDidAppear() {
interactor.checkBio()
}
}
private extension LoginPresenter {
func updateState() {
if interactor.isLocked {
view?.emptyDots()
view?.hideNavigation()
view?.lock(with: lockTimeMessage)
return
}
view?.unlock()
if interactor.inputCount > 0 {
view?.showDeleteButton()
view?.fillDots(count: interactor.inputCount)
} else {
view?.hideDeleteButton()
view?.emptyDots()
}
if interactor.isAfterWrongPIN {
view?.shakeDots()
view?.prepareScreen(
with: T.Security.incorrectPIN,
isError: true,
showReset: showReset,
leftButtonTitle: leftButtonTitle
)
return
}
view?.prepareScreen(
with: screenTitle,
isError: false,
showReset: showReset,
leftButtonTitle: leftButtonTitle
)
}
}

View File

@ -20,9 +20,20 @@
import UIKit
protocol LoginViewControlling: AnyObject {
func userWasAuthenticated()
func lockUI()
func unlockUI()
func prepareScreen(with title: String, isError: Bool, showReset: Bool, leftButtonTitle: String?)
func setDots(number: Int)
func fillDots(count: Int)
func emptyDots()
func shakeDots()
func showDeleteButton()
func hideDeleteButton()
func hideNavigation()
func lock(with message: String)
func unlock()
}
final class LoginViewController: UIViewController {
@ -41,27 +52,124 @@ final class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Theme.Colors.Fill.background
prepareDescriptionLabel()
let views = [titleLabel, dots, PINPad, leftButton, deleteButton]
UIView.prepareViewsForAutoLayout(withViews: views, superview: view)
let image = Asset.deleteCodeButton.image
.withRenderingMode(.alwaysTemplate)
deleteButton.setImage(image, for: .normal)
deleteButton.tintColor = Theme.Colors.Controls.inactive
deleteButton.accessibilityLabel = T.Voiceover.deleteButton
setupLayout(imageWidth: image.size.width)
leftButton.addTarget(self, action: #selector(leftButtonPressed), for: .touchUpInside)
deleteButton.addTarget(self, action: #selector(deleteButtonPressed), for: .touchUpInside)
PINPad.numberButtonAction = { [weak self] number in
self?.numberButtonPressed(number: number)
}
presenter.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
presenter.viewWillAppear()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presenter.viewDidAppear()
}
}
extension LoginViewController: LoginViewControlling {
func prepareScreen(with title: String, isError: Bool, showReset: Bool, leftButtonTitle: String?) {
titleLabel.text = title
VoiceOver.say(title)
if isError {
titleLabel.textColor = Theme.Colors.Text.theme
} else {
titleLabel.textColor = Theme.Colors.Text.main
}
if let buttonTitle = leftButtonTitle {
decorateButtonText(withButton: leftButton, text: title)
showCloseButton()
}
if showReset {
showResetButton()
}
}
func setDots(number: Int) {
dots.setDots(number: number)
}
func fillDots(count: Int) {
dots.fillDots(count: count, animated: true)
}
func emptyDots() {
dots.emptyDots(animated: true)
}
func shakeDots() {
dots.shake()
}
func showDeleteButton() {
deleteButton.isHidden = false
}
func hideDeleteButton() {
deleteButton.isHidden = true
}
func lock(with message: String) {
dots.alpha = Theme.Alpha.disabledElement
PINPad.isUserInteractionEnabled = false
PINPad.alpha = Theme.Alpha.disabledElement
titleLabel.text = message
VoiceOver.say(message)
}
func unlock() {
dots.alpha = 1
PINPad.isUserInteractionEnabled = true
PINPad.alpha = 1
}
func hideNavigation() {
hideCloseButton()
hideDeleteButton()
}
}
private extension LoginViewController {
func showCloseButton() {
leftButton.isHidden = false
}
func hideCloseButton() {
leftButton.isHidden = true
}
func setupLayout(imageWidth: CGFloat) {
let views = [titleLabel, dots, PINPad, leftButton, deleteButton]
UIView.prepareViewsForAutoLayout(withViews: views, superview: view)
let keySize = Theme.Metrics.PINButtonDimensionLarge
let deleteButtonOffset = round((keySize - image.size.width) / 2.0)
let deleteButtonOffset = round((keySize - imageWidth) / 2.0)
let topGuide = UILayoutGuide()
let bottomGuide = UILayoutGuide()
deleteButton.accessibilityLabel = T.Voiceover.deleteButton
view.addLayoutGuide(topGuide)
view.addLayoutGuide(bottomGuide)
@ -86,123 +194,9 @@ final class LoginViewController: UIViewController {
bottomGuide.centerXAnchor.constraint(equalTo: view.centerXAnchor),
bottomGuide.widthAnchor.constraint(equalToConstant: 10)
])
leftButton.addTarget(self, action: #selector(leftButtonPressed), for: .touchUpInside)
deleteButton.addTarget(self, action: #selector(deleteButtonPressed), for: .touchUpInside)
PINPad.numberButtonAction = numberButtonPressed(number:)
presenter.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
presenter.viewWillAppear()
}
// MARK: - Public API
func prepareScreen(withScreenData data: PINPadScreenData) {
titleLabel.text = data.screenTitle
switch data.titleType {
case .normal:
titleLabel.textColor = Theme.Colors.Text.main
case .error:
titleLabel.textColor = Theme.Colors.Text.theme
}
titleLabel.accessibilityLabel = data.screenTitle
if let title = data.buttonTitle {
decorateButtonText(withButton: leftButton, text: title)
showLeftButton()
}
}
func setDots(number: Int) {
dots.setDots(number: number)
}
func fillDots(count: Int) {
dots.fillDots(count: count, animated: true)
}
func emptyDots() {
dots.emptyDots(animated: true)
}
func shakeDots() {
dots.shake()
}
func showLeftButton() {
leftButton.isHidden = false
}
func hideLeftButton() {
leftButton.isHidden = true
}
func showDeleteButton() {
deleteButton.isHidden = false
}
func hideRightButton() {
deleteButton.isHidden = true
}
func lock(withMessage message: String) {
dots.alpha = Theme.Alpha.disabledElement
PINPad.isUserInteractionEnabled = false
PINPad.alpha = Theme.Alpha.disabledElement
titleLabel.text = message
}
func unlock() {
dots.alpha = 1
PINPad.isUserInteractionEnabled = true
PINPad.alpha = 1
}
func showReset() {
var buttonConfiguration = UIButton.Configuration.borderless()
buttonConfiguration.imagePadding = Theme.Metrics.doubleSpacing
buttonConfiguration.image = Asset.infoIcon.image
buttonConfiguration.baseForegroundColor = Theme.Colors.Controls.active
buttonConfiguration.imagePlacement = .trailing
var title = AttributedString(T.Restore.howToRestore)
var container = AttributeContainer()
container.font = Theme.Fonts.Text.content
title.setAttributes(container)
buttonConfiguration.attributedTitle = title
buttonConfiguration.titleAlignment = .center
let resetButton = UIButton(configuration: buttonConfiguration)
resetButton.configurationUpdateHandler = { button in
var config = button.configuration
config?.baseForegroundColor = button.isHighlighted ?
Theme.Colors.Controls.active :
Theme.Colors.Controls.highlighed
button.configuration = config
}
view.addSubview(resetButton, with: [
resetButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
resetButton.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: -Theme.Metrics.doubleMargin),
resetButton.leadingAnchor.constraint(
equalTo: view.safeLeadingAnchor,
constant: Theme.Metrics.standardMargin
),
resetButton.trailingAnchor.constraint(
equalTo: view.safeTrailingAnchor,
constant: -Theme.Metrics.standardMargin
)
])
resetButton.addTarget(self, action: #selector(resetAction), for: .touchUpInside)
}
// MARK: - Private
private func prepareDescriptionLabel() {
func prepareDescriptionLabel() {
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.allowsDefaultTighteningForTruncation = true
@ -211,7 +205,7 @@ final class LoginViewController: UIViewController {
titleLabel.textColor = Theme.Colors.Text.main
}
private func decorateButtonText(withButton button: UIButton, text: String) {
func decorateButtonText(withButton button: UIButton, text: String) {
let basicAttributes = [
NSAttributedString.Key.font: Theme.Fonts.Controls.title,
NSAttributedString.Key.foregroundColor: Theme.Colors.Text.subtitle
@ -230,22 +224,61 @@ final class LoginViewController: UIViewController {
button.setAttributedTitle(attributedStringOver, for: .highlighted)
}
@objc private func leftButtonPressed() {
@objc
func leftButtonPressed() {
actionFeedback.impactOccurred()
viewModel.leftButtonPressed()
presenter.onClose()
}
@objc private func deleteButtonPressed() {
@objc
func deleteButtonPressed() {
actionFeedback.impactOccurred()
viewModel.deleteButtonPressed()
presenter.onDelete()
}
@objc private func resetAction() {
viewModel.reset()
@objc
func resetAction() {
presenter.onReset()
}
private func numberButtonPressed(number: Int) {
func numberButtonPressed(number: Int) {
numberFeedback.impactOccurred()
viewModel.numberButtonPressed(number: number)
presenter.onNumberInput(number)
}
func showResetButton() {
var buttonConfiguration = UIButton.Configuration.borderless()
buttonConfiguration.imagePadding = Theme.Metrics.doubleSpacing
buttonConfiguration.image = Asset.infoIcon.image
buttonConfiguration.baseForegroundColor = Theme.Colors.Controls.active
buttonConfiguration.imagePlacement = .trailing
var title = AttributedString(T.Restore.howToRestore)
var container = AttributeContainer()
container.font = Theme.Fonts.Text.content
title.setAttributes(container)
buttonConfiguration.attributedTitle = title
buttonConfiguration.titleAlignment = .center
let resetButton = UIButton(configuration: buttonConfiguration)
resetButton.configurationUpdateHandler = { button in
var config = button.configuration
config?.baseForegroundColor = button.isHighlighted ?
Theme.Colors.Controls.active :
Theme.Colors.Controls.highlighed
button.configuration = config
}
view.addSubview(resetButton, with: [
resetButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
resetButton.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: -Theme.Metrics.doubleMargin),
resetButton.leadingAnchor.constraint(
equalTo: view.safeLeadingAnchor,
constant: Theme.Metrics.standardMargin
),
resetButton.trailingAnchor.constraint(
equalTo: view.safeTrailingAnchor,
constant: -Theme.Metrics.standardMargin
)
])
resetButton.addTarget(self, action: #selector(resetAction), for: .touchUpInside)
}
}

View File

@ -1,29 +0,0 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import Foundation
protocol PINPadDataModelProtocol: AnyObject {
var screenTitle: String { get }
var invalidInput: (() -> Void)? { get set }
var leftButton: String? { get }
var codeLength: Int { get }
func PINGathered(numbers: [Int])
}

View File

@ -1,30 +0,0 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import Foundation
struct PINPadScreenData {
enum TitleType {
case normal
case error
}
let screenTitle: String
let buttonTitle: String?
let titleType: TitleType
}

View File

@ -1,66 +0,0 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import UIKit
final class PINPadVerifyPINDataModel: PINPadDataModelProtocol {
enum VerifyType {
case login
case current
}
typealias CheckCode = ([Int]) -> Bool
var checkCode: CheckCode?
private(set) var codeLength: Int = 0
private let verifyType: VerifyType
private let leftButtonDescription: String?
init (verifyType: VerifyType, leftButtonDescription: String? = nil) {
self.verifyType = verifyType
self.leftButtonDescription = leftButtonDescription
}
func setCodeLenght(_ newCodeLenght: Int) {
codeLength = newCodeLenght
}
var screenTitle: String {
switch verifyType {
case .current:
return T.Security.enterCurrentPin
case .login:
return T.Security.enterPin
}
}
var invalidInput: (() -> Void)?
var leftButton: String? {
leftButtonDescription
}
func PINGathered(numbers: [Int]) {
guard let checkCode else { assertionFailure(); return }
if !checkCode(numbers) {
invalidInput?()
}
}
}

View File

@ -1,35 +0,0 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2023 Two Factor Authentication Service, Inc.
// Contributed by Zbigniew Cisiński. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import UIKit
protocol PINPadViewControllerProtocol: AnyObject {
func prepareScreen(withScreenData data: PINPadScreenData)
func fillDots(count: Int)
func emptyDots()
func shakeDots()
func showLeftButton()
func hideLeftButton()
func showDeleteButton()
func hideRightButton()
func lock(withMessage message: String)
func unlock()
func setDots(number: Int)
func showReset()
}

View File

@ -41,7 +41,7 @@ class PINKeyboardPresenter {
}()
var passcode: String {
numbers.concateToPostionString()
numbers.concateToPositionString()
}
init() {