mirror of
https://github.com/twofas/2fas-ios.git
synced 2024-11-25 03:40:26 +01:00
TF-643 Login module [WIP]
This commit is contained in:
parent
c6117ce7ff
commit
2a28439b88
@ -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 */,
|
||||
|
@ -28,7 +28,7 @@ extension Array {
|
||||
}
|
||||
|
||||
extension Array where Element == Int {
|
||||
func concateToPostionString() -> String {
|
||||
func concateToPositionString() -> String {
|
||||
self.map { String($0) }.reduce("", +)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
186
TwoFAS/TwoFAS/Login/Interactor/LoginModuleInteractor.swift
Normal file
186
TwoFAS/TwoFAS/Login/Interactor/LoginModuleInteractor.swift
Normal 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()
|
||||
// }
|
||||
//}
|
@ -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()
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol LoginCoordinatorDelegate: AnyObject {
|
||||
func authorized()
|
||||
func cancelled()
|
||||
enum LoginType {
|
||||
case login
|
||||
case verify
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
@ -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
|
||||
}
|
@ -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?()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -41,7 +41,7 @@ class PINKeyboardPresenter {
|
||||
}()
|
||||
|
||||
var passcode: String {
|
||||
numbers.concateToPostionString()
|
||||
numbers.concateToPositionString()
|
||||
}
|
||||
|
||||
init() {
|
||||
|
Loading…
Reference in New Issue
Block a user