diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..5180964ce Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e43b0f988 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/KssiusBank.xctestplan b/KssiusBank.xctestplan new file mode 100644 index 000000000..50ff6ef62 --- /dev/null +++ b/KssiusBank.xctestplan @@ -0,0 +1,62 @@ +{ + "configurations" : [ + { + "id" : "3C8794A6-A97D-4D4E-8400-D5052C80AF8A", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : { + "targets" : [ + { + "containerPath" : "container:KssiusBank.xcodeproj", + "identifier" : "DF0BA7C22A3BE5CD00A812F4", + "name" : "KssiusBank" + } + ] + }, + "targetForVariableExpansion" : { + "containerPath" : "container:KssiusBank.xcodeproj", + "identifier" : "DF0BA7C22A3BE5CD00A812F4", + "name" : "KssiusBank" + } + }, + "testTargets" : [ + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:KssiusBank.xcodeproj", + "identifier" : "DF0BA7DA2A3BE5D000A812F4", + "name" : "KssiusBankTests" + } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:KssiusBank.xcodeproj", + "identifier" : "DF0BA7E42A3BE5D000A812F4", + "name" : "KssiusBankUITests" + } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:KssiusBank.xcodeproj", + "identifier" : "DF0BA7DA2A3BE5D000A812F4", + "name" : "KssiusBankTests" + } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:KssiusBank.xcodeproj", + "identifier" : "DF0BA7E42A3BE5D000A812F4", + "name" : "KssiusBankUITests" + } + } + ], + "version" : 1 +} diff --git a/KssiusBank/KssiusBank.xcodeproj/project.pbxproj b/KssiusBank/KssiusBank.xcodeproj/project.pbxproj new file mode 100644 index 000000000..972193580 --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/project.pbxproj @@ -0,0 +1,1614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 53; + objects = { + +/* Begin PBXBuildFile section */ + 0C0A8A4ED390D04AF7F6CDB2 /* Pods_KssiusBank.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 496CC13B26568A1792A4FF06 /* Pods_KssiusBank.framework */; }; + 797591CA33A7351DB85A364A /* Pods_KssiusBankTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77B528124D5466903948FAD8 /* Pods_KssiusBankTests.framework */; }; + D781B2250A37C1AD29E84710 /* Pods_KssiusBank_KssiusBankUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD57596A225C4608F6EBE669 /* Pods_KssiusBank_KssiusBankUITests.framework */; }; + DF0BA7D02A3BE5CE00A812F4 /* KssiusBank.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA7CE2A3BE5CE00A812F4 /* KssiusBank.xcdatamodeld */; }; + DF0BA7F72A3BE65800A812F4 /* NetworkServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC186A2A3BDE29001277A2 /* NetworkServiceTest.swift */; }; + DF0BA7F82A3BE65B00A812F4 /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC186D2A3BDE99001277A2 /* MockURLProtocol.swift */; }; + DF0BA8042A3BE6CA00A812F4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DF0BA7FA2A3BE6C900A812F4 /* Main.storyboard */; }; + DF0BA8072A3BE6CA00A812F4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA7FD2A3BE6C900A812F4 /* SceneDelegate.swift */; }; + DF0BA8092A3BE6CA00A812F4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA7FF2A3BE6C900A812F4 /* AppDelegate.swift */; }; + DF0BA80C2A3BE6CA00A812F4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8022A3BE6CA00A812F4 /* ViewController.swift */; }; + DF0BA80D2A3BE6CA00A812F4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DF0BA8032A3BE6CA00A812F4 /* LaunchScreen.storyboard */; }; + DF0BA80E2A3BE6D400A812F4 /* KssiusBankTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA7FB2A3BE6C900A812F4 /* KssiusBankTests.swift */; }; + DF0BA80F2A3BE6ED00A812F4 /* KssiusBankUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA7FC2A3BE6C900A812F4 /* KssiusBankUITestsLaunchTests.swift */; }; + DF0BA8102A3BE6ED00A812F4 /* KssiusBankUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8002A3BE6CA00A812F4 /* KssiusBankUITests.swift */; }; + DF0BA8112A3BE9F200A812F4 /* UserAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18532A3BD28C001277A2 /* UserAccountModel.swift */; }; + DF0BA8122A3BE9F300A812F4 /* UserAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18532A3BD28C001277A2 /* UserAccountModel.swift */; }; + DF0BA8132A3BEC4700A812F4 /* BankApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18632A3BD4A2001277A2 /* BankApi.swift */; }; + DF0BA8142A3BEC4700A812F4 /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18662A3BDDA0001277A2 /* URLSessionProtocol.swift */; }; + DF0BA8152A3BEC4700A812F4 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18592A3BD423001277A2 /* NetworkService.swift */; }; + DF0BA8162A3BEC4800A812F4 /* BankApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18632A3BD4A2001277A2 /* BankApi.swift */; }; + DF0BA8172A3BEC4800A812F4 /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18662A3BDDA0001277A2 /* URLSessionProtocol.swift */; }; + DF0BA8182A3BEC4800A812F4 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18592A3BD423001277A2 /* NetworkService.swift */; }; + DF0BA8242A3BEE3D00A812F4 /* BankFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC185E2A3BD43B001277A2 /* BankFailure.swift */; }; + DF0BA8252A3BEE3D00A812F4 /* BankFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC185E2A3BD43B001277A2 /* BankFailure.swift */; }; + DF0BA8272A3BEEED00A812F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DF0BA8012A3BE6CA00A812F4 /* Assets.xcassets */; }; + DF0BA82F2A3BF03900A812F4 /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8292A3BF03900A812F4 /* LoginPresenter.swift */; }; + DF0BA8312A3BF03900A812F4 /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82B2A3BF03900A812F4 /* LoginRouter.swift */; }; + DF0BA8322A3BF03900A812F4 /* LoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82C2A3BF03900A812F4 /* LoginModels.swift */; }; + DF0BA8332A3BF03900A812F4 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82D2A3BF03900A812F4 /* LoginViewController.swift */; }; + DF0BA8342A3BF03900A812F4 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82E2A3BF03900A812F4 /* LoginInteractor.swift */; }; + DF0BA8362A3BF05800A812F4 /* UserWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18562A3BD314001277A2 /* UserWorker.swift */; }; + DF0BA8372A3BF05800A812F4 /* UserWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC18562A3BD314001277A2 /* UserWorker.swift */; }; + DF0BA8392A3BFB6100A812F4 /* Seeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8382A3BFB6100A812F4 /* Seeds.swift */; }; + DF0BA8432A3C7AAD00A812F4 /* AuthenticationRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8422A3C7AAD00A812F4 /* AuthenticationRepositoryProtocol.swift */; }; + DF0BA8472A3C7C0200A812F4 /* AutenticationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8462A3C7C0200A812F4 /* AutenticationRepository.swift */; }; + DF0BA8492A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8482A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift */; }; + DF0BA84A2A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8482A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift */; }; + DF0BA8552A3C836A00A812F4 /* StatementsServiceDatasourceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8532A3C836A00A812F4 /* StatementsServiceDatasourceTest.swift */; }; + DF0BA85B2A3C976900A812F4 /* LoginRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA85A2A3C976900A812F4 /* LoginRequestModel.swift */; }; + DF0BA85C2A3C976900A812F4 /* LoginRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA85A2A3C976900A812F4 /* LoginRequestModel.swift */; }; + DF0BA85E2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA85D2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift */; }; + DF0BA85F2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA85D2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift */; }; + DF0BA8622A3C998100A812F4 /* AuthenticationRepositoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8602A3C998100A812F4 /* AuthenticationRepositoryTest.swift */; }; + DF0BA8652A3C9A3E00A812F4 /* MockSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8632A3C9A3E00A812F4 /* MockSession.swift */; }; + DF0BA8692A3C9BA900A812F4 /* MockAutenticationServiceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8672A3C9BA900A812F4 /* MockAutenticationServiceDatasource.swift */; }; + DF0BA86A2A3C9CB700A812F4 /* AutenticationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8462A3C7C0200A812F4 /* AutenticationRepository.swift */; }; + DF0BA86C2A3CA9AB00A812F4 /* UserFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA86B2A3CA9AB00A812F4 /* UserFailure.swift */; }; + DF0BA86D2A3CA9AB00A812F4 /* UserFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA86B2A3CA9AB00A812F4 /* UserFailure.swift */; }; + DF0BA86F2A3CAC7D00A812F4 /* CommonValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA86E2A3CAC7D00A812F4 /* CommonValidator.swift */; }; + DF0BA8702A3CAC7D00A812F4 /* CommonValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA86E2A3CAC7D00A812F4 /* CommonValidator.swift */; }; + DF0BA8762A3CB91B00A812F4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF0BA8792A3CB91B00A812F4 /* Localizable.strings */; }; + DF0BA8772A3CB91B00A812F4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF0BA8792A3CB91B00A812F4 /* Localizable.strings */; }; + DF0BA87E2A3CC4ED00A812F4 /* XCAssets+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA87C2A3CC4ED00A812F4 /* XCAssets+Generated.swift */; }; + DF0BA87F2A3CC4ED00A812F4 /* Strings+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA87D2A3CC4ED00A812F4 /* Strings+Generated.swift */; }; + DF0BA8802A3CC4F300A812F4 /* Strings+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA87D2A3CC4ED00A812F4 /* Strings+Generated.swift */; }; + DF0BA8812A3CC4F500A812F4 /* XCAssets+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA87C2A3CC4ED00A812F4 /* XCAssets+Generated.swift */; }; + DF4624C62A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624C52A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift */; }; + DF4624C72A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624C52A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift */; }; + DF4624C92A3CF0D700CBC35D /* AutenticationLocalDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624C82A3CF0D700CBC35D /* AutenticationLocalDatasource.swift */; }; + DF4624CA2A3CF0D700CBC35D /* AutenticationLocalDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624C82A3CF0D700CBC35D /* AutenticationLocalDatasource.swift */; }; + DF4624CC2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624CB2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift */; }; + DF4624CD2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624CB2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift */; }; + DF4624CF2A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624CE2A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift */; }; + DF4624D02A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624CE2A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift */; }; + DF4624D52A3D080600CBC35D /* AuthenticationLocalRepositoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624D42A3D080600CBC35D /* AuthenticationLocalRepositoryTest.swift */; }; + DF4624D82A3D091400CBC35D /* MockAutenticationLocalDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624D72A3D091400CBC35D /* MockAutenticationLocalDatasourceProtocol.swift */; }; + DF4624DB2A3D0A8400CBC35D /* UserWorkersTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624DA2A3D0A8400CBC35D /* UserWorkersTest.swift */; }; + DF4624DE2A3D0DF000CBC35D /* MockAuthenticationLocalRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624DD2A3D0DF000CBC35D /* MockAuthenticationLocalRepository.swift */; }; + DF4624E22A3D1E3300CBC35D /* LoginWorkerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624E12A3D1E3300CBC35D /* LoginWorkerTest.swift */; }; + DF4624E52A3D1F2E00CBC35D /* MockAuthenticationRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4624E42A3D1F2E00CBC35D /* MockAuthenticationRepositoryProtocol.swift */; }; + DF4624E62A3D226400CBC35D /* AuthenticationRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8422A3C7AAD00A812F4 /* AuthenticationRepositoryProtocol.swift */; }; + DF46E1D92A4076BA00FD344A /* StatementCellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF46E1D82A4076BA00FD344A /* StatementCellTest.swift */; }; + DF7D3F8D2A3CE0A90009770F /* CommonValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF7D3F8C2A3CE0A90009770F /* CommonValidatorTest.swift */; }; + DF8735A02A3F2E17004818BC /* Date+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF87359F2A3F2E17004818BC /* Date+extensions.swift */; }; + DF8735A12A3F2F74004818BC /* Date+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF87359F2A3F2E17004818BC /* Date+extensions.swift */; }; + DF8735A32A3F53BA004818BC /* String+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735A22A3F53BA004818BC /* String+extensions.swift */; }; + DF8735A42A3F53BA004818BC /* String+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735A22A3F53BA004818BC /* String+extensions.swift */; }; + DF8735A92A3F6C42004818BC /* String+extensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735A82A3F6C42004818BC /* String+extensionsTest.swift */; }; + DF8735AC2A3F796D004818BC /* Date+extensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735AB2A3F796D004818BC /* Date+extensionsTest.swift */; }; + DF8735AF2A3F7F20004818BC /* HomeWorkerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735AE2A3F7F20004818BC /* HomeWorkerTest.swift */; }; + DF8735B22A3F80D9004818BC /* MockStatementsRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735B12A3F7F90004818BC /* MockStatementsRepositoryProtocol.swift */; }; + DF8735B42A3F8265004818BC /* HomeInteractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735B32A3F8265004818BC /* HomeInteractorTest.swift */; }; + DF8735B62A3F8CC9004818BC /* HomePresenterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735B52A3F8CC9004818BC /* HomePresenterTest.swift */; }; + DF8735B82A3F921C004818BC /* HomeViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735B72A3F921C004818BC /* HomeViewControllerTest.swift */; }; + DF8735BA2A3F9623004818BC /* LabelSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735B92A3F9623004818BC /* LabelSpy.swift */; }; + DF8735BC2A3F965F004818BC /* TableViewSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8735BB2A3F965F004818BC /* TableViewSpy.swift */; }; + DFC4E8312A3D3C13004B6718 /* LoginViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8302A3D3C13004B6718 /* LoginViewControllerTest.swift */; }; + DFC4E8342A3D41A0004B6718 /* TextFieldSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8332A3D41A0004B6718 /* TextFieldSpy.swift */; }; + DFC4E8372A3DD709004B6718 /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8362A3DD709004B6718 /* DefaultTextField.swift */; }; + DFC4E83C2A3DDA2F004B6718 /* UIColor+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E83B2A3DDA2F004B6718 /* UIColor+extensions.swift */; }; + DFC4E83D2A3DDA30004B6718 /* UIColor+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E83B2A3DDA2F004B6718 /* UIColor+extensions.swift */; }; + DFC4E83F2A3DDC03004B6718 /* DefaultButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E83E2A3DDC03004B6718 /* DefaultButton.swift */; }; + DFC4E8402A3DDC03004B6718 /* DefaultButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E83E2A3DDC03004B6718 /* DefaultButton.swift */; }; + DFC4E8422A3DDD19004B6718 /* UIFont+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8412A3DDD19004B6718 /* UIFont+extensions.swift */; }; + DFC4E8432A3DDD19004B6718 /* UIFont+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8412A3DDD19004B6718 /* UIFont+extensions.swift */; }; + DFC4E8452A3DE51D004B6718 /* UIResponder+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8442A3DE51D004B6718 /* UIResponder+extensions.swift */; }; + DFC4E8462A3DE51D004B6718 /* UIResponder+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8442A3DE51D004B6718 /* UIResponder+extensions.swift */; }; + DFC4E84D2A3E132B004B6718 /* HomePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8472A3E132B004B6718 /* HomePresenter.swift */; }; + DFC4E84E2A3E132B004B6718 /* HomePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8472A3E132B004B6718 /* HomePresenter.swift */; }; + DFC4E84F2A3E132B004B6718 /* HomeWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8482A3E132B004B6718 /* HomeWorker.swift */; }; + DFC4E8502A3E132B004B6718 /* HomeWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8482A3E132B004B6718 /* HomeWorker.swift */; }; + DFC4E8512A3E132B004B6718 /* HomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8492A3E132B004B6718 /* HomeRouter.swift */; }; + DFC4E8522A3E132B004B6718 /* HomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8492A3E132B004B6718 /* HomeRouter.swift */; }; + DFC4E8532A3E132B004B6718 /* HomeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E84A2A3E132B004B6718 /* HomeModels.swift */; }; + DFC4E8542A3E132B004B6718 /* HomeModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E84A2A3E132B004B6718 /* HomeModels.swift */; }; + DFC4E8552A3E132B004B6718 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E84B2A3E132B004B6718 /* HomeViewController.swift */; }; + DFC4E8562A3E132B004B6718 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E84B2A3E132B004B6718 /* HomeViewController.swift */; }; + DFC4E8572A3E132B004B6718 /* HomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E84C2A3E132B004B6718 /* HomeInteractor.swift */; }; + DFC4E8582A3E132B004B6718 /* HomeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E84C2A3E132B004B6718 /* HomeInteractor.swift */; }; + DFC4E85D2A3E3843004B6718 /* StatementsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E85C2A3E3843004B6718 /* StatementsModel.swift */; }; + DFC4E85E2A3E3843004B6718 /* StatementsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E85C2A3E3843004B6718 /* StatementsModel.swift */; }; + DFC4E86C2A3E445F004B6718 /* StatementsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8622A3E3AA6004B6718 /* StatementsRepository.swift */; }; + DFC4E86D2A3E445F004B6718 /* StatementsRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8612A3E3AA5004B6718 /* StatementsRepositoryProtocol.swift */; }; + DFC4E86E2A3E445F004B6718 /* StatementsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8622A3E3AA6004B6718 /* StatementsRepository.swift */; }; + DFC4E86F2A3E445F004B6718 /* StatementsRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8612A3E3AA5004B6718 /* StatementsRepositoryProtocol.swift */; }; + DFC4E8702A3E4463004B6718 /* StatementsServiceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8642A3E3ABA004B6718 /* StatementsServiceDatasource.swift */; }; + DFC4E8712A3E4463004B6718 /* StatementsServiceDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8632A3E3ABA004B6718 /* StatementsServiceDatasourceProtocol.swift */; }; + DFC4E8722A3E4463004B6718 /* StatementsServiceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8642A3E3ABA004B6718 /* StatementsServiceDatasource.swift */; }; + DFC4E8732A3E4463004B6718 /* StatementsServiceDatasourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8632A3E3ABA004B6718 /* StatementsServiceDatasourceProtocol.swift */; }; + DFC4E8742A3E478C004B6718 /* StatementsFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8662A3E3BD4004B6718 /* StatementsFailure.swift */; }; + DFC4E8752A3E478C004B6718 /* StatementsFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8662A3E3BD4004B6718 /* StatementsFailure.swift */; }; + DFC4E8762A3E48CB004B6718 /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8362A3DD709004B6718 /* DefaultTextField.swift */; }; + DFC4E8792A3E4E61004B6718 /* AutenticationLocalDatasourceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8772A3E4E61004B6718 /* AutenticationLocalDatasourceTest.swift */; }; + DFC4E87B2A3E4ED8004B6718 /* AutenticationServiceDatasourceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E87A2A3E4ED8004B6718 /* AutenticationServiceDatasourceTest.swift */; }; + DFC4E87F2A3E545C004B6718 /* StatementsRepositoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E87C2A3E530D004B6718 /* StatementsRepositoryTest.swift */; }; + DFC4E8802A3E5461004B6718 /* MockStatementsServiceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E87E2A3E5377004B6718 /* MockStatementsServiceDatasource.swift */; }; + DFC4E8832A3E7D9C004B6718 /* StatementCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8822A3E7D9C004B6718 /* StatementCell.swift */; }; + DFC4E8842A3E7D9C004B6718 /* StatementCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC4E8822A3E7D9C004B6718 /* StatementCell.swift */; }; + DFF0843A2A3D24F500672477 /* LoginInteractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF084392A3D24F500672477 /* LoginInteractorTest.swift */; }; + DFF084412A3D2B2700672477 /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82B2A3BF03900A812F4 /* LoginRouter.swift */; }; + DFF084422A3D2B2700672477 /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA8292A3BF03900A812F4 /* LoginPresenter.swift */; }; + DFF084432A3D2B2700672477 /* LoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82C2A3BF03900A812F4 /* LoginModels.swift */; }; + DFF084442A3D2B2700672477 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82E2A3BF03900A812F4 /* LoginInteractor.swift */; }; + DFF084462A3D2B2700672477 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82D2A3BF03900A812F4 /* LoginViewController.swift */; }; + DFF084472A3D2C5600672477 /* LoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82A2A3BF03900A812F4 /* LoginWorker.swift */; }; + DFF084482A3D2C5700672477 /* LoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0BA82A2A3BF03900A812F4 /* LoginWorker.swift */; }; + DFF0844A2A3D38AF00672477 /* LoginPresenterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF084492A3D38AF00672477 /* LoginPresenterTest.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + DF0BA7DC2A3BE5D000A812F4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DFEC18022A3BCFF9001277A2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DF0BA7C22A3BE5CD00A812F4; + remoteInfo = KssiusBank; + }; + DF0BA7E62A3BE5D100A812F4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DFEC18022A3BCFF9001277A2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DF0BA7C22A3BE5CD00A812F4; + remoteInfo = KssiusBank; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 07786122957DD35BED1B07AA /* Pods-KssiusBank-KssiusBankUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KssiusBank-KssiusBankUITests.debug.xcconfig"; path = "Target Support Files/Pods-KssiusBank-KssiusBankUITests/Pods-KssiusBank-KssiusBankUITests.debug.xcconfig"; sourceTree = ""; }; + 1F35001EECB5F430346C58D9 /* Pods-KssiusBank-KssiusBankUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KssiusBank-KssiusBankUITests.release.xcconfig"; path = "Target Support Files/Pods-KssiusBank-KssiusBankUITests/Pods-KssiusBank-KssiusBankUITests.release.xcconfig"; sourceTree = ""; }; + 496CC13B26568A1792A4FF06 /* Pods_KssiusBank.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KssiusBank.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 67D965C4039A5F4C8DAF287A /* Pods-KssiusBankTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KssiusBankTests.debug.xcconfig"; path = "Target Support Files/Pods-KssiusBankTests/Pods-KssiusBankTests.debug.xcconfig"; sourceTree = ""; }; + 77B528124D5466903948FAD8 /* Pods_KssiusBankTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KssiusBankTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9E03F5BEAD121A2725CAC1CF /* Pods-KssiusBank.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KssiusBank.release.xcconfig"; path = "Target Support Files/Pods-KssiusBank/Pods-KssiusBank.release.xcconfig"; sourceTree = ""; }; + ACDDEAB26AE6197E2B15A29D /* Pods-KssiusBankTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KssiusBankTests.release.xcconfig"; path = "Target Support Files/Pods-KssiusBankTests/Pods-KssiusBankTests.release.xcconfig"; sourceTree = ""; }; + B0965131F0D770E77CA79752 /* Pods-KssiusBank.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KssiusBank.debug.xcconfig"; path = "Target Support Files/Pods-KssiusBank/Pods-KssiusBank.debug.xcconfig"; sourceTree = ""; }; + DF0BA7C32A3BE5CD00A812F4 /* KssiusBank.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KssiusBank.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DF0BA7CF2A3BE5CE00A812F4 /* KssiusBank.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = KssiusBank.xcdatamodel; sourceTree = ""; }; + DF0BA7DB2A3BE5D000A812F4 /* KssiusBankTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KssiusBankTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DF0BA7E52A3BE5D100A812F4 /* KssiusBankUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KssiusBankUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DF0BA7FA2A3BE6C900A812F4 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + DF0BA7FB2A3BE6C900A812F4 /* KssiusBankTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KssiusBankTests.swift; sourceTree = ""; }; + DF0BA7FC2A3BE6C900A812F4 /* KssiusBankUITestsLaunchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KssiusBankUITestsLaunchTests.swift; sourceTree = ""; }; + DF0BA7FD2A3BE6C900A812F4 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + DF0BA7FE2A3BE6C900A812F4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DF0BA7FF2A3BE6C900A812F4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + DF0BA8002A3BE6CA00A812F4 /* KssiusBankUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KssiusBankUITests.swift; sourceTree = ""; }; + DF0BA8012A3BE6CA00A812F4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + DF0BA8022A3BE6CA00A812F4 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + DF0BA8032A3BE6CA00A812F4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + DF0BA8292A3BF03900A812F4 /* LoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; + DF0BA82A2A3BF03900A812F4 /* LoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorker.swift; sourceTree = ""; }; + DF0BA82B2A3BF03900A812F4 /* LoginRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouter.swift; sourceTree = ""; }; + DF0BA82C2A3BF03900A812F4 /* LoginModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginModels.swift; sourceTree = ""; }; + DF0BA82D2A3BF03900A812F4 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + DF0BA82E2A3BF03900A812F4 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = ""; }; + DF0BA8382A3BFB6100A812F4 /* Seeds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Seeds.swift; sourceTree = ""; }; + DF0BA8422A3C7AAD00A812F4 /* AuthenticationRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationRepositoryProtocol.swift; sourceTree = ""; }; + DF0BA8462A3C7C0200A812F4 /* AutenticationRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationRepository.swift; sourceTree = ""; }; + DF0BA8482A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationServiceDatasource.swift; sourceTree = ""; }; + DF0BA8532A3C836A00A812F4 /* StatementsServiceDatasourceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsServiceDatasourceTest.swift; sourceTree = ""; }; + DF0BA85A2A3C976900A812F4 /* LoginRequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequestModel.swift; sourceTree = ""; }; + DF0BA85D2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceDatasourceProtocol.swift; sourceTree = ""; }; + DF0BA8602A3C998100A812F4 /* AuthenticationRepositoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationRepositoryTest.swift; sourceTree = ""; }; + DF0BA8632A3C9A3E00A812F4 /* MockSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSession.swift; sourceTree = ""; }; + DF0BA8672A3C9BA900A812F4 /* MockAutenticationServiceDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutenticationServiceDatasource.swift; sourceTree = ""; }; + DF0BA86B2A3CA9AB00A812F4 /* UserFailure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFailure.swift; sourceTree = ""; }; + DF0BA86E2A3CAC7D00A812F4 /* CommonValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonValidator.swift; sourceTree = ""; }; + DF0BA8782A3CB91B00A812F4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + DF0BA87A2A3CC2B500A812F4 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + DF0BA87C2A3CC4ED00A812F4 /* XCAssets+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCAssets+Generated.swift"; sourceTree = ""; }; + DF0BA87D2A3CC4ED00A812F4 /* Strings+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Strings+Generated.swift"; sourceTree = ""; }; + DF4624C52A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationLocalDatasourceProtocol.swift; sourceTree = ""; }; + DF4624C82A3CF0D700CBC35D /* AutenticationLocalDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationLocalDatasource.swift; sourceTree = ""; }; + DF4624CB2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationLocalRepository.swift; sourceTree = ""; }; + DF4624CE2A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationLocalRepositoryProtocol.swift; sourceTree = ""; }; + DF4624D42A3D080600CBC35D /* AuthenticationLocalRepositoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationLocalRepositoryTest.swift; sourceTree = ""; }; + DF4624D72A3D091400CBC35D /* MockAutenticationLocalDatasourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutenticationLocalDatasourceProtocol.swift; sourceTree = ""; }; + DF4624DA2A3D0A8400CBC35D /* UserWorkersTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserWorkersTest.swift; sourceTree = ""; }; + DF4624DD2A3D0DF000CBC35D /* MockAuthenticationLocalRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationLocalRepository.swift; sourceTree = ""; }; + DF4624E12A3D1E3300CBC35D /* LoginWorkerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWorkerTest.swift; sourceTree = ""; }; + DF4624E42A3D1F2E00CBC35D /* MockAuthenticationRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationRepositoryProtocol.swift; sourceTree = ""; }; + DF46E1D82A4076BA00FD344A /* StatementCellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementCellTest.swift; sourceTree = ""; }; + DF7D3F8C2A3CE0A90009770F /* CommonValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonValidatorTest.swift; sourceTree = ""; }; + DF87359F2A3F2E17004818BC /* Date+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+extensions.swift"; sourceTree = ""; }; + DF8735A22A3F53BA004818BC /* String+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+extensions.swift"; sourceTree = ""; }; + DF8735A82A3F6C42004818BC /* String+extensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+extensionsTest.swift"; sourceTree = ""; }; + DF8735AB2A3F796D004818BC /* Date+extensionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+extensionsTest.swift"; sourceTree = ""; }; + DF8735AE2A3F7F20004818BC /* HomeWorkerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeWorkerTest.swift; sourceTree = ""; }; + DF8735B12A3F7F90004818BC /* MockStatementsRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStatementsRepositoryProtocol.swift; sourceTree = ""; }; + DF8735B32A3F8265004818BC /* HomeInteractorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeInteractorTest.swift; sourceTree = ""; }; + DF8735B52A3F8CC9004818BC /* HomePresenterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePresenterTest.swift; sourceTree = ""; }; + DF8735B72A3F921C004818BC /* HomeViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewControllerTest.swift; sourceTree = ""; }; + DF8735B92A3F9623004818BC /* LabelSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSpy.swift; sourceTree = ""; }; + DF8735BB2A3F965F004818BC /* TableViewSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSpy.swift; sourceTree = ""; }; + DFC4E8302A3D3C13004B6718 /* LoginViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewControllerTest.swift; sourceTree = ""; }; + DFC4E8332A3D41A0004B6718 /* TextFieldSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldSpy.swift; sourceTree = ""; }; + DFC4E8362A3DD709004B6718 /* DefaultTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTextField.swift; sourceTree = ""; }; + DFC4E83B2A3DDA2F004B6718 /* UIColor+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+extensions.swift"; sourceTree = ""; }; + DFC4E83E2A3DDC03004B6718 /* DefaultButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultButton.swift; sourceTree = ""; }; + DFC4E8412A3DDD19004B6718 /* UIFont+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+extensions.swift"; sourceTree = ""; }; + DFC4E8442A3DE51D004B6718 /* UIResponder+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIResponder+extensions.swift"; sourceTree = ""; }; + DFC4E8472A3E132B004B6718 /* HomePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePresenter.swift; sourceTree = ""; }; + DFC4E8482A3E132B004B6718 /* HomeWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeWorker.swift; sourceTree = ""; }; + DFC4E8492A3E132B004B6718 /* HomeRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRouter.swift; sourceTree = ""; }; + DFC4E84A2A3E132B004B6718 /* HomeModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModels.swift; sourceTree = ""; }; + DFC4E84B2A3E132B004B6718 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + DFC4E84C2A3E132B004B6718 /* HomeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeInteractor.swift; sourceTree = ""; }; + DFC4E85C2A3E3843004B6718 /* StatementsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsModel.swift; sourceTree = ""; }; + DFC4E8612A3E3AA5004B6718 /* StatementsRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsRepositoryProtocol.swift; sourceTree = ""; }; + DFC4E8622A3E3AA6004B6718 /* StatementsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsRepository.swift; sourceTree = ""; }; + DFC4E8632A3E3ABA004B6718 /* StatementsServiceDatasourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsServiceDatasourceProtocol.swift; sourceTree = ""; }; + DFC4E8642A3E3ABA004B6718 /* StatementsServiceDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsServiceDatasource.swift; sourceTree = ""; }; + DFC4E8662A3E3BD4004B6718 /* StatementsFailure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsFailure.swift; sourceTree = ""; }; + DFC4E8772A3E4E61004B6718 /* AutenticationLocalDatasourceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationLocalDatasourceTest.swift; sourceTree = ""; }; + DFC4E87A2A3E4ED8004B6718 /* AutenticationServiceDatasourceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutenticationServiceDatasourceTest.swift; sourceTree = ""; }; + DFC4E87C2A3E530D004B6718 /* StatementsRepositoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementsRepositoryTest.swift; sourceTree = ""; }; + DFC4E87E2A3E5377004B6718 /* MockStatementsServiceDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStatementsServiceDatasource.swift; sourceTree = ""; }; + DFC4E8822A3E7D9C004B6718 /* StatementCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementCell.swift; sourceTree = ""; }; + DFEC18532A3BD28C001277A2 /* UserAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAccountModel.swift; sourceTree = ""; }; + DFEC18562A3BD314001277A2 /* UserWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserWorker.swift; sourceTree = ""; }; + DFEC18592A3BD423001277A2 /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; + DFEC185E2A3BD43B001277A2 /* BankFailure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankFailure.swift; sourceTree = ""; }; + DFEC18632A3BD4A2001277A2 /* BankApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankApi.swift; sourceTree = ""; }; + DFEC18662A3BDDA0001277A2 /* URLSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = ""; }; + DFEC186A2A3BDE29001277A2 /* NetworkServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceTest.swift; sourceTree = ""; }; + DFEC186D2A3BDE99001277A2 /* MockURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = ""; }; + DFF084392A3D24F500672477 /* LoginInteractorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractorTest.swift; sourceTree = ""; }; + DFF084492A3D38AF00672477 /* LoginPresenterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenterTest.swift; sourceTree = ""; }; + FD57596A225C4608F6EBE669 /* Pods_KssiusBank_KssiusBankUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KssiusBank_KssiusBankUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + DF0BA7C02A3BE5CD00A812F4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0C0A8A4ED390D04AF7F6CDB2 /* Pods_KssiusBank.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DF0BA7D82A3BE5D000A812F4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 797591CA33A7351DB85A364A /* Pods_KssiusBankTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DF0BA7E22A3BE5D000A812F4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D781B2250A37C1AD29E84710 /* Pods_KssiusBank_KssiusBankUITests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8F1F1C114C91695D50430106 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 496CC13B26568A1792A4FF06 /* Pods_KssiusBank.framework */, + FD57596A225C4608F6EBE669 /* Pods_KssiusBank_KssiusBankUITests.framework */, + 77B528124D5466903948FAD8 /* Pods_KssiusBankTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + DF0BA7C42A3BE5CE00A812F4 /* KssiusBank */ = { + isa = PBXGroup; + children = ( + DF87359E2A3F2DEC004818BC /* Extensions */, + DFC4E8352A3DD57E004B6718 /* DesignSystem */, + DF0BA87B2A3CC4D300A812F4 /* Generated */, + DF0BA8722A3CB89F00A812F4 /* Resources */, + DF0BA8452A3C7B7A00A812F4 /* Core */, + DFEC18422A3BD060001277A2 /* Services */, + DFEC18412A3BD051001277A2 /* Workers */, + DFEC18402A3BD044001277A2 /* Scenes */, + DF0BA7FF2A3BE6C900A812F4 /* AppDelegate.swift */, + DF0BA8012A3BE6CA00A812F4 /* Assets.xcassets */, + DF0BA7FE2A3BE6C900A812F4 /* Info.plist */, + DF0BA8032A3BE6CA00A812F4 /* LaunchScreen.storyboard */, + DF0BA7FA2A3BE6C900A812F4 /* Main.storyboard */, + DF0BA7FD2A3BE6C900A812F4 /* SceneDelegate.swift */, + DF0BA8022A3BE6CA00A812F4 /* ViewController.swift */, + DF0BA7CE2A3BE5CE00A812F4 /* KssiusBank.xcdatamodeld */, + ); + path = KssiusBank; + sourceTree = ""; + }; + DF0BA7DE2A3BE5D000A812F4 /* KssiusBankTests */ = { + isa = PBXGroup; + children = ( + DF0BA84F2A3C830700A812F4 /* Core */, + DF8735AA2A3F793C004818BC /* Extensions */, + DF0BA7FB2A3BE6C900A812F4 /* KssiusBankTests.swift */, + DF0BA8632A3C9A3E00A812F4 /* MockSession.swift */, + DF4624DF2A3D1E0900CBC35D /* Scenes */, + DF0BA8382A3BFB6100A812F4 /* Seeds.swift */, + DFEC18692A3BDE0D001277A2 /* Services */, + DFC4E8322A3D4186004B6718 /* UIKitSpy */, + DF4624D92A3D0A6A00CBC35D /* Workers */, + ); + path = KssiusBankTests; + sourceTree = ""; + }; + DF0BA7E82A3BE5D100A812F4 /* KssiusBankUITests */ = { + isa = PBXGroup; + children = ( + DF0BA8002A3BE6CA00A812F4 /* KssiusBankUITests.swift */, + DF0BA7FC2A3BE6C900A812F4 /* KssiusBankUITestsLaunchTests.swift */, + ); + path = KssiusBankUITests; + sourceTree = ""; + }; + DF0BA8352A3BF04A00A812F4 /* Autentication */ = { + isa = PBXGroup; + children = ( + DF0BA8292A3BF03900A812F4 /* LoginPresenter.swift */, + DF0BA82A2A3BF03900A812F4 /* LoginWorker.swift */, + DF0BA82B2A3BF03900A812F4 /* LoginRouter.swift */, + DF0BA82C2A3BF03900A812F4 /* LoginModels.swift */, + DF0BA82D2A3BF03900A812F4 /* LoginViewController.swift */, + DF0BA82E2A3BF03900A812F4 /* LoginInteractor.swift */, + ); + path = Autentication; + sourceTree = ""; + }; + DF0BA83A2A3C796300A812F4 /* Data */ = { + isa = PBXGroup; + children = ( + DF0BA83C2A3C799300A812F4 /* Repositories */, + DF0BA83B2A3C798800A812F4 /* Datasources */, + DFEC18432A3BD068001277A2 /* Models */, + DF0BA86B2A3CA9AB00A812F4 /* UserFailure.swift */, + ); + path = Data; + sourceTree = ""; + }; + DF0BA83B2A3C798800A812F4 /* Datasources */ = { + isa = PBXGroup; + children = ( + DF0BA8482A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift */, + DF0BA85D2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift */, + DF4624C52A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift */, + DF4624C82A3CF0D700CBC35D /* AutenticationLocalDatasource.swift */, + ); + path = Datasources; + sourceTree = ""; + }; + DF0BA83C2A3C799300A812F4 /* Repositories */ = { + isa = PBXGroup; + children = ( + DF0BA8422A3C7AAD00A812F4 /* AuthenticationRepositoryProtocol.swift */, + DF0BA8462A3C7C0200A812F4 /* AutenticationRepository.swift */, + DF4624CB2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift */, + DF4624CE2A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + DF0BA8452A3C7B7A00A812F4 /* Core */ = { + isa = PBXGroup; + children = ( + DFC4E85A2A3E3822004B6718 /* Statements */, + DFC4E8662A3E3BD4004B6718 /* StatementsFailure.swift */, + DFEC185C2A3BD43B001277A2 /* Common */, + DF4624C42A3CF06200CBC35D /* User */, + ); + path = Core; + sourceTree = ""; + }; + DF0BA84F2A3C830700A812F4 /* Core */ = { + isa = PBXGroup; + children = ( + DFC4E8682A3E43AF004B6718 /* Statements */, + DFC4E8672A3E43A4004B6718 /* User */, + ); + path = Core; + sourceTree = ""; + }; + DF0BA8502A3C831400A812F4 /* Data */ = { + isa = PBXGroup; + children = ( + DF0BA8512A3C832D00A812F4 /* Datasources */, + ); + path = Data; + sourceTree = ""; + }; + DF0BA8512A3C832D00A812F4 /* Datasources */ = { + isa = PBXGroup; + children = ( + DFC4E8772A3E4E61004B6718 /* AutenticationLocalDatasourceTest.swift */, + DFC4E87A2A3E4ED8004B6718 /* AutenticationServiceDatasourceTest.swift */, + ); + path = Datasources; + sourceTree = ""; + }; + DF0BA8522A3C833500A812F4 /* Repositories */ = { + isa = PBXGroup; + children = ( + DF4624D62A3D080E00CBC35D /* Mock */, + DF0BA8602A3C998100A812F4 /* AuthenticationRepositoryTest.swift */, + DF4624D42A3D080600CBC35D /* AuthenticationLocalRepositoryTest.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + DF0BA8722A3CB89F00A812F4 /* Resources */ = { + isa = PBXGroup; + children = ( + DF0BA8792A3CB91B00A812F4 /* Localizable.strings */, + ); + path = Resources; + sourceTree = ""; + }; + DF0BA87B2A3CC4D300A812F4 /* Generated */ = { + isa = PBXGroup; + children = ( + DF0BA87D2A3CC4ED00A812F4 /* Strings+Generated.swift */, + DF0BA87C2A3CC4ED00A812F4 /* XCAssets+Generated.swift */, + ); + path = Generated; + sourceTree = ""; + }; + DF4624C42A3CF06200CBC35D /* User */ = { + isa = PBXGroup; + children = ( + DF0BA83A2A3C796300A812F4 /* Data */, + ); + path = User; + sourceTree = ""; + }; + DF4624D62A3D080E00CBC35D /* Mock */ = { + isa = PBXGroup; + children = ( + DF0BA8672A3C9BA900A812F4 /* MockAutenticationServiceDatasource.swift */, + DF4624D72A3D091400CBC35D /* MockAutenticationLocalDatasourceProtocol.swift */, + ); + path = Mock; + sourceTree = ""; + }; + DF4624D92A3D0A6A00CBC35D /* Workers */ = { + isa = PBXGroup; + children = ( + DF4624DC2A3D0DD500CBC35D /* Mock */, + DF4624DA2A3D0A8400CBC35D /* UserWorkersTest.swift */, + ); + path = Workers; + sourceTree = ""; + }; + DF4624DC2A3D0DD500CBC35D /* Mock */ = { + isa = PBXGroup; + children = ( + DF4624DD2A3D0DF000CBC35D /* MockAuthenticationLocalRepository.swift */, + ); + path = Mock; + sourceTree = ""; + }; + DF4624DF2A3D1E0900CBC35D /* Scenes */ = { + isa = PBXGroup; + children = ( + DF8735AD2A3F7EFE004818BC /* Home */, + DF4624E02A3D1E1300CBC35D /* Autentication */, + ); + path = Scenes; + sourceTree = ""; + }; + DF4624E02A3D1E1300CBC35D /* Autentication */ = { + isa = PBXGroup; + children = ( + DF4624E32A3D1EEA00CBC35D /* Mock */, + DF4624E12A3D1E3300CBC35D /* LoginWorkerTest.swift */, + DFF084392A3D24F500672477 /* LoginInteractorTest.swift */, + DFF084492A3D38AF00672477 /* LoginPresenterTest.swift */, + DFC4E8302A3D3C13004B6718 /* LoginViewControllerTest.swift */, + ); + path = Autentication; + sourceTree = ""; + }; + DF4624E32A3D1EEA00CBC35D /* Mock */ = { + isa = PBXGroup; + children = ( + DF4624E42A3D1F2E00CBC35D /* MockAuthenticationRepositoryProtocol.swift */, + ); + path = Mock; + sourceTree = ""; + }; + DF46E1D72A4076A300FD344A /* Cell */ = { + isa = PBXGroup; + children = ( + DF46E1D82A4076BA00FD344A /* StatementCellTest.swift */, + ); + path = Cell; + sourceTree = ""; + }; + DF7D3F8B2A3CE0880009770F /* Common */ = { + isa = PBXGroup; + children = ( + DF7D3F8C2A3CE0A90009770F /* CommonValidatorTest.swift */, + ); + path = Common; + sourceTree = ""; + }; + DF87359E2A3F2DEC004818BC /* Extensions */ = { + isa = PBXGroup; + children = ( + DF87359F2A3F2E17004818BC /* Date+extensions.swift */, + DF8735A22A3F53BA004818BC /* String+extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + DF8735AA2A3F793C004818BC /* Extensions */ = { + isa = PBXGroup; + children = ( + DF8735A82A3F6C42004818BC /* String+extensionsTest.swift */, + DF8735AB2A3F796D004818BC /* Date+extensionsTest.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + DF8735AD2A3F7EFE004818BC /* Home */ = { + isa = PBXGroup; + children = ( + DF46E1D72A4076A300FD344A /* Cell */, + DF8735B02A3F7F84004818BC /* Mock */, + DF8735AE2A3F7F20004818BC /* HomeWorkerTest.swift */, + DF8735B32A3F8265004818BC /* HomeInteractorTest.swift */, + DF8735B52A3F8CC9004818BC /* HomePresenterTest.swift */, + DF8735B72A3F921C004818BC /* HomeViewControllerTest.swift */, + ); + path = Home; + sourceTree = ""; + }; + DF8735B02A3F7F84004818BC /* Mock */ = { + isa = PBXGroup; + children = ( + DF8735B12A3F7F90004818BC /* MockStatementsRepositoryProtocol.swift */, + ); + path = Mock; + sourceTree = ""; + }; + DF8735BD2A3FB7FA004818BC /* Components */ = { + isa = PBXGroup; + children = ( + DFC4E8362A3DD709004B6718 /* DefaultTextField.swift */, + DFC4E83E2A3DDC03004B6718 /* DefaultButton.swift */, + ); + path = Components; + sourceTree = ""; + }; + DFC4E8322A3D4186004B6718 /* UIKitSpy */ = { + isa = PBXGroup; + children = ( + DFC4E8332A3D41A0004B6718 /* TextFieldSpy.swift */, + DF8735B92A3F9623004818BC /* LabelSpy.swift */, + DF8735BB2A3F965F004818BC /* TableViewSpy.swift */, + ); + path = UIKitSpy; + sourceTree = ""; + }; + DFC4E8352A3DD57E004B6718 /* DesignSystem */ = { + isa = PBXGroup; + children = ( + DF8735BD2A3FB7FA004818BC /* Components */, + DFC4E8392A3DDA0D004B6718 /* UI */, + ); + path = DesignSystem; + sourceTree = ""; + }; + DFC4E8392A3DDA0D004B6718 /* UI */ = { + isa = PBXGroup; + children = ( + DFC4E83A2A3DDA15004B6718 /* Extensions */, + ); + path = UI; + sourceTree = ""; + }; + DFC4E83A2A3DDA15004B6718 /* Extensions */ = { + isa = PBXGroup; + children = ( + DFC4E83B2A3DDA2F004B6718 /* UIColor+extensions.swift */, + DFC4E8412A3DDD19004B6718 /* UIFont+extensions.swift */, + DFC4E8442A3DE51D004B6718 /* UIResponder+extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + DFC4E8592A3E1332004B6718 /* Home */ = { + isa = PBXGroup; + children = ( + DFC4E8812A3E7D83004B6718 /* Cell */, + DFC4E8472A3E132B004B6718 /* HomePresenter.swift */, + DFC4E8482A3E132B004B6718 /* HomeWorker.swift */, + DFC4E8492A3E132B004B6718 /* HomeRouter.swift */, + DFC4E84A2A3E132B004B6718 /* HomeModels.swift */, + DFC4E84B2A3E132B004B6718 /* HomeViewController.swift */, + DFC4E84C2A3E132B004B6718 /* HomeInteractor.swift */, + ); + path = Home; + sourceTree = ""; + }; + DFC4E85A2A3E3822004B6718 /* Statements */ = { + isa = PBXGroup; + children = ( + DFC4E8652A3E3AE8004B6718 /* Models */, + DFC4E85B2A3E3832004B6718 /* Data */, + ); + path = Statements; + sourceTree = ""; + }; + DFC4E85B2A3E3832004B6718 /* Data */ = { + isa = PBXGroup; + children = ( + DFC4E8602A3E3A82004B6718 /* Datasources */, + DFC4E85F2A3E3A78004B6718 /* Repositories */, + ); + path = Data; + sourceTree = ""; + }; + DFC4E85F2A3E3A78004B6718 /* Repositories */ = { + isa = PBXGroup; + children = ( + DFC4E8612A3E3AA5004B6718 /* StatementsRepositoryProtocol.swift */, + DFC4E8622A3E3AA6004B6718 /* StatementsRepository.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + DFC4E8602A3E3A82004B6718 /* Datasources */ = { + isa = PBXGroup; + children = ( + DFC4E8642A3E3ABA004B6718 /* StatementsServiceDatasource.swift */, + DFC4E8632A3E3ABA004B6718 /* StatementsServiceDatasourceProtocol.swift */, + ); + path = Datasources; + sourceTree = ""; + }; + DFC4E8652A3E3AE8004B6718 /* Models */ = { + isa = PBXGroup; + children = ( + DFC4E85C2A3E3843004B6718 /* StatementsModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + DFC4E8672A3E43A4004B6718 /* User */ = { + isa = PBXGroup; + children = ( + DF7D3F8B2A3CE0880009770F /* Common */, + DF0BA8522A3C833500A812F4 /* Repositories */, + DF0BA8502A3C831400A812F4 /* Data */, + ); + path = User; + sourceTree = ""; + }; + DFC4E8682A3E43AF004B6718 /* Statements */ = { + isa = PBXGroup; + children = ( + DFC4E8692A3E43C4004B6718 /* Data */, + ); + path = Statements; + sourceTree = ""; + }; + DFC4E8692A3E43C4004B6718 /* Data */ = { + isa = PBXGroup; + children = ( + DFC4E86A2A3E43DE004B6718 /* Datasources */, + DFC4E86B2A3E43E7004B6718 /* Repositories */, + ); + path = Data; + sourceTree = ""; + }; + DFC4E86A2A3E43DE004B6718 /* Datasources */ = { + isa = PBXGroup; + children = ( + DF0BA8532A3C836A00A812F4 /* StatementsServiceDatasourceTest.swift */, + ); + path = Datasources; + sourceTree = ""; + }; + DFC4E86B2A3E43E7004B6718 /* Repositories */ = { + isa = PBXGroup; + children = ( + DFC4E87D2A3E5367004B6718 /* Mock */, + DFC4E87C2A3E530D004B6718 /* StatementsRepositoryTest.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + DFC4E87D2A3E5367004B6718 /* Mock */ = { + isa = PBXGroup; + children = ( + DFC4E87E2A3E5377004B6718 /* MockStatementsServiceDatasource.swift */, + ); + path = Mock; + sourceTree = ""; + }; + DFC4E8812A3E7D83004B6718 /* Cell */ = { + isa = PBXGroup; + children = ( + DFC4E8822A3E7D9C004B6718 /* StatementCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; + DFEC18012A3BCFF9001277A2 = { + isa = PBXGroup; + children = ( + DF0BA7C42A3BE5CE00A812F4 /* KssiusBank */, + DF0BA7DE2A3BE5D000A812F4 /* KssiusBankTests */, + DF0BA7E82A3BE5D100A812F4 /* KssiusBankUITests */, + DFEC180B2A3BCFF9001277A2 /* Products */, + E1D6F871C3B699217B1D6BFE /* Pods */, + 8F1F1C114C91695D50430106 /* Frameworks */, + ); + sourceTree = ""; + }; + DFEC180B2A3BCFF9001277A2 /* Products */ = { + isa = PBXGroup; + children = ( + DF0BA7C32A3BE5CD00A812F4 /* KssiusBank.app */, + DF0BA7DB2A3BE5D000A812F4 /* KssiusBankTests.xctest */, + DF0BA7E52A3BE5D100A812F4 /* KssiusBankUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + DFEC18402A3BD044001277A2 /* Scenes */ = { + isa = PBXGroup; + children = ( + DFC4E8592A3E1332004B6718 /* Home */, + DF0BA8352A3BF04A00A812F4 /* Autentication */, + ); + path = Scenes; + sourceTree = ""; + }; + DFEC18412A3BD051001277A2 /* Workers */ = { + isa = PBXGroup; + children = ( + DFEC18562A3BD314001277A2 /* UserWorker.swift */, + ); + path = Workers; + sourceTree = ""; + }; + DFEC18422A3BD060001277A2 /* Services */ = { + isa = PBXGroup; + children = ( + DFEC18622A3BD481001277A2 /* Autehntication */, + DFEC18592A3BD423001277A2 /* NetworkService.swift */, + DFEC18662A3BDDA0001277A2 /* URLSessionProtocol.swift */, + ); + path = Services; + sourceTree = ""; + }; + DFEC18432A3BD068001277A2 /* Models */ = { + isa = PBXGroup; + children = ( + DFEC18532A3BD28C001277A2 /* UserAccountModel.swift */, + DF0BA85A2A3C976900A812F4 /* LoginRequestModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + DFEC185C2A3BD43B001277A2 /* Common */ = { + isa = PBXGroup; + children = ( + DFEC185D2A3BD43B001277A2 /* error */, + DF0BA86E2A3CAC7D00A812F4 /* CommonValidator.swift */, + ); + path = Common; + sourceTree = ""; + }; + DFEC185D2A3BD43B001277A2 /* error */ = { + isa = PBXGroup; + children = ( + DFEC185E2A3BD43B001277A2 /* BankFailure.swift */, + ); + path = error; + sourceTree = ""; + }; + DFEC18622A3BD481001277A2 /* Autehntication */ = { + isa = PBXGroup; + children = ( + DFEC18632A3BD4A2001277A2 /* BankApi.swift */, + ); + path = Autehntication; + sourceTree = ""; + }; + DFEC18692A3BDE0D001277A2 /* Services */ = { + isa = PBXGroup; + children = ( + DFEC186A2A3BDE29001277A2 /* NetworkServiceTest.swift */, + DFEC186D2A3BDE99001277A2 /* MockURLProtocol.swift */, + ); + path = Services; + sourceTree = ""; + }; + E1D6F871C3B699217B1D6BFE /* Pods */ = { + isa = PBXGroup; + children = ( + B0965131F0D770E77CA79752 /* Pods-KssiusBank.debug.xcconfig */, + 9E03F5BEAD121A2725CAC1CF /* Pods-KssiusBank.release.xcconfig */, + 07786122957DD35BED1B07AA /* Pods-KssiusBank-KssiusBankUITests.debug.xcconfig */, + 1F35001EECB5F430346C58D9 /* Pods-KssiusBank-KssiusBankUITests.release.xcconfig */, + 67D965C4039A5F4C8DAF287A /* Pods-KssiusBankTests.debug.xcconfig */, + ACDDEAB26AE6197E2B15A29D /* Pods-KssiusBankTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + DF0BA7C22A3BE5CD00A812F4 /* KssiusBank */ = { + isa = PBXNativeTarget; + buildConfigurationList = DF0BA7ED2A3BE5D100A812F4 /* Build configuration list for PBXNativeTarget "KssiusBank" */; + buildPhases = ( + 198DABB992C70C4C79F5391A /* [CP] Check Pods Manifest.lock */, + DF0BA7BF2A3BE5CD00A812F4 /* Sources */, + DF0BA7C02A3BE5CD00A812F4 /* Frameworks */, + DF0BA7C12A3BE5CD00A812F4 /* Resources */, + DF0BA8712A3CB2D100A812F4 /* ShellScript */, + C432C7ABAE3DF7B8D4C63BC4 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = KssiusBank; + productName = KssiusBank; + productReference = DF0BA7C32A3BE5CD00A812F4 /* KssiusBank.app */; + productType = "com.apple.product-type.application"; + }; + DF0BA7DA2A3BE5D000A812F4 /* KssiusBankTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DF0BA7F02A3BE5D100A812F4 /* Build configuration list for PBXNativeTarget "KssiusBankTests" */; + buildPhases = ( + 1C669CFC8BFBE2EAEF727919 /* [CP] Check Pods Manifest.lock */, + DF0BA7D72A3BE5D000A812F4 /* Sources */, + DF0BA7D82A3BE5D000A812F4 /* Frameworks */, + DF0BA7D92A3BE5D000A812F4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DF0BA7DD2A3BE5D000A812F4 /* PBXTargetDependency */, + ); + name = KssiusBankTests; + productName = KssiusBankTests; + productReference = DF0BA7DB2A3BE5D000A812F4 /* KssiusBankTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + DF0BA7E42A3BE5D000A812F4 /* KssiusBankUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DF0BA7F32A3BE5D100A812F4 /* Build configuration list for PBXNativeTarget "KssiusBankUITests" */; + buildPhases = ( + B1D8C786446D7F712AFB90BD /* [CP] Check Pods Manifest.lock */, + DF0BA7E12A3BE5D000A812F4 /* Sources */, + DF0BA7E22A3BE5D000A812F4 /* Frameworks */, + DF0BA7E32A3BE5D000A812F4 /* Resources */, + EF40B5E372DFA420D037EEAE /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + DF0BA7E72A3BE5D100A812F4 /* PBXTargetDependency */, + ); + name = KssiusBankUITests; + productName = KssiusBankUITests; + productReference = DF0BA7E52A3BE5D100A812F4 /* KssiusBankUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + DFEC18022A3BCFF9001277A2 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = "Cassio Sousa"; + TargetAttributes = { + DF0BA7C22A3BE5CD00A812F4 = { + CreatedOnToolsVersion = 14.3; + }; + DF0BA7DA2A3BE5D000A812F4 = { + CreatedOnToolsVersion = 14.3; + TestTargetID = DF0BA7C22A3BE5CD00A812F4; + }; + DF0BA7E42A3BE5D000A812F4 = { + CreatedOnToolsVersion = 14.3; + TestTargetID = DF0BA7C22A3BE5CD00A812F4; + }; + }; + }; + buildConfigurationList = DFEC18052A3BCFF9001277A2 /* Build configuration list for PBXProject "KssiusBank" */; + compatibilityVersion = "Xcode 11.0"; + developmentRegion = "pt-BR"; + hasScannedForEncodings = 0; + knownRegions = ( + en, + "pt-BR", + Base, + ); + mainGroup = DFEC18012A3BCFF9001277A2; + productRefGroup = DFEC180B2A3BCFF9001277A2 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + DF0BA7C22A3BE5CD00A812F4 /* KssiusBank */, + DF0BA7DA2A3BE5D000A812F4 /* KssiusBankTests */, + DF0BA7E42A3BE5D000A812F4 /* KssiusBankUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + DF0BA7C12A3BE5CD00A812F4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DF0BA8272A3BEEED00A812F4 /* Assets.xcassets in Resources */, + DF0BA8762A3CB91B00A812F4 /* Localizable.strings in Resources */, + DF0BA80D2A3BE6CA00A812F4 /* LaunchScreen.storyboard in Resources */, + DF0BA8042A3BE6CA00A812F4 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DF0BA7D92A3BE5D000A812F4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DF0BA8772A3CB91B00A812F4 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DF0BA7E32A3BE5D000A812F4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 198DABB992C70C4C79F5391A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-KssiusBank-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 1C669CFC8BFBE2EAEF727919 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-KssiusBankTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B1D8C786446D7F712AFB90BD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-KssiusBank-KssiusBankUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C432C7ABAE3DF7B8D4C63BC4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-KssiusBank/Pods-KssiusBank-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-KssiusBank/Pods-KssiusBank-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-KssiusBank/Pods-KssiusBank-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + DF0BA8712A3CB2D100A812F4 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [[ -f \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" ]]; then\n \"${PODS_ROOT}/SwiftGen/bin/swiftgen\"\nelse\n echo \"warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it.\"\nfi \n"; + }; + EF40B5E372DFA420D037EEAE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-KssiusBank-KssiusBankUITests/Pods-KssiusBank-KssiusBankUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-KssiusBank-KssiusBankUITests/Pods-KssiusBank-KssiusBankUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-KssiusBank-KssiusBankUITests/Pods-KssiusBank-KssiusBankUITests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + DF0BA7BF2A3BE5CD00A812F4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DF0BA8142A3BEC4700A812F4 /* URLSessionProtocol.swift in Sources */, + DF0BA80C2A3BE6CA00A812F4 /* ViewController.swift in Sources */, + DF4624CF2A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift in Sources */, + DF0BA8242A3BEE3D00A812F4 /* BankFailure.swift in Sources */, + DFC4E83F2A3DDC03004B6718 /* DefaultButton.swift in Sources */, + DF4624C92A3CF0D700CBC35D /* AutenticationLocalDatasource.swift in Sources */, + DFC4E8742A3E478C004B6718 /* StatementsFailure.swift in Sources */, + DFF084472A3D2C5600672477 /* LoginWorker.swift in Sources */, + DF4624C62A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift in Sources */, + DFC4E8372A3DD709004B6718 /* DefaultTextField.swift in Sources */, + DFC4E84F2A3E132B004B6718 /* HomeWorker.swift in Sources */, + DF8735A02A3F2E17004818BC /* Date+extensions.swift in Sources */, + DF0BA8332A3BF03900A812F4 /* LoginViewController.swift in Sources */, + DF0BA8342A3BF03900A812F4 /* LoginInteractor.swift in Sources */, + DF0BA82F2A3BF03900A812F4 /* LoginPresenter.swift in Sources */, + DF0BA85B2A3C976900A812F4 /* LoginRequestModel.swift in Sources */, + DF0BA8152A3BEC4700A812F4 /* NetworkService.swift in Sources */, + DF0BA8132A3BEC4700A812F4 /* BankApi.swift in Sources */, + DFC4E84D2A3E132B004B6718 /* HomePresenter.swift in Sources */, + DF0BA8092A3BE6CA00A812F4 /* AppDelegate.swift in Sources */, + DFC4E8832A3E7D9C004B6718 /* StatementCell.swift in Sources */, + DF0BA87F2A3CC4ED00A812F4 /* Strings+Generated.swift in Sources */, + DF0BA85E2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift in Sources */, + DFC4E8422A3DDD19004B6718 /* UIFont+extensions.swift in Sources */, + DF8735A32A3F53BA004818BC /* String+extensions.swift in Sources */, + DF0BA8072A3BE6CA00A812F4 /* SceneDelegate.swift in Sources */, + DF0BA8362A3BF05800A812F4 /* UserWorker.swift in Sources */, + DFC4E8512A3E132B004B6718 /* HomeRouter.swift in Sources */, + DFC4E86C2A3E445F004B6718 /* StatementsRepository.swift in Sources */, + DFC4E8572A3E132B004B6718 /* HomeInteractor.swift in Sources */, + DFC4E86D2A3E445F004B6718 /* StatementsRepositoryProtocol.swift in Sources */, + DFC4E8532A3E132B004B6718 /* HomeModels.swift in Sources */, + DF0BA8322A3BF03900A812F4 /* LoginModels.swift in Sources */, + DF0BA8112A3BE9F200A812F4 /* UserAccountModel.swift in Sources */, + DF4624CC2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift in Sources */, + DFC4E83C2A3DDA2F004B6718 /* UIColor+extensions.swift in Sources */, + DF0BA7D02A3BE5CE00A812F4 /* KssiusBank.xcdatamodeld in Sources */, + DFC4E8712A3E4463004B6718 /* StatementsServiceDatasourceProtocol.swift in Sources */, + DF0BA8492A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift in Sources */, + DF0BA86F2A3CAC7D00A812F4 /* CommonValidator.swift in Sources */, + DF0BA86C2A3CA9AB00A812F4 /* UserFailure.swift in Sources */, + DFC4E8702A3E4463004B6718 /* StatementsServiceDatasource.swift in Sources */, + DF0BA87E2A3CC4ED00A812F4 /* XCAssets+Generated.swift in Sources */, + DFC4E8552A3E132B004B6718 /* HomeViewController.swift in Sources */, + DFC4E85D2A3E3843004B6718 /* StatementsModel.swift in Sources */, + DF0BA8312A3BF03900A812F4 /* LoginRouter.swift in Sources */, + DFC4E8452A3DE51D004B6718 /* UIResponder+extensions.swift in Sources */, + DF0BA8472A3C7C0200A812F4 /* AutenticationRepository.swift in Sources */, + DF0BA8432A3C7AAD00A812F4 /* AuthenticationRepositoryProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DF0BA7D72A3BE5D000A812F4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DFC4E87F2A3E545C004B6718 /* StatementsRepositoryTest.swift in Sources */, + DF0BA8252A3BEE3D00A812F4 /* BankFailure.swift in Sources */, + DFF084432A3D2B2700672477 /* LoginModels.swift in Sources */, + DF8735B82A3F921C004818BC /* HomeViewControllerTest.swift in Sources */, + DF4624CA2A3CF0D700CBC35D /* AutenticationLocalDatasource.swift in Sources */, + DF8735A42A3F53BA004818BC /* String+extensions.swift in Sources */, + DF0BA8692A3C9BA900A812F4 /* MockAutenticationServiceDatasource.swift in Sources */, + DF4624D02A3CF6EE00CBC35D /* AuthenticationLocalRepositoryProtocol.swift in Sources */, + DF0BA8172A3BEC4800A812F4 /* URLSessionProtocol.swift in Sources */, + DF4624C72A3CF0AC00CBC35D /* AutenticationLocalDatasourceProtocol.swift in Sources */, + DF0BA8812A3CC4F500A812F4 /* XCAssets+Generated.swift in Sources */, + DF0BA8802A3CC4F300A812F4 /* Strings+Generated.swift in Sources */, + DF4624D52A3D080600CBC35D /* AuthenticationLocalRepositoryTest.swift in Sources */, + DFC4E8402A3DDC03004B6718 /* DefaultButton.swift in Sources */, + DFC4E8802A3E5461004B6718 /* MockStatementsServiceDatasource.swift in Sources */, + DF8735A12A3F2F74004818BC /* Date+extensions.swift in Sources */, + DFC4E8762A3E48CB004B6718 /* DefaultTextField.swift in Sources */, + DF0BA7F82A3BE65B00A812F4 /* MockURLProtocol.swift in Sources */, + DF46E1D92A4076BA00FD344A /* StatementCellTest.swift in Sources */, + DF0BA80E2A3BE6D400A812F4 /* KssiusBankTests.swift in Sources */, + DF8735B62A3F8CC9004818BC /* HomePresenterTest.swift in Sources */, + DFC4E8312A3D3C13004B6718 /* LoginViewControllerTest.swift in Sources */, + DFC4E86E2A3E445F004B6718 /* StatementsRepository.swift in Sources */, + DFF084442A3D2B2700672477 /* LoginInteractor.swift in Sources */, + DFC4E8752A3E478C004B6718 /* StatementsFailure.swift in Sources */, + DFC4E8462A3DE51D004B6718 /* UIResponder+extensions.swift in Sources */, + DF4624E22A3D1E3300CBC35D /* LoginWorkerTest.swift in Sources */, + DF0BA8392A3BFB6100A812F4 /* Seeds.swift in Sources */, + DFC4E8562A3E132B004B6718 /* HomeViewController.swift in Sources */, + DFC4E87B2A3E4ED8004B6718 /* AutenticationServiceDatasourceTest.swift in Sources */, + DFC4E8432A3DDD19004B6718 /* UIFont+extensions.swift in Sources */, + DFC4E8842A3E7D9C004B6718 /* StatementCell.swift in Sources */, + DFF0844A2A3D38AF00672477 /* LoginPresenterTest.swift in Sources */, + DF8735B22A3F80D9004818BC /* MockStatementsRepositoryProtocol.swift in Sources */, + DFC4E8542A3E132B004B6718 /* HomeModels.swift in Sources */, + DFF084412A3D2B2700672477 /* LoginRouter.swift in Sources */, + DF0BA86D2A3CA9AB00A812F4 /* UserFailure.swift in Sources */, + DF4624DE2A3D0DF000CBC35D /* MockAuthenticationLocalRepository.swift in Sources */, + DF0BA8122A3BE9F300A812F4 /* UserAccountModel.swift in Sources */, + DFC4E8522A3E132B004B6718 /* HomeRouter.swift in Sources */, + DF0BA8552A3C836A00A812F4 /* StatementsServiceDatasourceTest.swift in Sources */, + DF0BA7F72A3BE65800A812F4 /* NetworkServiceTest.swift in Sources */, + DF4624E62A3D226400CBC35D /* AuthenticationRepositoryProtocol.swift in Sources */, + DFF084422A3D2B2700672477 /* LoginPresenter.swift in Sources */, + DFC4E8732A3E4463004B6718 /* StatementsServiceDatasourceProtocol.swift in Sources */, + DF8735AF2A3F7F20004818BC /* HomeWorkerTest.swift in Sources */, + DF4624E52A3D1F2E00CBC35D /* MockAuthenticationRepositoryProtocol.swift in Sources */, + DFC4E8502A3E132B004B6718 /* HomeWorker.swift in Sources */, + DF8735BA2A3F9623004818BC /* LabelSpy.swift in Sources */, + DF4624D82A3D091400CBC35D /* MockAutenticationLocalDatasourceProtocol.swift in Sources */, + DF0BA8702A3CAC7D00A812F4 /* CommonValidator.swift in Sources */, + DF0BA84A2A3C7C8E00A812F4 /* AutenticationServiceDatasource.swift in Sources */, + DFC4E8582A3E132B004B6718 /* HomeInteractor.swift in Sources */, + DFC4E84E2A3E132B004B6718 /* HomePresenter.swift in Sources */, + DF0BA8372A3BF05800A812F4 /* UserWorker.swift in Sources */, + DF8735AC2A3F796D004818BC /* Date+extensionsTest.swift in Sources */, + DFC4E86F2A3E445F004B6718 /* StatementsRepositoryProtocol.swift in Sources */, + DF7D3F8D2A3CE0A90009770F /* CommonValidatorTest.swift in Sources */, + DF8735A92A3F6C42004818BC /* String+extensionsTest.swift in Sources */, + DF4624CD2A3CF6DB00CBC35D /* AutenticationLocalRepository.swift in Sources */, + DF0BA8162A3BEC4800A812F4 /* BankApi.swift in Sources */, + DFF084482A3D2C5700672477 /* LoginWorker.swift in Sources */, + DFC4E8342A3D41A0004B6718 /* TextFieldSpy.swift in Sources */, + DFF084462A3D2B2700672477 /* LoginViewController.swift in Sources */, + DFC4E83D2A3DDA30004B6718 /* UIColor+extensions.swift in Sources */, + DFF0843A2A3D24F500672477 /* LoginInteractorTest.swift in Sources */, + DF8735B42A3F8265004818BC /* HomeInteractorTest.swift in Sources */, + DF0BA8622A3C998100A812F4 /* AuthenticationRepositoryTest.swift in Sources */, + DF0BA8182A3BEC4800A812F4 /* NetworkService.swift in Sources */, + DFC4E8722A3E4463004B6718 /* StatementsServiceDatasource.swift in Sources */, + DF0BA86A2A3C9CB700A812F4 /* AutenticationRepository.swift in Sources */, + DFC4E85E2A3E3843004B6718 /* StatementsModel.swift in Sources */, + DF4624DB2A3D0A8400CBC35D /* UserWorkersTest.swift in Sources */, + DFC4E8792A3E4E61004B6718 /* AutenticationLocalDatasourceTest.swift in Sources */, + DF8735BC2A3F965F004818BC /* TableViewSpy.swift in Sources */, + DF0BA85C2A3C976900A812F4 /* LoginRequestModel.swift in Sources */, + DF0BA85F2A3C985500A812F4 /* AuthenticationServiceDatasourceProtocol.swift in Sources */, + DF0BA8652A3C9A3E00A812F4 /* MockSession.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DF0BA7E12A3BE5D000A812F4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DF0BA80F2A3BE6ED00A812F4 /* KssiusBankUITestsLaunchTests.swift in Sources */, + DF0BA8102A3BE6ED00A812F4 /* KssiusBankUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + DF0BA7DD2A3BE5D000A812F4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DF0BA7C22A3BE5CD00A812F4 /* KssiusBank */; + targetProxy = DF0BA7DC2A3BE5D000A812F4 /* PBXContainerItemProxy */; + }; + DF0BA7E72A3BE5D100A812F4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DF0BA7C22A3BE5CD00A812F4 /* KssiusBank */; + targetProxy = DF0BA7E62A3BE5D100A812F4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + DF0BA8792A3CB91B00A812F4 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + DF0BA8782A3CB91B00A812F4 /* en */, + DF0BA87A2A3CC2B500A812F4 /* pt-BR */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + DF0BA7EE2A3BE5D100A812F4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B0965131F0D770E77CA79752 /* Pods-KssiusBank.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S54ZB46KKW; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = KssiusBank/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = KssiusBank; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UIStatusBarStyle = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.kssius.KssiusBank; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + DF0BA7EF2A3BE5D100A812F4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9E03F5BEAD121A2725CAC1CF /* Pods-KssiusBank.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S54ZB46KKW; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = KssiusBank/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = KssiusBank; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UIStatusBarStyle = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.kssius.KssiusBank; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + DF0BA7F12A3BE5D100A812F4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67D965C4039A5F4C8DAF287A /* Pods-KssiusBankTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S54ZB46KKW; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.kssius.KssiusBankTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KssiusBank.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/KssiusBank"; + }; + name = Debug; + }; + DF0BA7F22A3BE5D100A812F4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ACDDEAB26AE6197E2B15A29D /* Pods-KssiusBankTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S54ZB46KKW; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.kssius.KssiusBankTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KssiusBank.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/KssiusBank"; + }; + name = Release; + }; + DF0BA7F42A3BE5D100A812F4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 07786122957DD35BED1B07AA /* Pods-KssiusBank-KssiusBankUITests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S54ZB46KKW; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.kssius.KssiusBankUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = KssiusBank; + }; + name = Debug; + }; + DF0BA7F52A3BE5D100A812F4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1F35001EECB5F430346C58D9 /* Pods-KssiusBank-KssiusBankUITests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S54ZB46KKW; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.kssius.KssiusBankUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = KssiusBank; + }; + name = Release; + }; + DFEC18352A3BCFFC001277A2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + DFEC18362A3BCFFC001277A2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + DF0BA7ED2A3BE5D100A812F4 /* Build configuration list for PBXNativeTarget "KssiusBank" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DF0BA7EE2A3BE5D100A812F4 /* Debug */, + DF0BA7EF2A3BE5D100A812F4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DF0BA7F02A3BE5D100A812F4 /* Build configuration list for PBXNativeTarget "KssiusBankTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DF0BA7F12A3BE5D100A812F4 /* Debug */, + DF0BA7F22A3BE5D100A812F4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DF0BA7F32A3BE5D100A812F4 /* Build configuration list for PBXNativeTarget "KssiusBankUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DF0BA7F42A3BE5D100A812F4 /* Debug */, + DF0BA7F52A3BE5D100A812F4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DFEC18052A3BCFF9001277A2 /* Build configuration list for PBXProject "KssiusBank" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DFEC18352A3BCFFC001277A2 /* Debug */, + DFEC18362A3BCFFC001277A2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + DF0BA7CE2A3BE5CE00A812F4 /* KssiusBank.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + DF0BA7CF2A3BE5CE00A812F4 /* KssiusBank.xcdatamodel */, + ); + currentVersion = DF0BA7CF2A3BE5CE00A812F4 /* KssiusBank.xcdatamodel */; + path = KssiusBank.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = DFEC18022A3BCFF9001277A2 /* Project object */; +} diff --git a/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcuserdata/cassio.xcuserdatad/UserInterfaceState.xcuserstate b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcuserdata/cassio.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..3e29503d2 Binary files /dev/null and b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcuserdata/cassio.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcuserdata/cassio.xcuserdatad/WorkspaceSettings.xcsettings b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcuserdata/cassio.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..bbfef027f --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/project.xcworkspace/xcuserdata/cassio.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,14 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + ShowSharedSchemesAutomaticallyEnabled + + + diff --git a/KssiusBank/KssiusBank.xcodeproj/xcshareddata/xcschemes/KssiusBank.xcscheme b/KssiusBank/KssiusBank.xcodeproj/xcshareddata/xcschemes/KssiusBank.xcscheme new file mode 100644 index 000000000..302ded3d9 --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/xcshareddata/xcschemes/KssiusBank.xcscheme @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KssiusBank/KssiusBank.xcodeproj/xcuserdata/cassio.xcuserdatad/xcschemes/xcschememanagement.plist b/KssiusBank/KssiusBank.xcodeproj/xcuserdata/cassio.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..0e20a617f --- /dev/null +++ b/KssiusBank/KssiusBank.xcodeproj/xcuserdata/cassio.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + KssiusBank.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + DFEC18092A3BCFF9001277A2 + + primary + + + DFEC18222A3BCFFC001277A2 + + primary + + + DFEC182C2A3BCFFC001277A2 + + primary + + + + + diff --git a/KssiusBank/KssiusBank.xcworkspace/contents.xcworkspacedata b/KssiusBank/KssiusBank.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..fe5db8ace --- /dev/null +++ b/KssiusBank/KssiusBank.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/KssiusBank/KssiusBank.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KssiusBank/KssiusBank.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/KssiusBank/KssiusBank.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/KssiusBank/KssiusBank.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/KssiusBank/KssiusBank.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/KssiusBank/KssiusBank.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/UserInterfaceState.xcuserstate b/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..5d4656637 Binary files /dev/null and b/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/WorkspaceSettings.xcsettings b/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..bbfef027f --- /dev/null +++ b/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,14 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + ShowSharedSchemesAutomaticallyEnabled + + + diff --git a/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 000000000..4ba697185 --- /dev/null +++ b/KssiusBank/KssiusBank.xcworkspace/xcuserdata/cassio.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/KssiusBank/KssiusBank/.DS_Store b/KssiusBank/KssiusBank/.DS_Store new file mode 100644 index 000000000..82d9eb519 Binary files /dev/null and b/KssiusBank/KssiusBank/.DS_Store differ diff --git a/KssiusBank/KssiusBank/AppDelegate.swift b/KssiusBank/KssiusBank/AppDelegate.swift new file mode 100644 index 000000000..13222944d --- /dev/null +++ b/KssiusBank/KssiusBank/AppDelegate.swift @@ -0,0 +1,95 @@ +// +// AppDelegate.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import UIKit +import CoreData + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool + { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) + { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) + { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) + { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) + { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) + { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + // MARK: - Core Data stack + + lazy var persistentContainer: NSPersistentContainer = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "KssiusBank") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + return container + }() + + // MARK: - Core Data Saving support + + func saveContext () { + let context = persistentContainer.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } + } + +} + diff --git a/KssiusBank/KssiusBank/Assets.xcassets/AccentColor.colorset/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/AppIcon.appiconset/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/Logo.imageset/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/Logo.imageset/Contents.json new file mode 100644 index 000000000..e195e0614 --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/Logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/Logo.imageset/Logo.png b/KssiusBank/KssiusBank/Assets.xcassets/Logo.imageset/Logo.png new file mode 100644 index 000000000..66bdc8d5d Binary files /dev/null and b/KssiusBank/KssiusBank/Assets.xcassets/Logo.imageset/Logo.png differ diff --git a/KssiusBank/KssiusBank/Assets.xcassets/eye.imageset/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/eye.imageset/Contents.json new file mode 100644 index 000000000..aeb1b1452 --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/eye.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "eye.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/eye.imageset/eye.png b/KssiusBank/KssiusBank/Assets.xcassets/eye.imageset/eye.png new file mode 100644 index 000000000..e22d83abd Binary files /dev/null and b/KssiusBank/KssiusBank/Assets.xcassets/eye.imageset/eye.png differ diff --git a/KssiusBank/KssiusBank/Assets.xcassets/logout.imageset/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/logout.imageset/Contents.json new file mode 100644 index 000000000..8c8c954e5 --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/logout.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "logout 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/logout.imageset/logout 2.png b/KssiusBank/KssiusBank/Assets.xcassets/logout.imageset/logout 2.png new file mode 100644 index 000000000..de1e4ae3c Binary files /dev/null and b/KssiusBank/KssiusBank/Assets.xcassets/logout.imageset/logout 2.png differ diff --git a/KssiusBank/KssiusBank/Assets.xcassets/open-eye.imageset/Contents.json b/KssiusBank/KssiusBank/Assets.xcassets/open-eye.imageset/Contents.json new file mode 100644 index 000000000..9e2a29ef5 --- /dev/null +++ b/KssiusBank/KssiusBank/Assets.xcassets/open-eye.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "open-eye.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/KssiusBank/KssiusBank/Assets.xcassets/open-eye.imageset/open-eye.png b/KssiusBank/KssiusBank/Assets.xcassets/open-eye.imageset/open-eye.png new file mode 100644 index 000000000..118523d54 Binary files /dev/null and b/KssiusBank/KssiusBank/Assets.xcassets/open-eye.imageset/open-eye.png differ diff --git a/KssiusBank/KssiusBank/Core/Common/CommonValidator.swift b/KssiusBank/KssiusBank/Core/Common/CommonValidator.swift new file mode 100644 index 000000000..0b510c522 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/Common/CommonValidator.swift @@ -0,0 +1,57 @@ +// +// CommonValidate.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +enum CommonValidator { + enum User { + static func validatePassword(_ password: String) -> Bool { + let regex = #"^(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{3,}"# + let predicate = NSPredicate(format:"SELF MATCHES %@", regex) + return predicate.evaluate(with: password) + } + static func isEmail(_ email: String) -> Bool { + let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + let predicate = NSPredicate(format:"SELF MATCHES %@", regex) + return predicate.evaluate(with: email) + } + + static func isCpf(_ cpf: String) -> Bool { + let numbers = cpf.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() + guard numbers.count == 11 else { return false } + + let set = NSCountedSet(array: Array(numbers)) + guard set.count != 1 else { return false } + + let i1 = numbers.index(numbers.startIndex, offsetBy: 9) + let i2 = numbers.index(numbers.startIndex, offsetBy: 10) + let i3 = numbers.index(numbers.startIndex, offsetBy: 11) + let d1 = Int(numbers[i1.. Bool { + lhs.code == rhs.code && lhs.message == rhs.message + } +} + +enum BankFailure: Error, Equatable { + + // MARK: - Equatable + + static func == (lhs: BankFailure, rhs: BankFailure) -> Bool { + switch (lhs, rhs) { + case (.network(let lnet), .network(let rnet)): + return lnet == rnet + default: + return false + } + } + + // MARK: - Cases + + case network(ErrorResponse?) +} diff --git a/KssiusBank/KssiusBank/Core/Statements/Data/Datasources/StatementsServiceDatasource.swift b/KssiusBank/KssiusBank/Core/Statements/Data/Datasources/StatementsServiceDatasource.swift new file mode 100644 index 000000000..693d06b47 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/Statements/Data/Datasources/StatementsServiceDatasource.swift @@ -0,0 +1,38 @@ +// +// StatementsServiceDatasource.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +final class StatementsServiceDatasource: StatementsServiceDatasourceProtocol { + + // MARK: - Properties + private let networkService: NetworkService<[StatementsModel]> + + // MARK: - Inits + init(networkService: NetworkService<[StatementsModel]>) { + self.networkService = networkService + } +} + +// MARK: - Operations +extension StatementsServiceDatasource { + + func fetch(statements: BankApi.Endpoints, completion: @escaping (Result<[StatementsModel], BankFailure>) -> Void) { + networkService.request(endpoint: statements.endpoint) { result in + switch(result) { + case .success(let model): + DispatchQueue.main.async { + completion(.success(model)) + } + case .failure( _): + DispatchQueue.main.async { + completion(.failure(.network(.init()))) + } + } + } + } +} diff --git a/KssiusBank/KssiusBank/Core/Statements/Data/Datasources/StatementsServiceDatasourceProtocol.swift b/KssiusBank/KssiusBank/Core/Statements/Data/Datasources/StatementsServiceDatasourceProtocol.swift new file mode 100644 index 000000000..a8363e698 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/Statements/Data/Datasources/StatementsServiceDatasourceProtocol.swift @@ -0,0 +1,15 @@ +// +// StatementsServiceDatasourceProtocol.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +protocol StatementsServiceDatasourceProtocol { + + init(networkService: NetworkService<[StatementsModel]>) + + func fetch(statements: BankApi.Endpoints, completion: @escaping (Result<[StatementsModel], BankFailure>) -> Void) +} diff --git a/KssiusBank/KssiusBank/Core/Statements/Data/Repositories/StatementsRepository.swift b/KssiusBank/KssiusBank/Core/Statements/Data/Repositories/StatementsRepository.swift new file mode 100644 index 000000000..9a0e3849b --- /dev/null +++ b/KssiusBank/KssiusBank/Core/Statements/Data/Repositories/StatementsRepository.swift @@ -0,0 +1,36 @@ +// +// StatementsRepository.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +final class StatementsRepository: StatementsRepositoryProtocol { + + // MARK: - Properties + + private let statementsService: StatementsServiceDatasourceProtocol + + // MARK: - Inits + + init(statementsService: StatementsServiceDatasourceProtocol) { + self.statementsService = statementsService + } + +} + +// MARK: - POST Methods + +extension StatementsRepository { + func fetch(statements: BankApi.Endpoints, completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void) { + statementsService.fetch(statements: .statementsRequest){ result in + switch(result) { + case .success(let model): completion(.success(model)) + case .failure( _): + completion(.failure(.network(.init()))) + } + } + } +} diff --git a/KssiusBank/KssiusBank/Core/Statements/Data/Repositories/StatementsRepositoryProtocol.swift b/KssiusBank/KssiusBank/Core/Statements/Data/Repositories/StatementsRepositoryProtocol.swift new file mode 100644 index 000000000..2183ae81e --- /dev/null +++ b/KssiusBank/KssiusBank/Core/Statements/Data/Repositories/StatementsRepositoryProtocol.swift @@ -0,0 +1,13 @@ +// +// StatementsRepository.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +protocol StatementsRepositoryProtocol: AnyObject { + func fetch(statements: BankApi.Endpoints, + completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void) +} diff --git a/KssiusBank/KssiusBank/Core/Statements/Models/StatementsModel.swift b/KssiusBank/KssiusBank/Core/Statements/Models/StatementsModel.swift new file mode 100644 index 000000000..b65132494 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/Statements/Models/StatementsModel.swift @@ -0,0 +1,19 @@ +// +// StatementsModel.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +struct StatementsModel: Codable, Equatable { + enum StatementsType: String, Codable, Equatable { + case invoice, payment, withdrawal, deposit + } + let type: StatementsType + let description: String + let date: Date + let value: String +} diff --git a/KssiusBank/KssiusBank/Core/StatementsFailure.swift b/KssiusBank/KssiusBank/Core/StatementsFailure.swift new file mode 100644 index 000000000..6002c00e7 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/StatementsFailure.swift @@ -0,0 +1,27 @@ +// +// StatementsFailure.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +enum StatementsFailure: Error, Equatable { + + // MARK: - Equatable + + static func == (lhs: StatementsFailure, rhs: StatementsFailure) -> Bool { + switch (lhs, rhs) { + case (.network(let lnet), .network(let rnet)): + return lnet == rnet + default: + return false + } + } + + // MARK: - Cases + + case network(ErrorResponse?) + +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationLocalDatasource.swift b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationLocalDatasource.swift new file mode 100644 index 000000000..c53a37096 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationLocalDatasource.swift @@ -0,0 +1,49 @@ +// +// AutenticationLocalDatasource.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import KeychainSwift + +final class AuthenticationLocalDatasource { + + // MARK: - Properties + + private let keychain: KeychainSwift + private let userAccountKey = "userAccountKey" + + // MARK: - Inits + + init(keychain: KeychainSwift) { + self.keychain = keychain + } + +} + +// MARK: - Resources + +extension AuthenticationLocalDatasource : AuthenticationLocalDatasourceProtocol { + + func save(user: UserAccountModel) -> UserAccountModel? { + guard let encoded = try? JSONEncoder().encode(user), + let stringEncoded = String(data: encoded,encoding:.utf8) + else { + return nil + } + keychain.set(stringEncoded , forKey: userAccountKey) + return retrieveUser() + } + + func retrieveUser() -> UserAccountModel? { + guard let dataUser = keychain.get(userAccountKey)?.data(using: .utf8), + let decodedUser = try? JSONDecoder().decode(UserAccountModel.self, from: dataUser) + else { + return nil + } + return decodedUser + } +} + diff --git a/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationLocalDatasourceProtocol.swift b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationLocalDatasourceProtocol.swift new file mode 100644 index 000000000..9231873c2 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationLocalDatasourceProtocol.swift @@ -0,0 +1,15 @@ +// +// AutenticationLocalDatasourceProtocol.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +protocol AuthenticationLocalDatasourceProtocol { + @discardableResult + func save(user: UserAccountModel) -> UserAccountModel? + func retrieveUser() -> UserAccountModel? +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationServiceDatasource.swift b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationServiceDatasource.swift new file mode 100644 index 000000000..0cd99787e --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AutenticationServiceDatasource.swift @@ -0,0 +1,38 @@ +// +// AutenticationServiceDatasource.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +final class AuthenticationServiceDatasource: AuthenticationServiceDatasourceProtocol { + + // MARK: - Properties + private let networkService: NetworkService + + // MARK: - Inits + init(networkService: NetworkService) { + self.networkService = networkService + } +} + +// MARK: - Operations +extension AuthenticationServiceDatasource { + + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, completion: @escaping (Result) -> Void) { + networkService.request(endpoint: login.endpoint, payload: data ) { result in + switch(result) { + case .success(let model): + DispatchQueue.main.async { + completion(.success(model)) + } + case .failure( _): + DispatchQueue.main.async { + completion(.failure(.network(.init()))) + } + } + } + } +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Datasources/AuthenticationServiceDatasourceProtocol.swift b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AuthenticationServiceDatasourceProtocol.swift new file mode 100644 index 000000000..8589e68a2 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Datasources/AuthenticationServiceDatasourceProtocol.swift @@ -0,0 +1,15 @@ +// +// AuthenticationServiceDatasourceProtocol.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +protocol AuthenticationServiceDatasourceProtocol { + + init(networkService: NetworkService) + + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, completion: @escaping (Result) -> Void) +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Models/LoginRequestModel.swift b/KssiusBank/KssiusBank/Core/User/Data/Models/LoginRequestModel.swift new file mode 100644 index 000000000..f08298f6e --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Models/LoginRequestModel.swift @@ -0,0 +1,13 @@ +// +// LoginRequestModel.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +struct LoginRequestModel: Codable { + let username: String + let password: String +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Models/UserAccountModel.swift b/KssiusBank/KssiusBank/Core/User/Data/Models/UserAccountModel.swift new file mode 100644 index 000000000..d18ac37e6 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Models/UserAccountModel.swift @@ -0,0 +1,28 @@ +// +// UserAccount.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import Foundation + +struct UserAccountModel : Codable, Equatable { + let userId: String + let email: String + let cpf: String + let name: String + let accountNumber: String + let agency: String + let balance: String + + enum CodingKeys: String, CodingKey { + case userId = "id" + case email + case cpf + case name + case accountNumber + case agency + case balance + } +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Repositories/AutenticationLocalRepository.swift b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AutenticationLocalRepository.swift new file mode 100644 index 000000000..5c9f5e06f --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AutenticationLocalRepository.swift @@ -0,0 +1,38 @@ +// +// AutenticationLocalRepository.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + + +final class AuthenticationLocalRepository { + + // MARK: - Properties + + private let authenticationLocalDataSource: AuthenticationLocalDatasourceProtocol + + + // MARK: - Inits + + init(authenticationLocalDataSource: AuthenticationLocalDatasourceProtocol) { + self.authenticationLocalDataSource = authenticationLocalDataSource + } + +} + +// MARK: - Resources + +extension AuthenticationLocalRepository: AuthenticationLocalRepositoryProtocol { + + func save(user: UserAccountModel) -> UserAccountModel? { + return authenticationLocalDataSource.save(user: user) + } + + func retrieveUser() -> UserAccountModel? { + return authenticationLocalDataSource.retrieveUser() + } +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Repositories/AutenticationRepository.swift b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AutenticationRepository.swift new file mode 100644 index 000000000..0bc745788 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AutenticationRepository.swift @@ -0,0 +1,47 @@ +// +// AutenticationRepository.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +final class AuthenticationRepository: AuthenticationRepositoryProtocol { + + // MARK: - Properties + + private let authenticationService: AuthenticationServiceDatasourceProtocol + + // MARK: - Inits + + init(authenticationService: AuthenticationServiceDatasourceProtocol) { + self.authenticationService = authenticationService + } + +} + +// MARK: - POST Methods + +extension AuthenticationRepository { + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, completion: @escaping (Result) -> Void) { + + if (!CommonValidator.User.isCpf(data.username) && !CommonValidator.User.isEmail(data.username)) { + completion(.failure(.user)) + return + } + + if (!CommonValidator.User.validatePassword(data.password)) { + completion(.failure(.password)) + return + } + + authenticationService.perform(login: login, with: data){ result in + switch(result) { + case .success(let model): completion(.success(model)) + case .failure( _): + completion(.failure(.network(.init()))) + } + } + } +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Repositories/AuthenticationLocalRepositoryProtocol.swift b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AuthenticationLocalRepositoryProtocol.swift new file mode 100644 index 000000000..dec2fcc4a --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AuthenticationLocalRepositoryProtocol.swift @@ -0,0 +1,15 @@ +// +// AutenticationLocalRepositoryProtocol.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +protocol AuthenticationLocalRepositoryProtocol { + @discardableResult + func save(user: UserAccountModel) -> UserAccountModel? + func retrieveUser() -> UserAccountModel? +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/Repositories/AuthenticationRepositoryProtocol.swift b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AuthenticationRepositoryProtocol.swift new file mode 100644 index 000000000..3ceb40e6e --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/Repositories/AuthenticationRepositoryProtocol.swift @@ -0,0 +1,14 @@ +// +// AutenticationRepository.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +protocol AuthenticationRepositoryProtocol: AnyObject { + func perform(login: BankApi.Endpoints, + with data: LoginRequestModel, + completion: @escaping (Result) -> Void) +} diff --git a/KssiusBank/KssiusBank/Core/User/Data/UserFailure.swift b/KssiusBank/KssiusBank/Core/User/Data/UserFailure.swift new file mode 100644 index 000000000..80ea9f873 --- /dev/null +++ b/KssiusBank/KssiusBank/Core/User/Data/UserFailure.swift @@ -0,0 +1,32 @@ +// +// UserFailure.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +enum UserFailure: Error, Equatable { + + // MARK: - Equatable + + static func == (lhs: UserFailure, rhs: UserFailure) -> Bool { + switch (lhs, rhs) { + case (.password, .password): return true + case (.user, .user): return true + case (.network(let lnet), .network(let rnet)): + return lnet == rnet + default: + return false + } + } + + + // MARK: - Cases + + case network(ErrorResponse?) + case password + case user + +} diff --git a/KssiusBank/KssiusBank/DesignSystem/Components/DefaultButton.swift b/KssiusBank/KssiusBank/DesignSystem/Components/DefaultButton.swift new file mode 100644 index 000000000..359b6e390 --- /dev/null +++ b/KssiusBank/KssiusBank/DesignSystem/Components/DefaultButton.swift @@ -0,0 +1,53 @@ +// +// DefaultUIButton.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +final class DefaultButton: UIButton { + + // MARK: - Constants + + private let fontSize = 16.0 + + // MARK: - Inits + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + // MARK: - Setup + + private func setup() { + layer.cornerRadius = 4 + titleLabel?.font = .bankFont?.withSize(fontSize) + setTitleColor(.white, for: .normal) + setTitleColor(.white, for: .highlighted) + backgroundColor = .bankPrimary + shadow() + } + + private func shadow() { + layer.shadowColor = UIColor.bankPrimary.cgColor + layer.shadowOffset = CGSize(width: 0.0, height: 3.0) + layer.shadowOpacity = 0.3 + } + + override var isHighlighted: Bool { + didSet { + guard let backgroundColor = backgroundColor else { return } + self.backgroundColor = + isHighlighted ? backgroundColor.withAlphaComponent(0.5) : backgroundColor.withAlphaComponent(1) + } + } +} diff --git a/KssiusBank/KssiusBank/DesignSystem/Components/DefaultTextField.swift b/KssiusBank/KssiusBank/DesignSystem/Components/DefaultTextField.swift new file mode 100644 index 000000000..e265a1b9c --- /dev/null +++ b/KssiusBank/KssiusBank/DesignSystem/Components/DefaultTextField.swift @@ -0,0 +1,81 @@ +// +// BankTextField.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +class DefaultTextField: UITextField { + + // MARK: - Constants + + private var padding = UIEdgeInsets(top: 13, left: 13, bottom: 13, right: 20) + private let fontSize = 15.0 + + // MARK: - Properties + lazy var viewPasswordButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(asset: Asset.eye), for: .normal) + button.setImage(UIImage(asset: Asset.openEye), for: .selected) + button.contentMode = .scaleAspectFit + button.imageView?.tintColor = .bankLightGray + button.tintColor = .bankLightGray + button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -16, bottom: 0, right: 0) + button.frame = CGRect(x: CGFloat(self.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25)) + button.addTarget(self, action: #selector(self.togglePasswordView), for: .touchUpInside) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + // MARK: - Setup + + private func setup() { + borderStyle = .roundedRect + textColor = .bankLightGray + font = .bankFont?.withSize(fontSize) + layer.borderWidth = 1.0 + layer.borderColor = UIColor.bankGray.cgColor + layer.cornerRadius = 4 + } + + @objc private func doneButtonAction() { + self.resignFirstResponder() + } + + func showPasswordToggle(){ + padding = UIEdgeInsets(top: 13, left: 13, bottom: 13, right: 50) + rightView = viewPasswordButton + rightViewMode = .always + + } + @objc private func togglePasswordView(_ sender: Any) { + isSecureTextEntry.toggle() + viewPasswordButton.isSelected.toggle() + } + + // MARK: - Rect + + override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: padding) + } + + override func placeholderRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: padding) + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: padding) + } +} diff --git a/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIColor+extensions.swift b/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIColor+extensions.swift new file mode 100644 index 000000000..1cbe3f341 --- /dev/null +++ b/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIColor+extensions.swift @@ -0,0 +1,15 @@ +// +// UIColor+extension.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +extension UIColor { + static let bankGray = UIColor(red: 220/255, green: 226/255, blue: 238/255, alpha: 1.0) /* #dce2ee */ + static let bankLightGray = UIColor(red: 168/255, green: 180/255, blue: 196/255, alpha: 1.0) /* #a8b4c4 */ + static let bankPrimary = UIColor(red: 59/255, green: 72/255, blue: 238/255, alpha: 1.0) /* #3b48ee */ +} diff --git a/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIFont+extensions.swift b/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIFont+extensions.swift new file mode 100644 index 000000000..15ac19571 --- /dev/null +++ b/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIFont+extensions.swift @@ -0,0 +1,13 @@ +// +// UIFont.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +extension UIFont { + static let bankFont = UIFont(name: "HelveticaNeue", size: 15) +} diff --git a/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIResponder+extensions.swift b/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIResponder+extensions.swift new file mode 100644 index 000000000..91fc6f6e1 --- /dev/null +++ b/KssiusBank/KssiusBank/DesignSystem/UI/Extensions/UIResponder+extensions.swift @@ -0,0 +1,28 @@ +// +// UIResponder+extension.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +extension UIResponder { + + private struct Static { + static weak var responder: UIResponder? + } + + /// Finds the current first responder + /// - Returns: the current UIResponder if it exists + static func currentFirst() -> UIResponder? { + Static.responder = nil + UIApplication.shared.sendAction(#selector(UIResponder._trap), to: nil, from: nil, for: nil) + return Static.responder + } + + @objc private func _trap() { + Static.responder = self + } +} diff --git a/KssiusBank/KssiusBank/Extensions/Date+extensions.swift b/KssiusBank/KssiusBank/Extensions/Date+extensions.swift new file mode 100644 index 000000000..b6ce50a2e --- /dev/null +++ b/KssiusBank/KssiusBank/Extensions/Date+extensions.swift @@ -0,0 +1,18 @@ +// +// Date+extensions.swift +// KssiusBank +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +extension Date { + + var formatToString: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd/MM/yyyy" + return dateFormatter.string(from: self) + } +} diff --git a/KssiusBank/KssiusBank/Extensions/String+extensions.swift b/KssiusBank/KssiusBank/Extensions/String+extensions.swift new file mode 100644 index 000000000..5db551288 --- /dev/null +++ b/KssiusBank/KssiusBank/Extensions/String+extensions.swift @@ -0,0 +1,80 @@ +// +// String+extensions.swift +// KssiusBank +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +extension String { + var toCurrency : String { + let currencyFormatter = NumberFormatter() + currencyFormatter.currencyCode = "BRL" + currencyFormatter.numberStyle = .currencyAccounting + return currencyFormatter.string(from: NSDecimalNumber(string: self)) ?? "" + } + + var toAccountFormat : String? { + let accountRange = NSRange( + self.startIndex..\d{1,2})"# + + #"(?\d{1,4})"# + + #"(?\d{1,2})"# + + let accountRegex = try? NSRegularExpression( + pattern: capturePattern, + options: [] + ) + + // Find the matching capture groups + let matches = accountRegex?.matches( + in: self, + options: [], + range: accountRange + ) + + + guard let match = matches?.first else { + return nil + } + let firstPart = "firstPart", + secondPart = "secondPart", + thirdPart = "thirdPart", + elements = [firstPart, secondPart, thirdPart] + + //Extract name by group + var formattedAccount = "" + for name in elements { + let matchRange = match.range(withName: name) + + // Extract the substring matching the named capture group + var capturedValue = "" + if let substringRange = Range(matchRange, in: self) { + let capture = String(self[substringRange]) + capturedValue = capture + + } + // mount account string + switch name { + case firstPart: + formattedAccount = "\(capturedValue)." + continue + case secondPart: + formattedAccount = "\(formattedAccount)\(capturedValue)-" + continue + default: + formattedAccount = "\(formattedAccount)\(capturedValue)" + } + + } + + return formattedAccount + } +} diff --git a/KssiusBank/KssiusBank/Generated/Strings+Generated.swift b/KssiusBank/KssiusBank/Generated/Strings+Generated.swift new file mode 100644 index 000000000..69b59c7c7 --- /dev/null +++ b/KssiusBank/KssiusBank/Generated/Strings+Generated.swift @@ -0,0 +1,69 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +internal enum L10n { + internal enum Network { + internal enum Error { + /// Ops! aconteceu algum erro de rede. + internal static let general = L10n.tr("Localizable", "network.error.general", fallback: "Ops! aconteceu algum erro de rede.") + } + } + internal enum Statement { + internal enum Option { + /// Depósito + internal static let deposit = L10n.tr("Localizable", "statement.option.deposit", fallback: "Depósito") + /// Fatura + internal static let invoice = L10n.tr("Localizable", "statement.option.invoice", fallback: "Fatura") + /// Pagamento + internal static let payment = L10n.tr("Localizable", "statement.option.payment", fallback: "Pagamento") + /// Retirada + internal static let withdrawal = L10n.tr("Localizable", "statement.option.withdrawal", fallback: "Retirada") + } + } + internal enum User { + internal enum Authentication { + internal enum Error { + /// Localizable.strings + /// KssiusBank + /// + /// Created by Cassio Sousa on 16/06/23. + internal static let general = L10n.tr("Localizable", "user.authentication.error.general", fallback: "Não foi possível efetuar o seu login. Por favor verifique se seu usuário e senha estão corretos e tente novamente.") + /// Por favor informe um email ou CPF válido no campo de usuário. + internal static let invalidCpf = L10n.tr("Localizable", "user.authentication.error.invalidCpf", fallback: "Por favor informe um email ou CPF válido no campo de usuário.") + /// Por favor a senha precisa conter quatro digitos, uma letra maiúscula, caracter especial e caracter alfanumérico. + internal static let invalidPassword = L10n.tr("Localizable", "user.authentication.error.invalidPassword", fallback: "Por favor a senha precisa conter quatro digitos, uma letra maiúscula, caracter especial e caracter alfanumérico.") + } + } + } +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension L10n { + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/KssiusBank/KssiusBank/Generated/XCAssets+Generated.swift b/KssiusBank/KssiusBank/Generated/XCAssets+Generated.swift new file mode 100644 index 000000000..ad1a16d18 --- /dev/null +++ b/KssiusBank/KssiusBank/Generated/XCAssets+Generated.swift @@ -0,0 +1,140 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +#if os(macOS) + import AppKit +#elseif os(iOS) + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") +internal typealias AssetColorTypeAlias = ColorAsset.Color +@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") +internal typealias AssetImageTypeAlias = ImageAsset.Image + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +internal enum Asset { + internal static let accentColor = ColorAsset(name: "AccentColor") + internal static let logo = ImageAsset(name: "Logo") + internal static let eye = ImageAsset(name: "eye") + internal static let logout = ImageAsset(name: "logout") + internal static let openEye = ImageAsset(name: "open-eye") +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details + +internal final class ColorAsset { + internal fileprivate(set) var name: String + + #if os(macOS) + internal typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + internal private(set) lazy var color: Color = Color(asset: self) + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = BundleToken.bundle + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +internal extension ColorAsset.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init!(asset: ColorAsset) { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + +internal struct ImageAsset { + internal fileprivate(set) var name: String + + #if os(macOS) + internal typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + internal var image: Image { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = BundleToken.bundle + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif +} + +internal extension ImageAsset.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") + convenience init!(asset: ImageAsset) { + #if os(iOS) || os(tvOS) + let bundle = BundleToken.bundle + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/KssiusBank/KssiusBank/Info.plist b/KssiusBank/KssiusBank/Info.plist new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/KssiusBank/KssiusBank/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/KssiusBank/KssiusBank/KssiusBank.xcdatamodeld/.xccurrentversion b/KssiusBank/KssiusBank/KssiusBank.xcdatamodeld/.xccurrentversion new file mode 100644 index 000000000..b8689019c --- /dev/null +++ b/KssiusBank/KssiusBank/KssiusBank.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + KssiusBank.xcdatamodel + + diff --git a/KssiusBank/KssiusBank/KssiusBank.xcdatamodeld/KssiusBank.xcdatamodel/contents b/KssiusBank/KssiusBank/KssiusBank.xcdatamodeld/KssiusBank.xcdatamodel/contents new file mode 100644 index 000000000..50d2514e8 --- /dev/null +++ b/KssiusBank/KssiusBank/KssiusBank.xcdatamodeld/KssiusBank.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/KssiusBank/KssiusBank/LaunchScreen.storyboard b/KssiusBank/KssiusBank/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/KssiusBank/KssiusBank/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KssiusBank/KssiusBank/Main.storyboard b/KssiusBank/KssiusBank/Main.storyboard new file mode 100644 index 000000000..c2b32c88d --- /dev/null +++ b/KssiusBank/KssiusBank/Main.storyboard @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KssiusBank/KssiusBank/Resources/.DS_Store b/KssiusBank/KssiusBank/Resources/.DS_Store new file mode 100644 index 000000000..7fa56988e Binary files /dev/null and b/KssiusBank/KssiusBank/Resources/.DS_Store differ diff --git a/KssiusBank/KssiusBank/Resources/en.lproj/Localizable.strings b/KssiusBank/KssiusBank/Resources/en.lproj/Localizable.strings new file mode 100644 index 000000000..f65c1cd77 --- /dev/null +++ b/KssiusBank/KssiusBank/Resources/en.lproj/Localizable.strings @@ -0,0 +1,7 @@ +/* + Localizable.strings + KssiusBank + + Created by Cassio Sousa on 16/06/23. + +*/ diff --git a/KssiusBank/KssiusBank/Resources/pt-BR.lproj/Localizable.strings b/KssiusBank/KssiusBank/Resources/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..b76c86d30 --- /dev/null +++ b/KssiusBank/KssiusBank/Resources/pt-BR.lproj/Localizable.strings @@ -0,0 +1,21 @@ +/* + Localizable.strings + KssiusBank + + Created by Cassio Sousa on 16/06/23. + +*/ + +// mark - User Messagens + +"user.authentication.error.general" = "Não foi possível efetuar o seu login. Por favor verifique se seu usuário e senha estão corretos e tente novamente."; +"user.authentication.error.invalidCpf" = "Por favor informe um email ou CPF válido no campo de usuário."; +"user.authentication.error.invalidPassword" = "Por favor a senha precisa conter quatro digitos, uma letra maiúscula, caracter especial e caracter alfanumérico."; + + +"network.error.general" = "Ops! aconteceu algum erro de rede."; + +"statement.option.payment" = "Pagamento"; +"statement.option.deposit" = "Depósito"; +"statement.option.invoice" = "Fatura"; +"statement.option.withdrawal" = "Retirada"; diff --git a/KssiusBank/KssiusBank/SceneDelegate.swift b/KssiusBank/KssiusBank/SceneDelegate.swift new file mode 100644 index 000000000..e41e7c35f --- /dev/null +++ b/KssiusBank/KssiusBank/SceneDelegate.swift @@ -0,0 +1,55 @@ +// +// SceneDelegate.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +//import UIKit +// +//class SceneDelegate: UIResponder, UIWindowSceneDelegate { +// +// var window: UIWindow? +// +// +// func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { +// // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. +// // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. +// // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). +// guard let _ = (scene as? UIWindowScene) else { return } +// } +// +// func sceneDidDisconnect(_ scene: UIScene) { +// // Called as the scene is being released by the system. +// // This occurs shortly after the scene enters the background, or when its session is discarded. +// // Release any resources associated with this scene that can be re-created the next time the scene connects. +// // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). +// } +// +// func sceneDidBecomeActive(_ scene: UIScene) { +// // Called when the scene has moved from an inactive state to an active state. +// // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. +// } +// +// func sceneWillResignActive(_ scene: UIScene) { +// // Called when the scene will move from an active state to an inactive state. +// // This may occur due to temporary interruptions (ex. an incoming phone call). +// } +// +// func sceneWillEnterForeground(_ scene: UIScene) { +// // Called as the scene transitions from the background to the foreground. +// // Use this method to undo the changes made on entering the background. +// } +// +// func sceneDidEnterBackground(_ scene: UIScene) { +// // Called as the scene transitions from the foreground to the background. +// // Use this method to save data, release shared resources, and store enough scene-specific state information +// // to restore the scene back to its current state. +// +// // Save changes in the application's managed object context when the application transitions to the background. +// (UIApplication.shared.delegate as? AppDelegate)?.saveContext() +// } +// +// +//} + diff --git a/KssiusBank/KssiusBank/Scenes/Autentication/LoginInteractor.swift b/KssiusBank/KssiusBank/Scenes/Autentication/LoginInteractor.swift new file mode 100644 index 000000000..8a0778c7c --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Autentication/LoginInteractor.swift @@ -0,0 +1,103 @@ +// +// LoginInteractor.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +protocol LoginBusinessLogic +{ + func login(request: Login.Login.Request) + func fetchUser(request: Login.FetchUser.Request) +} + +protocol LoginDataStore{ + var userAccount: UserAccountModel? { get set } +} + +final class LoginInteractor: LoginBusinessLogic, LoginDataStore { + + // MARK: - Properties + + private let presenter: LoginPresentationLogic? + private let worker: LoginWorkerLogic? + private let userWork: UserWorkerLogic? + + // MARK: - DataSource + var userAccount: UserAccountModel? + + // MARK: - Inits + + init(presenter: LoginPresentationLogic? = nil, + worker: LoginWorkerLogic? = nil, + userWork: UserWorkerLogic? = nil) { + self.presenter = presenter + self.worker = worker + self.userWork = userWork + } + +} + +// MARK: - Login + +extension LoginInteractor { + + func login(request: Login.Login.Request) + { + guard let user = request.user, let password = request.password else { + let errorMessage = L10n.User.Authentication.Error.general + let response = Login.Login.Response(success: false, errorMessage: errorMessage) + self.presenter?.resolveLogin(response: response) + return + } + + worker?.login(username: user, password: password) {[weak self] result in + + switch( result ) { + case .success(let accountModel): + self?.handle(success: accountModel) + case .failure(let failure): + if let response = self?.handle(errors: failure) { + self?.presenter?.resolveLogin(response: response) + } + } + } + } + + private func handle(success accountModel: UserAccountModel ) { + userAccount = accountModel + userWork?.save(user: accountModel) + presenter?.resolveLogin(response: .init(success: true)) + + } + + private func handle(errors: UserFailure) -> Login.Login.Response { + switch(errors) { + case .user: + return .init(errorMessage: L10n.User.Authentication.Error.invalidCpf) + case .password: + return .init(errorMessage: L10n.User.Authentication.Error.invalidPassword) + case .network( _): + return .init(errorMessage: L10n.Network.Error.general) + } + + } + +} + +// MARK: - Fetch User + +extension LoginInteractor { + + func fetchUser(request: Login.FetchUser.Request) { + userAccount = userWork?.retrieveUser() + presenter?.displayUser(response: .init(user: userAccount?.email)) + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Autentication/LoginModels.swift b/KssiusBank/KssiusBank/Scenes/Autentication/LoginModels.swift new file mode 100644 index 000000000..2d4a05fb3 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Autentication/LoginModels.swift @@ -0,0 +1,61 @@ +// +// LoginModels.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +enum Login { + + // MARK: Use cases + + enum FetchUser { + struct Request {} + + struct Response { + var user: String? + } + + struct ViewModel { + var user: String? + } + } + + enum Login { + struct Request { + var user: String? + var password: String? + } + + struct Response { + let success: Bool + let errorMessage: String? + + init(success: Bool = false, errorMessage: String? = nil) { + self.success = success + self.errorMessage = errorMessage + } + + func toViewModel() -> ViewModel { + return .init( success: success, errorMessage: errorMessage) + } + } + + struct ViewModel { + var success: Bool + var errorMessage: String? + + init(success: Bool = false, errorMessage: String? = nil) { + self.success = success + self.errorMessage = errorMessage + } + } + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Autentication/LoginPresenter.swift b/KssiusBank/KssiusBank/Scenes/Autentication/LoginPresenter.swift new file mode 100644 index 000000000..7573f44b8 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Autentication/LoginPresenter.swift @@ -0,0 +1,36 @@ +// +// LoginPresenter.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// Copyright (c) 2023 ___ORGANIZATIONNAME___. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +protocol LoginPresentationLogic { + func displayUser(response: Login.FetchUser.Response) + func resolveLogin(response: Login.Login.Response) +} + +final class LoginPresenter { + weak var viewController: LoginDisplayLogic? +} + +// MARK: - Logics + +extension LoginPresenter: LoginPresentationLogic { + + func resolveLogin(response: Login.Login.Response){ + viewController?.resolveLogin(viewModel:response.toViewModel()) + } + + func displayUser(response: Login.FetchUser.Response) { + viewController?.displayUser(viewModel: .init(user: response.user)) + } +} + diff --git a/KssiusBank/KssiusBank/Scenes/Autentication/LoginRouter.swift b/KssiusBank/KssiusBank/Scenes/Autentication/LoginRouter.swift new file mode 100644 index 000000000..bcf14709e --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Autentication/LoginRouter.swift @@ -0,0 +1,59 @@ +// +// LoginRouter.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// Copyright (c) 2023 ___ORGANIZATIONNAME___. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +@objc protocol LoginRoutingLogic +{ + func routeToPresentHome(segue: UIStoryboardSegue?) +} + +protocol LoginDataPassing +{ + var dataStore: LoginDataStore? { get } +} + +final class LoginRouter: NSObject, LoginRoutingLogic, LoginDataPassing { + weak var viewController: LoginViewController? + var dataStore: LoginDataStore? + + // MARK: Routing + + func routeToPresentHome(segue: UIStoryboardSegue?){ + if let segue = segue { + let destinationVC = segue.destination as? HomeViewController + var destinationDS = destinationVC?.router?.dataStore + passDataToHome(source: dataStore!, destination: &destinationDS) + } else { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let destinationVC = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController + var destinationDS = destinationVC.router?.dataStore + passDataToHome(source: dataStore, destination: &destinationDS) + navigateToHome(source: viewController, destination: destinationVC) + } + } + + // MARK: Navigation + + func navigateToHome(source: LoginViewController?, destination: HomeViewController?){ + guard let destination = destination else { + printIfDebug("Cannot navigate to home.") + return } + source?.show(destination, sender: nil) + } + + // MARK: Passing data + + func passDataToHome(source: LoginDataStore?, destination: inout HomeDataStore?){ + destination?.userAccount = source?.userAccount + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Autentication/LoginViewController.swift b/KssiusBank/KssiusBank/Scenes/Autentication/LoginViewController.swift new file mode 100644 index 000000000..96ff21303 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Autentication/LoginViewController.swift @@ -0,0 +1,240 @@ +// +// LoginViewController.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit +import KeychainSwift + +protocol LoginDisplayLogic: AnyObject +{ + func displayUser(viewModel: Login.FetchUser.ViewModel) + func resolveLogin(viewModel: Login.Login.ViewModel) +} + +final class LoginViewController: UIViewController +{ + + // MARK: - Constants + private let defaultButtonBottomSpace = 33.0 + private let segueIdentifier = "PresentHome" + + var interactor: LoginBusinessLogic? + var router: (NSObjectProtocol & LoginRoutingLogic & LoginDataPassing)? + + // MARK: Object lifecycle + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) + { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + setup() + } + + required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + setup() + } + + // MARK: Setup + + private func setup() + { + //remote datasource + let authenticationServiceDataSource = AuthenticationServiceDatasource(networkService: .init()) + let autenticationRepository = AuthenticationRepository(authenticationService: authenticationServiceDataSource) + + //local datasource + let localDataSource = AuthenticationLocalDatasource(keychain: KeychainSwift()) + let localRepository = AuthenticationLocalRepository(authenticationLocalDataSource: localDataSource) + + let worker = LoginWorker(authenticaionRepository: autenticationRepository) + let userWork = UserWorker(authenticationLocalRepository: localRepository) + let viewController = self + + let presenter = LoginPresenter() + let interactor = LoginInteractor(presenter: presenter, worker: worker, userWork: userWork) + let router = LoginRouter() + viewController.interactor = interactor + viewController.router = router + presenter.viewController = viewController + router.viewController = viewController + router.dataStore = interactor + } + + // MARK: Routing + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let scene = segue.identifier { + let selector = NSSelectorFromString("routeTo\(scene)WithSegue:") + if let router = router, router.responds(to: selector) { + router.perform(selector, with: segue) + } + } + } + + // MARK: View lifecycle + + override func viewDidLoad() + { + super.viewDidLoad() + fetchUser() + setupUI() + setupKeyboardHiding() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + cleanPassword() + } + + // MARK: Login + + @IBOutlet weak var userTextField: DefaultTextField? + @IBOutlet weak var passwordTextField: DefaultTextField? + @IBOutlet weak var buttonBottomConstraint: NSLayoutConstraint? + @IBOutlet weak var contentStack: UIStackView? + @IBOutlet weak var contentViewTopConstraint: NSLayoutConstraint? + @IBOutlet weak var loginButton: DefaultButton? + @IBOutlet weak var indicatorView: UIActivityIndicatorView? + @IBOutlet weak var errorLabel: UILabel? + + private func setupUI () { + finishState() + errorLabel?.isHidden = true + passwordTextField?.showPasswordToggle() + userTextField?.returnKeyType = .next + + // delegates + userTextField?.delegate = self + passwordTextField?.delegate = self + } + + private func cleanPassword() { + passwordTextField?.text = "" + } + + // MARK: - keyboard + private func setupKeyboardHiding() { + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc func keyboardWillShow(sender: NSNotification) { + guard let userInfo = sender.userInfo, + let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return } + + let bottomSafeArea = view.safeAreaInsets.bottom + let bottomSpace = keyboardFrame.cgRectValue.height + 10 + contentViewTopConstraint?.constant = bottomSafeArea > 0 ? 90 : 30 + if buttonBottomConstraint?.constant ?? 0 == defaultButtonBottomSpace { + buttonBottomConstraint?.constant = bottomSpace - bottomSafeArea + UIView.animate(withDuration: 0.5) { + self.view.layoutIfNeeded() + } + } + } + + @objc func keyboardWillHide(notification: NSNotification) { + view.frame.origin.y = 0 + + buttonBottomConstraint?.constant = defaultButtonBottomSpace + contentViewTopConstraint?.constant = 105 + UIView.animate(withDuration: 0.5) { + self.view.layoutIfNeeded() + } + + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + view.endEditing(true) + } + +} + +extension LoginViewController: LoginDisplayLogic { + func displayUser(viewModel: Login.FetchUser.ViewModel) { + userTextField?.text = viewModel.user + } + + func resolveLogin(viewModel: Login.Login.ViewModel) { + if viewModel.success { + presentHomeScene() + }else { + presentError(viewModel: viewModel) + } + } +} + +// MARK: - UIActions +extension LoginViewController { + + @IBAction func didTapLogin(_ sender: Any) { + performLogin() + } + + private func performLogin() { + if let _ = userTextField?.text, let _ = passwordTextField?.text { + view.endEditing(true) + } + startState() + let userText = userTextField?.text + let passwordText = passwordTextField?.text + let request = Login.Login.Request(user: userText, password: passwordText) + interactor?.login(request: request) + } + + private func startState() { + errorLabel?.text = "" + errorLabel?.isHidden = true + indicatorView?.startAnimating() + loginButton?.isEnabled = false + } + + private func finishState() { + loginButton?.isEnabled = true + indicatorView?.stopAnimating() + } + + + + private func presentHomeScene() { + performSegue(withIdentifier: segueIdentifier, sender: self) + finishState() + } + + private func presentError(viewModel :Login.Login.ViewModel) { + errorLabel?.text = viewModel.errorMessage + errorLabel?.sizeToFit() + errorLabel?.isHidden = false + finishState() + } +} + +// MARK: - Fetch User +extension LoginViewController { + + private func fetchUser() { + interactor?.fetchUser(request: .init()) + } +} + +// MARK: - TextField delegate +extension LoginViewController: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField == userTextField { + passwordTextField?.becomeFirstResponder() + }else { + performLogin() + } + return true + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Autentication/LoginWorker.swift b/KssiusBank/KssiusBank/Scenes/Autentication/LoginWorker.swift new file mode 100644 index 000000000..b3cc2e3c0 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Autentication/LoginWorker.swift @@ -0,0 +1,39 @@ +// +// LoginWorker.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// Copyright (c) 2023 Cássio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +protocol LoginWorkerLogic { + func login(username: String, password: String, completion: @escaping (Result) -> Void) +} + +final class LoginWorker { + + // MARK: - Properties + + private let authenticaionRepository: AuthenticationRepositoryProtocol + + // MARK: - Inits + + init(authenticaionRepository: AuthenticationRepositoryProtocol) { + self.authenticaionRepository = authenticaionRepository + } +} + +extension LoginWorker: LoginWorkerLogic { + func login(username: String, password: String, completion: @escaping (Result) -> Void) { + let requestModel: LoginRequestModel = .init(username: username, password: password) + authenticaionRepository.perform(login: .loginRequest, + with: requestModel, completion: completion ) + } +} + diff --git a/KssiusBank/KssiusBank/Scenes/Home/Cell/StatementCell.swift b/KssiusBank/KssiusBank/Scenes/Home/Cell/StatementCell.swift new file mode 100644 index 000000000..1856c7c41 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/Cell/StatementCell.swift @@ -0,0 +1,49 @@ +// +// StatementCell.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +// MARK: - Cell for statements + +final class StatementCell: UITableViewCell { + + // MARK: - Properties + + @IBOutlet weak var mainView: UIView? + @IBOutlet weak var typeLabel: UILabel? + @IBOutlet weak var dateLabel: UILabel? + @IBOutlet weak var descriptionLabel: UILabel? + @IBOutlet weak var balanceLabel: UILabel? + + override func awakeFromNib() { + super.awakeFromNib() + + mainView?.layer.shadowColor = UIColor.bankLightGray.withAlphaComponent(0.5).cgColor + mainView?.layer.shadowOffset = CGSize(width: 0, height: 5) + mainView?.layer.shadowOpacity = 0.5 + mainView?.layer.shadowRadius = 12 + mainView?.layer.cornerRadius = 6.0 + } + + // MARK: - Display Data + + func setup(model: Home.GetStatements.StatementViewModel) { + cleanCells() + typeLabel?.text = model.type + dateLabel?.text = model.date + descriptionLabel?.text = model.description + balanceLabel?.text = model.value + } + + private func cleanCells() { + typeLabel?.text = "" + dateLabel?.text = "" + descriptionLabel?.text = "" + balanceLabel?.text = "" + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Home/HomeInteractor.swift b/KssiusBank/KssiusBank/Scenes/Home/HomeInteractor.swift new file mode 100644 index 000000000..518c8a365 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/HomeInteractor.swift @@ -0,0 +1,66 @@ +// +// HomeInteractor.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +protocol HomeBusinessLogic +{ + func fetchStatements(request: Home.GetStatements.Request) + func retrieveAccount(request: Home.GetAccount.Request) +} + +protocol HomeDataStore{ + var userAccount: UserAccountModel? { get set } + var statements: [StatementsModel] { get set } +} + +final class HomeInteractor: HomeBusinessLogic, HomeDataStore { + + // MARK: - Properties + + private let presenter: HomePresentationLogic? + private let worker: HomeWorkerLogic? + + var userAccount: UserAccountModel? + var statements: [StatementsModel] = [] + + // MARK: - Inits + + init(presenter: HomePresentationLogic? = nil, worker: HomeWorkerLogic? = nil, userAccount: UserAccountModel? = nil) { + self.presenter = presenter + self.worker = worker + self.userAccount = userAccount + } + + // MARK: - Present account + func retrieveAccount(request: Home.GetAccount.Request = .init()) { + guard let userAccount = userAccount else { return } + presenter?.presentAccount(response: .init(userAccount: userAccount)) + } + + + // MARK: - Ppresent statements + + func fetchStatements(request: Home.GetStatements.Request = .init()){ + + worker?.fetchStatements { [weak self] result in + switch( result ) { + case .success(let statements): + self?.statements = statements + self?.presenter?.presentStatements(response: .init(statements: statements)) + case .failure( _): + self?.presenter?.presentStatements(response: .init(success: false, errorMessage: L10n.Network.Error.general)) + } + + } + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Home/HomeModels.swift b/KssiusBank/KssiusBank/Scenes/Home/HomeModels.swift new file mode 100644 index 000000000..0be23ed46 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/HomeModels.swift @@ -0,0 +1,103 @@ +// +// HomeModels.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +enum Home +{ + // MARK: Use cases + + enum GetStatements + { + struct Request{} + + struct Response{ + let success: Bool + let errorMessage: String? + let statements: [StatementsModel] + + init(success: Bool = true, errorMessage: String? = nil, statements: [StatementsModel] = []) { + self.success = success + self.errorMessage = errorMessage + self.statements = statements + } + } + + struct StatementViewModel: Equatable { + let type: String + let description: String + let date: String + let value: String + + init(type: String, description: String, date: String, value: String) { + self.type = type + self.description = description + self.date = date + self.value = value + } + } + + struct ViewModel{ + + // MARK: - Properties + + let statements: [Home.GetStatements.StatementViewModel] + let success: Bool + + // MARK: - Inits + + init(statements: [Home.GetStatements.StatementViewModel], success: Bool) { + self.statements = statements + self.success = success + } + } + } + + enum GetAccount + { + struct Request{} + + struct Response{ + let userAccount: UserAccountModel + + init(userAccount: UserAccountModel) { + self.userAccount = userAccount + } + } + + struct ViewModel: Equatable { + + // MARK: - Properties + + let name: String + let agency: String + let accountNumber: String + let balance: String + + // MARK: - Inits + + init() { + self.init(name: "", agency: "", accountNumber: "", balance: "") + } + + init(name: String, + agency: String, + accountNumber: String, + balance: String){ + self.name = name + self.agency = agency + self.accountNumber = accountNumber + self.balance = balance + } + } + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Home/HomePresenter.swift b/KssiusBank/KssiusBank/Scenes/Home/HomePresenter.swift new file mode 100644 index 000000000..bc459642c --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/HomePresenter.swift @@ -0,0 +1,63 @@ +// +// HomePresenter.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +protocol HomePresentationLogic{ + func presentStatements(response: Home.GetStatements.Response) + func presentAccount(response: Home.GetAccount.Response) +} + +class HomePresenter: HomePresentationLogic{ + + weak var viewController: HomeDisplayLogic? + + // MARK: Present Account + + func presentAccount(response: Home.GetAccount.Response) { + let viewModel = Home.GetAccount.ViewModel( + name: response.userAccount.name, + agency: response.userAccount.agency, + accountNumber: response.userAccount.accountNumber, + balance: response.userAccount.balance + ) + viewController?.displayAccount(viewModel: viewModel) + } + + // MARK: Present Statements + + func presentStatements(response: Home.GetStatements.Response) { + let statements = mapToStatements(response.statements) + viewController?.displayStatements(viewModel: .init(statements: statements, success: response.success)) + } + + private func mapToStatements(_ model: [StatementsModel]) -> [Home.GetStatements.StatementViewModel] { + return model.map{.init(type: statements(type: $0.type), + description: $0.description, + date: $0.date.formatToString, + value: $0.value.toCurrency) + } + } + + private func statements(type: StatementsModel.StatementsType) -> String { + switch type { + case .payment: + return L10n.Statement.Option.payment + case .deposit: + return L10n.Statement.Option.deposit + case .invoice: + return L10n.Statement.Option.invoice + case .withdrawal: + return L10n.Statement.Option.withdrawal + } + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Home/HomeRouter.swift b/KssiusBank/KssiusBank/Scenes/Home/HomeRouter.swift new file mode 100644 index 000000000..3138de012 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/HomeRouter.swift @@ -0,0 +1,64 @@ +// +// HomeRouter.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +@objc protocol HomeRoutingLogic +{ + func dismiss() +} + +protocol HomeDataPassing +{ + var dataStore: HomeDataStore? { get } +} + +class HomeRouter: NSObject, HomeRoutingLogic, HomeDataPassing +{ + weak var viewController: HomeViewController? + var dataStore: HomeDataStore? + + // MARK: Routing + + //func routeToSomewhere(segue: UIStoryboardSegue?) + //{ + // if let segue = segue { + // let destinationVC = segue.destination as! SomewhereViewController + // var destinationDS = destinationVC.router!.dataStore! + // passDataToSomewhere(source: dataStore!, destination: &destinationDS) + // } else { + // let storyboard = UIStoryboard(name: "Main", bundle: nil) + // let destinationVC = storyboard.instantiateViewController(withIdentifier: "SomewhereViewController") as! SomewhereViewController + // var destinationDS = destinationVC.router!.dataStore! + // passDataToSomewhere(source: dataStore!, destination: &destinationDS) + // navigateToSomewhere(source: viewController!, destination: destinationVC) + // } + //} + + // MARK: Navigation + + //func navigateToSomewhere(source: HomeViewController, destination: SomewhereViewController) + //{ + // source.show(destination, sender: nil) + //} + + // MARK: Passing data + + //func passDataToSomewhere(source: HomeDataStore, destination: inout SomewhereDataStore) + //{ + // destination.name = source.name + //} + + func dismiss() { + viewController?.dismiss(animated: true) + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Home/HomeViewController.swift b/KssiusBank/KssiusBank/Scenes/Home/HomeViewController.swift new file mode 100644 index 000000000..6abfa1dff --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/HomeViewController.swift @@ -0,0 +1,147 @@ +// +// HomeViewController.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + +protocol HomeDisplayLogic: AnyObject { + func displayStatements(viewModel: Home.GetStatements.ViewModel) + func displayAccount(viewModel: Home.GetAccount.ViewModel) +} + +final class HomeViewController: UIViewController, HomeDisplayLogic { + var interactor: HomeBusinessLogic? + var router: (NSObjectProtocol & HomeRoutingLogic & HomeDataPassing)? + + // MARK: - Constants + + private let cellStatementIdentifier = "statementCell" + private var statements = [Home.GetStatements.StatementViewModel]() + + // MARK: Object lifecycle + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + // MARK: Setup + + private func setup() { + let viewController = self + + let statementsServiceDatasource = StatementsServiceDatasource(networkService: .init()) + let statementsRepository = StatementsRepository(statementsService: statementsServiceDatasource) + + let worker = HomeWorker(statementsRepository: statementsRepository) + + let presenter = HomePresenter() + let interactor = HomeInteractor(presenter: presenter, worker: worker) + + let router = HomeRouter() + viewController.interactor = interactor + viewController.router = router + presenter.viewController = viewController + router.viewController = viewController + router.dataStore = interactor + } + + // MARK: Routing + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let scene = segue.identifier { + let selector = NSSelectorFromString("routeTo\(scene)WithSegue:") + if let router = router, router.responds(to: selector) { + router.perform(selector, with: segue) + } + } + } + + // MARK: View lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + initializeData() + } + + + // MARK: - Outlets + + @IBOutlet weak var nameLabel: UILabel? + @IBOutlet weak var accountLabel: UILabel? + @IBOutlet weak var balanceLabel: UILabel? + @IBOutlet weak var tableView: UITableView? + @IBOutlet weak var activityIndicator: UIActivityIndicatorView? + + private func setupUI(){ + startState() + accountLabel?.center = tableView?.center ?? .zero + } + + @IBAction func didTapLogout(_ sender: Any) { + router?.dismiss() + } + + + // MARK: Fetch data to display + + private func initializeData() { + interactor?.retrieveAccount(request: .init()) + interactor?.fetchStatements(request: .init()) + } + + private func startState() { + activityIndicator?.startAnimating() + } + + private func finishState() { + activityIndicator?.stopAnimating() + } +} + +// MARK: - Display informations + +extension HomeViewController { + + func displayStatements(viewModel: Home.GetStatements.ViewModel){ + statements = viewModel.statements + tableView?.reloadData() + finishState() + } + + func displayAccount(viewModel: Home.GetAccount.ViewModel){ + nameLabel?.text = viewModel.name + let numberFormat = viewModel.accountNumber.toAccountFormat ?? "" + accountLabel?.text = "\(viewModel.agency) / \(numberFormat)" + balanceLabel?.text = viewModel.balance.toCurrency + } + +} + +// MARK: - TableView Delegates +extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return statements.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let row = tableView.dequeueReusableCell(withIdentifier: cellStatementIdentifier, for: indexPath) as? StatementCell + else { return .init() } + row.setup(model: statements[indexPath.row]) + return row + } +} diff --git a/KssiusBank/KssiusBank/Scenes/Home/HomeWorker.swift b/KssiusBank/KssiusBank/Scenes/Home/HomeWorker.swift new file mode 100644 index 000000000..176cadf78 --- /dev/null +++ b/KssiusBank/KssiusBank/Scenes/Home/HomeWorker.swift @@ -0,0 +1,33 @@ +// +// HomeWorker.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// Copyright (c) 2023 Cassio Sousa. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +import UIKit + + +protocol HomeWorkerLogic { + func fetchStatements( completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void ) +} + +final class HomeWorker { + private let statementsRepository: StatementsRepositoryProtocol + + init(statementsRepository: StatementsRepositoryProtocol) { + self.statementsRepository = statementsRepository + } + +} + +extension HomeWorker: HomeWorkerLogic { + func fetchStatements( completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void ) { + statementsRepository.fetch(statements: .statementsRequest, completion: completion) + } +} diff --git a/KssiusBank/KssiusBank/Services/Autehntication/BankApi.swift b/KssiusBank/KssiusBank/Services/Autehntication/BankApi.swift new file mode 100644 index 000000000..f985a3eef --- /dev/null +++ b/KssiusBank/KssiusBank/Services/Autehntication/BankApi.swift @@ -0,0 +1,30 @@ +// +// AutenticationApi.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import Foundation + + +enum BankApi{ + static let baseUrl = "https://648bb0bf17f1536d65eb2264.mockapi.io/api" + enum Endpoints { + case loginRequest, statementsRequest + } +} + +extension BankApi.Endpoints { + var endpoint: Endpoint { + switch self { + case .loginRequest: + return Endpoint(path: "login", method: .post, configuration: .init(baseUrl: BankApi.baseUrl)) + + case .statementsRequest: + return Endpoint(path: "statements", method: .get, configuration: .init(baseUrl: BankApi.baseUrl)) + } + } +} + + diff --git a/KssiusBank/KssiusBank/Services/NetworkService.swift b/KssiusBank/KssiusBank/Services/NetworkService.swift new file mode 100755 index 000000000..cf3eccb79 --- /dev/null +++ b/KssiusBank/KssiusBank/Services/NetworkService.swift @@ -0,0 +1,104 @@ +import Foundation + +func printIfDebug(_ string: String) { + #if DEBUG + print(string) + #endif +} + + +struct PathParameter { + let name: String + let value: String +} + +struct NetworkConfiguration { + let baseUrl: String +} + +struct Endpoint { + typealias RawValue = Int + + enum Method: String { + case get, post + } + let path: String + let method: Method + let configuration: NetworkConfiguration + + var fullPath: String { + "\(configuration.baseUrl)/\(path)" + } + +} + +enum ServiceError: Error, Equatable { + static func == (lhs: ServiceError, rhs: ServiceError) -> Bool { + switch (lhs, rhs) { + case (.invalidUrl, .invalidUrl): return true + case (.noData, .noData): return true + case (.decodeFail(let lnet), .decodeFail(let rnet)) : + return lnet == rnet + case (.network(let lnet), .network(let rnet)) : + return lnet == rnet + default: + return false + } + } + + case invalidUrl + case noData + case decodeFail(ErrorResponse?) + case network(ErrorResponse?) +} + +class NetworkService { + + // MARK: - Properties + + typealias Response = R + let urlSession: URLSession + + // MARK: - Initializers + init(urlSession: URLSession = URLSession.shared) { + self.urlSession = urlSession + } + + func request(endpoint: Endpoint, payload: Encodable? = nil, completion: @escaping (Result) -> Void) { + let path = endpoint.fullPath + guard let url = URL(string: path) else { + completion(.failure(.invalidUrl)) + return; + } + printIfDebug(path) + + var request = URLRequest(url: url) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept") + request.httpMethod = endpoint.method.rawValue + + if let payload = payload, let encodablePayload = try? JSONEncoder().encode(payload) { + request.httpBody = encodablePayload + } + + let task = urlSession.dataTask(with: request) { data, response, error in + guard let data = data else { + completion(.failure(.noData)) + return + } + do{ + let decoder = JSONDecoder() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + decoder.dateDecodingStrategy = .formatted(dateFormatter) + let body = try decoder.decode(Response.self, from: data) + completion(.success(body)) + }catch { + printIfDebug("\(error)") + completion(.failure(.decodeFail(.init(message: error.localizedDescription)))) + } + + } + task.resume() + } +} diff --git a/KssiusBank/KssiusBank/Services/URLSessionProtocol.swift b/KssiusBank/KssiusBank/Services/URLSessionProtocol.swift new file mode 100644 index 000000000..945d963ca --- /dev/null +++ b/KssiusBank/KssiusBank/Services/URLSessionProtocol.swift @@ -0,0 +1,23 @@ +// +// URLSessionProtocol.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import Foundation + +protocol URLSessionDataTaskProtocol { + func resume() + func cancel() +} + +protocol URLSessionProtocol { + func dataTask( + with request: URLRequest, + completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void + ) -> URLSessionDataTask +} + +extension URLSession : URLSessionProtocol{} +extension URLSessionDataTask : URLSessionDataTaskProtocol{} diff --git a/KssiusBank/KssiusBank/ViewController.swift b/KssiusBank/KssiusBank/ViewController.swift new file mode 100644 index 000000000..2ea3a1f94 --- /dev/null +++ b/KssiusBank/KssiusBank/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + diff --git a/KssiusBank/KssiusBank/Workers/UserWorker.swift b/KssiusBank/KssiusBank/Workers/UserWorker.swift new file mode 100644 index 000000000..06a0165ae --- /dev/null +++ b/KssiusBank/KssiusBank/Workers/UserWorker.swift @@ -0,0 +1,41 @@ +// +// UserWorker.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import Foundation + +protocol UserWorkerLogic { + @discardableResult + func save(user: UserAccountModel) -> UserAccountModel? + func retrieveUser() -> UserAccountModel? +} + +final class UserWorker { + + // MARK: - Properties + + private let authenticationLocalRepository: AuthenticationLocalRepositoryProtocol + + // MARK: - Inits + + init(authenticationLocalRepository: AuthenticationLocalRepositoryProtocol) { + self.authenticationLocalRepository = authenticationLocalRepository + } + +} + +// MARK: - Resources + +extension UserWorker: UserWorkerLogic { + + func save(user: UserAccountModel) -> UserAccountModel? { + return authenticationLocalRepository.save(user: user) + } + + func retrieveUser() -> UserAccountModel? { + return authenticationLocalRepository.retrieveUser() + } +} diff --git a/KssiusBank/KssiusBankTests/Core/Statements/Data/Datasources/StatementsServiceDatasourceTest.swift b/KssiusBank/KssiusBankTests/Core/Statements/Data/Datasources/StatementsServiceDatasourceTest.swift new file mode 100644 index 000000000..6569b5ecf --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/Statements/Data/Datasources/StatementsServiceDatasourceTest.swift @@ -0,0 +1,75 @@ +// +// AutenticationServiceDatasourceTest.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import XCTest +@testable import KssiusBank + +final class StatementsServiceDatasourceTest: XCTestCase { + var sut: StatementsServiceDatasourceProtocol? + let apiURL = URL(string: "https://example.com.br/statements")! + let baseUrl = "https://example.com.br" + + override func setUp() { + super.setUp() + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [MockURLProtocol.self] + let urlSession = URLSession.init(configuration: configuration) + + sut = StatementsServiceDatasource(networkService: .init(urlSession: urlSession)) + + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success Response + + func testSuccessResponse() { + let data = Seeds.Statements.json.data(using: .utf8) + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: self.apiURL, statusCode: 200, httpVersion: nil, headerFields: nil)! + return (response, data) + } + let expectation = expectation(description: "Waiting for fetch statements call to complete.") + sut?.fetch(statements: .statementsRequest ){ result in + switch(result){ + case .success(let userModel): + XCTAssertEqual(userModel.count, 3) + case .failure( _): + XCTFail("Should not return a failure") + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Server Error Response + + func testErrorResponse() { + + MockURLProtocol.requestHandler = { request in + throw ServiceError.invalidUrl + } + let expectation = expectation(description: "Waiting for fetch statements call to complete.") + sut?.fetch(statements: .statementsRequest){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + switch( error ){ + case .network( _): + XCTAssertEqual(error, BankFailure.network(.init())) + } + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} diff --git a/KssiusBank/KssiusBankTests/Core/Statements/Data/Repositories/Mock/MockStatementsServiceDatasource.swift b/KssiusBank/KssiusBankTests/Core/Statements/Data/Repositories/Mock/MockStatementsServiceDatasource.swift new file mode 100644 index 000000000..63e6d72c9 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/Statements/Data/Repositories/Mock/MockStatementsServiceDatasource.swift @@ -0,0 +1,40 @@ +// +// MockStatementsServiceDatasource.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +// MARK: - Success mock StatementsServiceDatasource + +final class MockSuccessStatementsServiceDatasource: StatementsServiceDatasourceProtocol { + convenience init() { + self.init(networkService: .init()) + } + + init(networkService: NetworkService<[StatementsModel]>) {} + + func fetch(statements: BankApi.Endpoints, completion: @escaping (Result<[StatementsModel], BankFailure>) -> Void) { + completion(.success(Seeds.Statements.statements) + ) + } +} + +// MARK: - Failure network AutenticationServiceDatasource + +final class MockFailureNetworkStatementsServiceDatasource: StatementsServiceDatasourceProtocol { + + convenience init() { + self.init(networkService: .init()) + } + + init(networkService: NetworkService<[StatementsModel]>) {} + + + func fetch(statements: BankApi.Endpoints, completion: @escaping (Result<[StatementsModel], BankFailure>) -> Void) { + completion(.failure(.network(.init()))) + } +} + diff --git a/KssiusBank/KssiusBankTests/Core/Statements/Data/Repositories/StatementsRepositoryTest.swift b/KssiusBank/KssiusBankTests/Core/Statements/Data/Repositories/StatementsRepositoryTest.swift new file mode 100644 index 000000000..64d0af3ed --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/Statements/Data/Repositories/StatementsRepositoryTest.swift @@ -0,0 +1,61 @@ +// +// StatementsRepositoryTest.swift +// KssiusBank +// +// Created by Cassio Sousa on 17/06/23. +// + +import XCTest +@testable import KssiusBank + +final class StatementsRepositoryTest: XCTestCase { + var sut: StatementsRepositoryProtocol? + + override func setUp() { + super.setUp() + sut = StatementsRepository(statementsService: MockSuccessStatementsServiceDatasource()) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success Authentication Response + + func testSuccessResponse() { + let expectation = expectation(description: "Waiting for execute fetch statements to complete.") + sut?.fetch(statements: .statementsRequest ){ result in + switch(result){ + case .success(let userModel): + XCTAssertEqual(userModel, Seeds.Statements.statements) + case .failure( _): + XCTFail("Should not return a failure") + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Server Error Response + + func testErrorResponse() { + + let failureNetwork = MockFailureNetworkStatementsServiceDatasource() + sut = StatementsRepository(statementsService: failureNetwork) + + let expectation = expectation(description: "Waiting for fetch statements to complete.") + + sut?.fetch(statements: .statementsRequest){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .network(.init())) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} + diff --git a/KssiusBank/KssiusBankTests/Core/User/Common/CommonValidatorTest.swift b/KssiusBank/KssiusBankTests/Core/User/Common/CommonValidatorTest.swift new file mode 100644 index 000000000..1099e5d77 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Common/CommonValidatorTest.swift @@ -0,0 +1,94 @@ +// +// CommonValidateTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// + +import XCTest +@testable import KssiusBank + +final class CommonValidatorTest: XCTestCase { + +} + +// MARK: - CPF Tests +extension CommonValidatorTest { + + // MARK: - Success CPF Authentication Response + + func testSuccessCpf() { + XCTAssertTrue(CommonValidator.User.isCpf(Seeds.cpf)) + } + + + // MARK: - Failure CPF Authentication + + func testFailureCpf() { + XCTAssertFalse(CommonValidator.User.isCpf("331.876.789.90")) + } +} + +// MARK: - Email Tests +extension CommonValidatorTest { + + // MARK: - Success Email Authentication Response + + func testSuccessEmail() { + XCTAssertTrue(CommonValidator.User.isEmail(Seeds.email)) + } + + + // MARK: - Failure Email Authentication + + func testFailureEmailDomain() { + XCTAssertFalse(CommonValidator.User.isEmail("teste@testecom")) + } + + // MARK: - Failure Email Authentication + + func testFailureEmailDomainDot() { + XCTAssertFalse(CommonValidator.User.isEmail("teste@testecom.")) + } + + // MARK: - Failure Email Authentication + + func testFailureEmailUser() { + XCTAssertFalse(CommonValidator.User.isEmail("teste @teste.com")) + } +} + +// MARK: - Password Tests + +extension CommonValidatorTest { + + // MARK: - Success Password Authentication + + func testSuccessPassword() { + XCTAssertTrue(CommonValidator.User.validatePassword(Seeds.password)) + } + + // MARK: - Failure Password Special char Authentication + + func testFailurePasswordSpecialCharResponse() { + XCTAssertFalse(CommonValidator.User.validatePassword("Tato123")) + } + + // MARK: - Failure Password Upper char Authentication + + func testFailurePasswordUpperCharResponse() { + XCTAssertFalse(CommonValidator.User.validatePassword("t@to123")) + } + + // MARK: - Failure Password Number Authentication + + func testFailurePasswordNumberResponse() { + XCTAssertFalse(CommonValidator.User.validatePassword("T@toBAC")) + } + + // MARK: - Failure Password Alpha char Authentication + + func testFailurePasswordAlphaResponse() { + XCTAssertFalse(CommonValidator.User.validatePassword("%@45123")) + } +} diff --git a/KssiusBank/KssiusBankTests/Core/User/Data/Datasources/AutenticationLocalDatasourceTest.swift b/KssiusBank/KssiusBankTests/Core/User/Data/Datasources/AutenticationLocalDatasourceTest.swift new file mode 100644 index 000000000..4ca922bbc --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Data/Datasources/AutenticationLocalDatasourceTest.swift @@ -0,0 +1,64 @@ +// +// AutenticationLocalDatasourceTest.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import KeychainSwift + +import XCTest +@testable import KssiusBank + +final class AutenticationLocalDatasourceTest: XCTestCase { + var sut: AuthenticationLocalDatasourceProtocol? + + override func setUp() { + super.setUp() + sut = AuthenticationLocalDatasource(keychain: KeychainSwift()) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success save user + + func testSuccessSaveUser() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + let result = sut?.save(user: user) + validate(userAccount: result) + } + + // MARK: - Failure save user + + func testSuccessGetUser() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + sut?.save(user: user) + let result = sut?.retrieveUser() + validate(userAccount: result) + } + + private func validate(userAccount result: UserAccountModel?) { + XCTAssertNotNil(result) + XCTAssertEqual(result?.userId, "3") + XCTAssertEqual(result?.cpf, "468.655.400-42") + XCTAssertEqual(result?.name, "Diana Leuschke") + XCTAssertEqual(result?.email, "Marquis_Gibson@hotmail.com") + XCTAssertEqual(result?.accountNumber, "74393734") + XCTAssertEqual(result?.agency, "827810101") + XCTAssertEqual(result?.balance, "472.29") + } +} diff --git a/KssiusBank/KssiusBankTests/Core/User/Data/Datasources/AutenticationServiceDatasourceTest.swift b/KssiusBank/KssiusBankTests/Core/User/Data/Datasources/AutenticationServiceDatasourceTest.swift new file mode 100644 index 000000000..27942c14d --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Data/Datasources/AutenticationServiceDatasourceTest.swift @@ -0,0 +1,79 @@ +// +// AutenticationServiceDatasourceTest.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import XCTest +@testable import KssiusBank + +final class AutenticationServiceDatasourceTest: XCTestCase { + var sut: AuthenticationServiceDatasourceProtocol? + let apiURL = URL(string: "https://example.com.br/login")! + let baseUrl = "https://example.com.br" + + override func setUp() { + super.setUp() + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [MockURLProtocol.self] + let urlSession = URLSession.init(configuration: configuration) + + sut = AuthenticationServiceDatasource(networkService: .init(urlSession: urlSession)) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success Response + + func testSuccessResponse() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + let modelRequest = LoginRequestModel(username: Seeds.cpf, password: Seeds.password) + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: self.apiURL, statusCode: 200, httpVersion: nil, headerFields: nil)! + return (response, data) + } + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success(let userModel): + XCTAssertEqual(userModel.email, "Marquis_Gibson@hotmail.com") + case .failure( _): + XCTFail("Should not return a failure") + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Server Error Response + + func testErrorResponse() { + + let modelRequest = LoginRequestModel(username: Seeds.cpf, password: Seeds.password) + + MockURLProtocol.requestHandler = { request in + throw ServiceError.invalidUrl + } + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest,with: modelRequest){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + switch( error ){ + case .network( _): + XCTAssertEqual(error, BankFailure.network(.init())) + } + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} diff --git a/KssiusBank/KssiusBankTests/Core/User/Repositories/AuthenticationLocalRepositoryTest.swift b/KssiusBank/KssiusBankTests/Core/User/Repositories/AuthenticationLocalRepositoryTest.swift new file mode 100644 index 000000000..4697809aa --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Repositories/AuthenticationLocalRepositoryTest.swift @@ -0,0 +1,66 @@ +// +// AutenticationLocalRepositoryTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + + +import XCTest +@testable import KssiusBank + +final class AuthenticationLocalRepositoryTest: XCTestCase { + var sut: AuthenticationLocalRepositoryProtocol? + + override func setUp() { + super.setUp() + let mock = MockSuccessAuthenticationLocalDatasource() + sut = AuthenticationLocalRepository(authenticationLocalDataSource: mock) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success save user + + func testSuccessSaveUser() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + let result = sut?.save(user: user) + validate(userAccount: result) + } + + // MARK: - Failure save user + + func testSuccessGetUser() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + sut?.save(user: user) + let result = sut?.retrieveUser() + validate(userAccount: result) + } + + private func validate(userAccount result: UserAccountModel?) { + XCTAssertNotNil(result) + XCTAssertEqual(result?.userId, "3") + XCTAssertEqual(result?.cpf, "468.655.400-42") + XCTAssertEqual(result?.name, "Diana Leuschke") + XCTAssertEqual(result?.email, "Marquis_Gibson@hotmail.com") + XCTAssertEqual(result?.accountNumber, "74393734") + XCTAssertEqual(result?.agency, "827810101") + XCTAssertEqual(result?.balance, "472.29") + } +} diff --git a/KssiusBank/KssiusBankTests/Core/User/Repositories/AuthenticationRepositoryTest.swift b/KssiusBank/KssiusBankTests/Core/User/Repositories/AuthenticationRepositoryTest.swift new file mode 100644 index 000000000..fa1958b01 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Repositories/AuthenticationRepositoryTest.swift @@ -0,0 +1,272 @@ +// +// AutenticationRepositoryTest.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import XCTest +@testable import KssiusBank + +final class AuthenticationRepositoryTest: XCTestCase { + var sut: AuthenticationRepositoryProtocol? + let successCpfModelRequest = LoginRequestModel(username: "468.655.400-42", password: "T@to123") + let successEmailModelRequest = LoginRequestModel(username: "Marquis_Gibson@hotmail.com", password: "T@to123") + + override func setUp() { + super.setUp() + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success Authentication Response + + func testSuccessResponse() { + let expectation = expectation(description: "Waiting for execute login to complete.") + sut?.perform(login: .loginRequest, with: successCpfModelRequest ){ result in + switch(result){ + case .success(let userModel): + XCTAssertEqual(userModel.email, "Marquis_Gibson@hotmail.com") + case .failure( _): + XCTFail("Should not return a failure") + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Server Error Response + + func testErrorResponse() { + + let failureNetwork = MockFailureNetworkAutenticationServiceDatasource() + sut = AuthenticationRepository(authenticationService: failureNetwork) + + let modelRequest = LoginRequestModel(username: "468.655.400-42", password: "T@to123") + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .network(.init())) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} + + +// MARK: - CPF Tests +extension AuthenticationRepositoryTest { + + // MARK: - Success CPF Authentication Response + + func testSuccessCpfResponse() { + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: successCpfModelRequest ){ result in + switch(result){ + case .success( let userModel): + XCTAssertEqual(userModel.email, "Marquis_Gibson@hotmail.com") + case .failure( _): + XCTFail("Should not return a failure") + + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + + // MARK: - Failure CPF Authentication Response + + func testFailureCpfResponse() { + let modelRequest = LoginRequestModel(username: "331.876.789.90", password: "pass") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .user) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} + + +// MARK: - Email Tests +extension AuthenticationRepositoryTest { + + // MARK: - Success Email Authentication + + func testSuccessEmailResponse() { + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: successEmailModelRequest ){ result in + switch(result){ + case .success( let userModel): + XCTAssertEqual(userModel.email, "Marquis_Gibson@hotmail.com") + case .failure( _): + XCTFail("Should not return a failure") + + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Success Email Authentication With Dot + + func testSuccessEmailWithDotResponse() { + let successEmailModelRequest = LoginRequestModel(username: "Marquis.Gibson@hotmail.com", password: "T@to123") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: successEmailModelRequest ){ result in + switch(result){ + case .success( let userModel): + XCTAssertEqual(userModel.email, "Marquis_Gibson@hotmail.com") + case .failure( _): + XCTFail("Should not return a failure") + + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + + // MARK: - Failure Email domain + + func testFailureEmailDomainResponse() { + let modelRequest = LoginRequestModel(username: "test@teste", password: "pass") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .user) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Failure Email Authentication Response + + func testFailureEmailTextResponse() { + let modelRequest = LoginRequestModel(username: "test tt@teste", password: "pass") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .user) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} + + + +// MARK: - Password Tests +extension AuthenticationRepositoryTest { + + // MARK: - Failure Password Special char Authentication + + func testFailurePasswordSpecialCharResponse() { + let modelRequest = LoginRequestModel(username: "test@test.com", password: "Tato123") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .password) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Failure Password Upper char Authentication + + func testFailurePasswordUpperCharResponse() { + let modelRequest = LoginRequestModel(username: "test@test.com", password: "t@to123") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .password) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Failure Password Number Authentication + + func testFailurePasswordNumberResponse() { + let modelRequest = LoginRequestModel(username: "test@test.com", password: "T@toBAC") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .password) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + // MARK: - Failure Password Alpha char Authentication + + func testFailurePasswordAlphaResponse() { + let modelRequest = LoginRequestModel(username: "test@test.com", password: "%@45123") + sut = AuthenticationRepository(authenticationService: MockSuccessAutenticationServiceDatasource()) + let expectation = expectation(description: "Waiting for execute login to complete.") + + sut?.perform(login: .loginRequest, with: modelRequest ){ result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + case .failure( let error): + XCTAssertEqual(error , .password) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } +} diff --git a/KssiusBank/KssiusBankTests/Core/User/Repositories/Mock/MockAutenticationLocalDatasourceProtocol.swift b/KssiusBank/KssiusBankTests/Core/User/Repositories/Mock/MockAutenticationLocalDatasourceProtocol.swift new file mode 100644 index 000000000..fcf188a93 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Repositories/Mock/MockAutenticationLocalDatasourceProtocol.swift @@ -0,0 +1,20 @@ +// +// MockAutenticationLocalDatasourceProtocol.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +final class MockSuccessAuthenticationLocalDatasource: AuthenticationLocalDatasourceProtocol { + + func save(user: UserAccountModel) -> UserAccountModel? { + return Seeds.user + } + + func retrieveUser() -> UserAccountModel? { + return Seeds.user + } +} diff --git a/KssiusBank/KssiusBankTests/Core/User/Repositories/Mock/MockAutenticationServiceDatasource.swift b/KssiusBank/KssiusBankTests/Core/User/Repositories/Mock/MockAutenticationServiceDatasource.swift new file mode 100644 index 000000000..40a256445 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Core/User/Repositories/Mock/MockAutenticationServiceDatasource.swift @@ -0,0 +1,39 @@ +// +// MockAutenticationServiceDatasource.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +// MARK: - Success mock AutenticationServiceDatasource + +final class MockSuccessAutenticationServiceDatasource: AuthenticationServiceDatasourceProtocol { + convenience init() { + self.init(networkService: .init()) + } + + init(networkService: NetworkService) {} + + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, completion: @escaping (Result) -> Void) { + completion(.success(Seeds.user) + ) + } +} + +// MARK: - Failure network AutenticationServiceDatasource + +final class MockFailureNetworkAutenticationServiceDatasource: AuthenticationServiceDatasourceProtocol { + + convenience init() { + self.init(networkService: .init()) + } + + init(networkService: NetworkService) {} + + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, completion: @escaping (Result) -> Void) { + completion(.failure(.network(.init()))) + } +} + diff --git a/KssiusBank/KssiusBankTests/Extensions/Date+extensionsTest.swift b/KssiusBank/KssiusBankTests/Extensions/Date+extensionsTest.swift new file mode 100644 index 000000000..d6b727311 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Extensions/Date+extensionsTest.swift @@ -0,0 +1,19 @@ +// +// Date+extensiosnTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest + +final class DateExtensionsTest: XCTestCase { + + // MARK: - Success date format + + func testSuccessDateFormat() { + let formattedDate = Date(year: 1986, month: 12, day: 26).formatToString + XCTAssertEqual("26/12/1986", formattedDate) + } +} diff --git a/KssiusBank/KssiusBankTests/Extensions/String+extensionsTest.swift b/KssiusBank/KssiusBankTests/Extensions/String+extensionsTest.swift new file mode 100644 index 000000000..fac95ac92 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Extensions/String+extensionsTest.swift @@ -0,0 +1,38 @@ +// +// String+extensionTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +import XCTest + +final class AccountNumberTest: XCTestCase { + + // MARK: - Success account format + + func testSuccessAccountFormat() { + let accountNumberFormated = "12345678".toAccountFormat + XCTAssertEqual("12.3456-78", accountNumberFormated) + } + + func testSuccessAccountMoreNumbersFormat() { + let accountNumberFormated = "123456789".toAccountFormat + XCTAssertEqual("12.3456-78", accountNumberFormated) + } + + // MARK: - Failure account format + func testFailureStringAccountFormat() { + let accountNumberFormated = "teste".toAccountFormat + XCTAssertNil(accountNumberFormated) + } + + // MARK: - Failure account format + func testFailureAccountFormat() { + let accountNumberFormated = "1234-5678".toAccountFormat + XCTAssertEqual("12.3-4",accountNumberFormated) + } +} diff --git a/KssiusBank/KssiusBankTests/KssiusBankTests.swift b/KssiusBank/KssiusBankTests/KssiusBankTests.swift new file mode 100644 index 000000000..a17de1184 --- /dev/null +++ b/KssiusBank/KssiusBankTests/KssiusBankTests.swift @@ -0,0 +1,36 @@ +// +// KssiusBankTests.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 15/06/23. +// + +import XCTest +@testable import KssiusBank + +final class KssiusBankTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/KssiusBank/KssiusBankTests/MockSession.swift b/KssiusBank/KssiusBankTests/MockSession.swift new file mode 100644 index 000000000..231a9c548 --- /dev/null +++ b/KssiusBank/KssiusBankTests/MockSession.swift @@ -0,0 +1,17 @@ +// +// MockSession.swift +// KssiusBank +// +// Created by Cassio Sousa on 16/06/23. +// + +import Foundation + +final class MockSession { + + static func sessionInstance() -> URLSession { + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [MockURLProtocol.self] + return URLSession.init(configuration: configuration) + } +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginInteractorTest.swift b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginInteractorTest.swift new file mode 100644 index 000000000..ee515ea5e --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginInteractorTest.swift @@ -0,0 +1,299 @@ +// +// LoginInteractor.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest +@testable import KssiusBank + +final class LoginInteractorTest: XCTestCase { + + // MARK: - Tests Error worker network + + func testFailureLoginWithNetwork() { + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerFailSpy(failure: .network(.init())) + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: Seeds.email, password: Seeds.password) + + + sut.login(request: request) + + XCTAssertTrue(workerSpy.loginCalled) + XCTAssertFalse(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertFalse(presenterSpy.presentedLoginSuccess) + XCTAssertEqual( + presenterSpy.presentedLoginErrorMessage, + L10n.Network.Error.general + ) + } + + // MARK: - Tests Success fetch user + func testSuccessFetchUser() { + + + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerSuccessSpy() + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: Seeds.email, password: Seeds.password) + + sut.login(request: request) + sut.fetchUser(request: .init()) + + let expectedUser = Seeds.email + XCTAssertTrue(userWorkerSpy.retrieveUserCalled) + XCTAssertTrue(presenterSpy.presentUserCalled) + XCTAssertEqual(presenterSpy.displayedUser, expectedUser) + } + + +} + +// MARK: - Tests Invalid login + +extension LoginInteractorTest { + + // MARK: - LoginSeuccess with email + + func testSuccessLoginWithEmail() { + + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerSuccessSpy() + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: Seeds.email, password: Seeds.password) + + sut.login(request: request) + + + XCTAssertTrue(workerSpy.loginCalled) + XCTAssertTrue(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertTrue(presenterSpy.presentedLoginSuccess) + XCTAssertNil(presenterSpy.presentedLoginErrorMessage) + } + + // MARK: - Login Success with cpf + + func testSuccessLoginWithCPF() { + + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerSuccessSpy() + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: Seeds.cpf, password: Seeds.password) + + sut.login(request: request) + + + XCTAssertTrue(workerSpy.loginCalled) + XCTAssertTrue(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertTrue(presenterSpy.presentedLoginSuccess) + XCTAssertNil(presenterSpy.presentedLoginErrorMessage) + } + + // MARK: - Login Failure with cpf + + func testFailureLoginWithoutUserAndPassword() { + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerSuccessSpy() + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: nil, password: nil) + + // WHEN + sut.login(request: request) + + // THEN + XCTAssertFalse(workerSpy.loginCalled) + XCTAssertFalse(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertFalse(presenterSpy.presentedLoginSuccess) + XCTAssertEqual( + presenterSpy.presentedLoginErrorMessage, + L10n.User.Authentication.Error.general + ) + } + + + // MARK: - Login Failure login with invalid email + + func testFailureLoginWithInvalidEmail() { + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerFailSpy(failure: .user) + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: "test@com", password: Seeds.password) + + + sut.login(request: request) + + XCTAssertTrue(workerSpy.loginCalled) + XCTAssertFalse(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertFalse(presenterSpy.presentedLoginSuccess) + XCTAssertEqual( + presenterSpy.presentedLoginErrorMessage, + L10n.User.Authentication.Error.invalidCpf + ) + } + + // MARK: - Login Failure login with invalid cpf + + func testFailureLoginWithInvalidCPF() { + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerFailSpy(failure: .user) + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user: "1231", password: Seeds.password) + + + sut.login(request: request) + + XCTAssertTrue(workerSpy.loginCalled) + XCTAssertFalse(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertFalse(presenterSpy.presentedLoginSuccess) + XCTAssertEqual( + presenterSpy.presentedLoginErrorMessage, + L10n.User.Authentication.Error.invalidCpf + ) + } + + + // MARK: - Login Failure login with invalid password + + func testFailureLoginWithInvalidPassword() { + let presenterSpy = LoginPresenterSpy() + let workerSpy = LoginWorkerFailSpy(failure: .password) + let userWorkerSpy = UserWorkerSuccessSpy() + + let sut = LoginInteractor( + presenter: presenterSpy, + worker: workerSpy, + userWork: userWorkerSpy + ) + + let request = Login.Login.Request(user:Seeds.cpf, password: "abc" ) + + sut.login(request: request) + + XCTAssertTrue(workerSpy.loginCalled) + XCTAssertFalse(userWorkerSpy.saveUserCalled) + XCTAssertTrue(presenterSpy.presentLoginCalled) + XCTAssertFalse(presenterSpy.presentedLoginSuccess) + XCTAssertEqual( + presenterSpy.presentedLoginErrorMessage, + L10n.User.Authentication.Error.invalidPassword + ) + } +} + + +final class LoginPresenterSpy: LoginPresentationLogic { + + var presentUserCalled = false + var presentLoginCalled = false + + var displayedUser: String? + + var presentedLoginSuccess = false + var presentedLoginErrorMessage: String? + + func displayUser(response: Login.FetchUser.Response) { + presentUserCalled = true + displayedUser = response.user + } + + func resolveLogin(response: Login.Login.Response) { + presentLoginCalled = true + presentedLoginSuccess = response.success + presentedLoginErrorMessage = response.errorMessage + } +} + +final class LoginWorkerSuccessSpy: LoginWorkerLogic { + var loginCalled = false + + func login(username: String, password: String, completion: @escaping (Result) -> Void) { + loginCalled = true + completion(.success(Seeds.user)) + } +} + +final class LoginWorkerFailSpy: LoginWorkerLogic { + var loginCalled = false + let failure: UserFailure + + init(failure: UserFailure) { + self.failure = failure + } + + func login(username: String, password: String, completion: @escaping (Result) -> Void) { + loginCalled = true + completion(.failure(failure)) + } +} + +final class UserWorkerSuccessSpy: UserWorkerLogic { + var saveUserCalled = false + var retrieveUserCalled = false + func save(user: UserAccountModel) -> UserAccountModel? { + saveUserCalled = true + return Seeds.user + } + + func retrieveUser() -> UserAccountModel? { + retrieveUserCalled = true + return Seeds.user + } +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginPresenterTest.swift b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginPresenterTest.swift new file mode 100644 index 000000000..fdb88ec1b --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginPresenterTest.swift @@ -0,0 +1,92 @@ +// +// LoginPresenterTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest +@testable import KssiusBank + +final class LoginPresenterTest: XCTestCase { + + + // MARK: - Presenter resolve login success + + func testSuccessDisplayUser() { + + let viewControllerSpy = LoginViewControllerSpy() + + let sut = LoginPresenter() + sut.viewController = viewControllerSpy + + let response = Login.FetchUser.Response(user: "test@hotmail.com") + + + sut.displayUser(response: response) + + XCTAssertTrue(viewControllerSpy.displayUserCalled) + XCTAssertEqual(viewControllerSpy.displayUserViewModel.user, "test@hotmail.com") + } + + // MARK: - Presenter resolve login success + + func testSuccessResolveLogin() { + + let viewControllerSpy = LoginViewControllerSpy() + + let sut = LoginPresenter() + sut.viewController = viewControllerSpy + + let response = Login.Login.Response(success: true, errorMessage: nil) + + + sut.resolveLogin(response: response) + + XCTAssertTrue(viewControllerSpy.resolveLoginCalled) + XCTAssertTrue(viewControllerSpy.resolveLoginViewModel.success) + XCTAssertNil(viewControllerSpy.resolveLoginViewModel.errorMessage) + } + + // MARK: - Presenter resolve login failure + + func testFailureResolveLogin() { + + let viewControllerSpy = LoginViewControllerSpy() + + let sut = LoginPresenter() + sut.viewController = viewControllerSpy + + let response = Login.Login.Response(success: false, errorMessage: L10n.User.Authentication.Error.invalidCpf) + + + sut.resolveLogin(response: response) + + + XCTAssertTrue(viewControllerSpy.resolveLoginCalled) + XCTAssertFalse(viewControllerSpy.resolveLoginViewModel.success) + XCTAssertEqual(viewControllerSpy.resolveLoginViewModel.errorMessage, L10n.User.Authentication.Error.invalidCpf) + } +} + +final class LoginViewControllerSpy : LoginDisplayLogic { + var resolveLoginCalled = false + var resolveLoginViewModel: Login.Login.ViewModel = .init() + + var displayUserCalled = false + var displayUserViewModel: Login.FetchUser.ViewModel = .init() + + func displayUser(viewModel: Login.FetchUser.ViewModel) { + displayUserCalled = true + displayUserViewModel = viewModel + } + + func resolveLogin(viewModel: Login.Login.ViewModel) { + resolveLoginCalled = true + resolveLoginViewModel = viewModel + } + + + +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginViewControllerTest.swift b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginViewControllerTest.swift new file mode 100644 index 000000000..de33585f8 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginViewControllerTest.swift @@ -0,0 +1,74 @@ +// +// LoginViewControllerTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +import XCTest + +import UIKit + +// MARK: - Test LoginInteractorSpy + +final class LoginInteractorSpy: LoginBusinessLogic, LoginDataStore { + var fetchUserCalled = false + var loginCalled = false + + var userAccount: UserAccountModel? + + func fetchUser(request: Login.FetchUser.Request) { + fetchUserCalled = true + } + + func login(request: Login.Login.Request) { + loginCalled = true + } +} + +// MARK: - Tests + +final class LoginViewControllerTests: XCTestCase { + + // MARK: - Verify fetch user on viewDidLoad + + func testFetchUserCalledWhenViewDidLoad() { + + let interactorSpy = LoginInteractorSpy() + + let viewController = LoginViewController() + viewController.interactor = interactorSpy + + viewController.beginAppearanceTransition(true, animated: false) + viewController.endAppearanceTransition() + + + viewController.viewDidLoad() + + + XCTAssertTrue(interactorSpy.fetchUserCalled) + } + + // MARK: - Verify display user on ViewDidLoad + + func testDisplayUser() { + + let viewController = LoginViewController() + + let textFieldSpy = TextFieldSpy() + viewController.userTextField = textFieldSpy + + viewController.beginAppearanceTransition(true, animated: false) + viewController.endAppearanceTransition() + + let viewModel = Login.FetchUser.ViewModel(user: "test@hotmail.com") + + + viewController.displayUser(viewModel: viewModel) + + XCTAssertEqual(viewController.userTextField?.text, "test@hotmail.com") + } +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginWorkerTest.swift b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginWorkerTest.swift new file mode 100644 index 000000000..29c19aaec --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Autentication/LoginWorkerTest.swift @@ -0,0 +1,88 @@ +// +// LoginWorkerTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest +@testable import KssiusBank + +final class LoginWorkTest: XCTestCase { + + var sut: LoginWorkerLogic? + + override func setUp() { + super.setUp() + // remove datasource + let authenticationRepository = MockSuccessAuthenticationRepository() + + sut = LoginWorker(authenticaionRepository: authenticationRepository) + + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success login + + func testSuccessLogin() { + // remove datasource + let authenticationRepository = MockSuccessAuthenticationRepository() + + let expectation = self.expectation(description: "testSuccessSaveUser") + + sut = LoginWorker(authenticaionRepository: authenticationRepository) + + sut?.login(username: Seeds.email, password: Seeds.password) { result in + XCTAssertEqual(result, .success(Seeds.user)) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1) + XCTAssertTrue(authenticationRepository.performLoginCalled) + + } + + // MARK: - Failure login password + + func testFailurePasswordLogin() { + + let authenticationRepository = MockFailureAuthenticationRepository(userFailure: .password) + let expectation = self.expectation(description: "testFailureLogin") + + sut = LoginWorker(authenticaionRepository: authenticationRepository) + + sut?.login(username: Seeds.email, password: Seeds.password) { result in + XCTAssertEqual(result, .failure(.password)) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1) + XCTAssertTrue(authenticationRepository.performLoginCalled) + + } + + // MARK: - Failure login user + + func testFailureUserLogin() { + + let authenticationRepository = MockFailureAuthenticationRepository(userFailure: .user) + let expectation = self.expectation(description: "testFailureLogin") + + sut = LoginWorker(authenticaionRepository: authenticationRepository) + + sut?.login(username: Seeds.email, password: Seeds.password) { result in + XCTAssertEqual(result, .failure(.user)) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1) + XCTAssertTrue(authenticationRepository.performLoginCalled) + + } + +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Autentication/Mock/MockAuthenticationRepositoryProtocol.swift b/KssiusBank/KssiusBankTests/Scenes/Autentication/Mock/MockAuthenticationRepositoryProtocol.swift new file mode 100644 index 000000000..6e745161e --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Autentication/Mock/MockAuthenticationRepositoryProtocol.swift @@ -0,0 +1,37 @@ +// +// MockAuthenticationRepositoryProtocol.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +final class MockSuccessAuthenticationRepository: AuthenticationRepositoryProtocol { + + var performLoginCalled = false + + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, + completion: @escaping (Result) -> Void) { + performLoginCalled = true + completion(.success(Seeds.user)) + } +} + +final class MockFailureAuthenticationRepository: AuthenticationRepositoryProtocol { + + var performLoginCalled = false + private let userFailure: UserFailure + + init(userFailure: UserFailure) { + self.userFailure = userFailure + } + + func perform(login: BankApi.Endpoints, with data: LoginRequestModel, + completion: @escaping (Result) -> Void) { + performLoginCalled = true + completion(.failure(userFailure)) + } +} + diff --git a/KssiusBank/KssiusBankTests/Scenes/Home/Cell/StatementCellTest.swift b/KssiusBank/KssiusBankTests/Scenes/Home/Cell/StatementCellTest.swift new file mode 100644 index 000000000..146fb6553 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Home/Cell/StatementCellTest.swift @@ -0,0 +1,73 @@ +// +// StatementCellTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 19/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +import XCTest +@testable import KssiusBank + +final class StatementCellTest: XCTestCase { + + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + // MARK: - Success display statement + + func testSuccesDisplayStatement() { + let sut = StatementCell() + let balanceLabelSpy = LabelSpy() + let dateLabelSpy = LabelSpy() + let descriptionLabelSpy = LabelSpy() + let typeLabelSpy = LabelSpy() + + sut.balanceLabel = balanceLabelSpy + sut.dateLabel = dateLabelSpy + sut.descriptionLabel = descriptionLabelSpy + sut.typeLabel = typeLabelSpy + + sut.setup(model: .init(type: "type" , description: "description", date: "date", value: "108.9")) + + XCTAssertEqual(typeLabelSpy.text, "type") + XCTAssertEqual(dateLabelSpy.text, "date") + XCTAssertEqual(descriptionLabelSpy.text, "description") + XCTAssertEqual(balanceLabelSpy.text, "108.9") + + } + + // MARK: - Success reusable display statement + + func testSuccesReusabelDisplayStatement() { + let sut = StatementCell() + let balanceLabelSpy = LabelSpy() + let dateLabelSpy = LabelSpy() + let descriptionLabelSpy = LabelSpy() + let typeLabelSpy = LabelSpy() + + sut.balanceLabel = balanceLabelSpy + sut.dateLabel = dateLabelSpy + sut.descriptionLabel = descriptionLabelSpy + sut.typeLabel = typeLabelSpy + + sut.setup(model: .init(type: "type" , description: "description", date: "date", value: "108.9")) + sut.setup(model: .init(type: "type2" , description: "description2", date: "date2", value: "100")) + + XCTAssertEqual(typeLabelSpy.text, "type2") + XCTAssertEqual(dateLabelSpy.text, "date2") + XCTAssertEqual(descriptionLabelSpy.text, "description2") + XCTAssertEqual(balanceLabelSpy.text, "100") + + + + } +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Home/HomeInteractorTest.swift b/KssiusBank/KssiusBankTests/Scenes/Home/HomeInteractorTest.swift new file mode 100644 index 000000000..12cbc4198 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Home/HomeInteractorTest.swift @@ -0,0 +1,126 @@ +// +// HomeInteractorTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest +@testable import KssiusBank + +final class HomeInteractorTest: XCTestCase { + + // MARK: - Success fetch statements interactor + + func testSuccesFetchStatements() { + let presenterSpy = HomePresenterSpy() + let workerSpy = HomeWorkerSpy() + let sut = HomeInteractor(presenter: presenterSpy, worker: workerSpy) + + sut.fetchStatements() + + //presenter + XCTAssertTrue(presenterSpy.presentStatementsCalled) + XCTAssertTrue(presenterSpy.presentStatementsSuccess) + XCTAssertNil(presenterSpy.presentStatementsErrorMessage) + XCTAssertEqual(Seeds.Statements.statements, presenterSpy.presentStatements) + + //worker + XCTAssertTrue(workerSpy.workerFetchStatementsCalled) + } + + // MARK: - Failure fetch statements interactor + + func testFailureFetchStatements() { + let presenterSpy = HomePresenterSpy() + let workerFailSpy = HomeWorkerFailSpy() + let sut = HomeInteractor(presenter: presenterSpy, worker: workerFailSpy) + + sut.fetchStatements() + + //presenter + XCTAssertTrue(presenterSpy.presentStatementsCalled) + XCTAssertFalse(presenterSpy.presentStatementsSuccess) + XCTAssertEqual(presenterSpy.presentStatementsErrorMessage, L10n.Network.Error.general) + XCTAssertEqual(presenterSpy.presentStatements, []) + + //worker + XCTAssertTrue(workerFailSpy.workerFetchStatementsCalled) + } + + + // MARK: - Success fetch account interactor + + func testSuccesFetchAccount() { + let presenterSpy = HomePresenterSpy() + let workerSpy = HomeWorkerSpy() + let sut = HomeInteractor(presenter: presenterSpy, worker: workerSpy, userAccount: Seeds.user) + + sut.retrieveAccount() + + //presenter + XCTAssertTrue(presenterSpy.presentAccountCalled) + XCTAssertEqual(presenterSpy.presentAccount, Seeds.user) + + //worker + XCTAssertFalse(workerSpy.workerFetchStatementsCalled) + } + + // MARK: - Failure fetch account interactor + + func testFailureFetchAccount() { + let presenterSpy = HomePresenterSpy() + let workerFailSpy = HomeWorkerFailSpy() + let sut = HomeInteractor(presenter: presenterSpy, worker: workerFailSpy) + + sut.retrieveAccount() + + //presenter + XCTAssertFalse(presenterSpy.presentAccountCalled) + XCTAssertNil(presenterSpy.presentAccount) + + //worker + XCTAssertFalse(workerFailSpy.workerFetchStatementsCalled) + } + + +} + +final class HomePresenterSpy: HomePresentationLogic { + var presentStatementsCalled = false + var presentStatementsSuccess = false + var presentStatementsErrorMessage : String? + var presentStatements = [StatementsModel]() + + var presentAccountCalled = false + var presentAccount: UserAccountModel? + + func presentStatements(response: Home.GetStatements.Response) { + presentStatementsCalled = true + presentStatementsErrorMessage = response.errorMessage + presentStatementsSuccess = response.success + presentStatements = response.statements + } + + func presentAccount(response: Home.GetAccount.Response) { + presentAccountCalled = true + presentAccount = response.userAccount + } +} + +final class HomeWorkerSpy: HomeWorkerLogic { + var workerFetchStatementsCalled = false + func fetchStatements(completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void) { + workerFetchStatementsCalled = true + completion(.success(Seeds.Statements.statements)) + } +} + +final class HomeWorkerFailSpy: HomeWorkerLogic { + var workerFetchStatementsCalled = false + func fetchStatements(completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void) { + workerFetchStatementsCalled = true + completion(.failure(.network(.init()))) + } +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Home/HomePresenterTest.swift b/KssiusBank/KssiusBankTests/Scenes/Home/HomePresenterTest.swift new file mode 100644 index 000000000..d9b957afb --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Home/HomePresenterTest.swift @@ -0,0 +1,95 @@ +// +// HomePresenterTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + + +import XCTest +@testable import KssiusBank + +final class HomePresenterTest: XCTestCase { + + // MARK: - Success present statements presenter + + func testSuccesDisplayStatements() { + + let viewControllerSpy = HomeViewControllerSpy() + let sut = HomePresenter() + sut.viewController = viewControllerSpy + + sut.presentStatements(response: .init(success: true,statements: Seeds.Statements.statements)) + let expectedStatements = Seeds.Statements.statements.map { + Home.GetStatements.StatementViewModel(type: statements(type: $0.type), + description: $0.description, + date: $0.date.formatToString, + value: $0.value.toCurrency) + } + + XCTAssertTrue(viewControllerSpy.displayStatementsCalled) + XCTAssertTrue(viewControllerSpy.displayStatementsSuccess) + XCTAssertEqual(viewControllerSpy.displayStatementsListStatements, expectedStatements) + + } + + // MARK: - Statements types + + private func statements(type: StatementsModel.StatementsType) -> String { + switch type { + case .payment: + return L10n.Statement.Option.payment + case .deposit: + return L10n.Statement.Option.deposit + case .invoice: + return L10n.Statement.Option.invoice + case .withdrawal: + return L10n.Statement.Option.withdrawal + } + } + + // MARK: - Success present statements presenter + + func testSuccesDisplayAccount() { + let seedUser = Seeds.user + let viewControllerSpy = HomeViewControllerSpy() + let sut = HomePresenter() + sut.viewController = viewControllerSpy + + + sut.presentAccount(response: .init(userAccount: seedUser)) + + let expectedAccount: Home.GetAccount.ViewModel = .init(name: seedUser.name, + agency: seedUser.agency, + accountNumber: seedUser.accountNumber, + balance: seedUser.balance) + + XCTAssertTrue(viewControllerSpy.displayAccountCalled) + XCTAssertEqual(viewControllerSpy.displayAccountViewModel, expectedAccount) + + } +} + +final class HomeViewControllerSpy: HomeDisplayLogic { + + var displayStatementsCalled = false + var displayStatementsSuccess = false + var displayStatementsListStatements = [Home.GetStatements.StatementViewModel]() + + var displayAccountCalled = false + var displayAccountViewModel: Home.GetAccount.ViewModel = .init() + + func displayStatements(viewModel: Home.GetStatements.ViewModel) { + displayStatementsCalled = true + displayStatementsSuccess = viewModel.success + displayStatementsListStatements = viewModel.statements + } + + func displayAccount(viewModel: Home.GetAccount.ViewModel) { + displayAccountCalled = true + displayAccountViewModel = viewModel + } + + +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Home/HomeViewControllerTest.swift b/KssiusBank/KssiusBankTests/Scenes/Home/HomeViewControllerTest.swift new file mode 100644 index 000000000..613940b3f --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Home/HomeViewControllerTest.swift @@ -0,0 +1,123 @@ +// +// HomeViewControllerTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest +@testable import KssiusBank + +final class HomeViewControllerTest: XCTestCase { + + + // MARK: - Verify fetch user on viewDidLoad + + func testGetUserCalledWhenViewDidLoad() { + + let interactorSpy = HomeInteractorSpy() + + let viewController = HomeViewController() + viewController.interactor = interactorSpy + + viewController.beginAppearanceTransition(true, animated: false) + viewController.endAppearanceTransition() + + + viewController.viewDidLoad() + + + XCTAssertTrue(interactorSpy.retrieveAccountCalled) + } + + // MARK: - Verify fetch user on viewDidLoad + + func testFetchStatementsCalledWhenViewDidLoad() { + + let interactorSpy = HomeInteractorSpy() + + let viewController = HomeViewController() + viewController.interactor = interactorSpy + + viewController.beginAppearanceTransition(true, animated: false) + viewController.endAppearanceTransition() + + + viewController.viewDidLoad() + + XCTAssertTrue(interactorSpy.fetchStatementsCalled) + } + + // MARK: - Test display account data + + func testShowAccountData() { + let viewController = HomeViewController() + + let nameLabel = LabelSpy() + let accountNumberLabel = LabelSpy() + let balanceLabel = LabelSpy() + + viewController.nameLabel = nameLabel + viewController.accountLabel = accountNumberLabel + viewController.balanceLabel = balanceLabel + + viewController.beginAppearanceTransition(true, animated: false) + viewController.endAppearanceTransition() + + let seed = Seeds.user + let viewModel: Home.GetAccount.ViewModel = .init(name: seed.name, + agency: seed.agency, + accountNumber: seed.accountNumber, + balance: seed.balance) + viewController.displayAccount(viewModel: viewModel) + + let expectedAccountNumber = "\(seed.agency) / \(seed.accountNumber.toAccountFormat ?? "")" + XCTAssertEqual(nameLabel.text, seed.name) + XCTAssertEqual(accountNumberLabel.text, expectedAccountNumber) + XCTAssertEqual(balanceLabel.text, seed.balance.toCurrency) + + } + + // MARK: - Test display statements + + func testShowStatements() { + let viewController = HomeViewController() + + let statementsTable = TableViewSpy() + viewController.tableView = statementsTable + + viewController.beginAppearanceTransition(true, animated: false) + viewController.endAppearanceTransition() + + let seed = Seeds.Statements.statements.first + + let viewModel: Home.GetStatements.ViewModel = .init(statements: [ + .init(type: "", description: seed?.description ?? "", date: seed?.date.formatToString ?? "", value: seed?.value.toCurrency ?? "") + ], success: true) + + viewController.displayStatements(viewModel: viewModel) + + XCTAssertTrue(statementsTable.reloadDataCalled) + + } +} + +final class HomeInteractorSpy: HomeBusinessLogic, HomeDataStore { + + + var userAccount: UserAccountModel? + var statements = [StatementsModel]() + + var fetchStatementsCalled = false + var retrieveAccountCalled = false + + func fetchStatements(request: Home.GetStatements.Request) { + fetchStatementsCalled = true + } + + func retrieveAccount(request: Home.GetAccount.Request) { + retrieveAccountCalled = true + } + +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Home/HomeWorkerTest.swift b/KssiusBank/KssiusBankTests/Scenes/Home/HomeWorkerTest.swift new file mode 100644 index 000000000..29ed444b3 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Home/HomeWorkerTest.swift @@ -0,0 +1,65 @@ +// +// HomeWorkerTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import XCTest +@testable import KssiusBank + +final class HomeWorkTest: XCTestCase { + + var sut: HomeWorkerLogic? + + override func setUp() { + super.setUp() + let statementsRepository = MockSuccessStatementsRepository() + sut = HomeWorker(statementsRepository: statementsRepository) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success fetch statements + + func testSuccessLogin() { + + let statementsRepository = MockSuccessStatementsRepository() + + let expectation = self.expectation(description: "Wait for execute fetch statements") + + sut = HomeWorker(statementsRepository: statementsRepository) + + sut?.fetchStatements { result in + XCTAssertEqual(result, .success(Seeds.Statements.statements)) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1) + XCTAssertTrue(statementsRepository.performLoginCalled) + + } + + // MARK: - Failure statements fetch network + + func testFailureNetwork() { + + let statementsRepository = MockFailureStatementsRepository(statementsFailure: .network(.init())) + let expectation = self.expectation(description: "Wait for execute fetch statements") + + sut = HomeWorker(statementsRepository: statementsRepository) + + sut?.fetchStatements { result in + XCTAssertEqual(result, .failure(.network(.init()))) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1) + XCTAssertTrue(statementsRepository.fetchStatementsCalled) + + } +} diff --git a/KssiusBank/KssiusBankTests/Scenes/Home/Mock/MockStatementsRepositoryProtocol.swift b/KssiusBank/KssiusBankTests/Scenes/Home/Mock/MockStatementsRepositoryProtocol.swift new file mode 100644 index 000000000..13e63207e --- /dev/null +++ b/KssiusBank/KssiusBankTests/Scenes/Home/Mock/MockStatementsRepositoryProtocol.swift @@ -0,0 +1,37 @@ +// +// MockAuthenticationRepositoryProtocol.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 17/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +final class MockSuccessStatementsRepository: StatementsRepositoryProtocol { + + var performLoginCalled = false + + func fetch(statements: BankApi.Endpoints, + completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void) { + performLoginCalled = true + completion(.success(Seeds.Statements.statements)) + } +} + +final class MockFailureStatementsRepository: StatementsRepositoryProtocol { + + var fetchStatementsCalled = false + private let statementsFailure: StatementsFailure + + init(statementsFailure: StatementsFailure) { + self.statementsFailure = statementsFailure + } + + func fetch(statements: BankApi.Endpoints, + completion: @escaping (Result<[StatementsModel], StatementsFailure>) -> Void) { + fetchStatementsCalled = true + completion(.failure(statementsFailure)) + } +} + diff --git a/KssiusBank/KssiusBankTests/Seeds.swift b/KssiusBank/KssiusBankTests/Seeds.swift new file mode 100644 index 000000000..b1f59afd6 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Seeds.swift @@ -0,0 +1,82 @@ +// +// Seeds.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 15/06/23. +// + +import Foundation + +struct Seeds { + enum Json: String { + case account = """ + { + "createdAt": "2023-06-15T23:36:43.182Z", + "name": "Diana Leuschke", + "email": "Marquis_Gibson@hotmail.com", + "cpf": "468.655.400-42", + "accountNumber": "74393734", + "agency": "827810101", + "balance": "472.29", + "id": "3" + } + """ + } + + static let email = "Marquis_Gibson@hotmail.com" + static let password = "T@to123" + static let cpf = "468.655.400-42" + + static let user = UserAccountModel(userId: "3", + email: "Marquis_Gibson@hotmail.com", + cpf: "468.655.400-42", + name: "Diana Leuschke", + accountNumber: "74393734", + agency: "827810101", + balance: "472.29") + + enum Statements { + static let json = """ + [ + { + "type": "payment", + "description": "Conta de Luz", + "date": "2023-06-18T06:10:28.187Z", + "value": "1550.50" + }, + { + "type": "invoice", + "description": "Conta de Luz", + "date": "2023-06-18T12:59:11.242Z", + "value": "300" + }, + { + "type": "deposit", + "description": "Conta de Luz", + "date": "2023-06-17T22:18:13.694Z", + "value": "200.30" + } + + ] + """ + + static let statements = [ + StatementsModel(type: .payment, description: "Conta de Luz", date: .init(year: 2020, month: 01, day: 2), value: "200.00"), + StatementsModel(type: .invoice, description: "Conta de Luz", date: .init(year: 2020, month: 02, day: 5), value: "300.00"), + StatementsModel(type: .deposit, description: "Conta de Luz", date: .init(year: 2020, month: 04, day: 3), value: "200.00") + ] + } +} + +extension Date { + + init( year: Int, month: Int, day: Int) { + var dateComponents = DateComponents() + dateComponents.month = month + dateComponents.day = day + dateComponents.year = year + dateComponents.timeZone = .current + dateComponents.calendar = .current + self = Calendar.current.date(from: dateComponents) ?? Date() + } +} diff --git a/KssiusBank/KssiusBankTests/Services/MockURLProtocol.swift b/KssiusBank/KssiusBankTests/Services/MockURLProtocol.swift new file mode 100644 index 000000000..a32bae983 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Services/MockURLProtocol.swift @@ -0,0 +1,58 @@ +// +// MockURLProtocol.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import Foundation + + +final class MockURLProtocol: URLProtocol { + + // 1. Handler to test the request and return mock response. + static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data?))? + + override class func canInit(with request: URLRequest) -> Bool { + // To check if this protocol can handle the given request. + return true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + // Here you return the canonical version of the request + //but most of the time you pass the orignal one. + return request + } + + override func startLoading() { + // This is where you create the mock response + //as per your test case and send it to the URLProtocolClient. + guard let handler = MockURLProtocol.requestHandler else { + fatalError("Handler is unavailable.") + } + + do { + // 2. Call handler with received request and capture the tuple of response and data. + let (response, data) = try handler(request) + + // 3. Send received response to the client. + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + + if let data = data { + // 4. Send received data to the client. + client?.urlProtocol(self, didLoad: data) + } + + // 5. Notify request has been finished. + client?.urlProtocolDidFinishLoading(self) + } catch { + // 6. Notify received error. + client?.urlProtocol(self, didFailWithError: error) + } + } + + override func stopLoading() { + // This is called if the request gets canceled or completed. + } + +} diff --git a/KssiusBank/KssiusBankTests/Services/NetworkServiceTest.swift b/KssiusBank/KssiusBankTests/Services/NetworkServiceTest.swift new file mode 100644 index 000000000..cbbae3938 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Services/NetworkServiceTest.swift @@ -0,0 +1,76 @@ +// +// NetworkServiceTest.swift +// KssiusBank +// +// Created by Cassio Sousa on 15/06/23. +// + +import XCTest +@testable import KssiusBank + +final class NetworkServiceTest: XCTestCase { + var sut: NetworkService? + var expectation: XCTestExpectation! + let apiURL = URL(string: "https://example.com.br/post/42")! + let baseUrl = "https://example.com.br" + + override func setUp() { + super.setUp() + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [MockURLProtocol.self] + let urlSession = URLSession.init(configuration: configuration) + + sut = NetworkService(urlSession: urlSession) + expectation = expectation(description: "NetworkServiceTest Expectation") + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success Response and parse to model + + func testSuccessResponse() { + + let postEndpoint: Endpoint = .init(path:"post/42" , method: .get, configuration: .init(baseUrl: baseUrl)) + let data = Seeds.Json.account.rawValue.data(using: .utf8) + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: self.apiURL, statusCode: 200, httpVersion: nil, headerFields: nil)! + return (response, data) + } + + sut?.request(endpoint:postEndpoint){ [weak self] result in + switch(result){ + case .success(let userModel): + XCTAssertEqual(userModel.email, "Marquis_Gibson@hotmail.com") + case .failure( _): + XCTFail("Should not return a failure") + } + self?.expectation.fulfill() + } + wait(for: [self.expectation], timeout: 1.0) + } + + // MARK: - Failure Network Response + + func testFailuteNetworkResponse() { + let postEndpoint: Endpoint = .init(path:"post/42" , method: .get, configuration: .init(baseUrl: baseUrl)) + MockURLProtocol.requestHandler = { request in + throw ServiceError.invalidUrl + } + sut?.request(endpoint: postEndpoint){ [weak self] result in + switch(result){ + case .success( _): + XCTFail("Should not return a failure") + + case .failure( let error): + XCTAssertEqual(error, ServiceError.noData) + } + self?.expectation.fulfill() + } + wait(for: [self.expectation], timeout: 1.0) + } +} + diff --git a/KssiusBank/KssiusBankTests/UIKitSpy/LabelSpy.swift b/KssiusBank/KssiusBankTests/UIKitSpy/LabelSpy.swift new file mode 100644 index 000000000..7c94e555c --- /dev/null +++ b/KssiusBank/KssiusBankTests/UIKitSpy/LabelSpy.swift @@ -0,0 +1,11 @@ +// +// LabelSpy.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +final class LabelSpy: UILabel{} diff --git a/KssiusBank/KssiusBankTests/UIKitSpy/TableViewSpy.swift b/KssiusBank/KssiusBankTests/UIKitSpy/TableViewSpy.swift new file mode 100644 index 000000000..ba0e4a826 --- /dev/null +++ b/KssiusBank/KssiusBankTests/UIKitSpy/TableViewSpy.swift @@ -0,0 +1,21 @@ +// +// TableViewSpy.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 18/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + + +final class TableViewSpy: UITableView { + var reloadDataCalled = false + var numberOfSectionsCalled = false + var cellForRowCalled = false + + override func reloadData() { + reloadDataCalled = true + } + +} diff --git a/KssiusBank/KssiusBankTests/UIKitSpy/TextFieldSpy.swift b/KssiusBank/KssiusBankTests/UIKitSpy/TextFieldSpy.swift new file mode 100644 index 000000000..6d9c64958 --- /dev/null +++ b/KssiusBank/KssiusBankTests/UIKitSpy/TextFieldSpy.swift @@ -0,0 +1,11 @@ +// +// TextFieldSpy.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import UIKit + +class TextFieldSpy: DefaultTextField {} diff --git a/KssiusBank/KssiusBankTests/Workers/Mock/MockAuthenticationLocalRepository.swift b/KssiusBank/KssiusBankTests/Workers/Mock/MockAuthenticationLocalRepository.swift new file mode 100644 index 000000000..0a2913bde --- /dev/null +++ b/KssiusBank/KssiusBankTests/Workers/Mock/MockAuthenticationLocalRepository.swift @@ -0,0 +1,32 @@ +// +// MockAuthenticationLocalRepository.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +// MARK: - Mock +import Foundation + +final class MockSuccessAuthenticationLocalRepository: AuthenticationLocalRepositoryProtocol { + + func save(user: UserAccountModel) -> UserAccountModel? { + return user + } + + func retrieveUser() -> UserAccountModel? { + return Seeds.user + } +} + +final class MockEmptyAuthenticationLocalRepository: AuthenticationLocalRepositoryProtocol { + + func save(user: UserAccountModel) -> UserAccountModel? { + return nil + } + + func retrieveUser() -> UserAccountModel? { + return nil + } +} diff --git a/KssiusBank/KssiusBankTests/Workers/UserWorkersTest.swift b/KssiusBank/KssiusBankTests/Workers/UserWorkersTest.swift new file mode 100644 index 000000000..4b013ea53 --- /dev/null +++ b/KssiusBank/KssiusBankTests/Workers/UserWorkersTest.swift @@ -0,0 +1,95 @@ +// +// UserWorkersTest.swift +// KssiusBankTests +// +// Created by Cassio Sousa on 16/06/23. +// Copyright © 2023 Cassio Sousa. All rights reserved. +// + +import Foundation + +import XCTest +@testable import KssiusBank + +final class UserWorkersTest: XCTestCase { + + var sut: UserWorkerLogic? + + override func setUp() { + super.tearDown() + sut = UserWorker(authenticationLocalRepository: MockSuccessAuthenticationLocalRepository() ) + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Success save user + + func testSuccessSaveUser() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + let result = sut?.save(user: user) + validate(userAccount: result) + } + + // MARK: - Failure save user + + func testSuccessGetUser() { + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + sut?.save(user: user) + let result = sut?.retrieveUser() + validate(userAccount: result) + } + + // MARK: - Failure save user + + func testFailureSaveUser() { + sut = UserWorker(authenticationLocalRepository: MockEmptyAuthenticationLocalRepository()) + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + let result = sut?.save(user: user) + XCTAssertNil(result) + } + + // MARK: - Failure save user + + func testEmptyGetUser() { + sut = UserWorker(authenticationLocalRepository: MockEmptyAuthenticationLocalRepository()) + let data = Seeds.Json.account.rawValue.data(using: .utf8) + guard let data = data, + let user = try? JSONDecoder().decode(UserAccountModel.self, from: data) else { + XCTFail("Should not return a failure") + return + } + sut?.save(user: user) + let result = sut?.retrieveUser() + XCTAssertNil(result) + } + + private func validate(userAccount result: UserAccountModel?) { + XCTAssertNotNil(result) + XCTAssertEqual(result?.userId, "3") + XCTAssertEqual(result?.cpf, "468.655.400-42") + XCTAssertEqual(result?.name, "Diana Leuschke") + XCTAssertEqual(result?.email, "Marquis_Gibson@hotmail.com") + XCTAssertEqual(result?.accountNumber, "74393734") + XCTAssertEqual(result?.agency, "827810101") + XCTAssertEqual(result?.balance, "472.29") + } + +} diff --git a/KssiusBank/KssiusBankUITests/KssiusBankUITests.swift b/KssiusBank/KssiusBankUITests/KssiusBankUITests.swift new file mode 100644 index 000000000..312274631 --- /dev/null +++ b/KssiusBank/KssiusBankUITests/KssiusBankUITests.swift @@ -0,0 +1,41 @@ +// +// KssiusBankUITests.swift +// KssiusBankUITests +// +// Created by Cassio Sousa on 15/06/23. +// + +import XCTest + +final class KssiusBankUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/KssiusBank/KssiusBankUITests/KssiusBankUITestsLaunchTests.swift b/KssiusBank/KssiusBankUITests/KssiusBankUITestsLaunchTests.swift new file mode 100644 index 000000000..0c8261168 --- /dev/null +++ b/KssiusBank/KssiusBankUITests/KssiusBankUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// KssiusBankUITestsLaunchTests.swift +// KssiusBankUITests +// +// Created by Cassio Sousa on 15/06/23. +// + +import XCTest + +final class KssiusBankUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/KssiusBank/Podfile b/KssiusBank/Podfile new file mode 100644 index 000000000..46ac32e07 --- /dev/null +++ b/KssiusBank/Podfile @@ -0,0 +1,21 @@ +# Uncomment the next line to define a global platform for your project + platform :ios, '11.0' + +target 'KssiusBank' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for KssiusBank + pod 'SwiftGen', '~> 6.6.2' + pod 'KeychainSwift', '~> 20.0' + + target 'KssiusBankTests' do + inherit! :search_paths + # Pods for testing + end + + target 'KssiusBankUITests' do + # Pods for testing + end + +end diff --git a/KssiusBank/swiftgen.yml b/KssiusBank/swiftgen.yml new file mode 100644 index 000000000..95c0f5f2a --- /dev/null +++ b/KssiusBank/swiftgen.yml @@ -0,0 +1,57 @@ +## Note: all of the config entries below are just examples with placeholders. Be sure to edit and adjust to your needs when uncommenting. + +## In case your config entries all use a common input/output parent directory, you can specify those here. +## Every input/output paths in the rest of the config will then be expressed relative to these. +## Those two top-level keys are optional and default to "." (the directory of the config file). + input_dir: KssiusBank/ + output_dir: KssiusBank/Generated/ + + +## Generate constants for your localized strings. +## Be sure that SwiftGen only parses ONE locale (typically Base.lproj, or en.lproj, or whichever your development region is); otherwise it will generate the same keys multiple times. +## SwiftGen will parse all `.strings` files found in that folder. + strings: + inputs: + - Resources/pt-BR.lproj + outputs: + - templateName: structured-swift5 + output: Strings+Generated.swift + + +## Generate constants for your Assets Catalogs, including constants for images, colors, ARKit resources, etc. +## This example also shows how to provide additional parameters to your template to customize the output. +## - Especially the `forceProvidesNamespaces: true` param forces to create sub-namespace for each folder/group used in your Asset Catalogs, even the ones without "Provides Namespace". Without this param, SwiftGen only generates sub-namespaces for folders/groups which have the "Provides Namespace" box checked in the Inspector pane. +## - To know which params are supported for a template, use `swiftgen template doc xcassets swift5` to open the template documentation on GitHub. + xcassets: + inputs: + - Assets.xcassets + outputs: + - templateName: swift4 + params: + forceProvidesNamespaces: true + output: XCAssets+Generated.swift + + +## Generate constants for your storyboards and XIBs. +## This one generates 2 output files, one containing the storyboard scenes, and another for the segues. +## (You can remove the segues entry if you don't use segues in your IB files). +## For `inputs` we can use "." here (aka "current directory", at least relative to `input_dir` = "MyLib/Sources"), +## and SwiftGen will recursively find all `*.storyboard` and `*.xib` files in there. +# ib: +# inputs: +# - . +# outputs: +# - templateName: scenes-swift5 +# output: IB-Scenes+Generated.swift +# - templateName: segues-swift5 +# output: IB-Segues+Generated.swift + + +## There are other parsers available for you to use depending on your needs, for example: +## - `fonts` (if you have custom ttf/ttc font files) +## - `coredata` (for CoreData models) +## - `json`, `yaml` and `plist` (to parse custom JSON/YAML/Plist files and generate code from their content) +## … +## +## For more info, use `swiftgen config doc` to open the full documentation on GitHub. +## https://github.com/SwiftGen/SwiftGen/tree/6.6.2/Documentation/ diff --git a/README.md b/README.md index c059fa6a5..535fa6329 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,33 @@ -# Show me the code +# Cássio Alexandre de Sousa - Desafio Banco Safra (Kaspper) -Esse repositório contem todo o material necessário para realizar o teste: -- A especificação do layout está na pasta 'bank_app_layout' abrindo o index.html, os icones estão na pasta 'assets' +O repositório contém o código do desafio proposto para a vaga de iOS no Banco Safra (Kaspper). -- Os dados da Api estão mockados, os exemplos e a especificação dos serviços (login e statements) se encontram no arquivo BankApp.postman_collection.json ( é necessário instalar o postman e importar a colection https://www.getpostman.com/apps) +## Setup -![Image of Yaktocat](https://github.com/SantanderTecnologia/TesteiOSv2/blob/master/telas.png) +Solução CocoaPods adotada como gerenciador de dependências. -### # DESAFIO: +É necessário instalar em sua máquina. Manual de instalação em [link](https://cocoapods.org). -Na primeira tela teremos um formulario de login, o campo user deve aceitar email ou cpf, -o campo password deve validar se a senha tem pelo menos uma letra maiuscula, um caracter especial e um caracter alfanumérico. -Apos a validação, realizar o login no endpoint https://6092aef785ff5100172136c2.mockapi.io/api/login e exibir os dados de retorno na próxima tela. -O ultimo usuário logado deve ser salvo de forma segura localmente, e exibido na tela de login se houver algum salvo. +Entrar na pasta do projeto `KssiusBank` executar o seguinte comando: `pod install` -Na segunda tela será exibido os dados formatados do retorno do login e será necessário fazer um segundo request para obter os lançamentos do usuário, no endpoint https://6092aef785ff5100172136c2.mockapi.io/api/statements/{idUser} que retornará uma lista de lançamentos +Após a instalação das dependências, sempre use o arquivo `.xcworkspace` para abrir o projeto. -### # Avaliação +Dentro da pasta do projeto no terminal `open KssiusBank.xcworkspace` -Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura do app. É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits. +## Execução -Obrigatórios: +Abrir no XCode o projeto a partir do arquivo `KssiusBank.xcworkspace` existente na pasta `KssiusBank`. +Utilizar o atalho do teclado `Command + shift + k ` para limpar a pasta de builds e `Command + R` para buildar e executar o aplicativo no simulador. -* Swift 3.0 ou superior -* Autolayout -* O app deve funcionar no iOS 11 -* Testes unitários, pode usar a ferramenta que você tem mais experiência, só nos explique o que ele tem de bom. -* Arquitetura a ser utilizada: Swift Clean ([https://clean-swift.com/handbook/](https://clean-swift.com/handbook/) && [https://github.com/Clean-Swift/CleanStore](https://github.com/Clean-Swift/CleanStore) -* Uso do git. +## Testes -### # Observações gerais +Abrir no XCode o projeto a partir do arquivo `KssiusBank.xcworkspace` existente na pasta `KssiusBank`. +Utilizar o atalho do teclado `Command + U` para buildar e executar os testes do projeto. -Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto. -Pedimos que trabalhe sozinho e não divulgue o resultado na internet. +## Cobertura dos testes -Faça um fork desse desse repositório em seu Github e ao finalizar nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando. +A cobertura dos testes está habilitada. -# Importante: não há prazo de entrega, faça com qualidade! +Após executar os testes unitários com o passo anterior, é possível visualizar a cobertura na última aba do Project Navigator `(Show the Report Navigator)` e clicando em `Coverage`. -# BOA SORTE! +A cobertura dos testes unitários do projeto está em 71.9%. \ No newline at end of file