diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c452fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + +## Gcc Patch +/*.gcno + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings +xcuserdata/ + +# End of https://www.toptal.com/developers/gitignore/api/swift,xcode,macos diff --git a/Ecomobility.xcodeproj/project.pbxproj b/Ecomobility.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8164c69 --- /dev/null +++ b/Ecomobility.xcodeproj/project.pbxproj @@ -0,0 +1,996 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + C43056FB25D5B11900D07A27 /* ProfileProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43056FA25D5B11900D07A27 /* ProfileProgressView.swift */; }; + C43056FE25D5BD2400D07A27 /* ProfileSwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43056FD25D5BD2400D07A27 /* ProfileSwitchView.swift */; }; + C430570125D5BFB100D07A27 /* ProfileActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C430570025D5BFB100D07A27 /* ProfileActionView.swift */; }; + C430572025D5E17500D07A27 /* UserInfo+DTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = C430571F25D5E17500D07A27 /* UserInfo+DTO.swift */; }; + C430572425D5EFF900D07A27 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C430572325D5EFF900D07A27 /* Colors.xcassets */; }; + C439A99625D35E4800F341D0 /* AuthCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = C439A99525D35E4800F341D0 /* AuthCredentials.swift */; }; + C439A99925D3649000F341D0 /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C439A99825D3649000F341D0 /* AuthRepository.swift */; }; + C439A99D25D364A900F341D0 /* AuthRepositable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C439A99C25D364A900F341D0 /* AuthRepositable.swift */; }; + C439A9A125D3662300F341D0 /* FetchAuthDataSourceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C439A9A025D3662300F341D0 /* FetchAuthDataSourceable.swift */; }; + C4661D4F25C8B8BA0055E0D8 /* EcomobilityApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4661D4E25C8B8BA0055E0D8 /* EcomobilityApp.swift */; }; + C4858A8D25CEF0FC005F90AD /* SocialFacebookButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4858A8C25CEF0FC005F90AD /* SocialFacebookButton.swift */; }; + C49237C125CB935F008F283C /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237C025CB935F008F283C /* SignInView.swift */; }; + C49237C525CB939F008F283C /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237C425CB939F008F283C /* LoginView.swift */; }; + C49237C825CB93BA008F283C /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237C725CB93BA008F283C /* SignUpView.swift */; }; + C49237D125CB9462008F283C /* SignInViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237D025CB9462008F283C /* SignInViewModel.swift */; }; + C49237D525CB948C008F283C /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237D425CB948C008F283C /* LoginViewModel.swift */; }; + C49237DC25CB97B2008F283C /* LoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237DB25CB97B2008F283C /* LoginButton.swift */; }; + C49237E925CBA88D008F283C /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237E825CBA88D008F283C /* SignUpViewModel.swift */; }; + C49237EF25CBAB53008F283C /* LoginTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237EE25CBAB53008F283C /* LoginTextField.swift */; }; + C49237F225CBAE50008F283C /* LoginSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237F125CBAE50008F283C /* LoginSecureField.swift */; }; + C49237F825CEC5DB008F283C /* LoginSignUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49237F725CEC5DB008F283C /* LoginSignUpButton.swift */; }; + C4A9F54725D4501D00D9C42A /* ProfileUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A9F54625D4501D00D9C42A /* ProfileUser.swift */; }; + C4A9F54D25D450D500D9C42A /* ProfileSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A9F54C25D450D500D9C42A /* ProfileSettingsButton.swift */; }; + C4A9F55125D4517600D9C42A /* ProfileLogoutButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A9F55025D4517600D9C42A /* ProfileLogoutButton.swift */; }; + C4A9F55425D4529400D9C42A /* ProfileView+Sections.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A9F55325D4529400D9C42A /* ProfileView+Sections.swift */; }; + C4A9F55725D4557600D9C42A /* ProfileDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A9F55625D4557600D9C42A /* ProfileDetailsView.swift */; }; + C4B41F8925D563920029D85E /* ProfileDetailsUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B41F8825D563920029D85E /* ProfileDetailsUser.swift */; }; + C4B41F9525D57BE60029D85E /* ProfileDetailsRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B41F9425D57BE60029D85E /* ProfileDetailsRow.swift */; }; + C4B41F9925D581260029D85E /* ProfileUserImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B41F9825D581260029D85E /* ProfileUserImage.swift */; }; + C4B41FA125D587E70029D85E /* MapScanButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B41FA025D587E70029D85E /* MapScanButton.swift */; }; + C4C8016125D457190045A063 /* UserDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8016025D457190045A063 /* UserDetailsViewModel.swift */; }; + C4C8016425D4632E0045A063 /* SignInViewModel+Social.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8016325D4632E0045A063 /* SignInViewModel+Social.swift */; }; + C4C8016725D4635C0045A063 /* SignUpViewModel+Social.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8016625D4635C0045A063 /* SignUpViewModel+Social.swift */; }; + C4CBDD0425D34A0C007DC5CA /* Auth0 in Frameworks */ = {isa = PBXBuildFile; productRef = C4CBDD0325D34A0C007DC5CA /* Auth0 */; }; + C4CBDD0725D34E5E007DC5CA /* Auth0.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4CBDD0625D34E5E007DC5CA /* Auth0.plist */; }; + C4CBDD0A25D35A07007DC5CA /* Auth0DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CBDD0925D35A07007DC5CA /* Auth0DataSource.swift */; }; + C4DA827E25D39DFF00C94F03 /* StoreAuthUseCaseable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA827D25D39DFF00C94F03 /* StoreAuthUseCaseable.swift */; }; + C4DA828125D39E4600C94F03 /* StoreAuthUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA828025D39E4600C94F03 /* StoreAuthUseCase.swift */; }; + C4DA828425D3A07100C94F03 /* StoreAuthDataSourceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA828325D3A07100C94F03 /* StoreAuthDataSourceable.swift */; }; + C4DA828A25D3A81500C94F03 /* CheckAuthUseCaseable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA828925D3A81500C94F03 /* CheckAuthUseCaseable.swift */; }; + C4DA829225D3A92800C94F03 /* CheckAuthUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA829125D3A92800C94F03 /* CheckAuthUseCase.swift */; }; + C4DA829525D3A98700C94F03 /* CheckAuthDataSourceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA829425D3A98700C94F03 /* CheckAuthDataSourceable.swift */; }; + C4DA829B25D3DCDE00C94F03 /* Credentials+DTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA829A25D3DCDE00C94F03 /* Credentials+DTO.swift */; }; + C4DA829F25D3DE8D00C94F03 /* Auth0Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA829E25D3DE8D00C94F03 /* Auth0Properties.swift */; }; + C4DA82A225D3F31B00C94F03 /* Auth0DataSource+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82A125D3F31B00C94F03 /* Auth0DataSource+Fetch.swift */; }; + C4DA82A525D3F36500C94F03 /* Auth0DataSource+Logout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82A425D3F36500C94F03 /* Auth0DataSource+Logout.swift */; }; + C4DA82A825D3F3A800C94F03 /* Auth0DataSource+Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82A725D3F3A800C94F03 /* Auth0DataSource+Store.swift */; }; + C4DA82AB25D3F3D000C94F03 /* Auth0DataSource+Check.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82AA25D3F3D000C94F03 /* Auth0DataSource+Check.swift */; }; + C4DA82B325D3F95200C94F03 /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82B225D3F95200C94F03 /* BaseView.swift */; }; + C4DA82B725D3F96900C94F03 /* BaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82B625D3F96900C94F03 /* BaseViewModel.swift */; }; + C4DA82BA25D4030A00C94F03 /* FetchUserAuthDataSourceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82B925D4030A00C94F03 /* FetchUserAuthDataSourceable.swift */; }; + C4DA82BF25D4036000C94F03 /* FetchUserAuthUseCaseable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82BE25D4036000C94F03 /* FetchUserAuthUseCaseable.swift */; }; + C4DA82C225D403B900C94F03 /* Auth0DataSource+FetchUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82C125D403B900C94F03 /* Auth0DataSource+FetchUser.swift */; }; + C4DA82C525D4068300C94F03 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82C425D4068300C94F03 /* User.swift */; }; + C4DA82CC25D414C300C94F03 /* FetchUserAuthUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DA82CB25D414C300C94F03 /* FetchUserAuthUseCase.swift */; }; + C4DAC86925CF1F180093D717 /* SocialGoogleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC86825CF1F180093D717 /* SocialGoogleButton.swift */; }; + C4DAC86C25CF21E70093D717 /* SocialAuthenticable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC86B25CF21E70093D717 /* SocialAuthenticable.swift */; }; + C4DAC87325CF6A580093D717 /* Injector.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC87225CF6A580093D717 /* Injector.swift */; }; + C4DAC88325D04D810093D717 /* ViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC88225D04D810093D717 /* ViewRouter.swift */; }; + C4DAC88B25D04FDC0093D717 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC88A25D04FDC0093D717 /* MapView.swift */; }; + C4DAC89225D054C00093D717 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC89125D054C00093D717 /* ProfileView.swift */; }; + C4DAC89725D0626F0093D717 /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC89625D0626F0093D717 /* MapViewModel.swift */; }; + C4DAC89C25D174E60093D717 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DAC89B25D174E60093D717 /* ProfileViewModel.swift */; }; + C4DC3A6E25D3793A00955374 /* FetchAuthUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DC3A6D25D3793A00955374 /* FetchAuthUseCase.swift */; }; + C4DC3A7325D3823100955374 /* FetchAuthUseCaseable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DC3A7225D3823100955374 /* FetchAuthUseCaseable.swift */; }; + C4DC3A7725D390D900955374 /* LogoutAuthUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DC3A7625D390D900955374 /* LogoutAuthUseCase.swift */; }; + C4DC3A7A25D3911400955374 /* LogoutAuthUseCaseable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DC3A7925D3911400955374 /* LogoutAuthUseCaseable.swift */; }; + C4DC3A7F25D3938900955374 /* LogoutAuthDataSourceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DC3A7E25D3938900955374 /* LogoutAuthDataSourceable.swift */; }; + C4DDE7DC25C9CA2200985600 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C4DDE7DB25C9CA2200985600 /* Images.xcassets */; }; + C4F6A28B25CB2BFA00C8B2A5 /* SocialAppleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F6A28A25CB2BFA00C8B2A5 /* SocialAppleButton.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + C43056FA25D5B11900D07A27 /* ProfileProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileProgressView.swift; sourceTree = ""; }; + C43056FD25D5BD2400D07A27 /* ProfileSwitchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSwitchView.swift; sourceTree = ""; }; + C430570025D5BFB100D07A27 /* ProfileActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileActionView.swift; sourceTree = ""; }; + C430571F25D5E17500D07A27 /* UserInfo+DTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserInfo+DTO.swift"; sourceTree = ""; }; + C430572325D5EFF900D07A27 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; + C439A99525D35E4800F341D0 /* AuthCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCredentials.swift; sourceTree = ""; }; + C439A99825D3649000F341D0 /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRepository.swift; sourceTree = ""; }; + C439A99C25D364A900F341D0 /* AuthRepositable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRepositable.swift; sourceTree = ""; }; + C439A9A025D3662300F341D0 /* FetchAuthDataSourceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAuthDataSourceable.swift; sourceTree = ""; }; + C4661D4B25C8B8BA0055E0D8 /* Ecomobility.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ecomobility.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C4661D4E25C8B8BA0055E0D8 /* EcomobilityApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EcomobilityApp.swift; sourceTree = ""; }; + C4661D5725C8B8BB0055E0D8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C4858A8C25CEF0FC005F90AD /* SocialFacebookButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialFacebookButton.swift; sourceTree = ""; }; + C49237C025CB935F008F283C /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; + C49237C425CB939F008F283C /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + C49237C725CB93BA008F283C /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; + C49237D025CB9462008F283C /* SignInViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewModel.swift; sourceTree = ""; }; + C49237D425CB948C008F283C /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; + C49237DB25CB97B2008F283C /* LoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginButton.swift; sourceTree = ""; }; + C49237E825CBA88D008F283C /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; + C49237EE25CBAB53008F283C /* LoginTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTextField.swift; sourceTree = ""; }; + C49237F125CBAE50008F283C /* LoginSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSecureField.swift; sourceTree = ""; }; + C49237F725CEC5DB008F283C /* LoginSignUpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSignUpButton.swift; sourceTree = ""; }; + C4A9F54625D4501D00D9C42A /* ProfileUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUser.swift; sourceTree = ""; }; + C4A9F54C25D450D500D9C42A /* ProfileSettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsButton.swift; sourceTree = ""; }; + C4A9F55025D4517600D9C42A /* ProfileLogoutButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileLogoutButton.swift; sourceTree = ""; }; + C4A9F55325D4529400D9C42A /* ProfileView+Sections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Sections.swift"; sourceTree = ""; }; + C4A9F55625D4557600D9C42A /* ProfileDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDetailsView.swift; sourceTree = ""; }; + C4B41F8825D563920029D85E /* ProfileDetailsUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDetailsUser.swift; sourceTree = ""; }; + C4B41F9425D57BE60029D85E /* ProfileDetailsRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDetailsRow.swift; sourceTree = ""; }; + C4B41F9825D581260029D85E /* ProfileUserImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUserImage.swift; sourceTree = ""; }; + C4B41FA025D587E70029D85E /* MapScanButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapScanButton.swift; sourceTree = ""; }; + C4B41FA925D58E760029D85E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + C4C8016025D457190045A063 /* UserDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsViewModel.swift; sourceTree = ""; }; + C4C8016325D4632E0045A063 /* SignInViewModel+Social.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SignInViewModel+Social.swift"; sourceTree = ""; }; + C4C8016625D4635C0045A063 /* SignUpViewModel+Social.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SignUpViewModel+Social.swift"; sourceTree = ""; }; + C4CBDD0625D34E5E007DC5CA /* Auth0.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Auth0.plist; sourceTree = ""; }; + C4CBDD0925D35A07007DC5CA /* Auth0DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth0DataSource.swift; sourceTree = ""; }; + C4DA827D25D39DFF00C94F03 /* StoreAuthUseCaseable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreAuthUseCaseable.swift; sourceTree = ""; }; + C4DA828025D39E4600C94F03 /* StoreAuthUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreAuthUseCase.swift; sourceTree = ""; }; + C4DA828325D3A07100C94F03 /* StoreAuthDataSourceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreAuthDataSourceable.swift; sourceTree = ""; }; + C4DA828925D3A81500C94F03 /* CheckAuthUseCaseable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAuthUseCaseable.swift; sourceTree = ""; }; + C4DA829125D3A92800C94F03 /* CheckAuthUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAuthUseCase.swift; sourceTree = ""; }; + C4DA829425D3A98700C94F03 /* CheckAuthDataSourceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAuthDataSourceable.swift; sourceTree = ""; }; + C4DA829A25D3DCDE00C94F03 /* Credentials+DTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Credentials+DTO.swift"; sourceTree = ""; }; + C4DA829E25D3DE8D00C94F03 /* Auth0Properties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth0Properties.swift; sourceTree = ""; }; + C4DA82A125D3F31B00C94F03 /* Auth0DataSource+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Auth0DataSource+Fetch.swift"; sourceTree = ""; }; + C4DA82A425D3F36500C94F03 /* Auth0DataSource+Logout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Auth0DataSource+Logout.swift"; sourceTree = ""; }; + C4DA82A725D3F3A800C94F03 /* Auth0DataSource+Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Auth0DataSource+Store.swift"; sourceTree = ""; }; + C4DA82AA25D3F3D000C94F03 /* Auth0DataSource+Check.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Auth0DataSource+Check.swift"; sourceTree = ""; }; + C4DA82B225D3F95200C94F03 /* BaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseView.swift; sourceTree = ""; }; + C4DA82B625D3F96900C94F03 /* BaseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewModel.swift; sourceTree = ""; }; + C4DA82B925D4030A00C94F03 /* FetchUserAuthDataSourceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchUserAuthDataSourceable.swift; sourceTree = ""; }; + C4DA82BE25D4036000C94F03 /* FetchUserAuthUseCaseable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchUserAuthUseCaseable.swift; sourceTree = ""; }; + C4DA82C125D403B900C94F03 /* Auth0DataSource+FetchUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Auth0DataSource+FetchUser.swift"; sourceTree = ""; }; + C4DA82C425D4068300C94F03 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + C4DA82CB25D414C300C94F03 /* FetchUserAuthUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchUserAuthUseCase.swift; sourceTree = ""; }; + C4DAC86825CF1F180093D717 /* SocialGoogleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialGoogleButton.swift; sourceTree = ""; }; + C4DAC86B25CF21E70093D717 /* SocialAuthenticable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthenticable.swift; sourceTree = ""; }; + C4DAC87225CF6A580093D717 /* Injector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injector.swift; sourceTree = ""; }; + C4DAC88225D04D810093D717 /* ViewRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewRouter.swift; sourceTree = ""; }; + C4DAC88A25D04FDC0093D717 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; + C4DAC89125D054C00093D717 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; + C4DAC89625D0626F0093D717 /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = ""; }; + C4DAC89B25D174E60093D717 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; + C4DC3A6D25D3793A00955374 /* FetchAuthUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAuthUseCase.swift; sourceTree = ""; }; + C4DC3A7225D3823100955374 /* FetchAuthUseCaseable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAuthUseCaseable.swift; sourceTree = ""; }; + C4DC3A7625D390D900955374 /* LogoutAuthUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutAuthUseCase.swift; sourceTree = ""; }; + C4DC3A7925D3911400955374 /* LogoutAuthUseCaseable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutAuthUseCaseable.swift; sourceTree = ""; }; + C4DC3A7E25D3938900955374 /* LogoutAuthDataSourceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutAuthDataSourceable.swift; sourceTree = ""; }; + C4DDE7DB25C9CA2200985600 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + C4F6A28A25CB2BFA00C8B2A5 /* SocialAppleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAppleButton.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C4661D4825C8B8BA0055E0D8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C4CBDD0425D34A0C007DC5CA /* Auth0 in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C4235FAB25C9AF8B005E9FAC /* Application */ = { + isa = PBXGroup; + children = ( + C4DAC87525CF6C930093D717 /* Router */, + C4DAC87125CF6A400093D717 /* DI */, + C4235FB025C9C080005E9FAC /* Lifecycle */, + C4235FAC25C9AF93005E9FAC /* Configuration */, + ); + path = Application; + sourceTree = ""; + }; + C4235FAC25C9AF93005E9FAC /* Configuration */ = { + isa = PBXGroup; + children = ( + C4661D5725C8B8BB0055E0D8 /* Info.plist */, + ); + path = Configuration; + sourceTree = ""; + }; + C4235FB025C9C080005E9FAC /* Lifecycle */ = { + isa = PBXGroup; + children = ( + C4661D4E25C8B8BA0055E0D8 /* EcomobilityApp.swift */, + ); + path = Lifecycle; + sourceTree = ""; + }; + C430571D25D5E01C00D07A27 /* Protocols */ = { + isa = PBXGroup; + children = ( + C4DAC86B25CF21E70093D717 /* SocialAuthenticable.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + C439A99B25D3649700F341D0 /* Repositories */ = { + isa = PBXGroup; + children = ( + C439A99C25D364A900F341D0 /* AuthRepositable.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + C439A9A325D368BE00F341D0 /* Protocols */ = { + isa = PBXGroup; + children = ( + C4DC3A7125D381FF00955374 /* UseCases */, + C439A99B25D3649700F341D0 /* Repositories */, + ); + path = Protocols; + sourceTree = ""; + }; + C439A9A425D368CA00F341D0 /* Protocols */ = { + isa = PBXGroup; + children = ( + C439A9A525D368D500F341D0 /* DataSources */, + ); + path = Protocols; + sourceTree = ""; + }; + C439A9A525D368D500F341D0 /* DataSources */ = { + isa = PBXGroup; + children = ( + C4DA829425D3A98700C94F03 /* CheckAuthDataSourceable.swift */, + C439A9A025D3662300F341D0 /* FetchAuthDataSourceable.swift */, + C4DA82B925D4030A00C94F03 /* FetchUserAuthDataSourceable.swift */, + C4DC3A7E25D3938900955374 /* LogoutAuthDataSourceable.swift */, + C4DA828325D3A07100C94F03 /* StoreAuthDataSourceable.swift */, + ); + path = DataSources; + sourceTree = ""; + }; + C4661D4225C8B8BA0055E0D8 = { + isa = PBXGroup; + children = ( + C4B41FA925D58E760029D85E /* README.md */, + C4661D4D25C8B8BA0055E0D8 /* Ecomobility */, + C4661D4C25C8B8BA0055E0D8 /* Products */, + ); + sourceTree = ""; + }; + C4661D4C25C8B8BA0055E0D8 /* Products */ = { + isa = PBXGroup; + children = ( + C4661D4B25C8B8BA0055E0D8 /* Ecomobility.app */, + ); + name = Products; + sourceTree = ""; + }; + C4661D4D25C8B8BA0055E0D8 /* Ecomobility */ = { + isa = PBXGroup; + children = ( + C4235FAB25C9AF8B005E9FAC /* Application */, + C4E72B9125CB88B800960793 /* Presentation */, + C4E72B9225CB88BE00960793 /* Domain */, + C4E72B9325CB88C300960793 /* Data */, + ); + path = Ecomobility; + sourceTree = ""; + }; + C4858A8A25CEF0D1005F90AD /* Buttons */ = { + isa = PBXGroup; + children = ( + C49237DB25CB97B2008F283C /* LoginButton.swift */, + C49237F725CEC5DB008F283C /* LoginSignUpButton.swift */, + C4F6A28A25CB2BFA00C8B2A5 /* SocialAppleButton.swift */, + C4858A8C25CEF0FC005F90AD /* SocialFacebookButton.swift */, + C4DAC86825CF1F180093D717 /* SocialGoogleButton.swift */, + ); + path = Buttons; + sourceTree = ""; + }; + C4858A8B25CEF0DC005F90AD /* Fields */ = { + isa = PBXGroup; + children = ( + C49237F125CBAE50008F283C /* LoginSecureField.swift */, + C49237EE25CBAB53008F283C /* LoginTextField.swift */, + ); + path = Fields; + sourceTree = ""; + }; + C4A9F54425D4500500D9C42A /* Components */ = { + isa = PBXGroup; + children = ( + C4A9F54B25D450AA00D9C42A /* Views */, + C4A9F54A25D450A500D9C42A /* Buttons */, + ); + path = Components; + sourceTree = ""; + }; + C4A9F54525D4500C00D9C42A /* Screens */ = { + isa = PBXGroup; + children = ( + C4A9F55625D4557600D9C42A /* ProfileDetailsView.swift */, + C4DAC89125D054C00093D717 /* ProfileView.swift */, + C4A9F55325D4529400D9C42A /* ProfileView+Sections.swift */, + ); + path = Screens; + sourceTree = ""; + }; + C4A9F54A25D450A500D9C42A /* Buttons */ = { + isa = PBXGroup; + children = ( + C4A9F55025D4517600D9C42A /* ProfileLogoutButton.swift */, + C4A9F54C25D450D500D9C42A /* ProfileSettingsButton.swift */, + ); + path = Buttons; + sourceTree = ""; + }; + C4A9F54B25D450AA00D9C42A /* Views */ = { + isa = PBXGroup; + children = ( + C430570025D5BFB100D07A27 /* ProfileActionView.swift */, + C4B41F9425D57BE60029D85E /* ProfileDetailsRow.swift */, + C4B41F8825D563920029D85E /* ProfileDetailsUser.swift */, + C43056FA25D5B11900D07A27 /* ProfileProgressView.swift */, + C43056FD25D5BD2400D07A27 /* ProfileSwitchView.swift */, + C4A9F54625D4501D00D9C42A /* ProfileUser.swift */, + C4B41F9825D581260029D85E /* ProfileUserImage.swift */, + ); + path = Views; + sourceTree = ""; + }; + C4B41F9F25D587D40029D85E /* Components */ = { + isa = PBXGroup; + children = ( + C4B41FA025D587E70029D85E /* MapScanButton.swift */, + ); + path = Components; + sourceTree = ""; + }; + C4C5425F25DA7480009F64D3 /* Configuration */ = { + isa = PBXGroup; + children = ( + C4CBDD0625D34E5E007DC5CA /* Auth0.plist */, + ); + path = Configuration; + sourceTree = ""; + }; + C4CBDD0C25D35A3A007DC5CA /* DataSources */ = { + isa = PBXGroup; + children = ( + C4DA829725D3D85A00C94F03 /* Auth0 */, + ); + path = DataSources; + sourceTree = ""; + }; + C4CBDD0D25D35A42007DC5CA /* Repositories */ = { + isa = PBXGroup; + children = ( + C439A99825D3649000F341D0 /* AuthRepository.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + C4CBDD0F25D35CA2007DC5CA /* Entities */ = { + isa = PBXGroup; + children = ( + C439A99525D35E4800F341D0 /* AuthCredentials.swift */, + C4DA82C425D4068300C94F03 /* User.swift */, + ); + path = Entities; + sourceTree = ""; + }; + C4DA829725D3D85A00C94F03 /* Auth0 */ = { + isa = PBXGroup; + children = ( + C4C5425F25DA7480009F64D3 /* Configuration */, + C4DA829925D3DCA500C94F03 /* Entities */, + C4CBDD0925D35A07007DC5CA /* Auth0DataSource.swift */, + C4DA82AA25D3F3D000C94F03 /* Auth0DataSource+Check.swift */, + C4DA82A125D3F31B00C94F03 /* Auth0DataSource+Fetch.swift */, + C4DA82C125D403B900C94F03 /* Auth0DataSource+FetchUser.swift */, + C4DA82A425D3F36500C94F03 /* Auth0DataSource+Logout.swift */, + C4DA82A725D3F3A800C94F03 /* Auth0DataSource+Store.swift */, + ); + path = Auth0; + sourceTree = ""; + }; + C4DA829925D3DCA500C94F03 /* Entities */ = { + isa = PBXGroup; + children = ( + C4DA829E25D3DE8D00C94F03 /* Auth0Properties.swift */, + C4DA829A25D3DCDE00C94F03 /* Credentials+DTO.swift */, + C430571F25D5E17500D07A27 /* UserInfo+DTO.swift */, + ); + path = Entities; + sourceTree = ""; + }; + C4DA82AF25D3F93100C94F03 /* Base */ = { + isa = PBXGroup; + children = ( + C4DA82B125D3F93F00C94F03 /* ViewModels */, + C4DA82B025D3F93900C94F03 /* Views */, + ); + path = Base; + sourceTree = ""; + }; + C4DA82B025D3F93900C94F03 /* Views */ = { + isa = PBXGroup; + children = ( + C4DA82B225D3F95200C94F03 /* BaseView.swift */, + ); + path = Views; + sourceTree = ""; + }; + C4DA82B125D3F93F00C94F03 /* ViewModels */ = { + isa = PBXGroup; + children = ( + C4DA82B625D3F96900C94F03 /* BaseViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + C4DAC87125CF6A400093D717 /* DI */ = { + isa = PBXGroup; + children = ( + C4DAC87225CF6A580093D717 /* Injector.swift */, + ); + path = DI; + sourceTree = ""; + }; + C4DAC87525CF6C930093D717 /* Router */ = { + isa = PBXGroup; + children = ( + C4DAC88225D04D810093D717 /* ViewRouter.swift */, + ); + path = Router; + sourceTree = ""; + }; + C4DAC88625D04FA30093D717 /* Map */ = { + isa = PBXGroup; + children = ( + C4DAC88825D04FB60093D717 /* ViewModels */, + C4DAC88725D04FB10093D717 /* Views */, + ); + path = Map; + sourceTree = ""; + }; + C4DAC88725D04FB10093D717 /* Views */ = { + isa = PBXGroup; + children = ( + C4DAC88925D04FC80093D717 /* Screens */, + C4B41F9F25D587D40029D85E /* Components */, + ); + path = Views; + sourceTree = ""; + }; + C4DAC88825D04FB60093D717 /* ViewModels */ = { + isa = PBXGroup; + children = ( + C4DAC89625D0626F0093D717 /* MapViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + C4DAC88925D04FC80093D717 /* Screens */ = { + isa = PBXGroup; + children = ( + C4DAC88A25D04FDC0093D717 /* MapView.swift */, + ); + path = Screens; + sourceTree = ""; + }; + C4DAC88E25D054A80093D717 /* Profile */ = { + isa = PBXGroup; + children = ( + C4DAC89025D054B40093D717 /* ViewModels */, + C4DAC88F25D054AE0093D717 /* Views */, + ); + path = Profile; + sourceTree = ""; + }; + C4DAC88F25D054AE0093D717 /* Views */ = { + isa = PBXGroup; + children = ( + C4A9F54525D4500C00D9C42A /* Screens */, + C4A9F54425D4500500D9C42A /* Components */, + ); + path = Views; + sourceTree = ""; + }; + C4DAC89025D054B40093D717 /* ViewModels */ = { + isa = PBXGroup; + children = ( + C4DAC89B25D174E60093D717 /* ProfileViewModel.swift */, + C4C8016025D457190045A063 /* UserDetailsViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + C4DC3A7125D381FF00955374 /* UseCases */ = { + isa = PBXGroup; + children = ( + C4DA828925D3A81500C94F03 /* CheckAuthUseCaseable.swift */, + C4DC3A7225D3823100955374 /* FetchAuthUseCaseable.swift */, + C4DA82BE25D4036000C94F03 /* FetchUserAuthUseCaseable.swift */, + C4DC3A7925D3911400955374 /* LogoutAuthUseCaseable.swift */, + C4DA827D25D39DFF00C94F03 /* StoreAuthUseCaseable.swift */, + ); + path = UseCases; + sourceTree = ""; + }; + C4DDE7DA25C9CA0A00985600 /* Assets */ = { + isa = PBXGroup; + children = ( + C430572325D5EFF900D07A27 /* Colors.xcassets */, + C4DDE7DB25C9CA2200985600 /* Images.xcassets */, + ); + path = Assets; + sourceTree = ""; + }; + C4E72B9125CB88B800960793 /* Presentation */ = { + isa = PBXGroup; + children = ( + C4E72B9625CB897E00960793 /* Scenes */, + C4DDE7DA25C9CA0A00985600 /* Assets */, + ); + path = Presentation; + sourceTree = ""; + }; + C4E72B9225CB88BE00960793 /* Domain */ = { + isa = PBXGroup; + children = ( + C439A9A325D368BE00F341D0 /* Protocols */, + C4CBDD0F25D35CA2007DC5CA /* Entities */, + C4E72B9D25CB8A7700960793 /* UseCases */, + ); + path = Domain; + sourceTree = ""; + }; + C4E72B9325CB88C300960793 /* Data */ = { + isa = PBXGroup; + children = ( + C439A9A425D368CA00F341D0 /* Protocols */, + C4CBDD0D25D35A42007DC5CA /* Repositories */, + C4CBDD0C25D35A3A007DC5CA /* DataSources */, + ); + path = Data; + sourceTree = ""; + }; + C4E72B9525CB896100960793 /* Login */ = { + isa = PBXGroup; + children = ( + C4E72B9925CB89DC00960793 /* ViewModels */, + C4E72B9825CB89CF00960793 /* Views */, + ); + path = Login; + sourceTree = ""; + }; + C4E72B9625CB897E00960793 /* Scenes */ = { + isa = PBXGroup; + children = ( + C4DA82AF25D3F93100C94F03 /* Base */, + C4DAC88E25D054A80093D717 /* Profile */, + C4DAC88625D04FA30093D717 /* Map */, + C4E72B9525CB896100960793 /* Login */, + ); + path = Scenes; + sourceTree = ""; + }; + C4E72B9825CB89CF00960793 /* Views */ = { + isa = PBXGroup; + children = ( + C4E72B9A25CB8A1900960793 /* Screens */, + C4E72B9B25CB8A2100960793 /* Components */, + ); + path = Views; + sourceTree = ""; + }; + C4E72B9925CB89DC00960793 /* ViewModels */ = { + isa = PBXGroup; + children = ( + C430571D25D5E01C00D07A27 /* Protocols */, + C49237D425CB948C008F283C /* LoginViewModel.swift */, + C49237D025CB9462008F283C /* SignInViewModel.swift */, + C4C8016325D4632E0045A063 /* SignInViewModel+Social.swift */, + C49237E825CBA88D008F283C /* SignUpViewModel.swift */, + C4C8016625D4635C0045A063 /* SignUpViewModel+Social.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + C4E72B9A25CB8A1900960793 /* Screens */ = { + isa = PBXGroup; + children = ( + C49237C425CB939F008F283C /* LoginView.swift */, + C49237C025CB935F008F283C /* SignInView.swift */, + C49237C725CB93BA008F283C /* SignUpView.swift */, + ); + path = Screens; + sourceTree = ""; + }; + C4E72B9B25CB8A2100960793 /* Components */ = { + isa = PBXGroup; + children = ( + C4858A8B25CEF0DC005F90AD /* Fields */, + C4858A8A25CEF0D1005F90AD /* Buttons */, + ); + path = Components; + sourceTree = ""; + }; + C4E72B9D25CB8A7700960793 /* UseCases */ = { + isa = PBXGroup; + children = ( + C4DA829125D3A92800C94F03 /* CheckAuthUseCase.swift */, + C4DC3A6D25D3793A00955374 /* FetchAuthUseCase.swift */, + C4DA82CB25D414C300C94F03 /* FetchUserAuthUseCase.swift */, + C4DC3A7625D390D900955374 /* LogoutAuthUseCase.swift */, + C4DA828025D39E4600C94F03 /* StoreAuthUseCase.swift */, + ); + path = UseCases; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C4661D4A25C8B8BA0055E0D8 /* Ecomobility */ = { + isa = PBXNativeTarget; + buildConfigurationList = C4661D5A25C8B8BB0055E0D8 /* Build configuration list for PBXNativeTarget "Ecomobility" */; + buildPhases = ( + C4661D4725C8B8BA0055E0D8 /* Sources */, + C4661D4825C8B8BA0055E0D8 /* Frameworks */, + C4661D4925C8B8BA0055E0D8 /* Resources */, + C4DA82AD25D3F63100C94F03 /* SwiftLint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Ecomobility; + packageProductDependencies = ( + C4CBDD0325D34A0C007DC5CA /* Auth0 */, + ); + productName = Ecomobility; + productReference = C4661D4B25C8B8BA0055E0D8 /* Ecomobility.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C4661D4325C8B8BA0055E0D8 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1240; + LastUpgradeCheck = 1240; + TargetAttributes = { + C4661D4A25C8B8BA0055E0D8 = { + CreatedOnToolsVersion = 12.4; + }; + }; + }; + buildConfigurationList = C4661D4625C8B8BA0055E0D8 /* Build configuration list for PBXProject "Ecomobility" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C4661D4225C8B8BA0055E0D8; + packageReferences = ( + C4CBDD0225D34A0C007DC5CA /* XCRemoteSwiftPackageReference "Auth0.swift" */, + ); + productRefGroup = C4661D4C25C8B8BA0055E0D8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C4661D4A25C8B8BA0055E0D8 /* Ecomobility */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C4661D4925C8B8BA0055E0D8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C430572425D5EFF900D07A27 /* Colors.xcassets in Resources */, + C4DDE7DC25C9CA2200985600 /* Images.xcassets in Resources */, + C4CBDD0725D34E5E007DC5CA /* Auth0.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + C4DA82AD25D3F63100C94F03 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f ~/com.raywenderlich.swiftlint.yml ]; then\n if which swiftlint >/dev/null; then\n swiftlint --no-cache --config ~/com.raywenderlich.swiftlint.yml\n fi\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C4661D4725C8B8BA0055E0D8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C4DA82A225D3F31B00C94F03 /* Auth0DataSource+Fetch.swift in Sources */, + C4DC3A7A25D3911400955374 /* LogoutAuthUseCaseable.swift in Sources */, + C4DA828A25D3A81500C94F03 /* CheckAuthUseCaseable.swift in Sources */, + C4DA829225D3A92800C94F03 /* CheckAuthUseCase.swift in Sources */, + C430570125D5BFB100D07A27 /* ProfileActionView.swift in Sources */, + C4B41F9925D581260029D85E /* ProfileUserImage.swift in Sources */, + C4DA829F25D3DE8D00C94F03 /* Auth0Properties.swift in Sources */, + C4DA82A525D3F36500C94F03 /* Auth0DataSource+Logout.swift in Sources */, + C4B41FA125D587E70029D85E /* MapScanButton.swift in Sources */, + C4C8016125D457190045A063 /* UserDetailsViewModel.swift in Sources */, + C49237F225CBAE50008F283C /* LoginSecureField.swift in Sources */, + C4DAC86C25CF21E70093D717 /* SocialAuthenticable.swift in Sources */, + C49237C825CB93BA008F283C /* SignUpView.swift in Sources */, + C4DC3A7325D3823100955374 /* FetchAuthUseCaseable.swift in Sources */, + C439A99625D35E4800F341D0 /* AuthCredentials.swift in Sources */, + C4B41F8925D563920029D85E /* ProfileDetailsUser.swift in Sources */, + C43056FE25D5BD2400D07A27 /* ProfileSwitchView.swift in Sources */, + C49237D525CB948C008F283C /* LoginViewModel.swift in Sources */, + C49237E925CBA88D008F283C /* SignUpViewModel.swift in Sources */, + C4DA828125D39E4600C94F03 /* StoreAuthUseCase.swift in Sources */, + C430572025D5E17500D07A27 /* UserInfo+DTO.swift in Sources */, + C4A9F54725D4501D00D9C42A /* ProfileUser.swift in Sources */, + C4858A8D25CEF0FC005F90AD /* SocialFacebookButton.swift in Sources */, + C4C8016725D4635C0045A063 /* SignUpViewModel+Social.swift in Sources */, + C4DAC89C25D174E60093D717 /* ProfileViewModel.swift in Sources */, + C4DA828425D3A07100C94F03 /* StoreAuthDataSourceable.swift in Sources */, + C4A9F55725D4557600D9C42A /* ProfileDetailsView.swift in Sources */, + C4DA82A825D3F3A800C94F03 /* Auth0DataSource+Store.swift in Sources */, + C4B41F9525D57BE60029D85E /* ProfileDetailsRow.swift in Sources */, + C4DAC86925CF1F180093D717 /* SocialGoogleButton.swift in Sources */, + C439A99925D3649000F341D0 /* AuthRepository.swift in Sources */, + C4DAC88B25D04FDC0093D717 /* MapView.swift in Sources */, + C439A99D25D364A900F341D0 /* AuthRepositable.swift in Sources */, + C4C8016425D4632E0045A063 /* SignInViewModel+Social.swift in Sources */, + C4DAC87325CF6A580093D717 /* Injector.swift in Sources */, + C4DA82BF25D4036000C94F03 /* FetchUserAuthUseCaseable.swift in Sources */, + C4DAC88325D04D810093D717 /* ViewRouter.swift in Sources */, + C4DA829525D3A98700C94F03 /* CheckAuthDataSourceable.swift in Sources */, + C4DC3A7F25D3938900955374 /* LogoutAuthDataSourceable.swift in Sources */, + C4DA82C225D403B900C94F03 /* Auth0DataSource+FetchUser.swift in Sources */, + C439A9A125D3662300F341D0 /* FetchAuthDataSourceable.swift in Sources */, + C4DA827E25D39DFF00C94F03 /* StoreAuthUseCaseable.swift in Sources */, + C4CBDD0A25D35A07007DC5CA /* Auth0DataSource.swift in Sources */, + C4661D4F25C8B8BA0055E0D8 /* EcomobilityApp.swift in Sources */, + C4DAC89725D0626F0093D717 /* MapViewModel.swift in Sources */, + C4DA82B325D3F95200C94F03 /* BaseView.swift in Sources */, + C49237DC25CB97B2008F283C /* LoginButton.swift in Sources */, + C4DA829B25D3DCDE00C94F03 /* Credentials+DTO.swift in Sources */, + C49237C525CB939F008F283C /* LoginView.swift in Sources */, + C4A9F55425D4529400D9C42A /* ProfileView+Sections.swift in Sources */, + C49237C125CB935F008F283C /* SignInView.swift in Sources */, + C43056FB25D5B11900D07A27 /* ProfileProgressView.swift in Sources */, + C4F6A28B25CB2BFA00C8B2A5 /* SocialAppleButton.swift in Sources */, + C49237D125CB9462008F283C /* SignInViewModel.swift in Sources */, + C4A9F54D25D450D500D9C42A /* ProfileSettingsButton.swift in Sources */, + C4A9F55125D4517600D9C42A /* ProfileLogoutButton.swift in Sources */, + C4DC3A6E25D3793A00955374 /* FetchAuthUseCase.swift in Sources */, + C4DA82CC25D414C300C94F03 /* FetchUserAuthUseCase.swift in Sources */, + C4DA82C525D4068300C94F03 /* User.swift in Sources */, + C4DAC89225D054C00093D717 /* ProfileView.swift in Sources */, + C49237EF25CBAB53008F283C /* LoginTextField.swift in Sources */, + C4DA82B725D3F96900C94F03 /* BaseViewModel.swift in Sources */, + C49237F825CEC5DB008F283C /* LoginSignUpButton.swift in Sources */, + C4DC3A7725D390D900955374 /* LogoutAuthUseCase.swift in Sources */, + C4DA82BA25D4030A00C94F03 /* FetchUserAuthDataSourceable.swift in Sources */, + C4DA82AB25D3F3D000C94F03 /* Auth0DataSource+Check.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C4661D5825C8B8BB0055E0D8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + 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 = 14.4; + 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; + }; + C4661D5925C8B8BB0055E0D8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + 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 = 14.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C4661D5B25C8B8BB0055E0D8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = W2HFS492D7; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Ecomobility/Application/Configuration/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rpairo.Ecomobility; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C4661D5C25C8B8BB0055E0D8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = W2HFS492D7; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Ecomobility/Application/Configuration/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rpairo.Ecomobility; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C4661D4625C8B8BA0055E0D8 /* Build configuration list for PBXProject "Ecomobility" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C4661D5825C8B8BB0055E0D8 /* Debug */, + C4661D5925C8B8BB0055E0D8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C4661D5A25C8B8BB0055E0D8 /* Build configuration list for PBXNativeTarget "Ecomobility" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C4661D5B25C8B8BB0055E0D8 /* Debug */, + C4661D5C25C8B8BB0055E0D8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + C4CBDD0225D34A0C007DC5CA /* XCRemoteSwiftPackageReference "Auth0.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/auth0/Auth0.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.30.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C4CBDD0325D34A0C007DC5CA /* Auth0 */ = { + isa = XCSwiftPackageProductDependency; + package = C4CBDD0225D34A0C007DC5CA /* XCRemoteSwiftPackageReference "Auth0.swift" */; + productName = Auth0; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = C4661D4325C8B8BA0055E0D8 /* Project object */; +} diff --git a/Ecomobility.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Ecomobility.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Ecomobility.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Ecomobility.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Ecomobility.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Ecomobility.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Ecomobility.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate b/Ecomobility.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..61d9a9b Binary files /dev/null and b/Ecomobility.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Ecomobility.xcodeproj/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist b/Ecomobility.xcodeproj/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..fc45fdf --- /dev/null +++ b/Ecomobility.xcodeproj/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,35 @@ + + + + + SchemeUserState + + Ecomobility.xcscheme_^#shared#^_ + + orderHint + 0 + + JWTDecode (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + JWTDecode (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + JWTDecode (Playground).xcscheme + + isShown + + orderHint + 0 + + + + diff --git a/Ecomobility/Application/Configuration/Auth0.plist b/Ecomobility/Application/Configuration/Auth0.plist new file mode 100644 index 0000000..d064528 --- /dev/null +++ b/Ecomobility/Application/Configuration/Auth0.plist @@ -0,0 +1,10 @@ + + + + + Domain + dev-9y2yk3-k.eu.auth0.com + ClientId + lP7d1yyxykzU6sHC6LqtT7WAXFuBiydz + + diff --git a/Ecomobility/Application/Configuration/Info.plist b/Ecomobility/Application/Configuration/Info.plist new file mode 100644 index 0000000..79b174b --- /dev/null +++ b/Ecomobility/Application/Configuration/Info.plist @@ -0,0 +1,68 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + None + CFBundleURLName + auth0 + CFBundleURLSchemes + + $(PRODUCT_BUNDLE_IDENTIFIER) + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIApplicationSupportsIndirectInputEvents + + UILaunchScreen + + UIImageRespectsSafeAreaInsets + + UIImageName + company_splash + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Ecomobility/Application/DI/Injector.swift b/Ecomobility/Application/DI/Injector.swift new file mode 100644 index 0000000..e958bfa --- /dev/null +++ b/Ecomobility/Application/DI/Injector.swift @@ -0,0 +1,126 @@ +// +// Injector.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 7/2/21. +// + +import Foundation + +struct Injector { + // MARK: Properties + let router = ViewRouter() + static let shared = Injector() + + // MARK: Constructor + private init() { } + + // MARK: Router + func resolve() -> ViewRouter { + router + } + + // MARK: Views + func resolve() -> LoginView { + LoginView(viewModel: resolve()) + } + + func resolve() -> SignInView { + SignInView(viewModel: resolve()) + } + + func resolve() -> LoginViewModel { + LoginViewModel() + } + + func resolve() -> SignUpView { + SignUpView(viewModel: resolve()) + } + + // MARK: ViewModels + func resolve() -> SignInViewModel { + SignInViewModel( + authUseCase: resolve(), + storeSession: resolve() + ) + } + + func resolve() -> SignUpViewModel { + SignUpViewModel( + authUseCase: resolve(), + storeSession: resolve() + ) + } + + func resolve() -> MapViewModel { + MapViewModel() + } + + func resolve() -> ProfileViewModel { + ProfileViewModel( + logoutAuthUseCase: resolve(), + fetchUserAuthUseCase: resolve() + ) + } + + func resolve() -> BaseViewModel { + BaseViewModel(authUseCase: resolve()) + } + + func resolve() -> UserDetailsViewModel { + UserDetailsViewModel(fetchUserAuthUseCase: resolve()) + } + + // MARK: Use cases + func resolve() -> FetchAuthUseCaseable { + FetchAuthUseCase(authRepository: resolve()) + } + + func resolve() -> LogoutAuthUseCaseable { + LogoutAuthUseCase(authRepository: resolve()) + } + + func resolve() -> StoreAuthUseCaseable { + StoreAuthUseCase(authRepository: resolve()) + } + + func resolve() -> CheckAuthUseCaseable { + CheckAuthUseCase(authRepository: resolve()) + } + + func resolve() -> FetchUserAuthUseCaseable { + FetchUserAuthUseCase(authRepository: resolve()) + } + + // MARK: Repositories + func resolve() -> AuthRepositable { + AuthRepository( + fetchingDataSource: resolve(), + logoutDataSource: resolve(), + storeDataSource: resolve(), + checkDataSource: resolve(), + fetchUserDataSource: resolve() + ) + } + + // MARK: Data sources + func resolve() -> FetchAuthDataSourceable { + Auth0DataSource() + } + + func resolve() -> LogoutAuthDataSourceable { + Auth0DataSource() + } + + func resolve() -> StoreAuthDataSourceable { + Auth0DataSource() + } + + func resolve() -> CheckAuthDataSourceable { + Auth0DataSource() + } + + func resolve() -> FetchUserAuthDataSourceable { + Auth0DataSource() + } +} diff --git a/Ecomobility/Application/Lifecycle/EcomobilityApp.swift b/Ecomobility/Application/Lifecycle/EcomobilityApp.swift new file mode 100644 index 0000000..afcb195 --- /dev/null +++ b/Ecomobility/Application/Lifecycle/EcomobilityApp.swift @@ -0,0 +1,34 @@ +// +// EcomobilityApp.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 1/2/21. +// + +import SwiftUI + +@main +struct EcomobilityApp: App { + // MARK: Properties + @StateObject var viewRouter: ViewRouter = Injector.shared.resolve() + + // MARK: Constructor + init() { + UINavigationBar.appearance().largeTitleTextAttributes = [ + NSAttributedString.Key.foregroundColor: UIColor(.accentColor) + ] + + UINavigationBar.appearance().titleTextAttributes = [ + NSAttributedString.Key.foregroundColor: UIColor(.accentColor) + ] + } + + // MARK: Scene + var body: some Scene { + WindowGroup { + BaseView(viewModel: Injector.shared.resolve()) + .environmentObject(viewRouter) + .statusBar(hidden: true) + } + } +} diff --git a/Ecomobility/Application/Router/ViewRouter.swift b/Ecomobility/Application/Router/ViewRouter.swift new file mode 100644 index 0000000..d9730c5 --- /dev/null +++ b/Ecomobility/Application/Router/ViewRouter.swift @@ -0,0 +1,19 @@ +// +// ViewRouter.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 7/2/21. +// + +import Foundation + +enum Scenes { + // MARK: Cases + case login + case base +} + +class ViewRouter: ObservableObject { + // MARK: Properties + @Published var currentScene: Scenes = .login +} diff --git a/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Check.swift b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Check.swift new file mode 100644 index 0000000..891db2d --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Check.swift @@ -0,0 +1,28 @@ +// +// Auth0DataSource+Check.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +extension Auth0DataSource: CheckAuthDataSourceable { + // MARK: Functionality + func check(onCompletion: @escaping CheckAuthResult) { + guard manager.hasValid() else { + onCompletion(.failure(NSError())) + return + } + + manager.credentials { error, credentials in + guard error == nil, let credentials = credentials else { + onCompletion(.failure(NSError())) + return + } + + onCompletion(.success(credentials.transform())) + } + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Fetch.swift b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Fetch.swift new file mode 100644 index 0000000..7963108 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Fetch.swift @@ -0,0 +1,29 @@ +// +// Auth0DataSource+Fetch.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +extension Auth0DataSource: FetchAuthDataSourceable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) { + guard let domain = domain else { return } + + Auth0 + .webAuth() + .scope("openid profile email offline_access") + .audience("https://\(domain)/userinfo") + .start { result in + switch result { + case .success(let credentials): + onCompletion(.success(credentials.transform())) + case .failure(let error): + onCompletion(.failure(error)) + } + } + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+FetchUser.swift b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+FetchUser.swift new file mode 100644 index 0000000..4751a08 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+FetchUser.swift @@ -0,0 +1,57 @@ +// +// Auth0DataSource+FetchUser.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +// It is necessary to use the Swift namespace by collision of names +// with the Result type and the Auth0 library +private typealias TokenResult = (Swift.Result) -> Void + +extension Auth0DataSource: FetchUserAuthDataSourceable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchUserAuthResult) { + fetchAccesToken { result in + switch result { + case .success(let token): + self.fetchUserInfo(token: token, onCompletion: onCompletion) + case .failure(let error): + onCompletion(.failure(error)) + } + } + } + + private func fetchAccesToken(onCompletion: @escaping TokenResult) { + manager.credentials { (error, credentials) in + if let error = error { + onCompletion(.failure(.unkown(error))) + return + } + + guard let accessToken = credentials?.accessToken else { + onCompletion(.failure(.credentials)) + return + } + + onCompletion(.success(accessToken)) + } + } + + private func fetchUserInfo(token: String, onCompletion: @escaping FetchUserAuthResult) { + Auth0 + .authentication() + .userInfo(withAccessToken: token) + .start { result in + switch result { + case .success(let profile): + onCompletion(.success(profile.transform())) + case .failure(let error): + onCompletion(.failure(.unkown(error))) + } + } + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Logout.swift b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Logout.swift new file mode 100644 index 0000000..74595b0 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Logout.swift @@ -0,0 +1,33 @@ +// +// Auth0DataSource+Logout.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +extension Auth0DataSource: LogoutAuthDataSourceable { + // MARK: Functionality + func logout(onCompletion: @escaping LogoutAuthResult) { + // Revoke token manager + manager.revoke { result in + if let error = result { + onCompletion(.failure(error)) + } + } + + // Remove auth session + Auth0 + .webAuth() + .clearSession(federated: false) { result in + switch result { + case true: + onCompletion(.success(result)) + case false: + onCompletion(.failure(NSError())) + } + } + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Store.swift b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Store.swift new file mode 100644 index 0000000..d16ee1e --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource+Store.swift @@ -0,0 +1,24 @@ +// +// Auth0DataSource+Store.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +extension Auth0DataSource: StoreAuthDataSourceable { + // MARK: Functionality + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) { + let cretendialsDTO = Credentials(credentials) + let result = manager.store(credentials: cretendialsDTO) + + switch result { + case true: + onCompletion(.success(result)) + case false: + onCompletion(.failure(NSError())) + } + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Auth0DataSource.swift b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource.swift new file mode 100644 index 0000000..159c3c6 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Auth0DataSource.swift @@ -0,0 +1,28 @@ +// +// Auth0DataSource.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +class Auth0DataSource { + // MARK: Properties + let manager = CredentialsManager(authentication: Auth0.authentication()) + + // Computed properties + var domain: String? { + let file = "Auth0" + let type = "plist" + + guard let url = Bundle.main.url(forResource: file, withExtension: type) else { return nil } + guard let data = try? Data(contentsOf: url) else { return nil } + + let decoder = PropertyListDecoder() + guard let properties = try? decoder.decode(Auth0Properties.self, from: data) else { return nil } + + return properties.domain + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Configuration/Auth0.plist b/Ecomobility/Data/DataSources/Auth0/Configuration/Auth0.plist new file mode 100644 index 0000000..d064528 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Configuration/Auth0.plist @@ -0,0 +1,10 @@ + + + + + Domain + dev-9y2yk3-k.eu.auth0.com + ClientId + lP7d1yyxykzU6sHC6LqtT7WAXFuBiydz + + diff --git a/Ecomobility/Data/DataSources/Auth0/Entities/Auth0Properties.swift b/Ecomobility/Data/DataSources/Auth0/Entities/Auth0Properties.swift new file mode 100644 index 0000000..585f3f3 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Entities/Auth0Properties.swift @@ -0,0 +1,27 @@ +// +// Auth0Properties.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct Auth0Properties: Decodable { + // MARK: Properties + var domain: String? + var cliendId: String? + + // MARK: Keys + enum CodingKeys: String, CodingKey { + case domain = "Domain" + case cliendId = "CientId" + } + + // MARK: Constructor + init(from decoder: Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + domain = try? container?.decode(String.self, forKey: .domain) + cliendId = try? container?.decode(String.self, forKey: .cliendId) + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Entities/Credentials+DTO.swift b/Ecomobility/Data/DataSources/Auth0/Entities/Credentials+DTO.swift new file mode 100644 index 0000000..8d9ad8d --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Entities/Credentials+DTO.swift @@ -0,0 +1,35 @@ +// +// Credentials+DTO.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import Auth0 + +extension Credentials { + // MARK: Constructor + convenience init(_ credentials: AuthCredentials) { + self.init( + accessToken: credentials.accessToken, + tokenType: credentials.tokenType, + idToken: credentials.idToken, + refreshToken: credentials.refreshToken, + expiresIn: credentials.expiresIn, + scope: credentials.scope + ) + } + + // MARK: Functionality + func transform() -> AuthCredentials { + AuthCredentials( + accessToken: self.accessToken, + expiresIn: self.expiresIn, + idToken: self.idToken, + refreshToken: self.refreshToken, + scope: self.scope, + tokenType: self.tokenType + ) + } +} diff --git a/Ecomobility/Data/DataSources/Auth0/Entities/UserInfo+DTO.swift b/Ecomobility/Data/DataSources/Auth0/Entities/UserInfo+DTO.swift new file mode 100644 index 0000000..f40a636 --- /dev/null +++ b/Ecomobility/Data/DataSources/Auth0/Entities/UserInfo+DTO.swift @@ -0,0 +1,23 @@ +// +// UserInfo+DTO.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import Foundation +import Auth0 + +extension UserInfo { + // MARK: Functionality + func transform() -> User { + User( + name: self.name, + email: self.email, + picture: self.picture?.absoluteString, + nickname: self.nickname, + familyName: self.familyName, + givenName: self.givenName + ) + } +} diff --git a/Ecomobility/Data/Protocols/DataSources/CheckAuthDataSourceable.swift b/Ecomobility/Data/Protocols/DataSources/CheckAuthDataSourceable.swift new file mode 100644 index 0000000..4691b2d --- /dev/null +++ b/Ecomobility/Data/Protocols/DataSources/CheckAuthDataSourceable.swift @@ -0,0 +1,13 @@ +// +// CheckAuthDataSourceable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +protocol CheckAuthDataSourceable { + // MARK: Functionality + func check(onCompletion: @escaping CheckAuthResult) +} diff --git a/Ecomobility/Data/Protocols/DataSources/FetchAuthDataSourceable.swift b/Ecomobility/Data/Protocols/DataSources/FetchAuthDataSourceable.swift new file mode 100644 index 0000000..055d7b7 --- /dev/null +++ b/Ecomobility/Data/Protocols/DataSources/FetchAuthDataSourceable.swift @@ -0,0 +1,13 @@ +// +// FetchAuthDataSourceable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +protocol FetchAuthDataSourceable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) +} diff --git a/Ecomobility/Data/Protocols/DataSources/FetchUserAuthDataSourceable.swift b/Ecomobility/Data/Protocols/DataSources/FetchUserAuthDataSourceable.swift new file mode 100644 index 0000000..68b75b9 --- /dev/null +++ b/Ecomobility/Data/Protocols/DataSources/FetchUserAuthDataSourceable.swift @@ -0,0 +1,13 @@ +// +// FetchUserAuthDataSourceable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +protocol FetchUserAuthDataSourceable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchUserAuthResult) +} diff --git a/Ecomobility/Data/Protocols/DataSources/LogoutAuthDataSourceable.swift b/Ecomobility/Data/Protocols/DataSources/LogoutAuthDataSourceable.swift new file mode 100644 index 0000000..e3cded7 --- /dev/null +++ b/Ecomobility/Data/Protocols/DataSources/LogoutAuthDataSourceable.swift @@ -0,0 +1,13 @@ +// +// LogoutAuthDataSourceable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +protocol LogoutAuthDataSourceable { + // MARK: Functionality + func logout(onCompletion: @escaping LogoutAuthResult) +} diff --git a/Ecomobility/Data/Protocols/DataSources/StoreAuthDataSourceable.swift b/Ecomobility/Data/Protocols/DataSources/StoreAuthDataSourceable.swift new file mode 100644 index 0000000..cecfd48 --- /dev/null +++ b/Ecomobility/Data/Protocols/DataSources/StoreAuthDataSourceable.swift @@ -0,0 +1,13 @@ +// +// StoreAuthDataSourceable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +protocol StoreAuthDataSourceable { + // MARK: Functionality + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) +} diff --git a/Ecomobility/Data/Repositories/AuthRepository.swift b/Ecomobility/Data/Repositories/AuthRepository.swift new file mode 100644 index 0000000..b9450de --- /dev/null +++ b/Ecomobility/Data/Repositories/AuthRepository.swift @@ -0,0 +1,38 @@ +// +// AuthRepository.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct AuthRepository: AuthRepositable { + // MARK: Properties + var fetchingDataSource: FetchAuthDataSourceable + var logoutDataSource: LogoutAuthDataSourceable + var storeDataSource: StoreAuthDataSourceable + var checkDataSource: CheckAuthDataSourceable + var fetchUserDataSource: FetchUserAuthDataSourceable + + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) { + fetchingDataSource.fetch(onCompletion: onCompletion) + } + + func logout(onCompletion: @escaping LogoutAuthResult) { + logoutDataSource.logout(onCompletion: onCompletion) + } + + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) { + storeDataSource.store(credentials: credentials, onCompletion: onCompletion) + } + + func check(onCompletion: @escaping CheckAuthResult) { + checkDataSource.check(onCompletion: onCompletion) + } + + func fetch(onCompletion: @escaping FetchUserAuthResult) { + fetchUserDataSource.fetch(onCompletion: onCompletion) + } +} diff --git a/Ecomobility/Domain/Entities/AuthCredentials.swift b/Ecomobility/Domain/Entities/AuthCredentials.swift new file mode 100644 index 0000000..ee7978a --- /dev/null +++ b/Ecomobility/Domain/Entities/AuthCredentials.swift @@ -0,0 +1,18 @@ +// +// AuthCredentials.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct AuthCredentials { + // MARK: Properties + let accessToken: String? + let expiresIn: Date? + let idToken: String? + let refreshToken: String? + let scope: String? + let tokenType: String? +} diff --git a/Ecomobility/Domain/Entities/User.swift b/Ecomobility/Domain/Entities/User.swift new file mode 100644 index 0000000..3434b64 --- /dev/null +++ b/Ecomobility/Domain/Entities/User.swift @@ -0,0 +1,26 @@ +// +// User.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct User { + // MARK: Properties + var name: String? + var email: String? + var picture: String? + var nickname: String? + var familyName: String? + var givenName: String? + + // Computed properties + var pictureData: Data? { + guard let picture = self.picture else { return nil } + guard let url = URL(string: picture) else { return nil } + guard let data = try? Data(contentsOf: url) else { return nil } + return data + } +} diff --git a/Ecomobility/Domain/Protocols/Repositories/AuthRepositable.swift b/Ecomobility/Domain/Protocols/Repositories/AuthRepositable.swift new file mode 100644 index 0000000..9b4f834 --- /dev/null +++ b/Ecomobility/Domain/Protocols/Repositories/AuthRepositable.swift @@ -0,0 +1,17 @@ +// +// AuthRepositable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +protocol AuthRepositable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) + func logout(onCompletion: @escaping LogoutAuthResult) + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) + func check(onCompletion: @escaping CheckAuthResult) + func fetch(onCompletion: @escaping FetchUserAuthResult) +} diff --git a/Ecomobility/Domain/Protocols/UseCases/CheckAuthUseCaseable.swift b/Ecomobility/Domain/Protocols/UseCases/CheckAuthUseCaseable.swift new file mode 100644 index 0000000..da4256e --- /dev/null +++ b/Ecomobility/Domain/Protocols/UseCases/CheckAuthUseCaseable.swift @@ -0,0 +1,15 @@ +// +// CheckAuthUseCaseable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +typealias CheckAuthResult = (Result) -> Void + +protocol CheckAuthUseCaseable { + // MARK: Functionality + func check(onCompletion: @escaping CheckAuthResult) +} diff --git a/Ecomobility/Domain/Protocols/UseCases/FetchAuthUseCaseable.swift b/Ecomobility/Domain/Protocols/UseCases/FetchAuthUseCaseable.swift new file mode 100644 index 0000000..531c349 --- /dev/null +++ b/Ecomobility/Domain/Protocols/UseCases/FetchAuthUseCaseable.swift @@ -0,0 +1,15 @@ +// +// FetchAuthUseCaseable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +typealias FetchAuthResult = (Result) -> Void + +protocol FetchAuthUseCaseable { + // MARK: Functionality + func execute(onCompletion: @escaping FetchAuthResult) +} diff --git a/Ecomobility/Domain/Protocols/UseCases/FetchUserAuthUseCaseable.swift b/Ecomobility/Domain/Protocols/UseCases/FetchUserAuthUseCaseable.swift new file mode 100644 index 0000000..ba64be2 --- /dev/null +++ b/Ecomobility/Domain/Protocols/UseCases/FetchUserAuthUseCaseable.swift @@ -0,0 +1,20 @@ +// +// FetchUserAuthUseCaseable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +typealias FetchUserAuthResult = (Result) -> Void + +enum FetchUserError: Error { + case credentials + case unkown(Error) +} + +protocol FetchUserAuthUseCaseable { + // MARK: Functionality + func execute(onCompletion: @escaping FetchUserAuthResult) +} diff --git a/Ecomobility/Domain/Protocols/UseCases/LogoutAuthUseCaseable.swift b/Ecomobility/Domain/Protocols/UseCases/LogoutAuthUseCaseable.swift new file mode 100644 index 0000000..88052af --- /dev/null +++ b/Ecomobility/Domain/Protocols/UseCases/LogoutAuthUseCaseable.swift @@ -0,0 +1,15 @@ +// +// LogoutAuthUseCaseable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +typealias LogoutAuthResult = (Result) -> Void + +protocol LogoutAuthUseCaseable { + // MARK: Functionality + func execute(onCompletion: @escaping LogoutAuthResult) +} diff --git a/Ecomobility/Domain/Protocols/UseCases/StoreAuthUseCaseable.swift b/Ecomobility/Domain/Protocols/UseCases/StoreAuthUseCaseable.swift new file mode 100644 index 0000000..f195c79 --- /dev/null +++ b/Ecomobility/Domain/Protocols/UseCases/StoreAuthUseCaseable.swift @@ -0,0 +1,15 @@ +// +// StoreAuthUseCaseable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +typealias StoreAuthResult = (Result) -> Void + +protocol StoreAuthUseCaseable { + // MARK: Functionality + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) +} diff --git a/Ecomobility/Domain/UseCases/CheckAuthUseCase.swift b/Ecomobility/Domain/UseCases/CheckAuthUseCase.swift new file mode 100644 index 0000000..1a2bd92 --- /dev/null +++ b/Ecomobility/Domain/UseCases/CheckAuthUseCase.swift @@ -0,0 +1,18 @@ +// +// CheckAuthUseCase.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct CheckAuthUseCase: CheckAuthUseCaseable { + // MARK: Properties + var authRepository: AuthRepositable + + // MARK: Functionality + func check(onCompletion: @escaping CheckAuthResult) { + authRepository.check(onCompletion: onCompletion) + } +} diff --git a/Ecomobility/Domain/UseCases/FetchAuthUseCase.swift b/Ecomobility/Domain/UseCases/FetchAuthUseCase.swift new file mode 100644 index 0000000..adead3d --- /dev/null +++ b/Ecomobility/Domain/UseCases/FetchAuthUseCase.swift @@ -0,0 +1,18 @@ +// +// FetchAuthUseCase.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct FetchAuthUseCase: FetchAuthUseCaseable { + // MARK: Properties + var authRepository: AuthRepositable + + // MARK: Functionality + func execute(onCompletion: @escaping FetchAuthResult) { + authRepository.fetch(onCompletion: onCompletion) + } +} diff --git a/Ecomobility/Domain/UseCases/FetchUserAuthUseCase.swift b/Ecomobility/Domain/UseCases/FetchUserAuthUseCase.swift new file mode 100644 index 0000000..2d52523 --- /dev/null +++ b/Ecomobility/Domain/UseCases/FetchUserAuthUseCase.swift @@ -0,0 +1,18 @@ +// +// FetchUserAuthUseCase.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct FetchUserAuthUseCase: FetchUserAuthUseCaseable { + // MARK: Properties + var authRepository: AuthRepositable + + // MARK: Functionality + func execute(onCompletion: @escaping FetchUserAuthResult) { + authRepository.fetch(onCompletion: onCompletion) + } +} diff --git a/Ecomobility/Domain/UseCases/LogoutAuthUseCase.swift b/Ecomobility/Domain/UseCases/LogoutAuthUseCase.swift new file mode 100644 index 0000000..6eefe51 --- /dev/null +++ b/Ecomobility/Domain/UseCases/LogoutAuthUseCase.swift @@ -0,0 +1,18 @@ +// +// LogoutAuthUseCase.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct LogoutAuthUseCase: LogoutAuthUseCaseable { + // MARK: Properties + var authRepository: AuthRepositable + + // MARK: Functionality + func execute(onCompletion: @escaping LogoutAuthResult) { + authRepository.logout(onCompletion: onCompletion) + } +} diff --git a/Ecomobility/Domain/UseCases/StoreAuthUseCase.swift b/Ecomobility/Domain/UseCases/StoreAuthUseCase.swift new file mode 100644 index 0000000..af6611e --- /dev/null +++ b/Ecomobility/Domain/UseCases/StoreAuthUseCase.swift @@ -0,0 +1,18 @@ +// +// StoreAuthUseCase.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +struct StoreAuthUseCase: StoreAuthUseCaseable { + // MARK: Properties + var authRepository: AuthRepositable + + // MARK: Functionality + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) { + authRepository.store(credentials: credentials, onCompletion: onCompletion) + } +} diff --git a/Ecomobility/Presentation/Assets/Colors.xcassets/AccentColor.colorset/Contents.json b/Ecomobility/Presentation/Assets/Colors.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..f167e00 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Colors.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.612", + "green" : "0.588", + "red" : "0.265" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Colors.xcassets/Contents.json b/Ecomobility/Presentation/Assets/Colors.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/100.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..4a45182 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/100.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/1024.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..7ff3d23 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/114.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..7b6a273 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/114.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/120.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..5b134b5 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/120.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/144.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..ed167d6 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/144.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/152.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..6ef66c7 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/152.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/167.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..9484721 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/167.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/180.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..14888fd Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/180.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/20.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..edf452a Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/20.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/29.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..d9a7d8b Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/29.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/40.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..0f275b1 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/40.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/50.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..ff0b263 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/50.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/57.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..00a7ce4 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/57.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/58.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..4918110 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/58.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/60.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..9a57531 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/60.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/72.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..6607687 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/72.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/76.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..3491fc7 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/76.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/80.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..6dbe184 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/80.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/87.png b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..4de2c8b Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/87.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..4fdf882 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,158 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/Contents.json new file mode 100644 index 0000000..a498a59 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "apple_logo@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "apple_logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "apple_logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@1x.png b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@1x.png new file mode 100644 index 0000000..697e9ab Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@1x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@2x.png b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@2x.png new file mode 100644 index 0000000..acc183e Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@2x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@3x.png b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@3x.png new file mode 100644 index 0000000..bed72d1 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/apple_logo.imageset/apple_logo@3x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/company_logo.imageset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/company_logo.imageset/Contents.json new file mode 100644 index 0000000..e2d0a20 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/company_logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "company_logo.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/company_logo.imageset/company_logo.png b/Ecomobility/Presentation/Assets/Images.xcassets/company_logo.imageset/company_logo.png new file mode 100644 index 0000000..71e5c03 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/company_logo.imageset/company_logo.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/company_splash.imageset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/company_splash.imageset/Contents.json new file mode 100644 index 0000000..5d730ea --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/company_splash.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "company_splash.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/company_splash.imageset/company_splash.png b/Ecomobility/Presentation/Assets/Images.xcassets/company_splash.imageset/company_splash.png new file mode 100644 index 0000000..6cf9ef0 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/company_splash.imageset/company_splash.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/Contents.json new file mode 100644 index 0000000..75c37b0 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "facebook_logo@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "facebook_logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "facebook_logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@1x.png b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@1x.png new file mode 100644 index 0000000..300d094 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@1x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@2x.png b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@2x.png new file mode 100644 index 0000000..08de02b Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@2x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@3x.png b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@3x.png new file mode 100644 index 0000000..aff6193 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/facebook_logo.imageset/facebook_logo@3x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/Contents.json new file mode 100644 index 0000000..cd6ea53 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "google_logo@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "google_logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "google_logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@1x.png b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@1x.png new file mode 100644 index 0000000..e7b9523 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@1x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@2x.png b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@2x.png new file mode 100644 index 0000000..49581ea Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@2x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@3x.png b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@3x.png new file mode 100644 index 0000000..b97bf4b Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/google_logo.imageset/google_logo@3x.png differ diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/login_mosaic.imageset/Contents.json b/Ecomobility/Presentation/Assets/Images.xcassets/login_mosaic.imageset/Contents.json new file mode 100644 index 0000000..417c1f7 --- /dev/null +++ b/Ecomobility/Presentation/Assets/Images.xcassets/login_mosaic.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "login_mosaic.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Presentation/Assets/Images.xcassets/login_mosaic.imageset/login_mosaic.png b/Ecomobility/Presentation/Assets/Images.xcassets/login_mosaic.imageset/login_mosaic.png new file mode 100644 index 0000000..ca1ee17 Binary files /dev/null and b/Ecomobility/Presentation/Assets/Images.xcassets/login_mosaic.imageset/login_mosaic.png differ diff --git a/Ecomobility/Presentation/Scenes/Base/ViewModels/BaseViewModel.swift b/Ecomobility/Presentation/Scenes/Base/ViewModels/BaseViewModel.swift new file mode 100644 index 0000000..b7a3cd0 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Base/ViewModels/BaseViewModel.swift @@ -0,0 +1,30 @@ +// +// BaseViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +final class BaseViewModel: ObservableObject { + // MARK: Properties + var authUseCase: CheckAuthUseCaseable + + // MARK: Constructor + init(authUseCase: CheckAuthUseCaseable) { + self.authUseCase = authUseCase + } + + // MARK: Lifecycle + func onAppear(onCompletion: @escaping (Scenes) -> Void) { + authUseCase.check { result in + switch result { + case .success: + onCompletion(.base) + case .failure: + onCompletion(.login) + } + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Base/Views/BaseView.swift b/Ecomobility/Presentation/Scenes/Base/Views/BaseView.swift new file mode 100644 index 0000000..a917a3b --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Base/Views/BaseView.swift @@ -0,0 +1,57 @@ +// +// BaseView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import SwiftUI + +struct BaseView: View { + // MARK: Properties + @EnvironmentObject var viewRouter: ViewRouter + @StateObject var viewModel: BaseViewModel + + // MARK: View + var body: some View { + VStack { + switch viewRouter.currentScene { + case .login: + loginFlow + case .base: + baseFlow + .transition(.scale) + } + }.onAppear { + viewModel.onAppear { scene in + viewRouter.currentScene = scene + } + } + } +} + +// MARK: Flows +extension BaseView { + // MARK: Views + var loginFlow: some View { + LoginView(viewModel: Injector.shared.resolve()) + } + + var baseFlow: some View { + TabView { + MapView(viewModel: Injector.shared.resolve()) + .tag(0) + .tabItem { + Text("Map") + Image(systemName: "map.fill") + } + + ProfileView(viewModel: Injector.shared.resolve()) + .tag(1) + .tabItem { + Text("Profile") + Image(systemName: "person.crop.circle") + } + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/ViewModels/LoginViewModel.swift b/Ecomobility/Presentation/Scenes/Login/ViewModels/LoginViewModel.swift new file mode 100644 index 0000000..9db6666 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/ViewModels/LoginViewModel.swift @@ -0,0 +1,25 @@ +// +// LoginViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import Foundation + +final class LoginViewModel: ObservableObject { + // MARK: Properties + @Published var showingSignIn = false + @Published var showingSignUp = false +} + +// MARK: Events +extension LoginViewModel { + func signInTapped() { + showingSignIn.toggle() + } + + func signUpTapped() { + showingSignUp.toggle() + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/ViewModels/Protocols/SocialAuthenticable.swift b/Ecomobility/Presentation/Scenes/Login/ViewModels/Protocols/SocialAuthenticable.swift new file mode 100644 index 0000000..d5651f1 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/ViewModels/Protocols/SocialAuthenticable.swift @@ -0,0 +1,19 @@ +// +// SocialAuthenticable.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 6/2/21. +// + +import Foundation +import AuthenticationServices + +protocol SocialAuthenticable { + // MARK: Functionality + func appleOnRequestAuthentication(request: ASAuthorizationAppleIDRequest) + func appleOnResultAuthentication(result: Result) + func facebookOnRequestAuthentication() + func facebookOnResultAuthentication() + func googleOnRequestAuthentication() + func googleOnResultAuthentication() +} diff --git a/Ecomobility/Presentation/Scenes/Login/ViewModels/SignInViewModel+Social.swift b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignInViewModel+Social.swift new file mode 100644 index 0000000..06bcf40 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignInViewModel+Social.swift @@ -0,0 +1,41 @@ +// +// SignInViewModel+Social.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import AuthenticationServices + +// MARK: Social authentication +extension SignInViewModel: SocialAuthenticable { + func appleOnRequestAuthentication(request: ASAuthorizationAppleIDRequest) { + request.requestedScopes = [.fullName, .email] + } + + func appleOnResultAuthentication(result: Result) { + switch result { + case .success(let authoritation): + NSLog(authoritation.description) + case .failure(let error): + NSLog(error.localizedDescription) + } + } + + func facebookOnRequestAuthentication() { + NSLog("Facebook login request") + } + + func facebookOnResultAuthentication() { + NSLog("Facebook login result") + } + + func googleOnRequestAuthentication() { + NSLog("Google login request") + } + + func googleOnResultAuthentication() { + NSLog("Google login result") + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/ViewModels/SignInViewModel.swift b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignInViewModel.swift new file mode 100644 index 0000000..856f7b4 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignInViewModel.swift @@ -0,0 +1,55 @@ +// +// SignInViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import Foundation + +final class SignInViewModel: ObservableObject { + // MARK: Properties + @Published var email = "" + @Published var password = "" + + // Use cases + var authUseCase: FetchAuthUseCaseable + var storeSessionUseCase: StoreAuthUseCaseable + + // MARK: Constructor + init(authUseCase: FetchAuthUseCaseable, storeSession: StoreAuthUseCaseable) { + self.authUseCase = authUseCase + self.storeSessionUseCase = storeSession + } +} + +// MARK: Functionality +extension SignInViewModel { + func storeSession(credentials: AuthCredentials, onCompletion: @escaping () -> Void) { + storeSessionUseCase.store(credentials: credentials) { result in + switch result { + case .success: + onCompletion() + case .failure(let error): + NSLog(error.localizedDescription) + } + } + } +} + +// MARK: Events +extension SignInViewModel { + func signInTapped(onCompletion: @escaping (Scenes) -> Void) { + authUseCase.execute { result in + switch result { + case .success(let credentials): + self.storeSession(credentials: credentials) { + onCompletion(.base) + } + case .failure(let error): + NSLog(error.localizedDescription) + onCompletion(.login) + } + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/ViewModels/SignUpViewModel+Social.swift b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignUpViewModel+Social.swift new file mode 100644 index 0000000..ec40833 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignUpViewModel+Social.swift @@ -0,0 +1,41 @@ +// +// SignUpViewModel+Social.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation +import AuthenticationServices + +// MARK: Social authentication +extension SignUpViewModel: SocialAuthenticable { + func appleOnRequestAuthentication(request: ASAuthorizationAppleIDRequest) { + request.requestedScopes = [.fullName, .email] + } + + func appleOnResultAuthentication(result: Result) { + switch result { + case .success(let authoritation): + NSLog(authoritation.description) + case .failure(let error): + NSLog(error.localizedDescription) + } + } + + func facebookOnRequestAuthentication() { + NSLog("Facebook login request") + } + + func facebookOnResultAuthentication() { + NSLog("Facebook login result") + } + + func googleOnRequestAuthentication() { + NSLog("Google login request") + } + + func googleOnResultAuthentication() { + NSLog("Google login result") + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/ViewModels/SignUpViewModel.swift b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignUpViewModel.swift new file mode 100644 index 0000000..b22b6be --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/ViewModels/SignUpViewModel.swift @@ -0,0 +1,56 @@ +// +// SignUpViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import Foundation + +final class SignUpViewModel: ObservableObject { + // MARK: Properties + @Published var name = "" + @Published var email = "" + @Published var password = "" + + // Use cases + var authUseCase: FetchAuthUseCaseable + var storeSession: StoreAuthUseCaseable + + // MARK: Constructor + init(authUseCase: FetchAuthUseCaseable, storeSession: StoreAuthUseCaseable) { + self.authUseCase = authUseCase + self.storeSession = storeSession + } +} + +// MARK: Functionality +extension SignUpViewModel { + func storeSession(credentials: AuthCredentials, stored: @escaping () -> Void) { + storeSession.store(credentials: credentials) { result in + switch result { + case .success: + stored() + case .failure(let error): + NSLog(error.localizedDescription) + } + } + } +} + +// MARK: Events +extension SignUpViewModel { + func signUpTapped(onCompletion: @escaping (Scenes) -> Void) { + authUseCase.execute { result in + switch result { + case .success(let credentials): + self.storeSession(credentials: credentials) { + onCompletion(.base) + } + case .failure(let error): + NSLog(error.localizedDescription) + onCompletion(.login) + } + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/LoginButton.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/LoginButton.swift new file mode 100644 index 0000000..7b460fa --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/LoginButton.swift @@ -0,0 +1,37 @@ +// +// LoginButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import SwiftUI + +struct LoginButton: View { + // MARK: Properties + var title: String + var action: () -> Void + + // MARK: View + var body: some View { + button + } +} + +extension LoginButton { + // MARK: Components + private var button: some View { + Button(action: { + action() + }, label: { + Text(title) + .fontWeight(.bold) + .foregroundColor(.white) + .frame(minWidth: 0, maxWidth: .infinity) + .padding() + }) + .background(Color.accentColor) + .clipShape(Capsule()) + .padding(.horizontal, 30) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/LoginSignUpButton.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/LoginSignUpButton.swift new file mode 100644 index 0000000..ce6fbdb --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/LoginSignUpButton.swift @@ -0,0 +1,30 @@ +// +// LoginSignUpButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 6/2/21. +// + +import SwiftUI + +struct LoginSignUpButton: View { + // MARK: Properties + var title: String + var action: () -> Void + + // MARK: View + var body: some View { + button + } +} + +extension LoginSignUpButton { + // MARK: Components + private var button: some View { + Button(title) { + action() + } + .font(.footnote) + .padding() + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialAppleButton.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialAppleButton.swift new file mode 100644 index 0000000..fab6f8f --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialAppleButton.swift @@ -0,0 +1,40 @@ +// +// SocialAppleButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 3/2/21. +// + +import SwiftUI +import AuthenticationServices + +struct SocialAppleButton: View { + // MARK: Properties + var onRequest: (ASAuthorizationAppleIDRequest) -> Void + var onResult: (Result) -> Void + + // MARK: View + var body: some View { + button + } +} + +extension SocialAppleButton { + // MARK: Components + private var button: some View { + Button(action: { + }, label: { + Image(uiImage: UIImage(named: "apple_logo")!) + .renderingMode(.template) + .foregroundColor(.black) + .aspectRatio(contentMode: .fill) + .padding(15) + }) + .overlay(border) + } + + private var border: some View { + RoundedRectangle(cornerRadius: 5) + .stroke(Color.black, lineWidth: 1) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialFacebookButton.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialFacebookButton.swift new file mode 100644 index 0000000..19834d7 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialFacebookButton.swift @@ -0,0 +1,41 @@ +// +// SocialFacebookButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 6/2/21. +// + +import SwiftUI + +struct SocialFacebookButton: View { + // MARK: Properties + var onRequest: () -> Void + var onResult: () -> Void + var title: String + + // MARK: View + var body: some View { + button + } +} + +extension SocialFacebookButton { + // MARK: Components + private var button: some View { + Button(action: { + onRequest() + }, label: { + Image(uiImage: UIImage(named: "facebook_logo")!) + .renderingMode(.template) + .foregroundColor(.blue) + .aspectRatio(contentMode: .fill) + .padding(15) + }) + .overlay(border) + } + + private var border: some View { + RoundedRectangle(cornerRadius: 5) + .stroke(Color.blue, lineWidth: 1) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialGoogleButton.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialGoogleButton.swift new file mode 100644 index 0000000..721bc5b --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Buttons/SocialGoogleButton.swift @@ -0,0 +1,45 @@ +// +// SocialGoogleButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 6/2/21. +// + +import SwiftUI + +struct SocialGoogleButton: View { + // MARK: Properties + var onRequest: () -> Void + var onResult: () -> Void + var title: String + + let gradient = LinearGradient( + gradient: Gradient(colors: [.red, .yellow, .green, .blue]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + + // MARK: View + var body: some View { + button + } +} + +extension SocialGoogleButton { + // MARK: Components + private var button: some View { + Button(action: { + onRequest() + }, label: { + Image(uiImage: UIImage(named: "google_logo")!) + .aspectRatio(contentMode: .fill) + .padding(15) + }) + .overlay(border) + } + + private var border: some View { + RoundedRectangle(cornerRadius: 5) + .stroke(gradient, lineWidth: 1) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Fields/LoginSecureField.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Fields/LoginSecureField.swift new file mode 100644 index 0000000..55d97f6 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Fields/LoginSecureField.swift @@ -0,0 +1,38 @@ +// +// LoginSecureField.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import SwiftUI + +struct LoginSecureField: View { + // MARK: Properties + var placeholder: String + @Binding var input: String + + // MARK: View + var body: some View { + field + } +} + +// MARK: Border +extension LoginSecureField { + // MARK: Components + private var field: some View { + SecureField(placeholder, text: $input) + .textContentType(.password) + .foregroundColor(.accentColor) + .frame(minWidth: 0, maxWidth: .infinity) + .padding() + .overlay(border) + .padding(.horizontal, 30) + } + + private var border: some View { + RoundedRectangle(cornerRadius: 5) + .stroke(Color.accentColor, lineWidth: 1) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Components/Fields/LoginTextField.swift b/Ecomobility/Presentation/Scenes/Login/Views/Components/Fields/LoginTextField.swift new file mode 100644 index 0000000..13bdd87 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Components/Fields/LoginTextField.swift @@ -0,0 +1,37 @@ +// +// LoginTextField.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import SwiftUI + +struct LoginTextField: View { + // MARK: Properties + var placeholder: String + @Binding var input: String + + // MARK: View + var body: some View { + button + } +} + +extension LoginTextField { + // MARK: Components + private var button: some View { + TextField(placeholder, text: $input) + .disableAutocorrection(true) + .foregroundColor(.accentColor) + .frame(minWidth: 0, maxWidth: .infinity) + .padding() + .overlay(border) + .padding(.horizontal, 30) + } + + private var border: some View { + RoundedRectangle(cornerRadius: 5) + .stroke(Color.accentColor, lineWidth: 1) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Screens/LoginView.swift b/Ecomobility/Presentation/Scenes/Login/Views/Screens/LoginView.swift new file mode 100644 index 0000000..41b44f7 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Screens/LoginView.swift @@ -0,0 +1,53 @@ +// +// LoginView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import SwiftUI + +struct LoginView: View { + // MARK: Properties + @StateObject var viewModel: LoginViewModel + + // MARK: View + var body: some View { + VStack { + logoSection + Spacer() + buttonsSection + } + } +} + +// MARK: Sections +extension LoginView { + // MARK: Views + var buttonsSection: some View { + VStack(alignment: .center) { + LoginButton(title: "SIGN IN") { + viewModel.signInTapped() + }.sheet(isPresented: $viewModel.showingSignIn) { + SignInView(viewModel: Injector.shared.resolve()) + } + + LoginSignUpButton(title: "Don't have an account? Sign up") { + viewModel.signUpTapped() + }.sheet(isPresented: $viewModel.showingSignUp) { + SignUpView(viewModel: Injector.shared.resolve()) + } + } + } + + // MARK: Components + var logoSection: some View { + GeometryReader { geometry in + Image("login_mosaic") + .resizable() + .scaledToFill() + .frame(width: geometry.size.width, height: geometry.size.height * 0.6) + } + .edgesIgnoringSafeArea(.top) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Screens/SignInView.swift b/Ecomobility/Presentation/Scenes/Login/Views/Screens/SignInView.swift new file mode 100644 index 0000000..9adf90f --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Screens/SignInView.swift @@ -0,0 +1,88 @@ +// +// SignInView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import SwiftUI + +struct SignInView: View { + // MARK: Properties + @StateObject var viewModel: SignInViewModel + @EnvironmentObject var viewRouter: ViewRouter + + // MARK: View + var body: some View { + VStack { + Spacer() + logoSection + fieldsSection + Spacer() + loginButtonSection + textSeparatorSection + socialButtonsSection + } + } +} + +// MARK: Sections +extension SignInView { + // MARK: Views + var fieldsSection: some View { + VStack(alignment: .center, spacing: 15) { + LoginTextField(placeholder: "Email", input: $viewModel.email) + .textContentType(.emailAddress) + .autocapitalization(.none) + + LoginSecureField(placeholder: "Password", input: $viewModel.password) + } + .padding(.top, 60) + } + + var socialButtonsSection: some View { + HStack(spacing: 15) { + SocialAppleButton( + onRequest: viewModel.appleOnRequestAuthentication, + onResult: viewModel.appleOnResultAuthentication + ) + + SocialFacebookButton( + onRequest: viewModel.facebookOnRequestAuthentication, + onResult: viewModel.facebookOnResultAuthentication, + title: "Sign in with Facebook" + ) + + SocialGoogleButton( + onRequest: viewModel.facebookOnRequestAuthentication, + onResult: viewModel.facebookOnResultAuthentication, + title: "Sign in with Google" + ) + } + .padding(.bottom, 5) + } + + // MARK: Components + var logoSection: some View { + Image("company_logo") + .resizable() + .scaledToFill() + .frame(width: 350, height: 50) + .padding() + } + + var loginButtonSection: some View { + LoginButton(title: "SIGN IN") { + viewModel.signInTapped { scene in + viewRouter.currentScene = scene + } + } + } + + var textSeparatorSection: some View { + Text("- OR -") + .padding(3) + .font(.caption) + .foregroundColor(.accentColor) + } +} diff --git a/Ecomobility/Presentation/Scenes/Login/Views/Screens/SignUpView.swift b/Ecomobility/Presentation/Scenes/Login/Views/Screens/SignUpView.swift new file mode 100644 index 0000000..688202f --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Login/Views/Screens/SignUpView.swift @@ -0,0 +1,91 @@ +// +// SignUpView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 4/2/21. +// + +import SwiftUI + +struct SignUpView: View { + // MARK: Properties + @StateObject var viewModel: SignUpViewModel + @EnvironmentObject var viewRouter: ViewRouter + + // MARK: View + var body: some View { + VStack { + Spacer() + logoSection + fieldsSection + Spacer() + loginButtonSection + textSeparatorSection + socialButtonsSection + } + } +} + +// MARK: Sections +extension SignUpView { + // MARK: Views + var fieldsSection: some View { + VStack(alignment: .center, spacing: 15) { + LoginTextField(placeholder: "Full name", input: $viewModel.name) + .textContentType(.name) + + LoginTextField(placeholder: "Email", input: $viewModel.email) + .textContentType(.emailAddress) + .autocapitalization(.none) + + LoginSecureField(placeholder: "Password", input: $viewModel.password) + } + .padding(.top, 60) + } + + var socialButtonsSection: some View { + HStack(spacing: 15) { + SocialAppleButton( + onRequest: viewModel.appleOnRequestAuthentication, + onResult: viewModel.appleOnResultAuthentication + ) + + SocialFacebookButton( + onRequest: viewModel.facebookOnRequestAuthentication, + onResult: viewModel.facebookOnResultAuthentication, + title: "Sign in with Facebook" + ) + + SocialGoogleButton( + onRequest: viewModel.facebookOnRequestAuthentication, + onResult: viewModel.facebookOnResultAuthentication, + title: "Sign in with Google" + ) + } + .padding(.bottom, 5) + } + + // MARK: Components + var logoSection: some View { + Image("company_logo") + .resizable() + .scaledToFill() + .frame(width: 350, height: 50) + .padding() + } + + var loginButtonSection: some View { + LoginButton(title: "SIGN UP") { + viewModel.signUpTapped { scene in + viewRouter.currentScene = scene + } + } + } + + var textSeparatorSection: some View { + Text("- OR -") + .padding(3) + .font(.caption) + .foregroundColor(.accentColor) + } +} diff --git a/Ecomobility/Presentation/Scenes/Map/ViewModels/MapViewModel.swift b/Ecomobility/Presentation/Scenes/Map/ViewModels/MapViewModel.swift new file mode 100644 index 0000000..1d39008 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Map/ViewModels/MapViewModel.swift @@ -0,0 +1,30 @@ +// +// MapViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 7/2/21. +// + +import Foundation +import MapKit + +final class MapViewModel: ObservableObject { + // MARK: Properties + @Published var region = MKCoordinateRegion( + center: CLLocationCoordinate2D( + latitude: 25.7617, + longitude: 80.1918 + ), + span: MKCoordinateSpan( + latitudeDelta: 10, + longitudeDelta: 10 + ) + ) +} + +// MARK: Events +extension MapViewModel { + func scanTapped() { + NSLog("Scaning...") + } +} diff --git a/Ecomobility/Presentation/Scenes/Map/Views/Components/MapScanButton.swift b/Ecomobility/Presentation/Scenes/Map/Views/Components/MapScanButton.swift new file mode 100644 index 0000000..caf0b86 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Map/Views/Components/MapScanButton.swift @@ -0,0 +1,51 @@ +// +// MapScanButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct MapScanButton: View { + // MARK: Properties + var action: () -> Void + + // MARK: View + var body: some View { + button + } +} + +extension MapScanButton { + // MARK: Components + private var button: some View { + Button(action: { + action() + }, label: { + content + }) + .background(Color.accentColor) + .clipShape(Capsule()) + .padding() + } + + private var content: some View { + HStack { + image + title + } + .padding() + } + + private var image: some View { + Image(systemName: "magnifyingglass") + .foregroundColor(.white) + } + + private var title: some View { + Text("Scan to unlock") + .fontWeight(.bold) + .foregroundColor(.white) + } +} diff --git a/Ecomobility/Presentation/Scenes/Map/Views/Screens/MapView.swift b/Ecomobility/Presentation/Scenes/Map/Views/Screens/MapView.swift new file mode 100644 index 0000000..c614d86 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Map/Views/Screens/MapView.swift @@ -0,0 +1,55 @@ +// +// MapView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 7/2/21. +// + +import SwiftUI +import MapKit + +struct MapView: View { + // MARK: Properties + @StateObject var viewModel: MapViewModel + + // MARK: Views + var body: some View { + NavigationView { + ZStack(alignment: .bottom) { + map + button + } + + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + companyLogo + } + } + } + } +} + +// MARK: Sections +extension MapView { + // MARK: Components + var map: some View { + Map(coordinateRegion: $viewModel.region, + showsUserLocation: true, + userTrackingMode: .constant(.follow)) + } + + var button: some View { + MapScanButton { + viewModel.scanTapped() + } + } + + var companyLogo: some View { + Image("company_logo") + .resizable() + .frame(width: 200, height: 200, alignment: .center) + .scaledToFit() + .clipped() + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/ViewModels/ProfileViewModel.swift b/Ecomobility/Presentation/Scenes/Profile/ViewModels/ProfileViewModel.swift new file mode 100644 index 0000000..22b9290 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/ViewModels/ProfileViewModel.swift @@ -0,0 +1,100 @@ +// +// ProfileViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 8/2/21. +// + +import Foundation + +final class ProfileViewModel: ObservableObject { + // MARK: Properties + @Published var user = User() + @Published var amount = "" + @Published var rides = "" + @Published var isPushNotificated = true + @Published var isNewsNotificated = false + + // Use cases + var logoutAuthUseCase: LogoutAuthUseCaseable + var fetchUserAuthUseCase: FetchUserAuthUseCaseable + + // MARK: Constructor + init(logoutAuthUseCase: LogoutAuthUseCaseable, + fetchUserAuthUseCase: FetchUserAuthUseCaseable) { + + self.logoutAuthUseCase = logoutAuthUseCase + self.fetchUserAuthUseCase = fetchUserAuthUseCase + } + + // MARK: Lifecycle + func onAppear() { + fetchUserAuth() + updateAmount(amount: 10.23) + updateRides(rides: 12) + } +} + +// MARK: Functionality +extension ProfileViewModel { + func fetchUserAuth() { + fetchUserAuthUseCase.execute { result in + switch result { + case .success(let user): + DispatchQueue.main.async { + self.user = user + } + case .failure(let error): + self.checkFetchUserError(error) + } + } + } + + func checkFetchUserError(_ error: FetchUserError) { + switch error { + case .credentials: + NSLog("Wrong credentials") + case .unkown: + NSLog("Unkown error") + } + } + + func updateAmount(amount: NSNumber) { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.locale = Locale(identifier: "en_GB") + + guard let currencyAmount = formatter.string(from: amount) else { return } + self.amount = currencyAmount + } + + func updateRides(rides: Int) { + self.rides = "\(rides) Rides" + } +} + +// MARK: Events +extension ProfileViewModel { + func settingsTapped() { + NSLog("Settings tapped") + } + + func ridesTapped() { + NSLog("Rides tapped") + } + + func amountTapped() { + NSLog("Amount tapped") + } + + func logoutTapped(onCompletion: @escaping (Scenes) -> Void) { + logoutAuthUseCase.execute { result in + switch result { + case .success: + onCompletion(.login) + case .failure: + onCompletion(.base) + } + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/ViewModels/UserDetailsViewModel.swift b/Ecomobility/Presentation/Scenes/Profile/ViewModels/UserDetailsViewModel.swift new file mode 100644 index 0000000..01f3028 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/ViewModels/UserDetailsViewModel.swift @@ -0,0 +1,64 @@ +// +// UserDetailsViewModel.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import Foundation + +final class UserDetailsViewModel: ObservableObject { + // MARK: Properties + @Published var user = User() + @Published var completionProgress: Float = 0 + + // Use cases + var fetchUserAuthUseCase: FetchUserAuthUseCaseable + + // MARK: Constructor + init(fetchUserAuthUseCase: FetchUserAuthUseCaseable) { + self.fetchUserAuthUseCase = fetchUserAuthUseCase + } + + // MARK: Lifecycle + func onAppear() { + fetchUserAuth() + updateCompletionProgress() + } +} + +// MARK: Functionality +extension UserDetailsViewModel { + func fetchUserAuth() { + fetchUserAuthUseCase.execute { result in + switch result { + case .success(let user): + DispatchQueue.main.async { + self.user = user + } + case .failure(let error): + self.checkFetchUserError(error) + } + } + } + + func checkFetchUserError(_ error: FetchUserError) { + switch error { + case .credentials: + NSLog("Wrong credentials") + case .unkown(let error): + NSLog(error.localizedDescription) + } + } + + func updateCompletionProgress() { + completionProgress = 70 + } +} + +// MARK: Events +extension UserDetailsViewModel { + func completeProfileTapped() { + NSLog("Complete profile") + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Buttons/ProfileLogoutButton.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Buttons/ProfileLogoutButton.swift new file mode 100644 index 0000000..ddf8247 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Buttons/ProfileLogoutButton.swift @@ -0,0 +1,35 @@ +// +// ProfileLogoutButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import SwiftUI + +struct ProfileLogoutButton: View { + // MARK: Properties + var action: () -> Void + + // MARK: View + var body: some View { + button + } +} + +extension ProfileLogoutButton { + // MARK: Components + private var button: some View { + Button(action: { + action() + }, label: { + title + }) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .center) + } + + private var title: some View { + Text("Log out") + .foregroundColor(.red) + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Buttons/ProfileSettingsButton.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Buttons/ProfileSettingsButton.swift new file mode 100644 index 0000000..ba0e343 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Buttons/ProfileSettingsButton.swift @@ -0,0 +1,33 @@ +// +// ProfileSettingsButton.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import SwiftUI + +struct ProfileSettingsButton: View { + // MARK: Properties + var action: () -> Void + + // MARK: View + var body: some View { + button + } +} + +extension ProfileSettingsButton { + // MARK: Components + private var button: some View { + Button(action: { + action() + }, label: { + image + }) + } + + private var image: some View { + Image(systemName: "gear") + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileActionView.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileActionView.swift new file mode 100644 index 0000000..056f9c2 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileActionView.swift @@ -0,0 +1,47 @@ +// +// ProfileActionView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct ProfileActionView: View { + // MARK: Properties + var title: String + var image: Image + var imageButton: Image + var action: () -> Void + + // MARK: View + var body: some View { + HStack { + icon + titleView + Spacer() + button + } + } +} + +extension ProfileActionView { + // MARK: Components + private var icon: some View { + image + .foregroundColor(.accentColor) + } + + private var titleView: some View { + Text(title) + .foregroundColor(Color(.darkGray)) + } + + private var button: some View { + Button(action: { + action() + }, label: { + imageButton + }) + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileDetailsRow.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileDetailsRow.swift new file mode 100644 index 0000000..e7d7f6c --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileDetailsRow.swift @@ -0,0 +1,56 @@ +// +// ProfileDetailsRow.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct ProfileDetailsRow: View { + // MARK: Properties + var title: String + var value: String + + // MARK: View + var body: some View { + HStack { + textSection + Spacer() + button + } + .padding(10) + .frame(minWidth: 0, maxWidth: .infinity) + } +} + +// MARK: Sections +extension ProfileDetailsRow { + // MARK: Views + private var textSection: some View { + VStack(alignment: .leading, spacing: 10) { + titleView + valueView + } + } + + // MARK: Components + private var titleView: some View { + Text(title) + .foregroundColor(.gray) + .font(.caption) + } + + private var valueView: some View { + Text(value) + .foregroundColor(Color(.darkGray)) + .font(.body) + } + + private var button: some View { + Button(action: { + }, label: { + Image(systemName: "pencil") + }) + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileDetailsUser.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileDetailsUser.swift new file mode 100644 index 0000000..45377bd --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileDetailsUser.swift @@ -0,0 +1,64 @@ +// +// ProfileDetailsUser.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct ProfileDetailsUser: View { + // MARK: Properties + var name: String + var picture: Data? + var radius: CGFloat = 20 + var progression: Float + var titleButton: String + var action: () -> Void + + // MARK: Views + var body: some View { + VStack { + userView + progressView + } + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: radius)) + .shadow(color: Color.gray.opacity(0.3), radius: 18, y: 6) + .padding() + } +} + +// MARK: Sections +extension ProfileDetailsUser { + // MARK: Views + private var userView: some View { + VStack { + userImage + userName + } + .frame(minWidth: 0, maxWidth: .infinity) + .padding() + .background(Color.accentColor) + .clipShape(RoundedRectangle(cornerRadius: radius)) + } + + // MARK: Components + private var userImage: some View { + ProfileUserImage(picture: picture, color: .white) + .frame(width: 80, height: 80) + .padding(10) + } + + private var userName: some View { + Text(name) + .foregroundColor(.white) + .font(.headline) + } + + private var progressView: some View { + ProfileProgressView(progression: progression, title: titleButton) { + action() + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileProgressView.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileProgressView.swift new file mode 100644 index 0000000..5a64127 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileProgressView.swift @@ -0,0 +1,59 @@ +// +// ProfileProgressView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct ProfileProgressView: View { + // MARK: Properties + var progression: Float + var title: String + var action: () -> Void + + // MARK: View + var body: some View { + VStack { + progressNumber + progressView + button + } + .padding(.vertical) + } +} + +extension ProfileProgressView { + // MARK: Components + private var progressNumber: some View { + Text("\(String(format: "%.f", progression))%") + .foregroundColor(Color.accentColor) + .font(.footnote) + .bold() + } + + private var progressView: some View { + ProgressView(value: progression, total: 100) + .padding(.horizontal) + .padding(.bottom) + } + + private var button: some View { + Button(action: { + action() + }, label: { + titleView + }) + .padding(.vertical, 10) + .padding(.horizontal, 20) + .background(Color.white) + .overlay(Capsule().stroke(Color.accentColor)) + } + + private var titleView: some View { + Text(title) + .foregroundColor(.accentColor) + .font(.footnote) + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileSwitchView.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileSwitchView.swift new file mode 100644 index 0000000..d61dc3d --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileSwitchView.swift @@ -0,0 +1,37 @@ +// +// ProfileSwitchView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct ProfileSwitchView: View { + // MARK: Properties + var title: String + var image: Image + @Binding var isOn: Bool + + // MARK: View + var body: some View { + HStack { + icon + toggle + } + } +} + +extension ProfileSwitchView { + // MARK: Components + private var icon: some View { + image + .foregroundColor(.accentColor) + } + + private var toggle: some View { + Toggle(title, isOn: $isOn) + .foregroundColor(Color(.darkGray)) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileUser.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileUser.swift new file mode 100644 index 0000000..d5856a3 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileUser.swift @@ -0,0 +1,53 @@ +// +// ProfileUser.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import SwiftUI + +struct ProfileUser: View { + // MARK: Properties + var name: String + var email: String + var picture: Data? + + // MARK: View + var body: some View { + HStack(spacing: 20) { + userImage + profileInfoSection + Spacer() + arrow + } + .padding(.vertical, 5) + } +} + +extension ProfileUser { + // MARK: Views + private var profileInfoSection: some View { + VStack(alignment: .leading, spacing: 5) { + Text(name) + .font(.headline) + .foregroundColor(Color(.darkGray)) + + Text(email) + .font(.footnote) + .foregroundColor(Color(.lightGray)) + } + } + + // MARK: Components + private var userImage: some View { + ProfileUserImage(picture: picture, color: Color(.lightGray)) + .frame(width: 60, height: 60) + } + + private var arrow: some View { + Image(systemName: "chevron.forward") + .renderingMode(.template) + .foregroundColor(Color(.lightGray)) + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileUserImage.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileUserImage.swift new file mode 100644 index 0000000..28b2dd7 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Components/Views/ProfileUserImage.swift @@ -0,0 +1,34 @@ +// +// ProfileUserImage.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 11/2/21. +// + +import SwiftUI + +struct ProfileUserImage: View { + // MARK: Properties + var picture: Data? + var color: Color + + // MARK: Views + var body: some View { + userImage + .resizable() + .scaledToFill() + .foregroundColor(color) + .clipShape(Circle()) + } +} + +extension ProfileUserImage { + // MARK: Components + private var userImage: Image { + if let data = picture, let image = UIImage(data: data) { + return Image(uiImage: image) + } + + return Image(systemName: "person.crop.circle.fill") + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileDetailsView.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileDetailsView.swift new file mode 100644 index 0000000..77d964a --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileDetailsView.swift @@ -0,0 +1,57 @@ +// +// ProfileDetailsView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import SwiftUI + +struct ProfileDetailsView: View { + // MARK: Properties + @StateObject var viewModel: UserDetailsViewModel + + // MARK: View + var body: some View { + VStack { + userImageSection + userInfoSection + } + .frame(minHeight: 0, maxHeight: .infinity, alignment: .top) + .onAppear { + viewModel.onAppear() + } + } +} + +// MARK: Sections +extension ProfileDetailsView { + // MARK: Views + var userImageSection: some View { + ProfileDetailsUser( + name: viewModel.user.name ?? "Name", + picture: viewModel.user.pictureData, + progression: viewModel.completionProgress, + titleButton: "Complete your profile" + ) { + viewModel.completeProfileTapped() + } + } + + var userInfoSection: some View { + VStack { + ProfileDetailsRow(title: "Given name", value: viewModel.user.givenName ?? "Given name") + Divider() + ProfileDetailsRow(title: "Family name", value: viewModel.user.familyName ?? "Family name") + Divider() + ProfileDetailsRow(title: "Nick name", value: viewModel.user.nickname ?? "Nick name") + Divider() + ProfileDetailsRow(title: "Email", value: viewModel.user.email ?? "Email") + } + .padding(10) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .shadow(color: Color.gray.opacity(0.3), radius: 18, y: 6) + .padding() + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileView+Sections.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileView+Sections.swift new file mode 100644 index 0000000..6e9e925 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileView+Sections.swift @@ -0,0 +1,75 @@ +// +// ProfileView+Sections.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 10/2/21. +// + +import SwiftUI + +// MARK: Sections +extension ProfileView { + var userSection: some View { + Section(header: Text("User")) { + ProfileUser( + name: viewModel.user.name ?? "Name", + email: viewModel.user.email ?? "Email", + picture: viewModel.user.pictureData + ) + .sheet(isPresented: $profileSheet, content: { + ProfileDetailsView(viewModel: Injector.shared.resolve()) + }) + .onTapGesture { + profileSheet.toggle() + } + } + } + + var amountSection: some View { + Section(header: Text("Amount")) { + ProfileActionView( + title: viewModel.amount, + image: Image(systemName: "creditcard"), + imageButton: Image(systemName: "plus.circle") + ) { + viewModel.amountTapped() + } + } + } + + var ridesSection: some View { + Section(header: Text("Rides")) { + ProfileActionView( + title: viewModel.rides, + image: Image(systemName: "mappin.and.ellipse"), + imageButton: Image(systemName: "list.bullet") + ) { + viewModel.ridesTapped() + } + } + } + + var notificationsSection: some View { + Section(header: Text("Notifications")) { + ProfileSwitchView( + title: "Push", + image: Image(systemName: "app.badge"), + isOn: $viewModel.isPushNotificated + ) + + ProfileSwitchView( + title: "News", + image: Image(systemName: "newspaper"), + isOn: $viewModel.isNewsNotificated + ) + } + } + + var profileButtonSection: some View { + ProfileLogoutButton { + viewModel.logoutTapped { scene in + viewRouter.currentScene = scene + } + } + } +} diff --git a/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileView.swift b/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileView.swift new file mode 100644 index 0000000..ee36c79 --- /dev/null +++ b/Ecomobility/Presentation/Scenes/Profile/Views/Screens/ProfileView.swift @@ -0,0 +1,35 @@ +// +// ProfileView.swift +// Ecomobility +// +// Created by Raúl Pera Pairó on 7/2/21. +// + +import SwiftUI + +struct ProfileView: View { + // MARK: Properties + @State internal var profileSheet = false + @EnvironmentObject var viewRouter: ViewRouter + @StateObject var viewModel: ProfileViewModel + + // MARK: View + var body: some View { + NavigationView { + Form { + userSection + amountSection + ridesSection + notificationsSection + profileButtonSection + } + + .navigationTitle("Profile") + .navigationBarItems(trailing: ProfileSettingsButton { + viewModel.settingsTapped() + }) + }.onAppear { + viewModel.onAppear() + } + } +} diff --git a/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/Contents.json b/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/company_logo.imageset/Contents.json b/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/company_logo.imageset/Contents.json new file mode 100644 index 0000000..824bc90 --- /dev/null +++ b/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/company_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "company_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/company_logo.imageset/company_logo.png b/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/company_logo.imageset/company_logo.png new file mode 100644 index 0000000..71e5c03 Binary files /dev/null and b/Ecomobility/Scenes/Login/Presentation/Assets/Assets.xcassets/company_logo.imageset/company_logo.png differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9793929 --- /dev/null +++ b/README.md @@ -0,0 +1,436 @@ +# Ecomobility + +![Icon](https://user-images.githubusercontent.com/14141324/107708942-a73b4000-6cc4-11eb-8755-1f97a142c0a0.png) + +This application is a portfolio to meet the challenge of a selection process. + +In my implementation I have used [SwiftUI](https://developer.apple.com/xcode/swiftui/) as a visual library. The reason is simple: it is the future of design in the Apple ecosystem. I have also decided to use the [SF Symbols](https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/) provided by the system, due to its integration with the idiosyncrasy of iOS. + +I have also built on the concepts of [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) along with the [MVVM](https://en.wikipedia.org/wiki/Model–view–viewmodel) visual pattern. The reason for using this combination is in the maintainability, scalability and decoupling they provide. + +As for the design, I have modified this one to comply with [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/). I realized that the design presented in the requirements was done as a fast MVP, without taking into account the idiosyncrasies of the platform. + +I have also been able to use MapKit instead of Google Maps or MapBox due to the efficiency and integration that the native solution provides. + +I have also added Apple's SSO, this is due to 2 reasons: It is [mandatory](https://developer.apple.com/news/?id=03042020d) to implement Apple's own solution whenever the SSO of another company has been implemented, and because the privacy and benefit for the user that they provide seem appropriate to me. + +This implementation offers a Login flow, maintaining the session. I have only implemented Google, Facebook, Apple, Microsoft and Twitter SSOs. Although I believe that the best implementation for production will be the native Apple solution, without intermediaries. + +I have also opted for the use of Dependency Injection to decouple the responsibility of creating the instances and a ViewRouter to manage the navigation flow between scenes. + +As the only external dependency I have decided to use the native library that Auth0 provides. I would have preferred to do a totally abstract implementation using HTTP, but given the time I had for testing I decided to use this solution. As a dependency manager I have used SPM, Apple's native solution, due to its cleanliness and integration. + +I have also implemented [SwiftLint](https://github.com/realm/SwiftLint) with [Ray Wenderlich's ](https://github.com/raywenderlich/swift-style-guide)setting as a linter. This is a tool to enforce Swift style and conventions. + +## Scenes + +I have grouped the views into different scenes, depending on the context of the flow of the set. Each View has a corresponding ViewModel, that is in charge of all the logical management of the view, freeing it from such responsibility, and I have undocked any reusable components. + +### Login + +|Login|Sign in|Sign up|Auth0| +|---|---|---|---| +|![Login](https://user-images.githubusercontent.com/14141324/107669447-06cc2800-6c92-11eb-8c10-a5a87f5bb674.PNG)|![SignIn](https://user-images.githubusercontent.com/14141324/107669396-fa47cf80-6c91-11eb-8674-1ebcc9878ae7.PNG)|![SignUp](https://user-images.githubusercontent.com/14141324/107669344-ebf9b380-6c91-11eb-8f6e-5f0351223e16.PNG)|![Auth0](https://user-images.githubusercontent.com/14141324/107669282-ddab9780-6c91-11eb-9cf4-8cbf924e0a19.PNG)| + +### Map + +|Map|Profile|Profile details| +|---|---|---| +|![Map](https://user-images.githubusercontent.com/14141324/107669234-d1273f00-6c91-11eb-9d9d-e981c94e7a67.PNG)|![Profile](https://user-images.githubusercontent.com/14141324/107669168-bf459c00-6c91-11eb-9215-977d8a52cea3.PNG)|![ProfileDetails](https://user-images.githubusercontent.com/14141324/107669074-a937db80-6c91-11eb-9881-df301a315bce.PNG) + + +## Architecture + +For the development of the architecture I have used concepts from Clean Architecture and SOLID. Making use of these to improve the maintainability of the project and providing a robust base from which to scale. Separating the project into 3 main layers. + +I have used dependency inversion to be able to provide versatility and decoupling to the code. In this way, through the injection of dependencies we obtain many facilities when testing and mocking the dependencies of the SUT. It also provides us with the versatility of being able to switch between logic modules that comply with the protocols and, in this way, have the possibility of exchanging certain behaviors without affecting the rest of the project. All this, complying with the principle of segregation. + +|Project structure|Presentation structure|Domain structure|Data structure|Application structure| +|---|---|---|---|---| +|![Project](https://user-images.githubusercontent.com/14141324/107710247-e1a5dc80-6cc6-11eb-9666-31bbe0bf3c4c.png)|![Presentation](https://user-images.githubusercontent.com/14141324/107710320-0306c880-6cc7-11eb-80af-2fbca8c6a0cd.png)|![Domain](https://user-images.githubusercontent.com/14141324/107710363-14e86b80-6cc7-11eb-9727-8cdd3cb7cf2c.png)|![Data](https://user-images.githubusercontent.com/14141324/107952707-6419ef00-6f9a-11eb-99ec-1f37343cf2b2.png)|![Application](https://user-images.githubusercontent.com/14141324/107952580-3cc32200-6f9a-11eb-9273-9fa60cb1ab7d.png)| + + +### Presentation layer + +In the presentation layer there are two main characters: the Views and the ViewModels. + +Views are a declarative representation based on state, decoupled from logic, as this will be handled by the viewmodels. + +We are going to analyze an example of View and ViewModel. + +#### View + +```swift +import SwiftUI + +struct SignInView: View { + // MARK: Properties + @StateObject var viewModel: SignInViewModel + @EnvironmentObject var viewRouter: ViewRouter + + // MARK: View + var body: some View { + VStack { + Spacer() + logoSection + fieldsSection + Spacer() + loginButtonSection + textSeparatorSection + socialButtonsSection + } + } +} + +// MARK: Sections +extension SignInView { + // MARK: Views + var fieldsSection: some View { + VStack(alignment: .center, spacing: 15) { + LoginTextField(placeholder: "Email", input: $viewModel.email) + .textContentType(.emailAddress) + .autocapitalization(.none) + + LoginSecureField(placeholder: "Password", input: $viewModel.password) + } + .padding(.top, 60) + } + + var socialButtonsSection: some View { + HStack(spacing: 15) { + SocialAppleButton( + onRequest: viewModel.appleOnRequestAuthentication, + onResult: viewModel.appleOnResultAuthentication + ) + + SocialFacebookButton( + onRequest: viewModel.facebookOnRequestAuthentication, + onResult: viewModel.facebookOnResultAuthentication, + title: "Sign in with Facebook" + ) + + SocialGoogleButton( + onRequest: viewModel.facebookOnRequestAuthentication, + onResult: viewModel.facebookOnResultAuthentication, + title: "Sign in with Google" + ) + } + .padding(.bottom, 5) + } + + // MARK: Components + var logoSection: some View { + Image("company_logo") + .resizable() + .scaledToFill() + .frame(width: 350, height: 50) + .padding() + } + + var loginButtonSection: some View { + LoginButton(title: "SIGN IN") { + viewModel.signInTapped { scene in + viewRouter.currentScene = scene + } + } + } + + var textSeparatorSection: some View { + Text("- OR -") + .padding(3) + .font(.caption) + .foregroundColor(.accentColor) + } +} +``` + +First of all, you can see the import of the ViewModel and the ViewRouter through their respective **PropertyWrappers** to provide the necessary logic in the performance of this view. + +It should be emphasized that the view should not have any responsibility for any logic, and should only be limited to displaying the user interface. + +We can observe a clear modularization of the views used in the composition of the main view, thus facilitating its maintainability and readability. + +In case of finding repetitions of view, it must be encapsulated as an independent view for reuse, complying with the **DRY** principle. + +All logic derived from user interactions or received events will be managed through the ViewModel, delegating the computation to it and receiving a possible result that changes the state of the view. + +In this case there are many magic numbers and strings, derived from the time available for the development of this technical test. In a real production case, this data should be centralized and localized in the case of strings, out of sight. + +#### ViewModel + +```swift +import Foundation + +final class SignInViewModel: ObservableObject { + // MARK: Properties + @Published var email = "" + @Published var password = "" + + // Use cases + var authUseCase: FetchAuthUseCaseable + var storeSessionUseCase: StoreAuthUseCaseable + + // MARK: Constructor + init(authUseCase: FetchAuthUseCaseable, storeSession: StoreAuthUseCaseable) { + self.authUseCase = authUseCase + self.storeSessionUseCase = storeSession + } +} + +// MARK: Functionality +extension SignInViewModel { + func storeSession(credentials: AuthCredentials, onCompletion: @escaping () -> Void) { + storeSessionUseCase.store(credentials: credentials) { result in + switch result { + case .success: + onCompletion() + case .failure(let error): + NSLog(error.localizedDescription) + } + } + } +} + +// MARK: Events +extension SignInViewModel { + func signInTapped(onCompletion: @escaping (Scenes) -> Void) { + authUseCase.execute { result in + switch result { + case .success(let credentials): + self.storeSession(credentials: credentials) { + onCompletion(.base) + } + case .failure(let error): + NSLog(error.localizedDescription) + onCompletion(.login) + } + } + } +} +``` +First of all, we can see the exposed properties using the PropertyWrapper **@Published** to feed them with the inputs from the view. + +The ViewModels have declared the use cases that they will require to fulfill their functionality. + +In this case, we have the **FetchAuthUseCaseable**, which is responsible for obtaining user authentication, and the **StoreAuthUseCaseable**, which is responsible for storing the session to be able to work with it later. + +I communicate with the use cases through Closures and Result types. I have chosen this method for its elegance and clarity. + +Through the use of extensions, we divide the functional contexts of the ViewModel to facilitate its maintainability. + +It is important to keep the ViewModel decoupled from the view, avoiding any dependency and/or import of visual libraries. Communication between the viewmodel and the view will occur through the observables. + +### Domain layer + +The domain layer is totally foreign to the rest of the layers, and it is strictly forbidden to know anything outside of it. Nor should the platform import libraries, recommending only using the language API and nothing else. In this way, we can reuse this layer if we wanted to in any other project, regardless of the version of the operating system or the visual language used, on any platform that supports the language. + +This layer is responsible for providing through the **Use Cases** what is requested. If necessary, they will need **repositories** to obtain the necessary data with which to fulfill their function. + +Communication with the repositories will be done through a protocol, thus abstracting itself from the data layer. + +#### UseCase protocol + +```swift +import Foundation + +typealias FetchUserAuthResult = (Result) -> Void + +enum FetchUserError: Error { + case credentials + case unkown(Error) +} + +protocol FetchUserAuthUseCaseable { + // MARK: Functionality + func execute(onCompletion: @escaping FetchUserAuthResult) +} +``` + +The use case protocol is the element that decouples the presentation layer from the domain. This will be in charge of communicating both layers. For convenience, I like to take advantage of the file of the declaration of this protocol to also declare the type of result that the use case is going to offer, and if necessary, a type of error of its own for it. + +#### UseCase + +```swift +import Foundation + +struct FetchUserAuthUseCase: FetchUserAuthUseCaseable { + // MARK: Properties + var authRepository: AuthRepositable + + // MARK: Functionality + func execute(onCompletion: @escaping FetchUserAuthResult) { + authRepository.fetch(onCompletion: onCompletion) + } +} +``` + +The use case is the element in charge of managing the logic necessary to construct the response that the presentation layer has requested. It can contain other use cases and repositories as dependencies to obtain the resources required for its function. + +#### Repository protocol + +```swift +import Foundation + +protocol AuthRepositable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) + func logout(onCompletion: @escaping LogoutAuthResult) + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) + func check(onCompletion: @escaping CheckAuthResult) + func fetch(onCompletion: @escaping FetchUserAuthResult) +} +``` + +Repository protocols are the elements in charge of decoupling the obtaining of data from the domain. They are used to maintain an abstract communication between the use case and the repository. + +The protocol must bring together the operations that belong to the same context. + +#### Entity + +```swift +import Foundation + +struct User { + // MARK: Properties + var name: String? + var email: String? + var picture: String? + var nickname: String? + var familyName: String? + var givenName: String? + + // Computed properties + var pictureData: Data? { + guard let picture = self.picture else { return nil } + guard let url = URL(string: picture) else { return nil } + guard let data = try? Data(contentsOf: url) else { return nil } + return data + } +} +``` + +The entity is nothing more than a flat model without logic, to transmit data between the different layers of the architecture. The domain layer is completely foreign to the presentation and data layers, so the latter will communicate with the domain using their own entities. + +### Data layer + +In the data layer we find two main actors: The Repositories and the DataSources. + +The repository is foreign to the implementation of the data source, so the latter must communicate with the repository with entities known to it. + +#### Repository + +```swift +import Foundation + +struct AuthRepository: AuthRepositable { + // MARK: Properties + var fetchingDataSource: FetchAuthDataSourceable + var logoutDataSource: LogoutAuthDataSourceable + var storeDataSource: StoreAuthDataSourceable + var checkDataSource: CheckAuthDataSourceable + var fetchUserDataSource: FetchUserAuthDataSourceable + + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) { + fetchingDataSource.fetch(onCompletion: onCompletion) + } + + func logout(onCompletion: @escaping LogoutAuthResult) { + logoutDataSource.logout(onCompletion: onCompletion) + } + + func store(credentials: AuthCredentials, onCompletion: @escaping StoreAuthResult) { + storeDataSource.store(credentials: credentials, onCompletion: onCompletion) + } + + func check(onCompletion: @escaping CheckAuthResult) { + checkDataSource.check(onCompletion: onCompletion) + } + + func fetch(onCompletion: @escaping FetchUserAuthResult) { + fetchUserDataSource.fetch(onCompletion: onCompletion) + } +} +``` + +The repositories are in charge of managing all the operations of the same context, and choosing a suitable data source for it. It could be the case of using different data sources for the same repository, that is why there is a data source corresponding to each operation. + +#### DataSource protocol +```swift +import Foundation + +protocol LogoutAuthDataSourceable { + // MARK: Functionality + func logout(onCompletion: @escaping LogoutAuthResult) +} +``` + +The data sources must implement the communication protocol with the repository, thus decoupling any dependencies between them. + +#### DataSource + +```swift +import Foundation +import Auth0 + +extension Auth0DataSource: FetchAuthDataSourceable { + // MARK: Functionality + func fetch(onCompletion: @escaping FetchAuthResult) { + guard let domain = domain else { return } + + Auth0 + .webAuth() + .scope("openid profile email offline_access") + .audience("https://\(domain)/userinfo") + .start { result in + switch result { + case .success(let credentials): + onCompletion(.success(credentials.transform())) + case .failure(let error): + onCompletion(.failure(error)) + } + } + } +} +``` + +The data sources is the element in charge of obtaining the data. It can be both remote data (http for example) or local data (from third party libraries for example). + +This layer is allowed to import external elements if necessary, since they will be encapsulated in it, and will not affect the rest of the architecture. + +#### Entities extensions + +```swift +import Foundation +import Auth0 + +extension Credentials { + // MARK: Constructor + convenience init(_ credentials: AuthCredentials) { + self.init( + accessToken: credentials.accessToken, + tokenType: credentials.tokenType, + idToken: credentials.idToken, + refreshToken: credentials.refreshToken, + expiresIn: credentials.expiresIn, + scope: credentials.scope + ) + } + + // MARK: Functionality + func transform() -> AuthCredentials { + AuthCredentials( + accessToken: self.accessToken, + expiresIn: self.expiresIn, + idToken: self.idToken, + refreshToken: self.refreshToken, + scope: self.scope, + tokenType: self.tokenType + ) + } +} +``` + +For the sake of cleanliness and readability, I like to extend the external models and implement transformation responsibility between the domain entity (BO) and it (DTO). + +To do this, I create a constructor that receives the BO and propagates its mapping to the constructor of the DTO. I also create a function that transforms the DTO into BO, mapping the local properties and calling the DTO's constructor with them. \ No newline at end of file