diff --git a/Nynja copy-Info.plist b/Nynja copy-Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..5472ab2b7a70a4cae93b9b54aaa3489d3b24f3c4 --- /dev/null +++ b/Nynja copy-Info.plist @@ -0,0 +1,122 @@ + + + + + AppGroup + $(AppGroup) + CFBundleDevelopmentRegion + en + CFBundleDisplayName + $(AppName) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 0.2.158 + ConfServerAddress + $(ConfServerAddress) + ConfServerPort + $(ConfServerPort) + ConfServerSecure + $(ConfServerSecure) + Config + $(Config) + Fabric + + APIKey + 595b68a8c4deb3533dcdfc24ca73fd3cffd99f3c + Kits + + + KitInfo + + KitName + Crashlytics + + + + LSApplicationQueriesSchemes + + cydia + comgooglemapsurl + comgooglemaps + googlechromes + + LSRequiresIPhoneOS + + ModelsVersion + $(ModelsVersion) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSCameraUsageDescription + NYNJA needs it to allow you make photos and video calls. + NSContactsUsageDescription + NYNJA needs it to allow you add new contacts from your phone contact book. + NSLocationAlwaysUsageDescription + NYNJA needs to know your location so that you can be able to share it. + NSLocationWhenInUseUsageDescription + NYNJA needs to know your location so that you can be able to share it. + NSMicrophoneUsageDescription + NYNJA needs it to allow you send audio messages and make voice calls. + NSPhotoLibraryAddUsageDescription + NYNJA needs it to save photos and video to your device. + NSPhotoLibraryUsageDescription + NYNJA needs it so that you can use your local images. + ServerPort + $(ServerPort) + ServerURL + $(ServerURL) + UIAppFonts + + Avenir.ttc + LatoBlack.ttf + Myriad Pro Regular.ttf + NotoSans-Bold.ttf + NotoSans-Regular.ttf + NotoSans-Medium.ttf + NotoSans-Italic.ttf + + UIBackgroundModes + + audio + fetch + remote-notification + voip + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + isServerConnectionSecure + $(isServerConnectionSecure) + + diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index c86e8d39c6ea9916c1a005fb360eeeed09857cc3..b69589c62f505404b2ff160e5cd4c3d5a064f398 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -736,10 +736,47 @@ 5BC1D38420D3B670002A44B3 /* CallCreatorMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC1D38320D3B670002A44B3 /* CallCreatorMediator.swift */; }; 5C468A609C445962C0D19DD3 /* HistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6900257B1AB2CA0BC834EB /* HistoryViewController.swift */; }; 5DBBAAF3AAB09B2D4E71B806 /* AddContactViaPhoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB993F14055EAE59F572530 /* AddContactViaPhoneViewController.swift */; }; + 5E07BC3D216DFD08000E4558 /* AuthViewsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC3C216DFD08000E4558 /* AuthViewsFactory.swift */; }; + 5E07BC40216E09F0000E4558 /* CodeConfirmationViewsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC3F216E09F0000E4558 /* CodeConfirmationViewsFactory.swift */; }; + 5E07BC42216E30A8000E4558 /* CountrySelectorViewsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC41216E30A8000E4558 /* CountrySelectorViewsFactory.swift */; }; + 5E07BC44216F56AF000E4558 /* AuthType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC43216F56AF000E4558 /* AuthType.swift */; }; + 5E07BC4D216F64EC000E4558 /* CreateProfileProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC4C216F64EC000E4558 /* CreateProfileProtocols.swift */; }; + 5E07BC4F216F659E000E4558 /* CreateProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC4E216F659E000E4558 /* CreateProfileViewController.swift */; }; + 5E07BC51216F6617000E4558 /* CreateProfileInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC50216F6617000E4558 /* CreateProfileInteractor.swift */; }; + 5E07BC53216F6661000E4558 /* CreateProfilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC52216F6661000E4558 /* CreateProfilePresenter.swift */; }; + 5E07BC55216F66F3000E4558 /* CreateProfileViewsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC54216F66F3000E4558 /* CreateProfileViewsFactory.swift */; }; + 5E07BC57216F6722000E4558 /* CreateProfileWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E07BC56216F6722000E4558 /* CreateProfileWireframe.swift */; }; + 5E0B9FF22170BCE600A95467 /* CreateProfileContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0B9FF12170BCE600A95467 /* CreateProfileContentView.swift */; }; 5E0CEA9A21490663004B3F7A /* TypingStatusCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0CEA9921490663004B3F7A /* TypingStatusCache.swift */; }; 5E278E14F45F56BACB71271C /* VideoPreviewWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F5541C91FE7845F3E5C7EB2 /* VideoPreviewWireframe.swift */; }; + 5E7E9FB9215BA0BE004D306B /* CountrySelectorProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7E9FB8215BA0BE004D306B /* CountrySelectorProtocols.swift */; }; + 5E7E9FBC215BA19B004D306B /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7E9FBB215BA19B004D306B /* Country.swift */; }; + 5E7E9FBE215BA51C004D306B /* CountrySelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7E9FBD215BA51C004D306B /* CountrySelectorViewController.swift */; }; + 5E7E9FC2215BA681004D306B /* CountryTVCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7E9FC1215BA681004D306B /* CountryTVCell.swift */; }; + 5E7E9FC4215BA68E004D306B /* CountryTVHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7E9FC3215BA68E004D306B /* CountryTVHeader.swift */; }; 5EB13FDBA6153EE67366115F /* ScheduleMessageInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5095F3CF5921F107D81C8652 /* ScheduleMessageInteractor.swift */; }; 5ED473EC698E99DC021E553A /* MapSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BD49CF323041B47A752603E /* MapSearchInteractor.swift */; }; + 5EEB73A4215D00E300D8ECE6 /* CountrySelectorInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73A3215D00E300D8ECE6 /* CountrySelectorInteractor.swift */; }; + 5EEB73A6215D00F100D8ECE6 /* CountrySelectorWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73A5215D00F100D8ECE6 /* CountrySelectorWireframe.swift */; }; + 5EEB73A8215D00FD00D8ECE6 /* CountrySelectorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73A7215D00FD00D8ECE6 /* CountrySelectorPresenter.swift */; }; + 5EEB73AA215D406400D8ECE6 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73A9215D406400D8ECE6 /* AuthCoordinator.swift */; }; + 5EEB73B2216046FE00D8ECE6 /* CodeConfirmationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73B1216046FE00D8ECE6 /* CodeConfirmationProtocols.swift */; }; + 5EEB73B4216047E000D8ECE6 /* CodeConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73B3216047E000D8ECE6 /* CodeConfirmationViewController.swift */; }; + 5EEB73B621604CF600D8ECE6 /* CodeConfirmationWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73B521604CF600D8ECE6 /* CodeConfirmationWireframe.swift */; }; + 5EEB73B821604DD900D8ECE6 /* CodeConfirmationInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73B721604DD900D8ECE6 /* CodeConfirmationInteractor.swift */; }; + 5EEB73BA21604E2300D8ECE6 /* CodeConfirmationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73B921604E2300D8ECE6 /* CodeConfirmationPresenter.swift */; }; + 5EEB73BD2161797900D8ECE6 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73BC2161797900D8ECE6 /* Result.swift */; }; + 5EEB73C5216199ED00D8ECE6 /* AuthProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73C4216199ED00D8ECE6 /* AuthProtocols.swift */; }; + 5EEB73C721619A5000D8ECE6 /* AuthWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73C621619A5000D8ECE6 /* AuthWireframe.swift */; }; + 5EEB73C92161CB8F00D8ECE6 /* AuthInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73C82161CB8F00D8ECE6 /* AuthInteractor.swift */; }; + 5EEB73CB2161CBF300D8ECE6 /* AuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73CA2161CBF300D8ECE6 /* AuthPresenter.swift */; }; + 5EEB73CD2161CC8A00D8ECE6 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73CC2161CC8A00D8ECE6 /* AuthViewController.swift */; }; + 5EEB73D02161CE2700D8ECE6 /* LoginOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73CF2161CE2700D8ECE6 /* LoginOptionsView.swift */; }; + 5EEB73D22161CEA100D8ECE6 /* LoginOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73D12161CEA100D8ECE6 /* LoginOption.swift */; }; + 5EEB73D42161D5C500D8ECE6 /* AuthHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73D32161D5C500D8ECE6 /* AuthHeaderView.swift */; }; + 5EEB73D62161DBF100D8ECE6 /* EmailLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73D52161DBF100D8ECE6 /* EmailLoginView.swift */; }; + 5EEB73D82162227B00D8ECE6 /* PhoneNumberLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73D72162227B00D8ECE6 /* PhoneNumberLoginView.swift */; }; + 5EEB73DE21623FF900D8ECE6 /* UIViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EEB73DD21623FF900D8ECE6 /* UIViewControllerExtensions.swift */; }; 619C44B00CC7B169077CDEC2 /* EditProfileProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B377AA90A6B6BA0120C31F1 /* EditProfileProtocols.swift */; }; 628E2C26BE0854DB1DF64990 /* SplashWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22259D46BE5732B494C4C7D /* SplashWireframe.swift */; }; 63E6537BBBD814F6DF3DC589 /* Pods_Nynja.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35B1AB871FA278D900E65233 /* Pods_Nynja.framework */; }; @@ -1470,9 +1507,7 @@ A4330A662109E04E0060BD93 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A642109DFA00060BD93 /* UserInfo.swift */; }; A4330A6A2109EA850060BD93 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A692109EA850060BD93 /* DatabaseManager.swift */; }; A4330A6E2109EBA70060BD93 /* CountriesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A6D2109EBA70060BD93 /* CountriesProvider.swift */; }; - A4330A6F2109EBA70060BD93 /* CountriesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A6D2109EBA70060BD93 /* CountriesProvider.swift */; }; A4330A712109EBB30060BD93 /* CountriesProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A702109EBB30060BD93 /* CountriesProviding.swift */; }; - A4330A722109EBB30060BD93 /* CountriesProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A702109EBB30060BD93 /* CountriesProviding.swift */; }; A4330A742109F0D40060BD93 /* StorageService+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A732109F0D40060BD93 /* StorageService+UserInfo.swift */; }; A4330A752109F0D40060BD93 /* StorageService+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A732109F0D40060BD93 /* StorageService+UserInfo.swift */; }; A433D9A120A5C18C00C946F9 /* ContactsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A433D9A020A5C18C00C946F9 /* ContactsProvider.swift */; }; @@ -2984,8 +3019,45 @@ 5BC1D38020D3B54B002A44B3 /* CallInfoViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallInfoViewLayout.swift; sourceTree = ""; }; 5BC1D38320D3B670002A44B3 /* CallCreatorMediator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallCreatorMediator.swift; sourceTree = ""; }; 5D3E868EE32625048BCB13A8 /* HistoryInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HistoryInteractor.swift; sourceTree = ""; }; + 5E07BC3C216DFD08000E4558 /* AuthViewsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewsFactory.swift; sourceTree = ""; }; + 5E07BC3F216E09F0000E4558 /* CodeConfirmationViewsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeConfirmationViewsFactory.swift; sourceTree = ""; }; + 5E07BC41216E30A8000E4558 /* CountrySelectorViewsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorViewsFactory.swift; sourceTree = ""; }; + 5E07BC43216F56AF000E4558 /* AuthType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthType.swift; sourceTree = ""; }; + 5E07BC4C216F64EC000E4558 /* CreateProfileProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfileProtocols.swift; sourceTree = ""; }; + 5E07BC4E216F659E000E4558 /* CreateProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfileViewController.swift; sourceTree = ""; }; + 5E07BC50216F6617000E4558 /* CreateProfileInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfileInteractor.swift; sourceTree = ""; }; + 5E07BC52216F6661000E4558 /* CreateProfilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfilePresenter.swift; sourceTree = ""; }; + 5E07BC54216F66F3000E4558 /* CreateProfileViewsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfileViewsFactory.swift; sourceTree = ""; }; + 5E07BC56216F6722000E4558 /* CreateProfileWireframe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfileWireframe.swift; sourceTree = ""; }; + 5E0B9FF12170BCE600A95467 /* CreateProfileContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfileContentView.swift; sourceTree = ""; }; 5E0CEA9921490663004B3F7A /* TypingStatusCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingStatusCache.swift; sourceTree = ""; }; + 5E7E9FB8215BA0BE004D306B /* CountrySelectorProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorProtocols.swift; sourceTree = ""; }; + 5E7E9FBB215BA19B004D306B /* Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Country.swift; sourceTree = ""; }; + 5E7E9FBD215BA51C004D306B /* CountrySelectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorViewController.swift; sourceTree = ""; }; + 5E7E9FC1215BA681004D306B /* CountryTVCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryTVCell.swift; sourceTree = ""; }; + 5E7E9FC3215BA68E004D306B /* CountryTVHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryTVHeader.swift; sourceTree = ""; }; 5EEA3D18EFB98D7959F993E4 /* AddParticipantsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddParticipantsProtocols.swift; sourceTree = ""; }; + 5EEB73A3215D00E300D8ECE6 /* CountrySelectorInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorInteractor.swift; sourceTree = ""; }; + 5EEB73A5215D00F100D8ECE6 /* CountrySelectorWireframe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorWireframe.swift; sourceTree = ""; }; + 5EEB73A7215D00FD00D8ECE6 /* CountrySelectorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountrySelectorPresenter.swift; sourceTree = ""; }; + 5EEB73A9215D406400D8ECE6 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = ""; }; + 5EEB73B1216046FE00D8ECE6 /* CodeConfirmationProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeConfirmationProtocols.swift; sourceTree = ""; }; + 5EEB73B3216047E000D8ECE6 /* CodeConfirmationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeConfirmationViewController.swift; sourceTree = ""; }; + 5EEB73B521604CF600D8ECE6 /* CodeConfirmationWireframe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeConfirmationWireframe.swift; sourceTree = ""; }; + 5EEB73B721604DD900D8ECE6 /* CodeConfirmationInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeConfirmationInteractor.swift; sourceTree = ""; }; + 5EEB73B921604E2300D8ECE6 /* CodeConfirmationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeConfirmationPresenter.swift; sourceTree = ""; }; + 5EEB73BC2161797900D8ECE6 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 5EEB73C4216199ED00D8ECE6 /* AuthProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthProtocols.swift; sourceTree = ""; }; + 5EEB73C621619A5000D8ECE6 /* AuthWireframe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthWireframe.swift; sourceTree = ""; }; + 5EEB73C82161CB8F00D8ECE6 /* AuthInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthInteractor.swift; sourceTree = ""; }; + 5EEB73CA2161CBF300D8ECE6 /* AuthPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthPresenter.swift; sourceTree = ""; }; + 5EEB73CC2161CC8A00D8ECE6 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; + 5EEB73CF2161CE2700D8ECE6 /* LoginOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginOptionsView.swift; sourceTree = ""; }; + 5EEB73D12161CEA100D8ECE6 /* LoginOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginOption.swift; sourceTree = ""; }; + 5EEB73D32161D5C500D8ECE6 /* AuthHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHeaderView.swift; sourceTree = ""; }; + 5EEB73D52161DBF100D8ECE6 /* EmailLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailLoginView.swift; sourceTree = ""; }; + 5EEB73D72162227B00D8ECE6 /* PhoneNumberLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberLoginView.swift; sourceTree = ""; }; + 5EEB73DD21623FF900D8ECE6 /* UIViewControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtensions.swift; sourceTree = ""; }; 5F509C0C8B9C738DBC7ABE07 /* SecurityViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = ""; }; 61B964D5CB991533BA5C164C /* HistoryPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HistoryPresenter.swift; sourceTree = ""; }; 61CB12AA514912C6B8E4F670 /* Pods-Nynja.devautotests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja.devautotests.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja/Pods-Nynja.devautotests.xcconfig"; sourceTree = ""; }; @@ -5932,6 +6004,7 @@ 3A768DE41ECB3E7600108F7C /* Library */ = { isa = PBXGroup; children = ( + 5EEB73BB2161797100D8ECE6 /* Result */, 4B7C73F5215A5522007924DB /* Debug */, B74BAFED21076ADB0049CD27 /* CircleMenuControl */, A46679EF20F10B2B00DBC6B4 /* RequestModelFactory */, @@ -6048,6 +6121,7 @@ 3A82187C1EDEEDF400337B05 /* UI */ = { isa = PBXGroup; children = ( + 5EEB73DC21623FED00D8ECE6 /* UIViewControllerExtensions */, 26A421CB217E026100120542 /* SnackBar */, 4B749EF2214FEABB002F3A33 /* LoginView */, 4BB0EFBA2151347900704136 /* AlertManager.swift */, @@ -6800,8 +6874,13 @@ 4B749F0E214FEFC8002F3A33 /* Auth */ = { isa = PBXGroup; children = ( + 5E07BC45216F64DB000E4558 /* CreateProfile */, + 5EEB73BE216199DE00D8ECE6 /* AuthModule */, + 5EEB73AB216046EA00D8ECE6 /* CodeConfirmation */, + 5E7E9FB3215BA0AD004D306B /* CountrySelector */, 3AB452082A8DAEAD93F689D8 /* Login */, 4B749EFF214FEE3C002F3A33 /* VerifyNumber */, + 5EEB73A9215D406400D8ECE6 /* AuthCoordinator.swift */, ); path = Auth; sourceTree = ""; @@ -7298,6 +7377,293 @@ path = View; sourceTree = ""; }; + 5E07BC3B216DFCFA000E4558 /* ViewsFactory */ = { + isa = PBXGroup; + children = ( + 5E07BC3C216DFD08000E4558 /* AuthViewsFactory.swift */, + ); + path = ViewsFactory; + sourceTree = ""; + }; + 5E07BC3E216E09DF000E4558 /* ViewsFactory */ = { + isa = PBXGroup; + children = ( + 5E07BC3F216E09F0000E4558 /* CodeConfirmationViewsFactory.swift */, + ); + path = ViewsFactory; + sourceTree = ""; + }; + 5E07BC45216F64DB000E4558 /* CreateProfile */ = { + isa = PBXGroup; + children = ( + 5E07BC46216F64DB000E4558 /* Presenter */, + 5E07BC47216F64DB000E4558 /* Wireframe */, + 5E07BC48216F64DB000E4558 /* View */, + 5E07BC4A216F64DB000E4558 /* Interactor */, + 5E07BC4C216F64EC000E4558 /* CreateProfileProtocols.swift */, + ); + path = CreateProfile; + sourceTree = ""; + }; + 5E07BC46216F64DB000E4558 /* Presenter */ = { + isa = PBXGroup; + children = ( + 5E07BC52216F6661000E4558 /* CreateProfilePresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + 5E07BC47216F64DB000E4558 /* Wireframe */ = { + isa = PBXGroup; + children = ( + 5E07BC56216F6722000E4558 /* CreateProfileWireframe.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + 5E07BC48216F64DB000E4558 /* View */ = { + isa = PBXGroup; + children = ( + 5E0B9FF02170BCD400A95467 /* Subviews */, + 5E07BC49216F64DB000E4558 /* ViewsFactory */, + 5E07BC4E216F659E000E4558 /* CreateProfileViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 5E07BC49216F64DB000E4558 /* ViewsFactory */ = { + isa = PBXGroup; + children = ( + 5E07BC54216F66F3000E4558 /* CreateProfileViewsFactory.swift */, + ); + path = ViewsFactory; + sourceTree = ""; + }; + 5E07BC4A216F64DB000E4558 /* Interactor */ = { + isa = PBXGroup; + children = ( + 5E07BC50216F6617000E4558 /* CreateProfileInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 5E0B9FF02170BCD400A95467 /* Subviews */ = { + isa = PBXGroup; + children = ( + 5E0B9FF12170BCE600A95467 /* CreateProfileContentView.swift */, + ); + path = Subviews; + sourceTree = ""; + }; + 5E7E9FB3215BA0AD004D306B /* CountrySelector */ = { + isa = PBXGroup; + children = ( + 5E7E9FBA215BA186004D306B /* Entities */, + 5E7E9FB4215BA0AD004D306B /* Presenter */, + 5E7E9FB5215BA0AD004D306B /* Wireframe */, + 5E7E9FB6215BA0AD004D306B /* View */, + 5E7E9FB7215BA0AD004D306B /* Interactor */, + 5E7E9FB8215BA0BE004D306B /* CountrySelectorProtocols.swift */, + ); + path = CountrySelector; + sourceTree = ""; + }; + 5E7E9FB4215BA0AD004D306B /* Presenter */ = { + isa = PBXGroup; + children = ( + 5EEB73A7215D00FD00D8ECE6 /* CountrySelectorPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + 5E7E9FB5215BA0AD004D306B /* Wireframe */ = { + isa = PBXGroup; + children = ( + 5EEB73A5215D00F100D8ECE6 /* CountrySelectorWireframe.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + 5E7E9FB6215BA0AD004D306B /* View */ = { + isa = PBXGroup; + children = ( + 5E7E9FBF215BA66E004D306B /* Cells */, + 5E7E9FC0215BA66E004D306B /* Headers */, + 5E7E9FBD215BA51C004D306B /* CountrySelectorViewController.swift */, + 5E07BC41216E30A8000E4558 /* CountrySelectorViewsFactory.swift */, + ); + path = View; + sourceTree = ""; + }; + 5E7E9FB7215BA0AD004D306B /* Interactor */ = { + isa = PBXGroup; + children = ( + 5EEB73A3215D00E300D8ECE6 /* CountrySelectorInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 5E7E9FBA215BA186004D306B /* Entities */ = { + isa = PBXGroup; + children = ( + 5E7E9FBB215BA19B004D306B /* Country.swift */, + ); + path = Entities; + sourceTree = ""; + }; + 5E7E9FBF215BA66E004D306B /* Cells */ = { + isa = PBXGroup; + children = ( + 5E7E9FC1215BA681004D306B /* CountryTVCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 5E7E9FC0215BA66E004D306B /* Headers */ = { + isa = PBXGroup; + children = ( + 5E7E9FC3215BA68E004D306B /* CountryTVHeader.swift */, + ); + path = Headers; + sourceTree = ""; + }; + 5EEB73AB216046EA00D8ECE6 /* CodeConfirmation */ = { + isa = PBXGroup; + children = ( + 5EEB73AC216046EA00D8ECE6 /* Presenter */, + 5EEB73AD216046EA00D8ECE6 /* Wireframe */, + 5EEB73AE216046EA00D8ECE6 /* View */, + 5EEB73AF216046EA00D8ECE6 /* Interactor */, + 5EEB73B0216046EA00D8ECE6 /* Entities */, + 5EEB73B1216046FE00D8ECE6 /* CodeConfirmationProtocols.swift */, + ); + path = CodeConfirmation; + sourceTree = ""; + }; + 5EEB73AC216046EA00D8ECE6 /* Presenter */ = { + isa = PBXGroup; + children = ( + 5EEB73B921604E2300D8ECE6 /* CodeConfirmationPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + 5EEB73AD216046EA00D8ECE6 /* Wireframe */ = { + isa = PBXGroup; + children = ( + 5EEB73B521604CF600D8ECE6 /* CodeConfirmationWireframe.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + 5EEB73AE216046EA00D8ECE6 /* View */ = { + isa = PBXGroup; + children = ( + 5E07BC3E216E09DF000E4558 /* ViewsFactory */, + 5EEB73B3216047E000D8ECE6 /* CodeConfirmationViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 5EEB73AF216046EA00D8ECE6 /* Interactor */ = { + isa = PBXGroup; + children = ( + 5EEB73B721604DD900D8ECE6 /* CodeConfirmationInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 5EEB73B0216046EA00D8ECE6 /* Entities */ = { + isa = PBXGroup; + children = ( + 5E07BC43216F56AF000E4558 /* AuthType.swift */, + ); + path = Entities; + sourceTree = ""; + }; + 5EEB73BB2161797100D8ECE6 /* Result */ = { + isa = PBXGroup; + children = ( + 5EEB73BC2161797900D8ECE6 /* Result.swift */, + ); + name = Result; + path = Library/Result; + sourceTree = ""; + }; + 5EEB73BE216199DE00D8ECE6 /* AuthModule */ = { + isa = PBXGroup; + children = ( + 5EEB73C4216199ED00D8ECE6 /* AuthProtocols.swift */, + 5EEB73C1216199DE00D8ECE6 /* View */, + 5EEB73BF216199DE00D8ECE6 /* Presenter */, + 5EEB73C2216199DE00D8ECE6 /* Interactor */, + 5EEB73C0216199DE00D8ECE6 /* Wireframe */, + 5EEB73C3216199DE00D8ECE6 /* Entities */, + ); + path = AuthModule; + sourceTree = ""; + }; + 5EEB73BF216199DE00D8ECE6 /* Presenter */ = { + isa = PBXGroup; + children = ( + 5EEB73CA2161CBF300D8ECE6 /* AuthPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + 5EEB73C0216199DE00D8ECE6 /* Wireframe */ = { + isa = PBXGroup; + children = ( + 5EEB73C621619A5000D8ECE6 /* AuthWireframe.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + 5EEB73C1216199DE00D8ECE6 /* View */ = { + isa = PBXGroup; + children = ( + 5E07BC3B216DFCFA000E4558 /* ViewsFactory */, + 5EEB73CE2161CDF700D8ECE6 /* Subviews */, + 5EEB73CC2161CC8A00D8ECE6 /* AuthViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 5EEB73C2216199DE00D8ECE6 /* Interactor */ = { + isa = PBXGroup; + children = ( + 5EEB73C82161CB8F00D8ECE6 /* AuthInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 5EEB73C3216199DE00D8ECE6 /* Entities */ = { + isa = PBXGroup; + children = ( + 5EEB73D12161CEA100D8ECE6 /* LoginOption.swift */, + ); + path = Entities; + sourceTree = ""; + }; + 5EEB73CE2161CDF700D8ECE6 /* Subviews */ = { + isa = PBXGroup; + children = ( + 5EEB73CF2161CE2700D8ECE6 /* LoginOptionsView.swift */, + 5EEB73D32161D5C500D8ECE6 /* AuthHeaderView.swift */, + 5EEB73D52161DBF100D8ECE6 /* EmailLoginView.swift */, + 5EEB73D72162227B00D8ECE6 /* PhoneNumberLoginView.swift */, + ); + path = Subviews; + sourceTree = ""; + }; + 5EEB73DC21623FED00D8ECE6 /* UIViewControllerExtensions */ = { + isa = PBXGroup; + children = ( + 5EEB73DD21623FF900D8ECE6 /* UIViewControllerExtensions.swift */, + ); + path = UIViewControllerExtensions; + sourceTree = ""; + }; 6002E0651297C852142C0DEF /* View */ = { isa = PBXGroup; children = ( @@ -14727,7 +15093,6 @@ 359EB23D1F9A1BE600147437 /* Queue.swift in Sources */, 359EB23C1F9A1BD800147437 /* Reachability.swift in Sources */, A4B5450020EFC52100EB7B0F /* StatusCode.swift in Sources */, - A4330A722109EBB30060BD93 /* CountriesProviding.swift in Sources */, A42CE5FC20692EDB000889CC /* userTask_Spec.swift in Sources */, A42CE5CE20692EDB000889CC /* Vox_Spec.swift in Sources */, A42CE56620692EDB000889CC /* receiveTask.swift in Sources */, @@ -14744,7 +15109,6 @@ A45F114E20B4222F00F45004 /* RecordingStatus.swift in Sources */, A42CE5B220692EDB000889CC /* log_Spec.swift in Sources */, A42CE54620692EDB000889CC /* messageEvent.swift in Sources */, - A4330A6F2109EBA70060BD93 /* CountriesProvider.swift in Sources */, A42CE54820692EDB000889CC /* reader.swift in Sources */, A42CE60A20692EDB000889CC /* Index_Spec.swift in Sources */, 26352916207572AA00DC6FBD /* JobExtension.swift in Sources */, @@ -14824,6 +15188,7 @@ A40F18BB20BFD9C60091B09E /* EmptyStateViewModel.swift in Sources */, A42D52B6206A53AA00EEB952 /* Service_Spec.swift in Sources */, 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */, + 5E07BC42216E30A8000E4558 /* CountrySelectorViewsFactory.swift in Sources */, 4B6D20EC2164D4AB003ADB29 /* DeliveryStatus.swift in Sources */, 4B7C73F3215A5509007924DB /* LogWriter.swift in Sources */, 262D43872033417F002F1E45 /* FriendExtansion+BERT.swift in Sources */, @@ -14918,6 +15283,7 @@ 4B8FC3182163CC0E00602D6B /* ChatCellModel.swift in Sources */, C9C694FD201FA55800A57297 /* SwipeBackHelper.swift in Sources */, 85CE26D820C5593600553FE7 /* HapticSelectionFeedbackGenerator.swift in Sources */, + 5E07BC44216F56AF000E4558 /* AuthType.swift in Sources */, A49381AA21355EE1006D28DD /* MessageInteractor+Forward.swift in Sources */, 26FA4210201821B400E6F6EC /* StarHandler.swift in Sources */, 26C0C1E42073DA3A00C530DA /* Muc+DB.swift in Sources */, @@ -14926,6 +15292,7 @@ E77764B61FBDA8E30042541D /* WheelContainerDataSource.swift in Sources */, A4B544E820EFB15C00EB7B0F /* errors.swift in Sources */, A42D51B0206A361400EEB952 /* Profile.swift in Sources */, + 5E07BC4F216F659E000E4558 /* CreateProfileViewController.swift in Sources */, 85D669FF20BD963C00FBD803 /* InputScheduleMessage.swift in Sources */, 269D9DEE1FC3987200324263 /* URLExtensions.swift in Sources */, 3A19FEAD1F3B7F1D00ACE750 /* MessageHandler.swift in Sources */, @@ -15055,6 +15422,7 @@ 4B02130220372C5700650298 /* OtherItemView.swift in Sources */, 853801282052CCAD002C6960 /* SoundCellModel.swift in Sources */, A458FABB20EB87BF0075D55E /* ActionContainerContent.swift in Sources */, + 5EEB73DE21623FF900D8ECE6 /* UIViewControllerExtensions.swift in Sources */, 00E9824C205C1E19008BF03D /* SecurityItemsFactory.swift in Sources */, 26342CA920ECBAEF00D2196B /* TranscribeNetworkClient.swift in Sources */, 852003F620D4194A007C0036 /* DBRecentSticker.swift in Sources */, @@ -15096,7 +15464,9 @@ A43B259D20AB1DFA00FF8107 /* PhoneField.swift in Sources */, 267BE90820693DE700153FB8 /* DBManagerProtocol.swift in Sources */, 26342CAB20ECBB0100D2196B /* TranscribeNetworkService.swift in Sources */, + 5E07BC57216F6722000E4558 /* CreateProfileWireframe.swift in Sources */, A44B4D5820CE9BDF00CA700A /* AvatarCell.swift in Sources */, + 5E7E9FBC215BA19B004D306B /* Country.swift in Sources */, 4B8996F7204EF77100DCB183 /* FeedDAO.swift in Sources */, A418DA3820EE1AFD00FE780B /* CountAppearanceModel.swift in Sources */, B763DD9320AA1C3400A30B63 /* ContactCellLayout.swift in Sources */, @@ -15114,6 +15484,7 @@ 4B6D20E82164D4AB003ADB29 /* ProgressDisplayable.swift in Sources */, 3A1EB9A51F3A848A00658E93 /* HistoryHandler.swift in Sources */, 850FC5F42032F4CE00832D87 /* ForwardTargets.swift in Sources */, + 5E07BC4D216F64EC000E4558 /* CreateProfileProtocols.swift in Sources */, 85788C422044237B003600C9 /* BuildNumberViewController.swift in Sources */, A4679BA920B2DD100021FE9C /* SubscribersTableDelegate.swift in Sources */, 2648C41E2069B5B300863614 /* ChangeNumberItemsFactory.swift in Sources */, @@ -15130,6 +15501,7 @@ F11786CE20A8E4FD007A9A1B /* MuteState.swift in Sources */, 85D66A1120BD965300FBD803 /* UserMentionCellModel.swift in Sources */, 0062D9472062EC4100B915AC /* InviteFriendsViewController.swift in Sources */, + 5EEB73B821604DD900D8ECE6 /* CodeConfirmationInteractor.swift in Sources */, 4B752B512163A04800E852B9 /* Array+BaseChatCellModel.swift in Sources */, 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */, 85D66A2120BD970400FBD803 /* BBCodeEntity.swift in Sources */, @@ -15201,6 +15573,7 @@ 3A2171511EFB25C400F34B8B /* BaseVC.swift in Sources */, 8580BAD920BD98E700239D9D /* ChatListMessageTableViewCell.swift in Sources */, 26C0C1DF2073D9B600C530DA /* Desc+DB.swift in Sources */, + 5EEB73BA21604E2300D8ECE6 /* CodeConfirmationPresenter.swift in Sources */, 2648C4132069B52100863614 /* ChangeNumberCodeViewLayout.swift in Sources */, 005B0B242029AC14000D6416 /* SaveDeleteView.swift in Sources */, E23A0140D090ECB141911B57 /* TutorialPresenter.swift in Sources */, @@ -15248,9 +15621,11 @@ A43B25AD20AB1DFA00FF8107 /* MyTextField.swift in Sources */, 267BE90D2069413A00153FB8 /* Handlers.swift in Sources */, F10AFEB820F7B1B000C7CE83 /* WheelChatItemPreview.swift in Sources */, + 5E7E9FC2215BA681004D306B /* CountryTVCell.swift in Sources */, FBCE841320E525A6003B7558 /* NetworkRouter.swift in Sources */, A45F112420B4218D00F45004 /* MessageTextView.swift in Sources */, 85D66A0420BD963C00FBD803 /* MessagePayloadBuilder.swift in Sources */, + 5E0B9FF22170BCE600A95467 /* CreateProfileContentView.swift in Sources */, 004581212036073100F8E413 /* JobMessageTable.swift in Sources */, 8572C3B62092315B00E4840C /* CollectionViewDataProxy.swift in Sources */, A45F110520B4218D00F45004 /* DisplayChatConfiguration.swift in Sources */, @@ -15273,6 +15648,7 @@ A48C153F20EF765E002DA994 /* MQTTServiceLink.swift in Sources */, A408A0BC20C174040029F54B /* ChannelsListProtocols.swift in Sources */, 3A2374D91F262A1600701045 /* ContactHandler.swift in Sources */, + 5EEB73AA215D406400D8ECE6 /* AuthCoordinator.swift in Sources */, 6D5168A21F30430900DA3728 /* SpeakerView.swift in Sources */, 8503B529205046A6006F0593 /* NotificationSettingsWireFrame.swift in Sources */, FBD885782147F9640099B8C3 /* FontsConstants.swift in Sources */, @@ -15335,6 +15711,7 @@ F117FBD520FF9DAF00BA1F82 /* MediaInfoView.swift in Sources */, A45F114B20B421E400F45004 /* ChatModel.swift in Sources */, FB0AD86B20F3A07100F052CE /* ImagePreviewTransitionAnimatable.swift in Sources */, + 5EEB73C721619A5000D8ECE6 /* AuthWireframe.swift in Sources */, 005B0B222029ABDA000D6416 /* DateTimeItemView.swift in Sources */, 4B5A714D204F069000A551F5 /* ChatService.swift in Sources */, 3A0281F71F53794800206871 /* UIViewExtenstions.swift in Sources */, @@ -15345,6 +15722,7 @@ 852003FE20D46680007C0036 /* StickerPack.swift in Sources */, B723C636204DA56600884FFD /* SettingsDataAndStorageTableDelegate.swift in Sources */, 3AC07E3C1F055B3F00ADBE26 /* DoubleExtensions.swift in Sources */, + 5E7E9FC4215BA68E004D306B /* CountryTVHeader.swift in Sources */, 85579882209322A8007050B8 /* StickerMenuDataSource.swift in Sources */, 8506F001206BF5DA008B2D7F /* ChatPlaceholderWheelItemView.swift in Sources */, 85C16C3C20D261C000EDB77E /* MessageStickerView.swift in Sources */, @@ -15388,6 +15766,7 @@ 4B8996E4204EEC5A00DCB183 /* MessageDAOProtocol.swift in Sources */, E76491961F7A529D001E741C /* WheelContainer.swift in Sources */, 2648C4122069B52100863614 /* ChangeNumberStep3ViewController.swift in Sources */, + 5E07BC55216F66F3000E4558 /* CreateProfileViewsFactory.swift in Sources */, 8E9601931FF295DF00E0C21D /* ItemsSelector.swift in Sources */, A42D51C9206A361400EEB952 /* writer.swift in Sources */, 2648C3E62069B49000863614 /* UITextField+Extension.swift in Sources */, @@ -15425,12 +15804,14 @@ C940514C204C7FAF00D72B04 /* DataAndStorageInteractor.swift in Sources */, F1A9FA3590CC1F834B727955 /* AddContactPresenter.swift in Sources */, 6DD72F601F1547AC008CFF83 /* GCD.swift in Sources */, + 5E07BC3D216DFD08000E4558 /* AuthViewsFactory.swift in Sources */, A49CC1D820E4AB2C00879D41 /* DisplayModeConfigFactory.swift in Sources */, FEA655FC2167777F00B44029 /* TransferDetailsViewController.swift in Sources */, 859C429F2056829300AE3797 /* NotificationSettings.swift in Sources */, A45F115F20B422AF00F45004 /* Room+DB.swift in Sources */, 85380EE62109FF340048042D /* IdentifierComponentValue.swift in Sources */, F10AFEB920F7B1B000C7CE83 /* WheelMapItemPreview.swift in Sources */, + 5EEB73D42161D5C500D8ECE6 /* AuthHeaderView.swift in Sources */, 267BE2AE1FE13AB600C47E18 /* ParticipantsInteractor.swift in Sources */, 850C301F204DA87A00DB26C2 /* PrivacyListWireFrame.swift in Sources */, 858BC123203320BB0022EB25 /* ForwardSelectorDataSource.swift in Sources */, @@ -15450,11 +15831,13 @@ F11786CD20A8E4FD007A9A1B /* CameraVideoPreviewInteractor.swift in Sources */, A4679BBA20B305360021FE9C /* LinkValidator.swift in Sources */, 4BEE89D69CACB85ABEE9046F /* QRCodeGeneratorPresenter.swift in Sources */, + 5EEB73B621604CF600D8ECE6 /* CodeConfirmationWireframe.swift in Sources */, 2605311B212740FD002E1CF1 /* LogOutputProtocols.swift in Sources */, FBCE841420E525A6003B7558 /* NetworkService.swift in Sources */, A409B1CF2108D48E0051C20B /* QueryFactory.swift in Sources */, A42D52B7206A53AA00EEB952 /* reader_Spec.swift in Sources */, F119E66A20D24B960043A532 /* MultiplePreviewProtocols.swift in Sources */, + 5E07BC53216F6661000E4558 /* CreateProfilePresenter.swift in Sources */, 850FC611203312FA00832D87 /* ForwardSelectorViewControllerLayout.swift in Sources */, 0062D93D2062EC4100B915AC /* InviteFriendsWireframe.swift in Sources */, 9BD8E3F120EF7898001384EC /* CallInProgressViewController.swift in Sources */, @@ -15509,10 +15892,12 @@ F10B0E1720B4401500528E7A /* GalleryPresenter.swift in Sources */, B7EF8ED9210C71E800E0E981 /* InterpretationType.swift in Sources */, 6D5157D21F30B822002A27DB /* MicrophoneView.swift in Sources */, + 5EEB73D22161CEA100D8ECE6 /* LoginOption.swift in Sources */, 260313AF20A0A50D009AC66D /* TranslationService.swift in Sources */, A42D51AD206A361400EEB952 /* cur.swift in Sources */, 8504DEAB206937A2006722AC /* MediaFullWheelItemView.swift in Sources */, BF20ED73252DE6954B6CDCA8 /* QRCodeReaderViewController.swift in Sources */, + 5EEB73C92161CB8F00D8ECE6 /* AuthInteractor.swift in Sources */, 268C341C21075B4700F1472A /* Cancelable.swift in Sources */, 26541F722007B93400AAEACF /* DBMessageAction.swift in Sources */, 264638251FFFE78E002590E6 /* RepliesWireFrame.swift in Sources */, @@ -15552,6 +15937,7 @@ 859B863920486068003272B2 /* CarouselPickerViewControllerLayout.swift in Sources */, A43B25BF20AB1E9600FF8107 /* LengthInputValidator.swift in Sources */, 85D669E520BD956000FBD803 /* UIButtonExtensions.swift in Sources */, + 5EEB73A8215D00FD00D8ECE6 /* CountrySelectorPresenter.swift in Sources */, E74EC9ED1FC2DA6E007268E6 /* RoomTable.swift in Sources */, A42D52C8206A53AB00EEB952 /* Auth_Spec.swift in Sources */, 267BE90920693F4400153FB8 /* ProfileDAO.swift in Sources */, @@ -15624,11 +16010,13 @@ A42D52C4206A53AA00EEB952 /* error2_Spec.swift in Sources */, 85458CD9212D6FED00BA8814 /* String+Split.swift in Sources */, 00E9824E205C2604008BF03D /* SessionItemView.swift in Sources */, + 5EEB73D02161CE2700D8ECE6 /* LoginOptionsView.swift in Sources */, 8520040920D4F9B4007C0036 /* MessageStickerRepliedView.swift in Sources */, 00102F40202C8E5300A877A9 /* NynjaCalendarView.swift in Sources */, 85150C2620BE9EA3005D311A /* StickerDetailsPreviewView.swift in Sources */, 0062D9432062EC4100B915AC /* ShareNynjaHeaderViewLayout.swift in Sources */, A42D52B9206A53AA00EEB952 /* Profile_Spec.swift in Sources */, + 5EEB73A4215D00E300D8ECE6 /* CountrySelectorInteractor.swift in Sources */, 26DCB25420692237001EF0AB /* Array+Feature.swift in Sources */, F1607B2E20B2DE8A00BDF60A /* CameraQRPreviewInteractor.swift in Sources */, B77C11EA2109254800CCB42E /* InterpretationTypeWireFrame.swift in Sources */, @@ -15787,6 +16175,7 @@ 8526187C20D05BF700824357 /* StickerGridPlaceholderCollectionViewCell.swift in Sources */, 26B32B601FE170FE00888A0A /* MigrationManager.swift in Sources */, 986BE2204D6D0813B13618B1 /* AddContactViaPhonePresenter.swift in Sources */, + 5E7E9FB9215BA0BE004D306B /* CountrySelectorProtocols.swift in Sources */, 265AEA171FE9AFD400AC4806 /* MemberModel.swift in Sources */, 2605311D21274116002E1CF1 /* LogOutputView.swift in Sources */, 263529152075729400DC6FBD /* Job+DB.swift in Sources */, @@ -15802,11 +16191,13 @@ A407348C20B712E9005762D5 /* UIView+Hierarchy.swift in Sources */, F117870F20ACF018007A9A1B /* CameraQualitySettingsViewController.swift in Sources */, E764919B1F7A5485001E741C /* MainWheelContainerDelegate.swift in Sources */, + 5EEB73A6215D00F100D8ECE6 /* CountrySelectorWireframe.swift in Sources */, A4330A6A2109EA850060BD93 /* DatabaseManager.swift in Sources */, 8E6C4BDE1FF40B97009C8374 /* GroupFilesCell.swift in Sources */, 26DCB24E2064B9DC001EF0AB /* ContactsTableDS.swift in Sources */, A45F114820B421AB00F45004 /* Contact+BaseChatModel.swift in Sources */, 6CED2C4CE125011A3A731D62 /* AddContactViaPhoneInteractor.swift in Sources */, + 5EEB73C5216199ED00D8ECE6 /* AuthProtocols.swift in Sources */, 260313AA20A0A4BA009AC66D /* ChatLanguageSettingsViewController.swift in Sources */, 859B862F204820DC003272B2 /* ThemePickerInteractor.swift in Sources */, 263A60AC1FB4F8F7006F9D52 /* ParticipantsDataSource.swift in Sources */, @@ -15914,6 +16305,7 @@ 265F5D25209B6987008ACCC8 /* LocationType.swift in Sources */, 4B2D063A202DDA2000010A0C /* BackSwipable.swift in Sources */, 4BE2C5DD2142EAC500A73DD9 /* SystemSoundManager.swift in Sources */, + 5E07BC40216E09F0000E4558 /* CodeConfirmationViewsFactory.swift in Sources */, F10AFEBC20F7B1D200C7CE83 /* WheelPreviewFactory.swift in Sources */, 4B3F055F2043F871002E0F54 /* ScheduleMessageConfiguration.swift in Sources */, A42D51A1206A361400EEB952 /* Friend.swift in Sources */, @@ -15946,6 +16338,7 @@ F10AFEB520F7B1B000C7CE83 /* WheelDefaultItemPreview.swift in Sources */, 858A72E5A4AE48CE24AFF649 /* MainInteractor.swift in Sources */, 8EC767DA200782CB00655F80 /* GroupImagesListVC.swift in Sources */, + 5EEB73BD2161797900D8ECE6 /* Result.swift in Sources */, E7A77FD81FACC360004AE609 /* UIDeviceExtension.swift in Sources */, 3AE0A8431F20321A008A04F3 /* CountryWheelItemView.swift in Sources */, A42D52D5206A53AB00EEB952 /* sequenceFlow_Spec.swift in Sources */, @@ -16035,6 +16428,7 @@ A432CF1620B4347D00993AFB /* FloatingPlaceholderProvider.swift in Sources */, E7302A931FC83477002892F8 /* Desc+DescMime.swift in Sources */, 8509FC7B2158CCA800734D93 /* MessageInteractor+Reply.swift in Sources */, + 5E7E9FBE215BA51C004D306B /* CountrySelectorViewController.swift in Sources */, A42D51CB206A361400EEB952 /* Job.swift in Sources */, E7F68D271FA22C45009C98D1 /* EditProfileVCStrings.swift in Sources */, 8ECC06801FC5C80C002CF225 /* MessagesProcessingManager.swift in Sources */, @@ -16053,6 +16447,7 @@ 9B96709E215151D20058E98F /* LeaveVoiceMessageWireFrame.swift in Sources */, 4B8996F2204EF5E900DCB183 /* ChatCheckpointDAO.swift in Sources */, 265F5D2E209B8C1C008ACCC8 /* MessageEditActionDAO.swift in Sources */, + 5EEB73B2216046FE00D8ECE6 /* CodeConfirmationProtocols.swift in Sources */, 853FB0772049B7CA000996C5 /* TextCellViewModel.swift in Sources */, 5BC1D38120D3B54B002A44B3 /* CallInfoView.swift in Sources */, 264638291FFFE835002590E6 /* RepliesInteractor.swift in Sources */, @@ -16113,6 +16508,7 @@ 0062D93B2062EC4100B915AC /* PhoneContact.swift in Sources */, 85433F24204D596D00B373A7 /* WebFullScreenProtocols.swift in Sources */, 4BAB9CE42035CB0A00385520 /* ScheduleTarget.swift in Sources */, + 5EEB73CD2161CC8A00D8ECE6 /* AuthViewController.swift in Sources */, 8514F17B20EA219F00883513 /* ContextMenuConfiguration+GroupStorage.swift in Sources */, CC59F623F661C99492F9F415 /* ImagePreviewProtocols.swift in Sources */, E77764B41FBDA8B50042541D /* WheelContainerDelegate.swift in Sources */, @@ -16303,8 +16699,11 @@ FBCE841220E525A6003B7558 /* NetworkClient.swift in Sources */, 7A8FE56A8E5D02256D8BE936 /* EditPhotoPresenter.swift in Sources */, E79061B61FBF1C8C009FD83A /* DescTable.swift in Sources */, + 5EEB73D62161DBF100D8ECE6 /* EmailLoginView.swift in Sources */, + 5EEB73B4216047E000D8ECE6 /* CodeConfirmationViewController.swift in Sources */, 2910A0129CA29C35161DD692 /* EditPhotoInteractor.swift in Sources */, 705B483A1FCDEA2273CEFE2C /* EditPhotoWireframe.swift in Sources */, + 5EEB73D82162227B00D8ECE6 /* PhoneNumberLoginView.swift in Sources */, A43B25A420AB1DFA00FF8107 /* TextField.swift in Sources */, E743B58F1FB0A32700F72F92 /* ParticipantsHeaderView.swift in Sources */, A4679BAA20B2DD100021FE9C /* SubscribersTableDataSource.swift in Sources */, @@ -16382,6 +16781,7 @@ C9B8BEFC204DDDA20018748C /* CheckmarkCellLayout.swift in Sources */, 4B8BEDE1204979AA00C7D625 /* ImagesView.swift in Sources */, A45F113720B4218D00F45004 /* ReplyPreview.swift in Sources */, + 5E07BC51216F6617000E4558 /* CreateProfileInteractor.swift in Sources */, A481BD1C20EE72CB008FFED8 /* ReplyCounterDelegate.swift in Sources */, A42D52D4206A53AB00EEB952 /* Message_Spec.swift in Sources */, 26C0C1E22073DA2E00C530DA /* P2P+DB.swift in Sources */, @@ -16586,6 +16986,7 @@ CA6AA942773DEBE97BDCFDD6 /* DateTimePickerViewController.swift in Sources */, 24AC9EAFA26353C7B95B60BF /* DateTimePickerPresenter.swift in Sources */, 85788C48204423A4003600C9 /* BuildNumberWireFrame.swift in Sources */, + 5EEB73CB2161CBF300D8ECE6 /* AuthPresenter.swift in Sources */, 5894F4C605B66B55F21D406E /* DateTimePickerInteractor.swift in Sources */, 8514DE912136A9CB00718DD8 /* StarActionDAOProtocol.swift in Sources */, 00E98254205C2726008BF03D /* SessionFooterView.swift in Sources */, diff --git a/Nynja/AppDelegate.swift b/Nynja/AppDelegate.swift index 332ecf388e8093ea60d5c0bc18587aecc72f37e8..63e768b9d12933a065af4c501cd0f6cbb38a6ec9 100644 --- a/Nynja/AppDelegate.swift +++ b/Nynja/AppDelegate.swift @@ -105,9 +105,12 @@ private extension AppDelegate { self.window = UIWindow(frame: UIScreen.main.bounds) let navigation = UINavigationController() navigation.isNavigationBarHidden = true - SplashWireFrame().presentSplash(navigation: navigation) +// SplashWireFrame().presentSplash(navigation: navigation) + let coordinator = AuthCoordinator(navigation: navigation, serviceFactory: ServiceFactory()) self.window?.rootViewController = navigation self.window?.makeKeyAndVisible() + + coordinator.start() } private func configureDependencies() { @@ -181,3 +184,293 @@ private extension AppDelegate { Intercom.setApiKey(intercomServiceConfig.apiKey, forAppId: intercomServiceConfig.appId) } } + + + + + + +//// MARK: - Service factory +// +//protocol ServiceFactoryProtocol { +// func makeService1(/*Some parameters*/) -> Service1 +// func makeService2(/*Some parameters*/) -> Service2 +// // Another services +//} +// +//final class ServiceFactory: ServiceFactoryProtocol { +// func makeService1(/*Some parameters*/) -> Service1 { +// return Service1.shared +// } +// +// func makeService2(/*Some parameters*/) -> Service2 { +// let service = Service2(/*Some parameters*/) +// return service2 +// } +// +// // Another services +//} +// +//// MARK: - Modules stack +// +//protocol ModulesStackProtocol { +// func present(view: UIViewController) +// func back() +// func close() +//} +// +//final class ModulesStack: ModulesStackProtocol { +// private weak var navigationController: UINavigationController? +// private var viewControllers: [UIViewController] +// +// init(navigationController: UINavigationController?) { +// self.navigationController = navigationController +// viewControllers = [] +// } +// +// func present(view: UIViewController) { +// // Some code for presenting +// } +// +// func back() { +// // Some code for popping +// } +// +// func close() { +// // Some code for closing all stack +// } +//} +// +//// MARK: - Coordinators factory +// +//protocol CoordinatorsFactoryProtocol { +// func makeCoordinator1() -> CoordinatorProtocol +// func makeCoordinator2(/*Some parameters*/) -> CoordinatorProtocol +// // Another coordinators +//} +// +//final class CoordinatorsFactory: CoordinatorsFactoryProtocol { +// func makeCoordinator1() -> CoordinatorProtocol { +// return Coordinator1() +// } +// +// func makeCoordinator2(/*Some parameters*/) -> CoordinatorProtocol { +// return Coordinator2(/*Some parameters*/) +// } +// +// // Another coordinators +//} +// +//// MARK: - Coordinators stack +// +//protocol CoordinatorsStackProtocol { +// +//} +// +//final class CoordinatorsStack: CoordinatorsStackProtocol { +// +//} +// +//// MARK: - AppCoordinator +// +//protocol AppCoordinatorProtocol { +// +//} +// +//final class AppCoordinator: AppCoordinatorProtocol { +// +//} +// +//// MARK: - Coordinator +// +//protocol CoordinatorProtocol { +// func start() +// func end() +//} +// +//final class Coordinator: CoordinatorProtocol, WireframeCoordinatorProtocol { +// private weak var stack: ModulesStackProtocol +// private let serviceFactory: ServiceFactoryProtocol +// // Some properties +// +// init(stack: ModulesStackProtocol, serviceFactory: ServiceFactoryProtocol/*, Some parameters*/) { +// self.stack = stack +// } +// +// func start() { +// let wireframe = Wireframe(coordinator: self) +// let parameters = Wireframe.Parameters(someParameter: someParameter) +// let dependencies = Wireframe.Dependencies(someService1: serviceFactory.makeSomeService1) +// +// let view = wireframe.prepareModule(parameters: parameters, dependencies: dependencies) +// stack.present(view: view) +// } +// +// func end() { +// stack.close() +// // Some code +// } +// +// func wireframe(_ wireframe: Wireframe, finishedWithState state: Wireframe.State) { +// switch state { +// case .stateForOpen1: // Some code +// break +// case .stateForOpen2(/*Some parameters*/): stack.back() +// break +// } +// } +//} +// +//// MARK: - Wireframe +// +//protocol WireframeCoordinatorProtocol { +// func wireframe(_ wireframe: Wireframe, finishedWithState state: Wireframe.State) +//} +// +//protocol WireframeProtocol { +// associatedtype Parameters +// associatedtype Dependencies +// associatedtype State +// +// func prepareModule(parameters: Parameters, dependencies: Dependencies) -> UIViewController +// func open1(/*Some parameters*/) +// func open2(/*Some parameters*/) +//} +// +//final class Wireframe: WireframeProtocol { +// struct Parameters { +// let someParameter: SomeType +// // Another parameters +// } +// +// struct Dependencies { +// let someService1: SomeService1 +// // Another dependencies +// } +// +// enum State { +// case stateForOpen1 +// case stateForOpen2(/*Some parameters*/) +// } +// +// private let coordinator: WireframeCoordinatorProtocol +// +// init(coordinator: WireframeCoordinatorProtocol) { +// self.coordinator = coordinator +// } +// +// func prepareModule(parameters: Parameters, dependencies: Dependencies) -> UIViewController { +// let view = View() +// let presenter = Presenter() +// let interactor = Interactor() +// +// let viewDependencies = View.Dependencies(presenter: presenter, someService1: dependencies.makeService1) +// let interactorDependencies = Interactor.Dependencies(presenter: presenter, someService1: dependencies.makeService1) +// let presenterDependencies = Presenter.Dependencies(interactor: interactor, someService1: dependencies.makeService1) +// +// // set some parameters from Parameters structure +// +// view.inject(viewDependencies) +// presenter.inject(presenterDependencies) +// interactor.inject(interactorDependencies) +// +// return view +// } +// +// func open1(/*Some parameters*/) { +// coordinator.wireframe(self, finishedWithState: .stateForOpen1) +// } +// +// func open2(/*Some parameters*/) { +// coordinator.wireframe(self, finishedWithState: .stateForOpen2(/*Some parameters*/)) +// } +//} +// +//// MARK: - Presenter +// +//protocol PresenterProtocol: SetInjectable { +// func someMethod1() +//} +// +//final class Presenter: PresenterProtocol { +// private let interactor: InteractorProtocol +// private weak var view: ViewProtocol? +// private let someService: SomeService1 +// // Another properties +// +// struct Dependencies { +// let interactor: InteractorProtocol +// let view: ViewProtocol +// let someService1: SomeService1 +// // Another dependencies +// } +// +// func inject(dependencies: Presenter.Dependencies) { +// interactor = dependencies.interactor +// view = dependencies.view +// someService = dependencies.someService1 +// // Another dependencies +// } +// +// func someMethod1() { +// // Some code +// } +//} +// +//// MARK: - Interactor +// +//protocol InteractorProtocol: SetInjectable { +// func someMethod1() +//} +// +//final class Interactor: InteractorProtocol { +// private weak var presenter: PresenterProtocol? +// private let someService1: SomeService1 +// // Another properties +// +// struct Dependencies { +// let presenter: PresenterProtocol +// let someService1: SomeService1 +// // Another dependencies +// } +// +// func inject(dependencies: Interactor.Dependencies) { +// presenter = dependencies.presenter +// someService = dependencies.someService1 +// // Another dependencies +// } +// +// func someMethod1() { +// // Some code +// } +//} +// +//// MARK: - View +// +//protocol ViewProtocol: SetInjectable { +// func someMethod1() +//} +// +//final class View: ViewProtocol { +// private weak var presenter: PresenterProtocol? +// private let someService1: SomeService1 +// // Another properties +// +// struct Dependencies { +// let presenter: PresenterProtocol +// let someService1: SomeService1 +// // Another dependencies +// } +// +// func inject(dependencies: View.Dependencies) { +// presenter = dependencies.presenter +// someService = dependencies.someService1 +// // Another dependencies +// } +// +// func someMethod1() { +// // Some code +// } +//} + + diff --git a/Nynja/CountriesProvider.swift b/Nynja/CountriesProvider.swift index e00258d91f1534e92b76721324904e1c7a101114..061db9e77611b52453ebf7ec5efb51b942882f71 100644 --- a/Nynja/CountriesProvider.swift +++ b/Nynja/CountriesProvider.swift @@ -10,6 +10,7 @@ import Foundation final class CountriesProvider: CountriesProviding { + // FIXME: return array of Country func fetchCountries() -> [CountryModel] { let path = Bundle.main.path(forResource: "countries", ofType: "txt")! guard let text = try? String(contentsOfFile: path, encoding: .utf8) else { @@ -22,4 +23,13 @@ final class CountriesProvider: CountriesProviding { .filter { !$0.name.isEmpty } .sorted { $0.name > $1.name } } + + func fetchDefaultCountry() -> Country { + let countries = fetchCountries().map { + // FIXME: + Country(ISO: $0.ISO, name: $0.name, code: $0.code, numberTemplate: $0.placeHolder ?? "") + } + let code = (NSLocale.current.regionCode ?? countries.last?.code)?.replacingOccurrences(of: "+", with: "") + return countries.first { $0.code == code || $0.ISO == code } ?? countries.last! + } } diff --git a/Nynja/CountriesProviding.swift b/Nynja/CountriesProviding.swift index 72e3338030ccbf9a04d06a5ed0de4acc438203a8..42f966b20bbb0dbcdf4c348de29cbbe750f3761d 100644 --- a/Nynja/CountriesProviding.swift +++ b/Nynja/CountriesProviding.swift @@ -8,4 +8,5 @@ protocol CountriesProviding { func fetchCountries() -> [CountryModel] + func fetchDefaultCountry() -> Country } diff --git a/Nynja/Extensions/SwiftLibrary/Array/ArrayExtension.swift b/Nynja/Extensions/SwiftLibrary/Array/ArrayExtension.swift index ae063c708a8b60fe0276084784ca5fcc8bda805a..4c3fd5ae17b8ae53b7be980e82c15526cd4e5912 100644 --- a/Nynja/Extensions/SwiftLibrary/Array/ArrayExtension.swift +++ b/Nynja/Extensions/SwiftLibrary/Array/ArrayExtension.swift @@ -62,3 +62,34 @@ extension Array { } } + +extension Array where Element : Equatable { + func next(after element: Element) -> Element? { + guard let indexOfCurrent = index(of: element) else { + return nil + } + + let indexForNewElement = index(after: indexOfCurrent) + + if indexForNewElement <= count - 1 { + return self[indexForNewElement] + } else { + return nil + } + + } + + func previous(before element: Element) -> Element? { + guard let indexOfCurrent = index(of: element) else { + return nil + } + + let indexForNewElement = index(before: indexOfCurrent) + + if indexForNewElement >= 0 { + return self[indexForNewElement] + } else { + return nil + } + } +} diff --git a/Nynja/Generated/AssetsConstants.swift b/Nynja/Generated/AssetsConstants.swift index 78d60956439b5eb9980280a4463adf34bed3dd02..39b965e470654a123b3627c7e3fad1a806cde8df 100644 --- a/Nynja/Generated/AssetsConstants.swift +++ b/Nynja/Generated/AssetsConstants.swift @@ -126,10 +126,16 @@ internal extension Image { static var group1: ImageAsset { return ImageAsset(name: "Group_1") } /// "Group_2" static var group2: ImageAsset { return ImageAsset(name: "Group_2") } + /// "Icons_General_ic_accept_call" + static var iconsGeneralIcAcceptCall: ImageAsset { return ImageAsset(name: "Icons_General_ic_accept_call") } /// "Icons_General_ic_close" static var iconsGeneralIcClose: ImageAsset { return ImageAsset(name: "Icons_General_ic_close") } + /// "Icons_General_ic_email" + static var iconsGeneralIcEmail: ImageAsset { return ImageAsset(name: "Icons_General_ic_email") } /// "Icons_General_ic_eye" static var iconsGeneralIcEye: ImageAsset { return ImageAsset(name: "Icons_General_ic_eye") } + /// "Icons_General_ic_google" + static var iconsGeneralIcGoogle: ImageAsset { return ImageAsset(name: "Icons_General_ic_google") } /// "Icons_General_ic_grey_close" static var iconsGeneralIcGreyClose: ImageAsset { return ImageAsset(name: "Icons_General_ic_grey_close") } /// "Icons_General_ic_translate" @@ -431,8 +437,6 @@ internal extension Image { static var messageBttn: ImageAsset { return ImageAsset(name: "message-bttn") } /// "microphone-bttn" static var microphoneBttn: ImageAsset { return ImageAsset(name: "microphone-bttn") } - /// "next-bttn" - static var nextBttn: ImageAsset { return ImageAsset(name: "next-bttn") } /// "outgoing_dark" static var outgoingDark: ImageAsset { return ImageAsset(name: "outgoing_dark") } /// "outgoing_light" @@ -443,8 +447,6 @@ internal extension Image { static var outgoingVideoLight: ImageAsset { return ImageAsset(name: "outgoing_video_light") } /// "phone-number" static var phoneNumber: ImageAsset { return ImageAsset(name: "phone-number") } - /// "qr-code" - static var qrCode: ImageAsset { return ImageAsset(name: "qr-code") } /// "rec-bar" static var recBar: ImageAsset { return ImageAsset(name: "rec-bar") } /// "rec-process" @@ -626,12 +628,6 @@ internal extension Image { static var wheelRightImage: ImageAsset { return ImageAsset(name: "wheel_right_image") } } } - enum WheelPosition { - /// "wheel_left_image" - static var wheelLeftImage: ImageAsset { return ImageAsset(name: "wheel_left_image") } - /// "wheel_right_image" - static var wheelRightImage: ImageAsset { return ImageAsset(name: "wheel_right_image") } - } /// "arrow_collapse" static var arrowCollapse: ImageAsset { return ImageAsset(name: "arrow_collapse") } /// "arrow_expand" @@ -686,8 +682,6 @@ internal extension Image { static var icBackNavigation: ImageAsset { return ImageAsset(name: "ic_back_navigation") } /// "ic_bottom_arrow" static var icBottomArrow: ImageAsset { return ImageAsset(name: "ic_bottom_arrow") } - /// "ic_camera_frame" - static var icCameraFrame: ImageAsset { return ImageAsset(name: "ic_camera_frame") } /// "ic_change_camera_ios" static var icChangeCameraIos: ImageAsset { return ImageAsset(name: "ic_change_camera_ios") } /// "ic_checkmark_red" @@ -708,6 +702,10 @@ internal extension Image { static var icEditDone: ImageAsset { return ImageAsset(name: "ic_edit_done") } /// "ic_email_storage" static var icEmailStorage: ImageAsset { return ImageAsset(name: "ic_email_storage") } + /// "ic_empty_avatar" + static var icEmptyAvatar: ImageAsset { return ImageAsset(name: "ic_empty_avatar") } + /// "ic_facebook" + static var icFacebook: ImageAsset { return ImageAsset(name: "ic_facebook") } /// "ic_flashlight_auto" static var icFlashlightAuto: ImageAsset { return ImageAsset(name: "ic_flashlight_auto") } /// "ic_flashlight_off" @@ -738,8 +736,6 @@ internal extension Image { static var icMic: ImageAsset { return ImageAsset(name: "ic_mic") } /// "ic_mic_dark_gray" static var icMicDarkGray: ImageAsset { return ImageAsset(name: "ic_mic_dark_gray") } - /// "ic_new_group" - static var icNewGroup: ImageAsset { return ImageAsset(name: "ic_new_group") } /// "ic_participants_search" static var icParticipantsSearch: ImageAsset { return ImageAsset(name: "ic_participants_search") } /// "ic_phone_storage" @@ -752,8 +748,6 @@ internal extension Image { static var icQrCode: ImageAsset { return ImageAsset(name: "ic_qr_code") } /// "ic_scheduled_msg_check" static var icScheduledMsgCheck: ImageAsset { return ImageAsset(name: "ic_scheduled_msg_check") } - /// "ic_search" - static var icSearch: ImageAsset { return ImageAsset(name: "ic_search") } /// "ic_search_empty" static var icSearchEmpty: ImageAsset { return ImageAsset(name: "ic_search_empty") } /// "ic_send_as_file" @@ -846,6 +840,8 @@ internal extension Image { static var leftButton: ImageAsset { return ImageAsset(name: "left_button") } /// "lock" static var lock: ImageAsset { return ImageAsset(name: "lock") } + /// "logo-2" + static var logo2: ImageAsset { return ImageAsset(name: "logo-2") } /// "maximaze" static var maximaze: ImageAsset { return ImageAsset(name: "maximaze") } /// "minimaze" diff --git a/Nynja/Library/Result/Result.swift b/Nynja/Library/Result/Result.swift new file mode 100644 index 0000000000000000000000000000000000000000..e5d11afa5f7c814a5bdb98e8c355a2babfbc6217 --- /dev/null +++ b/Nynja/Library/Result/Result.swift @@ -0,0 +1,154 @@ +// +// Result.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +import Foundation + + +public enum Result { + case success(Value) + case failure(Error) + + public var isSuccess: Bool { + switch self { + case .success: + return true + case .failure: + return false + } + } + + public var isFailure: Bool { + return !isSuccess + } + + public var value: Value? { + switch self { + case .success(let value): + return value + case .failure: + return nil + } + } + + public var error: Error? { + switch self { + case .success: + return nil + case .failure(let error): + return error + } + } +} + +// MARK: - CustomStringConvertible + +extension Result: CustomStringConvertible { + public var description: String { + switch self { + case .success: + return "SUCCESS" + case .failure: + return "FAILURE" + } + } +} + +// MARK: - CustomDebugStringConvertible + +extension Result: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .success(let value): + return "SUCCESS: \(value)" + case .failure(let error): + return "FAILURE: \(error)" + } + } +} + +// MARK: - Functional APIs + +extension Result { + public init(value: () throws -> Value) { + do { + self = try .success(value()) + } catch { + self = .failure(error) + } + } + + public func unwrap() throws -> Value { + switch self { + case .success(let value): + return value + case .failure(let error): + throw error + } + } + + public func map(_ transform: (Value) -> T) -> Result { + switch self { + case .success(let value): + return .success(transform(value)) + case .failure(let error): + return .failure(error) + } + } + + public func flatMap(_ transform: (Value) throws -> T) -> Result { + switch self { + case .success(let value): + do { + return try .success(transform(value)) + } catch { + return .failure(error) + } + case .failure(let error): + return .failure(error) + } + } + + public func mapError(_ transform: (Error) -> T) -> Result { + switch self { + case .failure(let error): + return .failure(transform(error)) + case .success: + return self + } + } + + public func flatMapError(_ transform: (Error) throws -> T) -> Result { + switch self { + case .failure(let error): + do { + return try .failure(transform(error)) + } catch { + return .failure(error) + } + case .success: + return self + } + } + + @discardableResult + public func onSuccess(_ closure: (Value) -> Void) -> Result { + if case let .success(value) = self { closure(value) } + + return self + } + + @discardableResult + public func onFailure(_ closure: (Error) -> Void) -> Result { + if case let .failure(error) = self { closure(error) } + + return self + } +} diff --git a/Nynja/Library/UI/Extensions/UI/UIImageView/UIImageExtensions.swift b/Nynja/Library/UI/Extensions/UI/UIImageView/UIImageExtensions.swift index c8be625099afa7ae4530ebb7c55fac283d067abe..226efceee1472e3c313cdd75ea632dee73367f99 100644 --- a/Nynja/Library/UI/Extensions/UI/UIImageView/UIImageExtensions.swift +++ b/Nynja/Library/UI/Extensions/UI/UIImageView/UIImageExtensions.swift @@ -217,4 +217,22 @@ extension UIImage { return croppedImage } + + static func makeImageFromColor(_ color: UIColor) -> UIImage? { + let rect = CGRect(x: 0, y: 0, width: 1, height: 1) + + UIGraphicsBeginImageContext(rect.size) + + guard let context = UIGraphicsGetCurrentContext() else { + return nil + } + + context.setFillColor(color.cgColor) + context.fill(rect) + + let img = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return img + } } diff --git a/Nynja/Library/UI/TextInput/Material/Base/MaterialTextContainer.swift b/Nynja/Library/UI/TextInput/Material/Base/MaterialTextContainer.swift index f7ba3eecabfb43801890b2ff841ab4fb687d8920..917623cf6c256fd8e1eb4e91fb24cfdaa04ea1d5 100644 --- a/Nynja/Library/UI/TextInput/Material/Base/MaterialTextContainer.swift +++ b/Nynja/Library/UI/TextInput/Material/Base/MaterialTextContainer.swift @@ -38,6 +38,8 @@ class MaterialTextContainer: BaseView, MaterialTextInput { var textColor: UIColor = defaultConfig.textColor var cursorColor: UIColor = defaultConfig.cursorColor var keyboardType: UIKeyboardType = defaultConfig.keyboardType + var keyboardAppearance: UIKeyboardAppearance = defaultConfig.keyboardAppearance + var returnKeyType: UIReturnKeyType = defaultConfig.returnKeyType var isSecureTextEntry: Bool = defaultConfig.isSecureTextEntry var activeSeparatorHeight: CGFloat = defaultConfig.activeSeparatorHeight { diff --git a/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift b/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift index ff8a5dcdab510b259b4d27478d2932fed811b8ba..aac04a918202a570a68565b7f5cd3c391c5897ee 100644 --- a/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift +++ b/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift @@ -17,6 +17,8 @@ struct NynjaMTIConfig: MTIConfigProtocol { let cursorColor: UIColor = defaultColor let keyboardType: UIKeyboardType = .default + let keyboardAppearance: UIKeyboardAppearance = .default + let returnKeyType: UIReturnKeyType = .default let isSecureTextEntry = false let placeholderFont: UIFont = defaultFont diff --git a/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift b/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift index b11f96c41360045fba6ebd99b706cbaf9772fc42..76b86dc89885cb6e26954dcf02ce439df22cb4d7 100644 --- a/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift +++ b/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift @@ -32,6 +32,14 @@ class MaterialTextField: MaterialTextContainer { override var keyboardType: UIKeyboardType { didSet { textField.keyboardType = keyboardType } } + + override var keyboardAppearance: UIKeyboardAppearance { + didSet { textField.keyboardAppearance = keyboardAppearance } + } + + override var returnKeyType: UIReturnKeyType { + didSet { textField.returnKeyType = returnKeyType } + } override var isSecureTextEntry: Bool { didSet { textField.isSecureTextEntry = isSecureTextEntry } @@ -52,10 +60,9 @@ class MaterialTextField: MaterialTextContainer { var autocapitalizationType: UITextAutocapitalizationType = UITextAutocapitalizationType.sentences { didSet { textField.autocapitalizationType = autocapitalizationType } } - - func setTextFieldFirstResponder() { - textField.becomeFirstResponder() - } + + var returnHandler: ((MaterialTextField) -> Bool)? + // MARK: - Views @@ -87,9 +94,13 @@ class MaterialTextField: MaterialTextContainer { } override func becomeFirstResponder() -> Bool { - return self.textField.becomeFirstResponder() + return textField.becomeFirstResponder() } + override func resignFirstResponder() -> Bool { + return textField.resignFirstResponder() + } + // MARK: - Actions @@ -110,6 +121,10 @@ extension MaterialTextField: UITextFieldDelegate { endEditingText() } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + return returnHandler?(self) ?? false + } + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { return shouldTextChanged?(self, range, string) ?? true } diff --git a/Nynja/Library/UI/UIViewControllerExtensions/UIViewControllerExtensions.swift b/Nynja/Library/UI/UIViewControllerExtensions/UIViewControllerExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..2e0a9e8682331ee0613446bc3271700dfcd91a9b --- /dev/null +++ b/Nynja/Library/UI/UIViewControllerExtensions/UIViewControllerExtensions.swift @@ -0,0 +1,25 @@ +// +// UIViewControllerExtensions.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +extension UIViewController { + func enableKeyboardHidingWhenTappedAround() { + let tap: UITapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(UIViewController.dismissKeyboard)) + + tap.cancelsTouchesInView = false + view.addGestureRecognizer(tap) + } + + @objc func dismissKeyboard() { + view.endEditing(true) + } +} diff --git a/Nynja/Library/UI/View/UIViewExtensions.swift b/Nynja/Library/UI/View/UIViewExtensions.swift index e91e2edc91e14aaeb8ffd97a1001456cea15229e..54c8edd1d4afb605f5858af35d9112a6c04cd6da 100644 --- a/Nynja/Library/UI/View/UIViewExtensions.swift +++ b/Nynja/Library/UI/View/UIViewExtensions.swift @@ -11,6 +11,17 @@ import UIKit // MARK: - Factory methods extension UIView { + func appendBottomBorder(color: UIColor, width: CGFloat) { + let view = UIView() + view.backgroundColor = color + addSubview(view) + + view.snp.makeConstraints { (make) in + make.bottom.left.right.equalToSuperview() + make.height.equalTo(width) + } + } + static func makeStatusBarBackgroundView(on view: UIView) -> UIView { struct StatusBarBackgroundLayout { static let height = 20 @@ -31,21 +42,50 @@ extension UIView { return background } + + static func makeHeaderView(on view: UIView, config: NavigationView.Config) -> NavigationView { + let navigationView = makeHeaderViewWithoutConstraints(on: view, config: config) + + navigationView.snp.makeConstraints { (maker) in + maker.top.equalToSuperview() + maker.left.right.equalToSuperview() + } + + return navigationView + } static func makeHeaderView(on view: UIView, top: UIView, config: NavigationView.Config) -> NavigationView { - let navigationView = NavigationView() - - navigationView.configure(config: config) - - view.addSubview(navigationView) - - navigationView.backgroundColor = UIColor.nynja.clear + let navigationView = makeHeaderViewWithoutConstraints(on: view, config: config) navigationView.snp.makeConstraints { (maker) in maker.top.equalTo(top.snp.bottom) + maker.left.right.equalToSuperview() } - + + return navigationView + } + + static func makeHeaderView(on view: UIView, top: UILayoutGuide, config: NavigationView.Config) -> NavigationView { + let navigationView = makeHeaderViewWithoutConstraints(on: view, config: config) + + navigationView.snp.makeConstraints { (maker) in + maker.top.equalTo(top.snp.bottom) + maker.left.right.equalToSuperview() + } + + return navigationView + } + + private static func makeHeaderViewWithoutConstraints(on view: UIView, config: NavigationView.Config) -> NavigationView { + let navigationView = NavigationView() + + navigationView.configure(config: config) + + view.addSubview(navigationView) + + navigationView.backgroundColor = UIColor.nynja.darkLight + return navigationView } diff --git a/Nynja/Modules/Auth/AuthCoordinator.swift b/Nynja/Modules/Auth/AuthCoordinator.swift new file mode 100644 index 0000000000000000000000000000000000000000..4a4a94d24b5f8db148629ea757ced9d30acd5de2 --- /dev/null +++ b/Nynja/Modules/Auth/AuthCoordinator.swift @@ -0,0 +1,164 @@ +// +// AuthCoordinator.swift +// Nynja +// +// Created by Ash on 9/27/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation +import SDWebImage + +final class AuthCoordinator: CoordinatorProtocol, CountrySelectorCoordinatorProtocol, CodeConfirmationCoordinatorProtocol, AuthCoordinatorProtocol, CreateProfileCoordinatorProtocol { + private weak var navigation: UINavigationController? + private let serviceFactory: ServiceFactoryProtocol + + private var selectCountryCallback: ((Result) -> Void)? + + init(navigation: UINavigationController, serviceFactory: ServiceFactoryProtocol) { + self.navigation = navigation + self.serviceFactory = serviceFactory + } + + func start() { + let wireframe = AuthWireframe(coordinator: self) + let view = wireframe.prepareModule( + parameters: NSNull(), + dependencies: AuthWireframe.Dependencies(countriesProvider: serviceFactory.makeCountriesProvider()) + ) + navigation?.pushViewController(view, animated: true) + } + + func end() { + + } +} + +// MARK: - CountrySelectorCoordinatorProtocol + +extension AuthCoordinator { + func wireframe(_ wireframe: CountrySelectorWireframe, endWithState state: CountrySelectorWireframe.State) { + switch state { + case .endWith(let country): selectCountryCallback?(.success(country)) + case .back: break + } + + navigation?.popViewController(animated: true) + } +} + +// MARK: - CodeConfirmationCoordinatorProtocol + +extension AuthCoordinator { + func wireframe(_ wireframe: CodeConfirmationWireframe, didEndWith state: CodeConfirmationWireframe.State) { + switch state { + case .back: navigation?.popViewController(animated: true) + case .invalidCode: break + case .validCode(let type): handleType(type) + } + } + + private func handleType(_ type: AuthenticationType) { + let view = CreateProfileWireframe(coordinator: self).prepareModule(parameters: CreateProfileWireframe.Parameters(), dependencies: CreateProfileWireframe.Dependencies()) + + navigation?.pushViewController(view, animated: true) + + switch type { + case .login: break + case .register: break + } + } +} + +// MARK: - AuthCoordinatorProtocol + +extension AuthCoordinator { + func wireframe(_ wireframe: AuthWireframe, didEndWithState state: AuthWireframe.State) { + switch state { + case .continueLogin(let loginOption): continueLoginProcess(with: loginOption) + case .getCountry(let callback): + selectCountryCallback = callback + let wireframe = CountrySelectorWireframe(coordinator: self) + let view = wireframe.prepareModule( + parameters: NSNull(), + dependencies: CountrySelectorWireframe.Dependencies( + storageService: serviceFactory.makeStorageService())) + + navigation?.pushViewController(view, animated: true) + } + } + + private func continueLoginProcess(with loginOption: LoginOption) { + switch loginOption { + case .email, .phoneNumber: showConfirmationPopup(loginOption: loginOption) + default: break + } + } + + private func showConfirmationPopup(loginOption: LoginOption) { + let popup = UIAlertController(title: titleForPopup(loginOption: loginOption), message: messageForPopup(loginOption: loginOption), preferredStyle: .alert) + + let modify = UIAlertAction(title: "Modify".localized, style: .cancel, handler: nil) + let confirm = UIAlertAction.init(title: "Confirm".localized, style: .default) { [weak self] (action) in + guard let `self` = self else { + return + } + + switch loginOption { + case .email(let email): + let wireframe = CodeConfirmationWireframe(coordinator: self) + let view = wireframe.prepareModule( + parameters: CodeConfirmationWireframe.Parameters(address: email, authType: .email), + dependencies: CodeConfirmationWireframe.Dependencies()) + + self.navigation?.pushViewController(view, animated: true) + case .phoneNumber(let number): + let wireframe = CodeConfirmationWireframe(coordinator: self) + let view = wireframe.prepareModule( + parameters: CodeConfirmationWireframe.Parameters(address: number, authType: .phoneNumber), + dependencies: CodeConfirmationWireframe.Dependencies()) + + self.navigation?.pushViewController(view, animated: true) + default: break + } + } + + [modify, confirm].forEach { popup.addAction($0) } + + navigation?.present(popup, animated: true, completion: nil) + } + + private func titleForPopup(loginOption: LoginOption) -> String { + switch loginOption { + case .email(let email): return "Please confirm the email you entered is correct".localized + case .phoneNumber(let number): return "Please confirm the number you entered is correct".localized + default: return "" + } + } + + private func messageForPopup(loginOption: LoginOption) -> String { + switch loginOption { + case .email(let email): return email + case .phoneNumber(let number): return "+" + number + default: return "" + } + } +} + +// MARK: - CreateProfileCoordinatorProtocol + +extension AuthCoordinator { + func wireframe(_ wireframe: CreateProfileWireframe, didEndWithState state: CreateProfileWireframe.State) { + switch state { + case .back: navigation?.popViewController(animated: true) + case .next: end() + case .chooseAvatar(let completion): + let dependencies = SelectAvatarFlowCoordinator.Dependencies(source: .gallery, rootViewController: navigation!.viewControllers.last!, serviceFactory: serviceFactory) { (url) in + completion(UIImage.sd_image(with: try? Data(contentsOf: url))) + } + let chooseAvatarCoordinator = SelectAvatarFlowCoordinator.init(dependencies: dependencies) + chooseAvatarCoordinator.start() + } + } +} + diff --git a/Nynja/Modules/Auth/AuthModule/AuthProtocols.swift b/Nynja/Modules/Auth/AuthModule/AuthProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..13c315292ae13aa910f6c9daad75697198cbd679 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/AuthProtocols.swift @@ -0,0 +1,48 @@ +// +// AuthProtocols.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol AuthWireframeProtocol: class { + func selectCountry(completion: @escaping (Result) -> Void) + func continueLogin(loginOption: LoginOption) +} + +protocol AuthViewProtocol: class where Self: UIViewController { + func update(country: Country) +} + +protocol AuthPresenterProtocol: class { + var loginOption: LoginOption { get } + + var selectedCountry: Country { get } + + func switchLoginOption() + + func loginViaFacebook(completion: (Result) -> Void) + func loginViaGoogle(completion: (Result) -> Void) + func loginViaEmail(_ email: String, completion: (Result) -> Void) + func loginViaPhoneNumber(_ phoneNumber: String, completion: (Result) -> Void) + + func selectCountry() +} + +protocol AuthInputInteractorProtocol: class { + typealias Code = String + + func loginViaFacebook(completion: (Result) -> Void) + func loginViaGoogle(completion: (Result) -> Void) + func loginViaEmail(_ email: String, completion: (Result) -> Void) + func loginViaPhoneNumber(_ phoneNumber: String, completion: (Result) -> Void) + + func fetchDefaultCountry() -> Country +} + +protocol AuthOutputInteractorProtocol: class { + +} diff --git a/Nynja/Modules/Auth/AuthModule/Entities/LoginOption.swift b/Nynja/Modules/Auth/AuthModule/Entities/LoginOption.swift new file mode 100644 index 0000000000000000000000000000000000000000..8e95c227329689d53d8427fa9011bb7f276264b0 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/Entities/LoginOption.swift @@ -0,0 +1,16 @@ +// +// LoginOption.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +enum LoginOption { + case phoneNumber(number: String) + case email(email: String) + case facebook(code: String) + case google(code: String) +} diff --git a/Nynja/Modules/Auth/AuthModule/Interactor/AuthInteractor.swift b/Nynja/Modules/Auth/AuthModule/Interactor/AuthInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..c25f1687d4d87d38be07fc16ec1646843f32de21 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/Interactor/AuthInteractor.swift @@ -0,0 +1,54 @@ +// +// AuthInteractor.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class AuthInteractor: AuthInputInteractorProtocol, SetInjectable { + private weak var presenter: AuthOutputInteractorProtocol? + private var countriesProvider: CountriesProviding! +} + +// MARK: - SetInjectable + +extension AuthInteractor { + struct Dependencies { + let presenter: AuthOutputInteractorProtocol + let countriesProvider: CountriesProviding + } + + func inject(dependencies: AuthInteractor.Dependencies) { + presenter = dependencies.presenter + countriesProvider = dependencies.countriesProvider + } +} + +// MARK: - AuthInputInteractorProtocol + +extension AuthInteractor { + + func fetchDefaultCountry() -> Country { + return countriesProvider.fetchDefaultCountry() + } + + func loginViaFacebook(completion: (Result) -> Void) { + completion(.success("Some code")) + } + + func loginViaGoogle(completion: (Result) -> Void) { + completion(.success("Some code")) + } + + func loginViaEmail(_ email: String, completion: (Result) -> Void) { + completion(.success(())) + } + + func loginViaPhoneNumber(_ phoneNumber: String, completion: (Result) -> Void) { + completion(.success(())) + } +} diff --git a/Nynja/Modules/Auth/AuthModule/Presenter/AuthPresenter.swift b/Nynja/Modules/Auth/AuthModule/Presenter/AuthPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..6aee9706f5192076304c71827616b1e4a0f5d779 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/Presenter/AuthPresenter.swift @@ -0,0 +1,105 @@ +// +// AuthPresenter.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class AuthPresenter: AuthPresenterProtocol, AuthOutputInteractorProtocol, SetInjectable { + private weak var view: AuthViewProtocol? + private var interactor: AuthInputInteractorProtocol! + private var wireframe: AuthWireframeProtocol! + + private(set) var loginOption: LoginOption = .phoneNumber(number: "") + + private(set) lazy var selectedCountry: Country = { + return interactor.fetchDefaultCountry() + }() + + func switchLoginOption() { + switch loginOption { + case .email: + loginOption = .phoneNumber(number: "") + case .phoneNumber: + loginOption = .email(email: "") + default: + break + } + } + + func loginViaFacebook(completion: (Result) -> Void) { + interactor?.loginViaFacebook { + $0.onSuccess { + completion(.success(())) + wireframe?.continueLogin(loginOption: .facebook(code: $0)) + }.onFailure { + completion(.failure($0)) + } + } + } + + func loginViaGoogle(completion: (Result) -> Void) { + interactor?.loginViaGoogle { + $0.onSuccess { + completion(.success(())) + wireframe?.continueLogin(loginOption: .google(code: $0)) + }.onFailure { + completion(.failure($0)) + } + } + } + + func loginViaEmail(_ email: String, completion: (Result) -> Void) { + interactor?.loginViaEmail(email) { + $0.onSuccess { + completion(.success(())) + wireframe?.continueLogin(loginOption: .email(email: email)) + }.onFailure { + completion(.failure($0)) + } + } + } + + func loginViaPhoneNumber(_ phoneNumber: String, completion: (Result) -> Void) { + interactor?.loginViaPhoneNumber(phoneNumber) { + $0.onSuccess { + completion(.success(())) + wireframe?.continueLogin(loginOption: .phoneNumber(number: phoneNumber)) + }.onFailure { + completion(.failure($0)) + } + } + } + + func selectCountry() { + wireframe?.selectCountry { result in + switch result { + case let .success(country): + self.selectedCountry = country + self.view?.update(country: country) + case .failure: + break + } + } + } +} + +// MARK: - SetInjectable + +extension AuthPresenter { + struct Dependencies { + let view: AuthViewProtocol + let interactor: AuthInteractor + let wireframe: AuthWireframe + } + + func inject(dependencies: AuthPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireframe = dependencies.wireframe + } +} diff --git a/Nynja/Modules/Auth/AuthModule/View/AuthViewController.swift b/Nynja/Modules/Auth/AuthModule/View/AuthViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..5460303be50df1cce16dc6d8b3000d20d0486426 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/View/AuthViewController.swift @@ -0,0 +1,140 @@ +// +// AuthViewController.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class AuthViewController: UIViewController, AuthViewProtocol, InitializeInjectable, KeyboardInteractive { + private let presenter: AuthPresenterProtocol + private let viewsFactory: AuthViewsFactoryProtocol + + private lazy var headerView: AuthHeaderView = viewsFactory.makeHeaderView(on: view) + + private lazy var scrollView: UIScrollView = viewsFactory.makeScrollView(on: view, top: headerView, bottom: bottomView) + private lazy var scrollContentView: UIView = viewsFactory.makeScrollContentView(on: scrollView, baseView: view) + + private weak var emailLoginView: EmailLoginView? + private weak var phoneNumberLoginView: PhoneNumberLoginView? + + private lazy var bottomView: LoginOptionsView = viewsFactory.makeBottomView(on: view, + presenter: presenter, + showEmailLoginAction: showEmailLogin, + showPhoneNumberLoginAction: showPhoneNumberLogin) + + init(dependencies: AuthViewController.Dependencies) { + presenter = dependencies.presenter + viewsFactory = dependencies.viewsFactory + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.nynja.backgroundColor + + _ = [headerView, scrollView, scrollContentView, bottomView] + + showPhoneNumberLogin(animated: false) + + enableKeyboardHidingWhenTappedAround() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + registerForKeyboardNotifications() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + unregisterForKeyboardNotifications() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} + +// MARK: - AuthViewProtocol + +extension AuthViewController { + + func update(country: Country) { + phoneNumberLoginView?.updateCountry(country) + } +} + +// MARK: - KeyboardInteractive + +extension AuthViewController { + func keyboardNotified(endFrame: CGRect) { + var bottomInset: CGFloat = 0 + + if endFrame.origin.y < UIScreen.main.bounds.size.height { + bottomInset = endFrame.height - bottomView.bounds.size.height + } + + scrollView.snp.updateConstraints { (make) in + make.bottom.equalTo(bottomView.snp.top).inset(-bottomInset) + } + } +} + +// MARK: - Private + +private extension AuthViewController { + func showPhoneNumberLogin(animated: Bool) { + phoneNumberLoginView = viewsFactory.makePhoneNumberLoginView(on: scrollContentView, + presenter: presenter, + country: presenter.selectedCountry) + + if animated { + animateChangingViews(first: emailLoginView, second: phoneNumberLoginView) + } else { + emailLoginView?.removeFromSuperview() + } + } + + func showEmailLogin(animated: Bool) { + emailLoginView = viewsFactory.makeEmailLoginView(on: scrollContentView, presenter: presenter) + + if animated { + animateChangingViews(first: phoneNumberLoginView, second: emailLoginView) + } else { + phoneNumberLoginView?.removeFromSuperview() + } + } + + func animateChangingViews(first: UIView?, second: UIView?) { + second?.alpha = 0 + view.layoutIfNeeded() + + UIView.animate( + withDuration: 0.25, + animations: { [weak self] in + first?.alpha = 0 + second?.alpha = 1 + self?.view.layoutIfNeeded() + }) { _ in + first?.removeFromSuperview() + } + } +} + +// MARK: - InitializeInjectable + +extension AuthViewController { + struct Dependencies { + let presenter: AuthPresenterProtocol + let viewsFactory: AuthViewsFactoryProtocol + } +} diff --git a/Nynja/Modules/Auth/AuthModule/View/Subviews/AuthHeaderView.swift b/Nynja/Modules/Auth/AuthModule/View/Subviews/AuthHeaderView.swift new file mode 100644 index 0000000000000000000000000000000000000000..d47e3bb5e89230267800350cc63c48196aedac80 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/View/Subviews/AuthHeaderView.swift @@ -0,0 +1,37 @@ +// +// AuthHeaderView.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class AuthHeaderView: UIView, Configurable { + private lazy var welcomeLabel: UILabel = viewsFactory.makeWelcomeLabel(on: self) + private lazy var logoImageView: UIImageView = viewsFactory.makeLogoImageView(on: self, top: welcomeLabel) + + private let viewsFactory: AuthViewsFactoryProtocol + + init(viewsFactory: AuthViewsFactoryProtocol) { + self.viewsFactory = viewsFactory + + super.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Configurable + +extension AuthHeaderView { + typealias Config = NSNull + + func configure(config: NSNull) { + backgroundColor = UIColor.nynja.clear + _ = [welcomeLabel, logoImageView] + } +} diff --git a/Nynja/Modules/Auth/AuthModule/View/Subviews/EmailLoginView.swift b/Nynja/Modules/Auth/AuthModule/View/Subviews/EmailLoginView.swift new file mode 100644 index 0000000000000000000000000000000000000000..13fc35fb57176bf8c6542d2b1bdb8e48a63d0e01 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/View/Subviews/EmailLoginView.swift @@ -0,0 +1,118 @@ +// +// EmailLoginView.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class EmailLoginView: UIView, Configurable { + private lazy var inputFieldContainer = viewsFactory.makeInputFieldContainer(on: self) + private lazy var inputField = viewsFactory.makeInputField(on: inputFieldContainer) + private lazy var detailsLabel = viewsFactory.makeDetailsLabel(on: self, top: inputFieldContainer) + private lazy var nextButton = viewsFactory.makeNextButton(on: self, top: detailsLabel, target: self, selector: #selector(next(sender:))) + + private var textFieldController: TextFieldController? + private var nextAction: ((String) -> Void)? + + private let viewsFactory: AuthViewsFactoryProtocol + + init(viewsFactory: AuthViewsFactoryProtocol) { + self.viewsFactory = viewsFactory + + super.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Configurable + +extension EmailLoginView { + struct Config { + let nextAction: (String) -> Void + } + + func configure(config: EmailLoginView.Config) { + textFieldController = TextFieldController(validator: Validator()) { [weak self] (result) in + if result { + self?.nextButton.backgroundColor = UIColor.nynja.mainRed + } else { + self?.nextButton.backgroundColor = UIColor.nynja.darkRed + } + + self?.nextButton.isEnabled = result + } + + nextAction = config.nextAction + + inputField.shouldTextChanged = { [weak self] textInput, range, string in + return self?.textFieldController? + .textInput(textInput, + shouldChangeCharactersIn: range, + replacementString: string) ?? true + } + + inputField.returnHandler = { [weak self] textInput in + return self?.textFieldController?.textInputShouldReturn(textInput) ?? false + } + + _ = [inputFieldContainer, inputField, detailsLabel, nextButton] + } +} + +// MARK: - Aсtions + +private extension EmailLoginView { + @objc func next(sender: UIButton) { + nextAction?(inputField.text ?? "") + } +} + +// MARK: - Validator + +private extension EmailLoginView { + struct Validator { + func isValid(email: String) -> Bool { + let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) + + return emailTest.evaluate(with: email) + } + } +} + +// MARK: - TextFieldController + +private extension EmailLoginView { + + final class TextFieldController { + private let validator: Validator + private let validationAction: (Bool) -> Void + + init(validator: Validator, validationAction: @escaping (Bool) -> Void) { + self.validator = validator + self.validationAction = validationAction + } + + func textInput(_ textInput: MaterialTextInput, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if let str = textInput.text as NSString? { + let resultStr = str.replacingCharacters(in: range, with: string) + validationAction(validator.isValid(email: resultStr)) + } + + return true + } + + func textInputShouldReturn(_ textInput: MaterialTextField) -> Bool { + textInput.resignFirstResponder() + return false + } + } +} diff --git a/Nynja/Modules/Auth/AuthModule/View/Subviews/LoginOptionsView.swift b/Nynja/Modules/Auth/AuthModule/View/Subviews/LoginOptionsView.swift new file mode 100644 index 0000000000000000000000000000000000000000..55c53abd647d272263bb323155e9eb2c5ea147a4 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/View/Subviews/LoginOptionsView.swift @@ -0,0 +1,99 @@ +// +// LoginOptionsView.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class LoginOptionsView: UIView, Configurable { + private let viewsFactory: AuthViewsFactoryProtocol + + private lazy var switchLoginButton: UIButton = viewsFactory.makeSwitchLoginButton(on: self, + bottom: loginWithFacebook, + target: self, + selector: #selector(switchLogin(sender:))) + private lazy var loginWithFacebook: UIButton = viewsFactory.makeLoginWithFacebookButton(on: self, + bottom: loginWithGoogle, + target: self, + selector: #selector(loginWithFacebook(sender:))) + private lazy var loginWithGoogle: UIButton = viewsFactory.makeLoginWithGoogleButton(on: self, + target: self, + selector: #selector(loginWithGoogle(sender:))) + + private var switchLoginAction: (() -> LoginOption)? + private var facebookLoginAction: (() -> Void)? + private var googleLoginAction: (() -> Void)? + + init(viewsFactory: AuthViewsFactoryProtocol) { + self.viewsFactory = viewsFactory + + super.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Configurable + +extension LoginOptionsView { + struct Config { + let loginOption: LoginOption + let switchLoginAction: () -> LoginOption + let facebookLoginAction: () -> Void + let googleLoginAction: () -> Void + } + + func configure(config: LoginOptionsView.Config) { + backgroundColor = UIColor.nynja.clear + + switchLoginAction = config.switchLoginAction + facebookLoginAction = config.facebookLoginAction + googleLoginAction = config.googleLoginAction + + updateSwitchButton(loginOption: config.loginOption) + + _ = [switchLoginButton, loginWithFacebook, loginWithGoogle] + } +} + +// MARK: - Actions + +private extension LoginOptionsView { + @objc func switchLogin(sender: UIButton) { + guard let loginOption = switchLoginAction?() else { + return + } + + updateSwitchButton(loginOption: loginOption) + } + + @objc func loginWithFacebook(sender: UIButton) { + facebookLoginAction?() + } + + @objc func loginWithGoogle(sender: UIButton) { + googleLoginAction?() + } +} + +// MARK: - Private + +private extension LoginOptionsView { + func updateSwitchButton(loginOption: LoginOption) { + switch loginOption { + case .email: + switchLoginButton.setTitle("Log in with phone number".localized.uppercased(), for: .normal) + switchLoginButton.setImage(UIImage(named: "icons_general_ic_accept_call"), for: .normal) + case .phoneNumber: + switchLoginButton.setTitle("Log in with email".localized.uppercased(), for: .normal) + switchLoginButton.setImage(UIImage(named: "icons_general_ic_email"), for: .normal) + default: break + } + } +} diff --git a/Nynja/Modules/Auth/AuthModule/View/Subviews/PhoneNumberLoginView.swift b/Nynja/Modules/Auth/AuthModule/View/Subviews/PhoneNumberLoginView.swift new file mode 100644 index 0000000000000000000000000000000000000000..2e7283dbf2917a47a84032922249509c020bef97 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/View/Subviews/PhoneNumberLoginView.swift @@ -0,0 +1,198 @@ +// +// PhoneNumberLoginView.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class PhoneNumberLoginView: UIView, Configurable { + private lazy var countrySelector: UIButton = viewsFactory.makeCountrySelector(on: self, target: self, selector: #selector(changeCountry(sender:))) + + private lazy var countryCodeContainer: UIView = viewsFactory.makeCountryCodeContainer(on: self, top: countrySelector) + private lazy var countryCodeLabel: UILabel = viewsFactory.makeCountryCodeLabel(on: countryCodeContainer) + + private var phoneNumberTextFieldController: TextFieldController? + + private lazy var phoneNumberContainer: UIView = viewsFactory.makePhoneNumberContainer(on: self, left: countryCodeLabel) + private lazy var phoneNumberTextField: UITextField = viewsFactory.makePhoneNumberTextField(on: phoneNumberContainer) + + private lazy var detailsLabel: UILabel = viewsFactory.makeDetailsNumberLabel(on: self, top: countryCodeContainer) + private lazy var nextButton: UIButton = viewsFactory.makeNextButton(on: self, top: detailsLabel, target: self, selector: #selector(next(sender:))) + + private var country: Country? + private var countrySelectorAction: (() -> Void)? + private var nextAction: ((String) -> Void)? + + private let viewsFactory: AuthViewsFactoryProtocol + + init(viewsFactory: AuthViewsFactoryProtocol) { + self.viewsFactory = viewsFactory + + super.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Configurable + +extension PhoneNumberLoginView { + struct Config { + let country: Country + let countrySelectorAction: () -> Void + let nextAction: (String) -> Void + } + + func configure(config: PhoneNumberLoginView.Config) { + country = config.country + countrySelectorAction = config.countrySelectorAction + nextAction = config.nextAction + phoneNumberTextFieldController = TextFieldController(template: config.country.numberTemplate) { [weak self] result in + if result { + self?.nextButton.backgroundColor = UIColor.nynja.mainRed + } else { + self?.nextButton.backgroundColor = UIColor.nynja.darkRed + } + + self?.nextButton.isEnabled = result + } + + phoneNumberTextField.delegate = phoneNumberTextFieldController + updateCountry(config.country) + + _ = [countrySelector, countryCodeContainer, countryCodeLabel, phoneNumberContainer, phoneNumberTextField, detailsLabel, nextButton] + } +} + +// MARK: - Public + +extension PhoneNumberLoginView { + func updateCountry(_ country: Country) { + self.country = country + + phoneNumberTextFieldController?.template = country.numberTemplate + + countrySelector.setTitle(country.name, for: .normal) + countryCodeLabel.text = "+" + country.code + + phoneNumberTextField.text = "".updateWithMask(placeHolder: country.numberTemplate) + } +} + +// MARK: - Actions + +private extension PhoneNumberLoginView { + @objc func changeCountry(sender: UIButton) { + countrySelectorAction?() + } + + @objc func next(sender: UIButton) { + let number = (country?.code ?? "") + (phoneNumberTextField.text ?? "") + nextAction?(number) + } +} + +// MARK: - Text field controller + +private extension PhoneNumberLoginView { + final class TextFieldController: NSObject, UITextFieldDelegate { + var template: String + var isFullFilelledAction: ((Bool) -> Void)? + + init(template: String, isFullFilelledAction: ((Bool) -> Void)?) { + self.template = template + self.isFullFilelledAction = isFullFilelledAction + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + textField.text = textAfterUpdate(textField: textField, range: range, replacementString: string) + .updateWithMask(placeHolder: template) + + let offset = string != "" ? + cursorOffsetForNonEmptyString(textField: textField, range: range) : + cursorOffsetForEmptyString(textField: textField, range: range) + + updateCursorPosition(on: textField, position: range.location + offset) + + isFullFilelledAction?(isFullfiled(textField: textField, template: template)) + + return false + } + + func textFieldDidBeginEditing(_ textField: UITextField) { + let position = calculatedCursorPosition(on: textField) + updateCursorPosition(on: textField, position: position) + } + + // MARK: - Private + + private func textAfterUpdate(textField: UITextField, range: NSRange, replacementString string: String) -> String { + let text = textField.text ?? "" + + let updatedRange = newRange(text: text, oldRange: range, replacementString: string) + + return (text as NSString) + .replacingCharacters(in: updatedRange, with: string) + .replacingOccurrences(of: " ", with: "") + } + + private func newRange(text: String, oldRange: NSRange,replacementString string: String) -> NSRange { + if string == "", Array(text)[oldRange.location] == " " { + var range = oldRange + range.location = range.location - 1 + return range + } + + return oldRange + } + + private func cursorOffsetForNonEmptyString(textField: UITextField, range: NSRange) -> Int { + guard let text = textField.text else { + return 1 + } + + let index = range.location + 1 + let arr = Array(text) + + if arr.count > index, arr[index] == " " { + return 2 + } + + return 1 + } + + private func cursorOffsetForEmptyString(textField: UITextField, range: NSRange) -> Int { + guard let text = textField.text else { + return 0 + } + + return Array(text)[range.location] == " " ? -1 : 0 + } + + private func updateCursorPosition(on textField: UITextField, position: Int) { + guard let newPosition = textField.position(from: textField.beginningOfDocument, offset: position) else { + return + } + + textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition) + } + + private func calculatedCursorPosition(on textField: UITextField) -> Int { + return textField.text? + .enumerated() + .filter { $1 != " " && $1 != "\u{2013}" } + .last? + .offset ?? 0 + } + + private func isFullfiled(textField: UITextField, template: String) -> Bool { + return (textField.text ?? "").filter { $0 != "\u{2013}" }.count == template.count + } + } +} diff --git a/Nynja/Modules/Auth/AuthModule/View/ViewsFactory/AuthViewsFactory.swift b/Nynja/Modules/Auth/AuthModule/View/ViewsFactory/AuthViewsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..c6c897c40f3bae086c2d197a133ec6c516d4aae4 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/View/ViewsFactory/AuthViewsFactory.swift @@ -0,0 +1,502 @@ +// +// AuthViewsFactory.swift +// Nynja +// +// Created by Ash on 10/10/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol AuthViewsFactoryProtocol { + + // MARK: - Auth View Controller + + func makeHeaderView(on view: UIView) -> AuthHeaderView + func makeScrollView(on view: UIView, top: UIView, bottom: UIView) -> UIScrollView + func makeScrollContentView(on view: UIView, baseView: UIView) -> UIView + func makeEmailLoginView(on view: UIView, presenter: AuthPresenterProtocol) -> EmailLoginView + func makePhoneNumberLoginView(on view: UIView, presenter: AuthPresenterProtocol, country: Country) -> PhoneNumberLoginView + func makeBottomView(on view: UIView, presenter: AuthPresenterProtocol, showEmailLoginAction: @escaping (Bool) -> Void, + showPhoneNumberLoginAction: @escaping (Bool) -> Void) -> LoginOptionsView + + // MARK: - Login Options View + + func makeLoginWithGoogleButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeLoginWithFacebookButton(on view: UIView, bottom: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeSwitchLoginButton(on view: UIView, bottom: UIView, target: AnyObject, selector: Selector) -> UIButton + + // MARK: - AuthHeaderView + + func makeWelcomeLabel(on view: UIView) -> UILabel + func makeLogoImageView(on view: UIView, top: UIView) -> UIImageView + + // MARK: - Email Login View + + func makeInputFieldContainer(on view: UIView) -> UIView + func makeInputField(on view: UIView) -> MaterialTextField + func makeDetailsLabel(on view: UIView, top: UIView) -> UILabel + func makeEmailNextButton(on view: UIView, top: UIView, target: AnyObject, selector: Selector) -> UIButton + + // MARK: - Phone Number Login View + + func makeCountrySelector(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeCountryCodeContainer(on view: UIView, top: UIView) -> UIView + func makeCountryCodeLabel(on view: UIView) -> UILabel + func makePhoneNumberContainer(on view: UIView, left: UIView) -> UIView + func makePhoneNumberTextField(on view: UIView) -> UITextField + func makeDetailsNumberLabel(on view: UIView, top: UIView) -> UILabel + func makeNextButton(on view: UIView, top: UIView, target: AnyObject, selector: Selector) -> UIButton +} + +final class AuthViewsFactory: AuthViewsFactoryProtocol { + + // MARK: - Auth View Controller + + func makeHeaderView(on view: UIView) -> AuthHeaderView { + let header = AuthHeaderView(viewsFactory: self) + header.configure(config: NSNull()) + + view.addSubview(header) + header.snp.makeConstraints { (make) in + make.top.left.right.equalToSuperview() + } + + return header + } + + func makeScrollView(on view: UIView, top: UIView, bottom: UIView) -> UIScrollView { + let scrollView = UIScrollView() + view.addSubview(scrollView) + + scrollView.snp.makeConstraints { (make) in + make.left.right.equalToSuperview() + make.bottom.equalTo(bottom.snp.top) + make.top.equalTo(top.snp.bottom) + } + + return scrollView + } + + func makeScrollContentView(on view: UIView, baseView: UIView) -> UIView { + let contentView = UIView() + view.addSubview(contentView) + + contentView.backgroundColor = UIColor.nynja.clear + + contentView.snp.makeConstraints { (make) in + make.right.left.top.bottom.equalToSuperview() + make.width.equalTo(baseView.snp.width) + make.height.equalTo(baseView.snp.height) + } + + return contentView + } + + func makeEmailLoginView(on view: UIView, presenter: AuthPresenterProtocol) -> EmailLoginView { + let loginView = EmailLoginView(viewsFactory: self) + view.addSubview(loginView) + + loginView.configure(config: EmailLoginView.Config(nextAction: { + presenter.loginViaEmail($0) { (result) in + print(#function) + } + })) + + loginView.snp.makeConstraints { (make) in + make.top.left.right.equalToSuperview() + make.bottom.lessThanOrEqualToSuperview() + } + + return loginView + } + + func makePhoneNumberLoginView(on view: UIView, presenter: AuthPresenterProtocol, country: Country) -> PhoneNumberLoginView { + let loginView = PhoneNumberLoginView(viewsFactory: self) + view.addSubview(loginView) + + loginView.configure(config: PhoneNumberLoginView.Config( + country: country, + countrySelectorAction: { [weak presenter] in + presenter?.selectCountry() + }, + nextAction: { [weak presenter] in + presenter?.loginViaPhoneNumber($0) { (result) in + print(#function) + } + })) + + loginView.snp.makeConstraints { (make) in + make.top.left.right.equalToSuperview() + make.bottom.lessThanOrEqualToSuperview() + } + + return loginView + } + + func makeBottomView(on view: UIView, presenter: AuthPresenterProtocol, showEmailLoginAction: @escaping (Bool) -> Void, + showPhoneNumberLoginAction: @escaping (Bool) -> Void) -> LoginOptionsView { + let bottom = LoginOptionsView(viewsFactory: self) + + bottom.configure(config: LoginOptionsView.Config( + loginOption: presenter.loginOption, + switchLoginAction: { [weak self] () -> LoginOption in + presenter.switchLoginOption() + let loginOption = presenter.loginOption + + switch loginOption { + case .email: showEmailLoginAction(true) + case .phoneNumber: showPhoneNumberLoginAction(true) + default: break + } + + return loginOption + }, + facebookLoginAction: { + presenter.loginViaFacebook { (result) in + print(#function) + } + }, + googleLoginAction: { + presenter.loginViaGoogle { (result) in + print(#function) + } + })) + + view.addSubview(bottom) + bottom.snp.makeConstraints { (make) in + make.bottom.left.right.equalToSuperview() + } + + return bottom + } + + // MARK: - Login Options View + + func makeLoginWithGoogleButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.backgroundColor = UIColor.nynja.white + button.setTitle("Log in with Google".localized.uppercased(), for: .normal) + button.setTitleColor(UIColor.nynja.subtitleGray, for: .normal) + button.setImage(UIImage(named: "icons_general_ic_google"), for: .normal) + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 14) + + button.layer.cornerRadius = 22 + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.bottom.equalToSuperview().offset(-30) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.height.equalTo(44) + } + + return button + } + + func makeLoginWithFacebookButton(on view: UIView, bottom: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.backgroundColor = UIColor.nynja.dodgerBlue + button.setTitle("Log in with Facebook".localized.uppercased(), for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .normal) + button.setImage(UIImage(named: "ic_facebook"), for: .normal) + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 14) + + button.layer.cornerRadius = 22 + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.bottom.equalTo(bottom.snp.top).offset(-16) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.height.equalTo(44) + } + + return button + } + + func makeSwitchLoginButton(on view: UIView, bottom: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.backgroundColor = UIColor.nynja.mainRed + button.setTitleColor(UIColor.nynja.white, for: .normal) + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 14) + + button.layer.cornerRadius = 22 + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(30) + make.bottom.equalTo(bottom.snp.top).offset(-16) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.height.equalTo(44) + } + + return button + } + + // MARK: - AuthHeaderView + + func makeWelcomeLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.font = FontFamily.NotoSans.medium.font(size: 16) + label.textColor = UIColor.nynja.white + + label.text = "Welcome to".localized + + label.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(70) + make.centerX.equalToSuperview() + } + + return label + } + + func makeLogoImageView(on view: UIView, top: UIView) -> UIImageView { + let imageView = UIImageView() + view.addSubview(imageView) + + imageView.contentMode = .scaleAspectFill + imageView.image = UIImage.nynja.logo2.image + + imageView.snp.makeConstraints { (make) in + make.top.equalTo(top.snp.bottom).offset(16) + make.bottom.equalToSuperview().offset(-16) + make.centerX.equalToSuperview() + make.width.equalToSuperview().multipliedBy(9/20) + } + + return imageView + } + + // MARK: - Email Login View + + func makeInputFieldContainer(on view: UIView) -> UIView { + let container = UIView() + view.addSubview(container) + + container.backgroundColor = UIColor.nynja.clear + + container.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(16) + make.height.equalTo(44) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + } + + return container + } + + func makeInputField(on view: UIView) -> MaterialTextField { + let textField = MaterialTextField() + view.addSubview(textField) + + textField.placeholderColor = UIColor.nynja.dustyGray + textField.placeholder = "Email".localized + + textField.textColor = UIColor.nynja.white + textField.font = FontFamily.NotoSans.medium.font(size: 16) + + textField.separatorColor = UIColor.nynja.dustyGray + + textField.keyboardType = .emailAddress + textField.returnKeyType = .done + + textField.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview() + make.right.equalToSuperview() + } + + return textField + } + + func makeDetailsLabel(on view: UIView, top: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.text = "Enter your email address to receive the login code.".localized + label.font = FontFamily.NotoSans.regular.font(size: 14) + label.textColor = UIColor.nynja.dustyGray + + label.snp.makeConstraints { (make) in + make.height.equalTo(40) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.top.equalTo(top.snp.bottom) + } + + return label + } + + func makeEmailNextButton(on view: UIView, top: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.layer.cornerRadius = 22 + button.setTitle("next".localized.uppercased(), for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .normal) + button.backgroundColor = UIColor.nynja.darkRed + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 16) + + button.isEnabled = false + + button.snp.makeConstraints { (make) in + make.height.equalTo(44) + make.bottom.equalToSuperview().offset(-16) + make.top.equalTo(top.snp.bottom).offset(88) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + } + + return button + } + + // MARK: - Phone Number Login View + + func makeCountrySelector(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 16) + button.setTitleColor(UIColor.nynja.white, for: .normal) + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.titleLabel?.appendBottomBorder(color: UIColor.nynja.dustyGray, width: 1) + + button.titleLabel?.snp.makeConstraints { (make) in + make.width.equalToSuperview() + } + + button.snp.makeConstraints { (make) in + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.height.equalTo(44) + make.top.equalToSuperview().offset(10) + } + + return button + } + + func makeCountryCodeContainer(on view: UIView, top: UIView) -> UIView { + let container = UIView() + view.addSubview(container) + + container.backgroundColor = UIColor.nynja.clear + + container.snp.makeConstraints { (make) in + make.top.equalTo(top.snp.bottom) + make.left.equalToSuperview().offset(16) + make.width.equalTo(100) + make.height.equalTo(64) + } + + return container + } + + func makeCountryCodeLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.appendBottomBorder(color: UIColor.nynja.dustyGray, width: 1) + label.font = FontFamily.NotoSans.medium.font(size: 16) + label.textColor = UIColor.nynja.white + + label.snp.makeConstraints { (make) in + make.left.equalToSuperview() + make.right.equalToSuperview().offset(-16) + make.centerY.equalToSuperview() + } + + return label + } + + func makePhoneNumberContainer(on view: UIView, left: UIView) -> UIView { + let container = UIView() + view.addSubview(container) + + container.backgroundColor = UIColor.nynja.clear + + container.snp.makeConstraints { (make) in + make.height.equalTo(left.snp.height) + make.right.equalToSuperview().offset(-16) + make.left.equalTo(left.snp.right) + make.centerY.equalTo(left.snp.centerY) + } + + return container + } + + func makePhoneNumberTextField(on view: UIView) -> UITextField { + let textField = UITextField() + view.addSubview(textField) + + textField.appendBottomBorder(color: UIColor.nynja.dustyGray, width: 1) + + textField.font = FontFamily.NotoSans.medium.font(size: 16) + textField.textColor = UIColor.nynja.white + textField.keyboardType = .numberPad + + textField.snp.makeConstraints { (make) in + make.centerY.right.equalToSuperview() + make.left.equalToSuperview().offset(16) + } + + return textField + } + + func makeDetailsNumberLabel(on view: UIView, top: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.text = "Please choose your country code and enter your phone number.".localized + label.font = FontFamily.NotoSans.regular.font(size: 14) + label.textColor = UIColor.nynja.dustyGray + label.numberOfLines = 0 + + label.snp.makeConstraints { (make) in + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + make.top.equalTo(top.snp.bottom) + } + + return label + } + + func makeNextButton(on view: UIView, top: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.layer.cornerRadius = 22 + button.setTitle("next".localized.uppercased(), for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .normal) + button.backgroundColor = UIColor.nynja.darkRed + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 16) + + button.isEnabled = false + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.height.equalTo(44) + make.bottom.equalToSuperview().offset(-16) + make.top.equalTo(top.snp.bottom).offset(32) + make.left.equalToSuperview().offset(16) + make.right.equalToSuperview().offset(-16) + } + + return button + } +} diff --git a/Nynja/Modules/Auth/AuthModule/Wireframe/AuthWireframe.swift b/Nynja/Modules/Auth/AuthModule/Wireframe/AuthWireframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..90cb9d0c1c4d979cc596407782008e58ff1dd080 --- /dev/null +++ b/Nynja/Modules/Auth/AuthModule/Wireframe/AuthWireframe.swift @@ -0,0 +1,56 @@ +// +// AuthWireframe.swift +// Nynja +// +// Created by Ash on 10/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol AuthCoordinatorProtocol { + func wireframe(_ wireframe: AuthWireframe, didEndWithState state: AuthWireframe.State) +} + +final class AuthWireframe: WireframeProtocol, AuthWireframeProtocol { + + private let coordinator: AuthCoordinatorProtocol + + init(coordinator: AuthCoordinatorProtocol) { + self.coordinator = coordinator + } + + typealias Parameters = NSNull + + struct Dependencies { + let countriesProvider: CountriesProviding + } + + enum State { + case continueLogin(loginOption: LoginOption) + case getCountry(callback: (Result) -> Void) + } + + func prepareModule(parameters: Parameters, dependencies: Dependencies) -> UIViewController { + let presenter = AuthPresenter() + let view = AuthViewController(dependencies: AuthViewController.Dependencies(presenter: presenter, + viewsFactory: AuthViewsFactory())) + let interactor = AuthInteractor() + + let presenterDep = AuthPresenter.Dependencies(view: view, interactor: interactor, wireframe: self) + let interactorDep = AuthInteractor.Dependencies(presenter: presenter, countriesProvider: dependencies.countriesProvider) + + presenter.inject(dependencies: presenterDep) + interactor.inject(dependencies: interactorDep) + + return view + } + + func selectCountry(completion: @escaping (Result) -> Void) { + coordinator.wireframe(self, didEndWithState: .getCountry(callback: completion)) + } + + func continueLogin(loginOption: LoginOption) { + coordinator.wireframe(self, didEndWithState: .continueLogin(loginOption: loginOption)) + } +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/CodeConfirmationProtocols.swift b/Nynja/Modules/Auth/CodeConfirmation/CodeConfirmationProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..96f9d2cd06c0957a255a3c45ba90d63894e76750 --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/CodeConfirmationProtocols.swift @@ -0,0 +1,47 @@ +// +// CodeConfirmationProtocols.swift +// Nynja +// +// Created by Ash on 9/30/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol CodeConfirmationWireframeProtocol: WireframeProtocol { + func codeValid(with type: AuthenticationType) + func codeInvalid() + func back() +} + +protocol CodeConfirmationViewProtocol: class where Self: UIViewController { + func updateTimerLabel(text: String) + func showButtons() +} + +protocol CodeConfirmationPresenterProtocol: NavigationProtocol { + var isCanAskForCall: Bool { get } + var address: String { get } + var descriptionText: String { get } + + func viewDidLoad() + + func sendConfirmationCode(code: String, completion: (Result) -> Void) + + func resendCode() + func askForCall() +} + +protocol CodeConfirmationInputInteractorProtocol { + var address: String { get } + var authProviderType: AuthProviderType { get } + + func sendConfirmationCode(code: String, completion: (Result) -> Void) + + func resendCode() + func askForCall() +} + +protocol CodeConfirmationOutputInteractorProtocol { + +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/Entities/AuthType.swift b/Nynja/Modules/Auth/CodeConfirmation/Entities/AuthType.swift new file mode 100644 index 0000000000000000000000000000000000000000..e0513a49b8074012c0bd0050846f46778be37cb5 --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/Entities/AuthType.swift @@ -0,0 +1,15 @@ +// +// AuthType.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +enum AuthenticationType { + case register + case login +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/Interactor/CodeConfirmationInteractor.swift b/Nynja/Modules/Auth/CodeConfirmation/Interactor/CodeConfirmationInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..eaa40195679507a6a510b1c6a93541ba4d2a9153 --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/Interactor/CodeConfirmationInteractor.swift @@ -0,0 +1,46 @@ +// +// CodeConfirmationInteractor.swift +// Nynja +// +// Created by Ash on 9/30/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CodeConfirmationInteractor: CodeConfirmationInputInteractorProtocol, SetInjectable { + private var presenter: CodeConfirmationOutputInteractorProtocol? + + private(set) var address: String + private(set) var authProviderType: AuthProviderType + + init(address: String, authProviderType: AuthProviderType) { + self.address = address + self.authProviderType = authProviderType + } + + func sendConfirmationCode(code: String, completion: (Result) -> Void) { + completion(.success(.register)) + } + + func resendCode() { + + } + + func askForCall() { + + } +} + +// MARK: - SetInjectable + +extension CodeConfirmationInteractor { + struct Dependencies { + let presenter: CodeConfirmationOutputInteractorProtocol + } + + func inject(dependencies: CodeConfirmationInteractor.Dependencies) { + presenter = dependencies.presenter + } +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/Presenter/CodeConfirmationPresenter.swift b/Nynja/Modules/Auth/CodeConfirmation/Presenter/CodeConfirmationPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..8d8b43f8da8744a188f0810ce58943d46dfbf01d --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/Presenter/CodeConfirmationPresenter.swift @@ -0,0 +1,112 @@ +// +// CodeConfirmationPresenter.swift +// Nynja +// +// Created by Ash on 9/30/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CodeConfirmationPresenter: CodeConfirmationPresenterProtocol, CodeConfirmationOutputInteractorProtocol, SetInjectable { + private var view: CodeConfirmationViewProtocol? + private var interactor: CodeConfirmationInputInteractorProtocol? + private var wireframe: CodeConfirmationWireframe? + private var timerValue = 0 { + didSet { + if timerValue > 60 { + view?.updateTimerLabel(text: "You should receive it within \((timerValue / 60) + 1) minutes.") + } else { + view?.updateTimerLabel(text: "You should receive it within \(timerValue) seconds.") + } + + if timerValue == 0 { + view?.showButtons() + timer?.invalidate() + timer = nil + } + } + } + private var timer: Timer? + + var isCanAskForCall: Bool { + guard let interactor = interactor else { + return false + } + + return interactor.authProviderType == .phoneNumber + } + + var address: String { + return interactor?.address ?? "" + } + + var descriptionText: String { + guard let interactor = interactor else { + return "" + } + + if interactor.authProviderType == .phoneNumber { + return "We've sent code to your phone".localized + } else { + return "We've sent code to your email".localized + } + + } + + func viewDidLoad() { + switch interactor!.authProviderType { + case .email: timerValue = 15 * 60 + case .phoneNumber: timerValue = 60 + } + + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in + self.timerValue = self.timerValue - 1 + } + } + + func sendConfirmationCode(code: String, completion: (Result) -> Void) { + interactor?.sendConfirmationCode(code: code) { + $0.onSuccess { + completion(.success(())) + wireframe?.codeValid(with: $0) + }.onFailure { + completion(.failure($0)) + wireframe?.codeInvalid() + } + } + } + + func resendCode() { + interactor?.resendCode() + } + + func askForCall() { + guard isCanAskForCall else { + return + } + + interactor?.askForCall() + } + + func back() { + wireframe?.back() + } +} + +// MARK: - SetInjectable + +extension CodeConfirmationPresenter { + struct Dependencies { + let view: CodeConfirmationViewProtocol + let interactor: CodeConfirmationInputInteractorProtocol + let wireframe: CodeConfirmationWireframe + } + + func inject(dependencies: CodeConfirmationPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireframe = dependencies.wireframe + } +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/View/CodeConfirmationViewController.swift b/Nynja/Modules/Auth/CodeConfirmation/View/CodeConfirmationViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..2196824baaf43f74b7c5d93f6e1bd0d6c1c4a890 --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/View/CodeConfirmationViewController.swift @@ -0,0 +1,196 @@ +// +// CodeConfirmationViewController.swift +// Nynja +// +// Created by Ash on 9/30/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CodeConfirmationViewController: UIViewController, CodeConfirmationViewProtocol, InitializeInjectable { + private let viewsFactory: CodeConfirmationViewsFactoryProtocol + private let presenter: CodeConfirmationPresenterProtocol + + private lazy var backButton: UIButton = viewsFactory.makeBackButton(on: view, target: self, selector: #selector(back(sender:))) + private lazy var welcomeLabel: UILabel = viewsFactory.makeWelcomeLabel(on: view) + private lazy var logoImageView: UIImageView = viewsFactory.makeLogoImageView(on: view, top: welcomeLabel) + private lazy var addressLabel: UILabel = viewsFactory.makeAddressLabel(on: view, top: logoImageView) + + private lazy var textFieldsContainer: UIView = viewsFactory.makeTextFieldsContainer(on: view, top: addressLabel) + private let textFieldsController: TextFieldsController + + private lazy var textField1: UITextField = viewsFactory.makeFirstTextField(on: textFieldsContainer) + private lazy var textField2: UITextField = viewsFactory.makeMiddleTextField(on: textFieldsContainer, left: textField1) + private lazy var textField3: UITextField = viewsFactory.makeMiddleTextField(on: textFieldsContainer, left: textField2) + private lazy var textField4: UITextField = viewsFactory.makeMiddleTextField(on: textFieldsContainer, left: textField3) + private lazy var textField5: UITextField = viewsFactory.makeMiddleTextField(on: textFieldsContainer, left: textField4) + private lazy var textField6: UITextField = viewsFactory.makeLastTextField(on: textFieldsContainer, left: textField5) + + private lazy var descriptionLabel: UILabel = viewsFactory.makeDescriptionLabel(on: view, top: textFieldsContainer) + + private lazy var timerLabel: UILabel = viewsFactory.makeTimerLabel(on: view) + + private weak var resendCodeButton: UIButton? + private weak var callMeButton: UIButton? + + init(dependencies: Dependencies) { + presenter = dependencies.presenter + viewsFactory = dependencies.viewsFactory + textFieldsController = TextFieldsController() + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + _ = [backButton, welcomeLabel, logoImageView, addressLabel, textFieldsContainer, textField1, textField2, textField3, textField4, textField5, textField6, descriptionLabel, timerLabel, resendCodeButton, callMeButton] + view.backgroundColor = UIColor.nynja.backgroundColor + + addressLabel.text = presenter.address + descriptionLabel.text = presenter.descriptionText + + textField1.becomeFirstResponder() + + view.layoutIfNeeded() + + [textField1, textField2, textField3, textField4, textField5, textField6] + .forEach { $0.appendBottomBorder(color: UIColor.nynja.mainRed, width: 2) } + + textFieldsController.add(textFields: [textField1, textField2, textField3, textField4, textField5, textField6]) + textFieldsController.allFieldsFilledAction = { [weak self] code in + self?.showHUD() + self?.presenter.sendConfirmationCode(code: code) { _ in self?.hideHUD()} + } + + presenter.viewDidLoad() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} + +// MARK: - CodeConfirmationViewProtocol + +extension CodeConfirmationViewController { + func updateTimerLabel(text: String) { + timerLabel.text = text + } + + func showButtons() { + timerLabel.isHidden = true + resendCodeButton = viewsFactory.makeResendCodeButton(on: view, target: self, selector: #selector(resendCode(sender:))) + + if presenter.isCanAskForCall { + callMeButton = viewsFactory.makeCallMeButton(on: view, top: resendCodeButton!, target: self, selector: #selector(callMe(sender:))) + } + } +} + +// MARK: - Actions + +extension CodeConfirmationViewController { + @objc func back(sender: UIButton) { + presenter.back() + } + + @objc func resendCode(sender: UIButton) { + presenter.resendCode() + } + + @objc func callMe(sender: UIButton) { + presenter.askForCall() + } +} + +// MARK: - SetInjectable + +extension CodeConfirmationViewController { + struct Dependencies { + let presenter: CodeConfirmationPresenterProtocol + let viewsFactory: CodeConfirmationViewsFactoryProtocol + } +} + +// MARK: - Private + +private extension CodeConfirmationViewController { + func showHUD() { + + } + + func hideHUD() { + + } +} + +// MARK: - Text field controller + +private extension CodeConfirmationViewController { + final class TextFieldsController: NSObject, UITextFieldDelegate { + private var textFields: [UITextField] = [] + var allFieldsFilledAction: ((_ code: String) -> Void)? + + func add(textFields: [UITextField]) { + self.textFields.forEach { $0.delegate = nil } + self.textFields = textFields + self.textFields.forEach { $0.delegate = self } + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if string == "" { + textFields.previous(before: textField)?.becomeFirstResponder() + textField.text = string + return false + } + + var isNew = false + + if range.location > 0 || range.length > 0 { + if let newTextField = textFields.next(after: textField) { + newTextField.becomeFirstResponder() + newTextField.text = string + isNew = true + } else { + if isAllFieldsFilled() { + allFieldsFilledAction?(allValues()) + } + + textField.text = String(string.last ?? Character("")) + + return false + } + } + + if isAllFieldsFilled() { + allFieldsFilledAction?(allValues()) + } + + if string.count == 1 && !isNew { + textField.text = string + } + + return false + } + + private func isAllFieldsFilled() -> Bool { + return textFields + .filter { ($0.text ?? "").count == 0 } + .count < 1 + } + + private func allValues() -> String { + return textFields + .map { $0.text } + .compactMap { $0 } + .joined() + } + } +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/View/ViewsFactory/CodeConfirmationViewsFactory.swift b/Nynja/Modules/Auth/CodeConfirmation/View/ViewsFactory/CodeConfirmationViewsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..1c598c07a7f096f36c5304bdaa140d8cb2cb84ed --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/View/ViewsFactory/CodeConfirmationViewsFactory.swift @@ -0,0 +1,225 @@ +// +// CodeConfirmationViewsFactory.swift +// Nynja +// +// Created by Ash on 10/10/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol CodeConfirmationViewsFactoryProtocol { + func makeBackButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeWelcomeLabel(on view: UIView) -> UILabel + func makeLogoImageView(on view: UIView, top: UIView) -> UIImageView + func makeAddressLabel(on view: UIView, top: UIView) -> UILabel + func makeTextFieldsContainer(on view: UIView, top: UIView) -> UIView + func makeFirstTextField(on view: UIView) -> UITextField + func makeMiddleTextField(on view: UIView, left: UIView) -> UITextField + func makeLastTextField(on view: UIView, left: UIView) -> UITextField + func makeDescriptionLabel(on view: UIView, top: UIView) -> UILabel + func makeTimerLabel(on view: UIView) -> UILabel + func makeResendCodeButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeCallMeButton(on view: UIView, top: UIView, target: AnyObject, selector: Selector) -> UIButton +} + +final class CodeConfirmationViewsFactory: CodeConfirmationViewsFactoryProtocol { + func makeBackButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.setImage(UIImage.nynja.icBackNavigation.image, for: .normal) + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.left.equalToSuperview().offset(8) + make.top.equalToSuperview().offset(37) + make.width.height.equalTo(24) + } + + return button + } + + func makeWelcomeLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.font = FontFamily.NotoSans.medium.font(size: 16) + label.textColor = UIColor.nynja.white + + label.text = "Welcome to".localized + + label.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(70) + make.centerX.equalToSuperview() + } + + return label + } + + func makeLogoImageView(on view: UIView, top: UIView) -> UIImageView { + let imageView = UIImageView() + view.addSubview(imageView) + + imageView.contentMode = .scaleAspectFill + imageView.image = UIImage.nynja.logo2.image + + imageView.snp.makeConstraints { (make) in + make.top.equalTo(top.snp.bottom).offset(16) + make.centerX.equalToSuperview() + make.width.equalToSuperview().multipliedBy(9/20) + } + + return imageView + } + + func makeAddressLabel(on view: UIView, top: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.font = FontFamily.NotoSans.medium.font(size: 16) + label.textColor = UIColor.nynja.white + + label.snp.makeConstraints { (make) in + make.top.equalTo(top.snp.bottom).offset(42) + make.centerX.equalToSuperview() + } + + return label + } + + func makeTextFieldsContainer(on view: UIView, top: UIView) -> UIView { + let container = UIView() + view.addSubview(container) + container.snp.makeConstraints { (make) in + make.height.equalTo(64) + make.centerX.equalToSuperview() + make.top.equalTo(top.snp.bottom).offset(16) + } + + return container + } + + func makeFirstTextField(on view: UIView) -> UITextField { + let textField = UITextField() + view.addSubview(textField) + + textField.keyboardType = .numberPad + textField.tintColor = UIColor.nynja.mainRed + textField.textAlignment = .center + textField.textColor = UIColor.nynja.white + + textField.snp.makeConstraints { (make) in + make.left.equalToSuperview() + make.centerY.equalToSuperview() + make.width.equalTo(36) + } + + return textField + } + + func makeMiddleTextField(on view: UIView, left: UIView) -> UITextField { + let textField = UITextField() + view.addSubview(textField) + + textField.keyboardType = .numberPad + textField.tintColor = UIColor.nynja.mainRed + textField.textAlignment = .center + textField.textColor = UIColor.nynja.white + + textField.snp.makeConstraints { (make) in + make.left.equalTo(left.snp.right).offset(5) + make.centerY.equalToSuperview() + make.width.equalTo(36) + } + + return textField + } + + func makeLastTextField(on view: UIView, left: UIView) -> UITextField { + let textField = UITextField() + view.addSubview(textField) + + textField.keyboardType = .numberPad + textField.tintColor = UIColor.nynja.mainRed + textField.textAlignment = .center + textField.textColor = UIColor.nynja.white + + textField.snp.makeConstraints { (make) in + make.left.equalTo(left.snp.right).offset(5) + make.centerY.equalToSuperview() + make.width.equalTo(36) + make.right.equalToSuperview() + } + + return textField + } + + func makeDescriptionLabel(on view: UIView, top: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.font = FontFamily.NotoSans.regular.font(size: 14) + label.textColor = UIColor.nynja.manatee + + label.snp.makeConstraints { (make) in + make.top.equalTo(top.snp.bottom) + make.centerX.equalToSuperview() + } + return label + } + + func makeTimerLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.textAlignment = .center + label.textColor = UIColor.nynja.white + label.font = FontFamily.NotoSans.regular.font(size: 16) + + label.snp.makeConstraints { (make) in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-300) + make.width.lessThanOrEqualToSuperview() + } + + return label + } + + func makeResendCodeButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.setTitle("Resend code".localized, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.darkRed, for: .highlighted) + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 16) + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-300) + } + + return button + } + + func makeCallMeButton(on view: UIView, top: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.setTitle("Call me".localized, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.darkRed, for: .highlighted) + button.titleLabel?.font = FontFamily.NotoSans.medium.font(size: 16) + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.centerX.equalToSuperview() + make.top.equalTo(top.snp.bottom).offset(10) + + } + + return button + } +} diff --git a/Nynja/Modules/Auth/CodeConfirmation/Wireframe/CodeConfirmationWireframe.swift b/Nynja/Modules/Auth/CodeConfirmation/Wireframe/CodeConfirmationWireframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..b4adfec5aebade5d6503e3ca5d6774b5350c5052 --- /dev/null +++ b/Nynja/Modules/Auth/CodeConfirmation/Wireframe/CodeConfirmationWireframe.swift @@ -0,0 +1,68 @@ +// +// CodeConfirmationWireframe.swift +// Nynja +// +// Created by Ash on 9/30/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +enum AuthProviderType { + case email + case phoneNumber +} + +protocol CodeConfirmationCoordinatorProtocol { + func wireframe(_ wireframe: CodeConfirmationWireframe, didEndWith state: CodeConfirmationWireframe.State) +} + +final class CodeConfirmationWireframe: CodeConfirmationWireframeProtocol { + private let coordinator: CodeConfirmationCoordinatorProtocol + + init(coordinator: CodeConfirmationCoordinatorProtocol) { + self.coordinator = coordinator + } + + struct Parameters { + let address: String + let authType: AuthProviderType + } + + struct Dependencies { + + } + + enum State { + case validCode(type: AuthenticationType) + case invalidCode + case back + } + + func prepareModule(parameters: CodeConfirmationWireframe.Parameters, dependencies: CodeConfirmationWireframe.Dependencies) -> UIViewController { + let presenter = CodeConfirmationPresenter() + let viewDep = CodeConfirmationViewController.Dependencies(presenter: presenter, viewsFactory: CodeConfirmationViewsFactory()) + let view = CodeConfirmationViewController(dependencies: viewDep) + let interactor = CodeConfirmationInteractor(address: parameters.address, authProviderType: parameters.authType) + + let presenterDep = CodeConfirmationPresenter.Dependencies(view: view, interactor: interactor, wireframe: self) + let interactorDep = CodeConfirmationInteractor.Dependencies(presenter: presenter) + + presenter.inject(dependencies: presenterDep) + interactor.inject(dependencies: interactorDep) + + return view + } + + func codeValid(with type: AuthenticationType) { + coordinator.wireframe(self, didEndWith: .validCode(type: type)) + } + + func codeInvalid() { + coordinator.wireframe(self, didEndWith: .invalidCode) + } + + func back() { + coordinator.wireframe(self, didEndWith: .back) + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/CountrySelectorProtocols.swift b/Nynja/Modules/Auth/CountrySelector/CountrySelectorProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..9f85c4ed8dd0a4bfbf576d988e6e74042d9b6280 --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/CountrySelectorProtocols.swift @@ -0,0 +1,36 @@ +// +// CountrySelectorProtocols.swift +// Nynja +// +// Created by Ash on 9/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +protocol CountrySelectorWireframeProtocol: WireframeProtocol { + func selectCountry(_ country: Country) +} + +protocol CountrySelectorViewProtocol: class where Self: UIViewController { + func reloadData() +} + +protocol CountrySelectorPresenterProtocol: NavigationProtocol { + var sections: [CountriesSection] { get } + + func viewDidLoad() + + func applyFilter(_ filter: String) + func selectCountry(_ country: Country) +} + +protocol CountrySelectorInteractorOutputProtocol { + func filteredCountries(_ countries: [Country]) +} + +protocol CountrySelectorInteractorInputProtocol { + var filter: String { get set } + var filteredCountries: [Country] { get } +} diff --git a/Nynja/Modules/Auth/CountrySelector/Entities/Country.swift b/Nynja/Modules/Auth/CountrySelector/Entities/Country.swift new file mode 100644 index 0000000000000000000000000000000000000000..870fd83dab678ceac3a30127f11a6a8adac67343 --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/Entities/Country.swift @@ -0,0 +1,21 @@ +// +// Country.swift +// Nynja +// +// Created by Ash on 9/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +struct CountriesSection { + let symbol: String + var countries: [Country] +} + +struct Country: Equatable, Hashable { + let ISO: String + let name: String + let code: String + let numberTemplate: String +} diff --git a/Nynja/Modules/Auth/CountrySelector/Interactor/CountrySelectorInteractor.swift b/Nynja/Modules/Auth/CountrySelector/Interactor/CountrySelectorInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..51a03faa26c517f8850dacd6d38e032f61413e67 --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/Interactor/CountrySelectorInteractor.swift @@ -0,0 +1,54 @@ +// +// CountrySelectorInteractor.swift +// Nynja +// +// Created by Ash on 9/27/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class CountrySelectorInteractor: CountrySelectorInteractorInputProtocol, SetInjectable { + private var presenter: CountrySelectorInteractorOutputProtocol? + private var storageService: StorageServiceProtocol? + + var filter: String = "" { + didSet { + guard filter != "" else { + return filteredCountries = countries + } + + filteredCountries = countries.filter { + $0.name.contains(substring: filter, options: .caseInsensitive) + } + } + } + + private var countries: [Country] { + get { + return (storageService?.countries.map { + Country(ISO: $0.ISO, name: $0.name, code: $0.code, numberTemplate: $0.placeHolder ?? "") + } ?? []).sorted { $0.name < $1.name } + } + } + + private(set) var filteredCountries: [Country] = [] { + didSet { + presenter?.filteredCountries(filteredCountries) + } + } +} + +// MARK: - SetInjectable + +extension CountrySelectorInteractor { + struct Dependencies { + let presenter: CountrySelectorInteractorOutputProtocol + let storageService: StorageServiceProtocol + } + + func inject(dependencies: CountrySelectorInteractor.Dependencies) { + presenter = dependencies.presenter + storageService = dependencies.storageService + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/Presenter/CountrySelectorPresenter.swift b/Nynja/Modules/Auth/CountrySelector/Presenter/CountrySelectorPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..b66cc50b556e77168aeea6a68ebc27f3ab634e56 --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/Presenter/CountrySelectorPresenter.swift @@ -0,0 +1,69 @@ +// +// CountrySelectorPresenter.swift +// Nynja +// +// Created by Ash on 9/27/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class CountrySelectorPresenter: CountrySelectorInteractorOutputProtocol, CountrySelectorPresenterProtocol, SetInjectable { + private weak var view: CountrySelectorViewProtocol? + private var interactor: CountrySelectorInteractorInputProtocol? + private var wireframe: CountrySelectorWireframe? + + private(set) var sections: [CountriesSection] = [] { + didSet { + view?.reloadData() + } + } + + func viewDidLoad() { + interactor?.filter = "" + } + + func applyFilter(_ filter: String) { + interactor?.filter = filter + } + + func selectCountry(_ country: Country) { + wireframe?.selectCountry(country) + } + + func back() { + wireframe?.back() + } +} + +// MARK: - SetInjectable + +extension CountrySelectorPresenter { + struct Dependencies { + let view: CountrySelectorViewProtocol + let interactor: CountrySelectorInteractorInputProtocol + let wireframe: CountrySelectorWireframe + } + + func inject(dependencies: CountrySelectorPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireframe = dependencies.wireframe + } +} + +// MARK: - CountrySelectorInteractorOutputProtocol + +extension CountrySelectorPresenter { + func filteredCountries(_ countries: [Country]) { + let filterAction: (String, [Country]) -> [Country] = { symbol, countries in + countries.filter { $0.name.first == symbol.first } + } + + sections = countries + .compactMap { $0.name.first } + .map { String($0) } + .uniqueWithPreservedOrder() + .map { CountriesSection(symbol: $0, countries: filterAction($0, countries)) } + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/View/Cells/CountryTVCell.swift b/Nynja/Modules/Auth/CountrySelector/View/Cells/CountryTVCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..433565986d9eeac87ff01eb82d79f00b962bf533 --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/View/Cells/CountryTVCell.swift @@ -0,0 +1,103 @@ +// +// CountryTVCell.swift +// Nynja +// +// Created by Ash on 9/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class CountryTVCell: UITableViewCell, Configurable, IdentityProtocol { + static var identifier: String = "CountryTVCell" + + private var titleLabel: UILabel? + private var codeLabel: UILabel? +} + +// MARK: - Configurable + +extension CountryTVCell { + struct Config { + let name: String + let code: String + } + + func configure(config: CountryTVCell.Config) { + separatorInset = UIEdgeInsets( + top: separatorInset.top, + left: CGFloat(SelfLayout.separatorLeftInset), + bottom: separatorInset.bottom, + right: separatorInset.right) + + backgroundColor = UIColor.nynja.clear + contentView.backgroundColor = UIColor.nynja.clear + + titleLabel = titleLabel ?? makeTitleLabel(on: contentView) + codeLabel = codeLabel ?? makeCodeLabel(on: contentView) + + titleLabel?.text = config.name + codeLabel?.text = "+" + config.code + } +} + +// MARK: - ui factory methods + +private extension CountryTVCell { + func makeTitleLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.textAlignment = .left + label.textColor = UIColor.nynja.white + label.font = FontFamily.NotoSans.medium.font(size: CGFloat(TitleLabelLayout.fontSize)) + + label.snp.makeConstraints { (make) in + make.left.equalTo(TitleLabelLayout.left) + make.height.equalTo(TitleLabelLayout.height) + make.centerY.equalToSuperview() + } + + return label + } + + func makeCodeLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.textAlignment = .right + label.textColor = UIColor.nynja.dustyGray + label.font = FontFamily.NotoSans.medium.font(size: CGFloat(CodeLabelLayout.fontSize)) + + label.snp.makeConstraints { (make) in + make.right.equalTo(-CodeLabelLayout.right) + make.height.equalTo(CodeLabelLayout.height) + make.centerY.equalToSuperview() + } + + return label + } +} + +// MARK: - Layout + +private extension CountryTVCell { + enum TitleLabelLayout { + static let left = 68.0 + static let height = 22.0 + + static let fontSize = 16.0 + } + + enum CodeLabelLayout { + static let right = 16.0 + static let height = 20.0 + + static let fontSize = 14.0 + } + + enum SelfLayout { + static let separatorLeftInset = 68.0 + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/View/CountrySelectorViewController.swift b/Nynja/Modules/Auth/CountrySelector/View/CountrySelectorViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..e1759e46b28a09dadc6231248d3702aa9ee1c6af --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/View/CountrySelectorViewController.swift @@ -0,0 +1,160 @@ +// +// CountrySelectorViewController.swift +// Nynja +// +// Created by Ash on 9/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CountrySelectorViewController: UIViewController, CountrySelectorViewProtocol, InitializeInjectable, UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate, KeyboardInteractive { + private let presenter: CountrySelectorPresenterProtocol + private let viewsFactory: CountrySelectorViewsFactoryProtocol + + private lazy var topHeaderLayoutGuide: UILayoutGuide = viewsFactory.makeTopLayoutGuide(on: view) + private lazy var headerView: NavigationView = viewsFactory.makeHeaderView(on: view, layoutGuide: topHeaderLayoutGuide, presenter: presenter) + private lazy var tableView: UITableView = viewsFactory.makeTableView(on: view, top: headerView, bottom: controlContainerView, delegate: self) + private lazy var controlContainerView: NynjaControlContainerView = viewsFactory.makeControlContainerView(on: view, searchField: searchField, verticalInsetCalculationAction: adjustVerticalInset) + + private lazy var searchField: NynjaSearchField = viewsFactory.makeSearchField(presenter: presenter) + + init(dependencies: Dependencies) { + presenter = dependencies.presenter + viewsFactory = dependencies.viewsFactory + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.nynja.darkLight + + _ = [topHeaderLayoutGuide, headerView, tableView, controlContainerView, searchField] + + presenter.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + registerForKeyboardNotifications() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + unregisterForKeyboardNotifications() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} + +// MARK: - SetInjectable + +extension CountrySelectorViewController { + struct Dependencies { + let presenter: CountrySelectorPresenterProtocol + let viewsFactory: CountrySelectorViewsFactoryProtocol + } +} + +// MARK: - CountrySelectorViewProtocol + +extension CountrySelectorViewController { + func reloadData() { + tableView.reloadData() + } +} + +// MARK: - UITableViewDelegate + +extension CountrySelectorViewController { + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = CountryHeaderTVView() + let section = presenter.sections[section] + + view.configure(config: CountryHeaderTVView.Config(symbol: section.symbol)) + + return view + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 52 + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 36 + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + presenter.selectCountry(countryWithIndexPath(indexPath)) + } +} + +// MARK: - UITableViewDataSource + +extension CountrySelectorViewController { + func numberOfSections(in tableView: UITableView) -> Int { + return presenter.sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let sections = presenter.sections + let currentSection = sections[section] + let countriesInSection = currentSection.countries + + return countriesInSection.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: CountryTVCell.identifier, for: indexPath) as? CountryTVCell else { + fatalError() + } + + let country = countryWithIndexPath(indexPath) + cell.configure(config: CountryTVCell.Config(name: country.name, code: country.code)) + + return cell + } +} + +// MARK: - UIScrollViewDelegate + +extension CountrySelectorViewController { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let verticalIndicator = scrollView.subviews[scrollView.subviews.count - 1] + + verticalIndicator.backgroundColor = UIColor.nynja.mainRed + } +} + +// MARK: - KeyboardInteractive + +extension CountrySelectorViewController { + func keyboardNotified(endFrame: CGRect) { + if endFrame.origin.y >= UIScreen.main.bounds.size.height { + updateToHide(view: controlContainerView, offset: -28) + } else { + updateToShow(view: controlContainerView, offset: -28 - endFrame.height) + } + } +} + +// MARK: - Private + +private extension CountrySelectorViewController { + func sectionWithIndexPath(_ indexPath: IndexPath) -> CountriesSection { + return presenter.sections[indexPath.section] + } + + func countryWithIndexPath(_ indexPath: IndexPath) -> Country { + return sectionWithIndexPath(indexPath).countries[indexPath.row] + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/View/CountrySelectorViewsFactory.swift b/Nynja/Modules/Auth/CountrySelector/View/CountrySelectorViewsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..af8ed7095953ff96135282581e78ef8ba289ed04 --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/View/CountrySelectorViewsFactory.swift @@ -0,0 +1,96 @@ +// +// CountrySelectorViewsFactory.swift +// Nynja +// +// Created by Ash on 10/10/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation +import SnapKit + +typealias VerticalInsetCalculationAction = (_ inset: AdjustedInset, _ make: ConstraintMaker, _ offset: Int) -> Void + +protocol CountrySelectorViewsFactoryProtocol { + func makeTopLayoutGuide(on view: UIView) -> UILayoutGuide + func makeHeaderView(on view: UIView, layoutGuide: UILayoutGuide, presenter: CountrySelectorPresenterProtocol) -> NavigationView + func makeTableView(on view: UIView, top: UIView, bottom: UIView, delegate: UITableViewDelegate & UITableViewDataSource) -> UITableView + func makeControlContainerView(on view: UIView, searchField: NynjaSearchField, verticalInsetCalculationAction: VerticalInsetCalculationAction) -> NynjaControlContainerView + func makeSearchField(presenter: CountrySelectorPresenterProtocol) -> NynjaSearchField +} + +final class CountrySelectorViewsFactory: CountrySelectorViewsFactoryProtocol { + func makeTopLayoutGuide(on view: UIView) -> UILayoutGuide { + let layoutGuide = UILayoutGuide() + view.addLayoutGuide(layoutGuide) + + layoutGuide.snp.makeConstraints { (make) in + make.top.left.right.equalToSuperview() + make.height.equalTo(20 + UIWindow.safeAreaTopPadding()) + } + + return layoutGuide + } + + func makeHeaderView(on view: UIView, layoutGuide: UILayoutGuide, presenter: CountrySelectorPresenterProtocol) -> NavigationView { + let config = NavigationView.Config( + isVisibleSeparator: true, + isVisibleBackButton: true, + title: "select country".uppercased(), + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image) + + return UIView.makeHeaderView(on: view, top: layoutGuide, config: config) + } + + func makeTableView(on view: UIView, top: UIView, bottom: UIView, delegate: UITableViewDelegate & UITableViewDataSource) -> UITableView { + let tableView = UITableView() + view.addSubview(tableView) + + tableView.keyboardDismissMode = .interactive + tableView.backgroundColor = UIColor.nynja.clear + tableView.separatorStyle = .singleLine + tableView.tableFooterView = UIView() + tableView.showsHorizontalScrollIndicator = false + tableView.rowHeight = 52 + tableView.estimatedRowHeight = tableView.rowHeight + + tableView.delegate = delegate + tableView.dataSource = delegate + + tableView.snp.makeConstraints { (make) in + make.top.equalTo(top.snp.bottom) + make.bottom.equalTo(bottom.snp.top) + make.left.right.equalToSuperview() + } + + tableView.register(CountryTVCell.self, + forCellReuseIdentifier: CountryTVCell.identifier) + + return tableView + } + + func makeControlContainerView(on view: UIView, searchField: NynjaSearchField, verticalInsetCalculationAction: VerticalInsetCalculationAction) -> NynjaControlContainerView { + let containerView = NynjaControlContainerView(contentView: searchField) + view.addSubview(containerView) + + containerView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + verticalInsetCalculationAction(.bottom, make, -28) + } + + containerView.addGradientView() + + return containerView + } + + func makeSearchField(presenter: CountrySelectorPresenterProtocol) -> NynjaSearchField { + let searchField = NynjaSearchField() + + searchField.searchTextChangeHandler = { [weak self] searchQuery in + presenter.applyFilter(searchQuery ?? "") + } + + return searchField + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/View/Headers/CountryTVHeader.swift b/Nynja/Modules/Auth/CountrySelector/View/Headers/CountryTVHeader.swift new file mode 100644 index 0000000000000000000000000000000000000000..b436967ebb2aae102f7e72e82b3ff4f6bef08eaf --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/View/Headers/CountryTVHeader.swift @@ -0,0 +1,74 @@ +// +// CountryTVHeader.swift +// Nynja +// +// Created by Ash on 9/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + + +final class CountryHeaderTVView: UIView, Configurable, IdentityProtocol { + static var identifier: String = "CountryHeaderTVView" + + private var title: UILabel? +} + +// MARK: - Configurable + +extension CountryHeaderTVView { + struct Config { + let symbol: String + } + + func configure(config: CountryHeaderTVView.Config) { + backgroundColor = UIColor.nynja.clear + + title = title ?? makeTitleLabel(on: self) + title?.text = config.symbol + } +} + +// MARK: - UI factory methods + +private extension CountryHeaderTVView { + func makeTitleLabel(on view: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.layer.cornerRadius = CGFloat(TitleLabelLayout.cornerRadius) + + label.backgroundColor = UIColor.nynja.mainRed + label.clipsToBounds = true + + label.textColor = UIColor.nynja.white + label.textAlignment = .center + label.font = FontFamily.NotoSans.medium.font(size: CGFloat(TitleLabelLayout.fontSize)) + + label.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.width.equalTo(TitleLabelLayout.width) + make.height.equalTo(TitleLabelLayout.height) + make.left.equalTo(TitleLabelLayout.left) + } + + return label + } +} + +// MARK: - Layout + +private extension CountryHeaderTVView { + enum TitleLabelLayout { + static let width = 28.0 + static let height = 28.0 + + static let cornerRadius = width / 2 + + static let left = 16.0 + + static let fontSize = 16.0 + } +} diff --git a/Nynja/Modules/Auth/CountrySelector/Wireframe/CountrySelectorWireframe.swift b/Nynja/Modules/Auth/CountrySelector/Wireframe/CountrySelectorWireframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..222c5a2dd65a29dc93e49334dad0989ee1d9bade --- /dev/null +++ b/Nynja/Modules/Auth/CountrySelector/Wireframe/CountrySelectorWireframe.swift @@ -0,0 +1,55 @@ +// +// CountrySelectorWireframe.swift +// Nynja +// +// Created by Ash on 9/27/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol CountrySelectorCoordinatorProtocol { + func wireframe(_ wireframe: CountrySelectorWireframe, endWithState state: CountrySelectorWireframe.State) +} + +final class CountrySelectorWireframe: CountrySelectorWireframeProtocol { + typealias Parameters = NSNull + + struct Dependencies { + let storageService: StorageServiceProtocol + } + + enum State { + case back + case endWith(country: Country) + } + + private var coordinator: CountrySelectorCoordinatorProtocol? + + init(coordinator: CountrySelectorCoordinatorProtocol) { + self.coordinator = coordinator + } + + func prepareModule(parameters: Parameters, dependencies: Dependencies) -> UIViewController { + let presenter = CountrySelectorPresenter() + let viewDep = CountrySelectorViewController.Dependencies(presenter: presenter, viewsFactory: CountrySelectorViewsFactory()) + let view = CountrySelectorViewController(dependencies: viewDep) + let interactor = CountrySelectorInteractor() + + let presenterDep = CountrySelectorPresenter.Dependencies(view: view, interactor: interactor, wireframe: self) + let interactorDep = CountrySelectorInteractor.Dependencies(presenter: presenter, storageService: dependencies.storageService) + + presenter.inject(dependencies: presenterDep) + interactor.inject(dependencies: interactorDep) + + return view + } + + func back() { + coordinator?.wireframe(self, endWithState: .back) + } + + func selectCountry(_ country: Country) { + coordinator?.wireframe(self, endWithState: .endWith(country: country)) + } +} diff --git a/Nynja/Modules/Auth/CreateProfile/CreateProfileProtocols.swift b/Nynja/Modules/Auth/CreateProfile/CreateProfileProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..3b767f395255622743569f4433e193a5963dd1cb --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/CreateProfileProtocols.swift @@ -0,0 +1,64 @@ +// +// CreateProfileProtocols.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +enum ProfileField { + case firstName + case lastName + case accountName + case userName + + var isRequired: Bool { + return self == .firstName + } + + var validationRule: String { + return "^([a-zA-Z]|[0-9]|_){2,}$" + } + + var placeholder: String { + switch self { + case .firstName: return "First Name" + case .lastName: return "Last Name" + case .accountName: return "Account Name" + case .userName: return "Username" + } + } +} + +protocol CreateProfileWireframeProtocol: WireframeProtocol { + func back() + func end() + func chooseAvatar(completion: @escaping (UIImage?) -> Void) +} + +protocol CreateProfileViewProtocol: class where Self: UIViewController { + func updateProfileField(_ field: ProfileField, value: String) + func setCreateEnabled(_ enabled: Bool) +} + +protocol CreateProfilePresenterProtocol: NavigationProtocol { + func createAccount() + func isValidValue(_ value: String, for field: ProfileField) -> Result + func setProfileField(value: String, field: ProfileField) + func chooseAvatar(completion: @escaping (UIImage?) -> Void) + func checkTermsOfUse() -> Bool +} + +protocol CreateProfileInputInteractorProtocol { + func setProfileField(_ field: ProfileField, value: String) + func checkTermsOfUse() -> Bool + func setAvatar(image: UIImage?) + func isValidValue(_ value: String, for field: ProfileField) -> Result +} + +protocol CreateProfileOutputInteractorProtocol { + func minimalRequirementsAreSatisfied(_ satisfied: Bool) + func profileFieldUpdated(_ profileField: ProfileField, value: String) +} diff --git a/Nynja/Modules/Auth/CreateProfile/Interactor/CreateProfileInteractor.swift b/Nynja/Modules/Auth/CreateProfile/Interactor/CreateProfileInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..c91a407225d4e9484f0326da257a40d1bbacdfa0 --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/Interactor/CreateProfileInteractor.swift @@ -0,0 +1,110 @@ +// +// CreateProfileInteractor.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class CreateProfileInteractor: CreateProfileInputInteractorProtocol, SetInjectable { + private var presenter: CreateProfileOutputInteractorProtocol? + + private var checkTermsOfUsage: Bool = false { + didSet { + presenter?.minimalRequirementsAreSatisfied(isAllRequirementsAreSatisfied()) + } + } + + private var avatar: UIImage? = nil + + private var firstName: String = "" { + didSet { + if oldValue == userName { + userName = firstName + } + + presenter?.profileFieldUpdated(.firstName, value: firstName) + presenter?.minimalRequirementsAreSatisfied(isAllRequirementsAreSatisfied()) + } + } + private var lastName: String = "" { + didSet { + presenter?.profileFieldUpdated(.lastName, value: lastName) + presenter?.minimalRequirementsAreSatisfied(isAllRequirementsAreSatisfied()) + } + } + + private var accountName: String = "" { + didSet { + presenter?.profileFieldUpdated(.accountName, value: accountName) + presenter?.minimalRequirementsAreSatisfied(isAllRequirementsAreSatisfied()) + } + } + private var userName: String = "" { + didSet { + presenter?.profileFieldUpdated(.userName, value: userName) + presenter?.minimalRequirementsAreSatisfied(isAllRequirementsAreSatisfied()) + } + } +} + +// MARK: - CreateProfileInputInteractorProtocol + +extension CreateProfileInteractor { + func checkTermsOfUse() -> Bool { + checkTermsOfUsage = !checkTermsOfUsage + + return checkTermsOfUsage + } + + func setAvatar(image: UIImage?) { + avatar = image + } + + func setProfileField(_ field: ProfileField, value: String) { + switch field { + case .firstName: firstName = value + case .lastName: lastName = value + case .accountName: accountName = value + case .userName: userName = value + } + } + + func isValidValue(_ value: String, for field: ProfileField) -> Result { + enum ValidationError: Error { + case somethingWentWrong + } + + let predicate = NSPredicate(format:"SELF MATCHES %@", field.validationRule) + + return predicate.evaluate(with: value) + ? .success(()) + : .failure(ValidationError.somethingWentWrong) + } +} + +// MARK: - SetInjectable + +extension CreateProfileInteractor { + struct Dependencies { + let presenter: CreateProfileOutputInteractorProtocol + } + + func inject(dependencies: CreateProfileInteractor.Dependencies) { + presenter = dependencies.presenter + } +} + +// MARK: - Private + +private extension CreateProfileInteractor { + func isAllRequirementsAreSatisfied() -> Bool { + return checkTermsOfUsage + && isValidValue(firstName, for: .firstName).isSuccess + && firstName.count >= 2 + && isValidValue(userName, for: .userName).isSuccess + && userName.count >= 2 + } +} diff --git a/Nynja/Modules/Auth/CreateProfile/Presenter/CreateProfilePresenter.swift b/Nynja/Modules/Auth/CreateProfile/Presenter/CreateProfilePresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..492cb6ac050cc7288fba186b3cee624479f15609 --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/Presenter/CreateProfilePresenter.swift @@ -0,0 +1,71 @@ +// +// CreateProfilePresenter.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CreateProfilePresenter: CreateProfileOutputInteractorProtocol, CreateProfilePresenterProtocol, SetInjectable { + private var wireframe: CreateProfileWireframe? + private var interactor: CreateProfileInputInteractorProtocol? + private weak var view: CreateProfileViewProtocol? +} + +// MARK: - CreateProfileOutputInteractorProtocol & CreateProfilePresenterProtocol + +extension CreateProfilePresenter { + func isValidValue(_ value: String, for field: ProfileField) -> Result { + return interactor?.isValidValue(value, for: field) ?? .success(()) + } + + func profileFieldUpdated(_ profileField: ProfileField, value: String) { + view?.updateProfileField(profileField, value: value) + } + + func createAccount() { + wireframe?.end() + } + + func setProfileField(value: String, field: ProfileField) { + interactor?.setProfileField(field, value: value) + } + + func chooseAvatar(completion: @escaping (UIImage?) -> Void) { + wireframe?.chooseAvatar(completion: { (image) in + completion(image) + self.interactor?.setAvatar(image: image) + }) + } + + func checkTermsOfUse() -> Bool { + return interactor?.checkTermsOfUse() ?? false + } + + func minimalRequirementsAreSatisfied(_ satisfied: Bool) { + view?.setCreateEnabled(satisfied) + } + + func back() { + wireframe?.back() + } +} + +// MARK: - SetInjectable + +extension CreateProfilePresenter { + struct Dependencies { + let wireframe: CreateProfileWireframe + let interactor: CreateProfileInputInteractorProtocol + let view: CreateProfileViewProtocol + } + + func inject(dependencies: CreateProfilePresenter.Dependencies) { + wireframe = dependencies.wireframe + interactor = dependencies.interactor + view = dependencies.view + } +} diff --git a/Nynja/Modules/Auth/CreateProfile/View/CreateProfileViewController.swift b/Nynja/Modules/Auth/CreateProfile/View/CreateProfileViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..96c9fcc7a609efd8239db22fdb6643f25c1077b2 --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/View/CreateProfileViewController.swift @@ -0,0 +1,104 @@ +// +// CreateProfileViewController.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CreateProfileViewController: UIViewController, CreateProfileViewProtocol, InitializeInjectable, KeyboardInteractive { + private let presenter: CreateProfilePresenterProtocol + private let viewsFactory: CreateProfileViewsFactoryProtocol + + private lazy var topHeaderLayoutGuide: UILayoutGuide = viewsFactory.makeTopLayoutGuide(on: view) + private lazy var headerView: NavigationView = viewsFactory.makeHeaderView(on: view, topLayoutGuide: topHeaderLayoutGuide, navigationHandler: presenter) + private lazy var createButton: UIButton = viewsFactory.makeCreateButton(on: view, target: self, selector: #selector(createAccount(sender:))) + private lazy var container: UIView = viewsFactory.makeContainer(on: view, headerView: headerView, footerView: createButton) + private lazy var scrollView: UIScrollView = viewsFactory.makeScrollView(on: container) + private lazy var contentContainer: CreateProfileContentView = viewsFactory.makeContentContainer(on: scrollView, widthView: view, presenter: presenter) + + init(dependencies: Dependencies) { + presenter = dependencies.presenter + viewsFactory = dependencies.viewsFactory + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.nynja.darkLight + + _ = [topHeaderLayoutGuide, headerView, createButton, container, scrollView, contentContainer] + + enableKeyboardHidingWhenTappedAround() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + registerForKeyboardNotifications() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + unregisterForKeyboardNotifications() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} + +// MARK: - CreateProfileViewProtocol + +extension CreateProfileViewController { + func updateProfileField(_ field: ProfileField, value: String) { + contentContainer.textFieldValueDidChange(value: value, field: field) + } + + func setCreateEnabled(_ enabled: Bool) { + createButton.isEnabled = enabled + } +} + +// MARK: - InitializeInjectable + +extension CreateProfileViewController { + struct Dependencies { + let presenter: CreateProfilePresenterProtocol + let viewsFactory: CreateProfileViewsFactoryProtocol + } +} + +// MARK: - KeyboardInteractive + +extension CreateProfileViewController { + func keyboardNotified(endFrame: CGRect) { + var bottomInset: CGFloat = 28 + + if endFrame.origin.y < UIScreen.main.bounds.size.height { + bottomInset += endFrame.height + } else { + bottomInset += UIWindow.safeAreaBottomPadding() + } + + createButton.snp.updateConstraints { (make) in + make.bottom.equalToSuperview().inset(bottomInset) + } + } +} + +//MARK: - Actions + +extension CreateProfileViewController { + @objc func createAccount(sender: UIButton) { + presenter.createAccount() + } +} diff --git a/Nynja/Modules/Auth/CreateProfile/View/Subviews/CreateProfileContentView.swift b/Nynja/Modules/Auth/CreateProfile/View/Subviews/CreateProfileContentView.swift new file mode 100644 index 0000000000000000000000000000000000000000..e239588538684af8658abdac9ce05dda51a17ea6 --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/View/Subviews/CreateProfileContentView.swift @@ -0,0 +1,144 @@ +// +// CreateProfileContentView.swift +// Nynja +// +// Created by Ash on 10/12/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + + +final class CreateProfileContentView: UIView, Configurable { + private let viewsFactory: CreateProfileViewsFactoryProtocol + + private var chooseAvatarAction: (() -> Void)? + private var markCheckAction: (() -> Bool)? + private var textChangedAction: ((String, ProfileField) -> Void)? + private var isValidAction: ((String, ProfileField) -> Result)? + + init(viewsFactory: CreateProfileViewsFactoryProtocol) { + self.viewsFactory = viewsFactory + + super.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private lazy var avatarbutton: UIButton = viewsFactory.makeAvatarButton(on: self, target: self, selector: #selector(chooseAvatar(sender:))) + + private lazy var firstNameTextField: MaterialTextField = viewsFactory.makeFirstNameTextField( + on: self, + top: avatarbutton, + textChangedHandler: textChangedHandler, + shouldChangeTextHandler: shouldChangeTextHandler) + + private lazy var lastNameTextField: MaterialTextField = viewsFactory.makeLastNameTextField( + on: self, + top: firstNameTextField, + textChangedHandler: textChangedHandler, + shouldChangeTextHandler: shouldChangeTextHandler) + + private lazy var accountNameTextField: MaterialTextField = viewsFactory.makeAccountNameTextField( + on: self, + top: lastNameTextField, + textChangedHandler: textChangedHandler, + shouldChangeTextHandler: shouldChangeTextHandler) + + private lazy var usernameTextField: MaterialTextField = viewsFactory.makeUsernameTextField( + on: self, + top: accountNameTextField, + textChangedHandler: textChangedHandler, + shouldChangeTextHandler: shouldChangeTextHandler) + + private lazy var descriptionLabel: UILabel = viewsFactory.makeDescriptionLabel(on: self, top: usernameTextField) + + private lazy var checkBoxContainerView: UIView = viewsFactory.makeCheckBoxContainerView(on: self, top: descriptionLabel) + private lazy var checkButton: UIButton = viewsFactory.makeCheckButton(on: checkBoxContainerView, target: self, selector: #selector(markCheck(sender:))) + private lazy var termsOfUseTextView: UITextView = viewsFactory.makeTermsOfUseTextView(on: checkBoxContainerView, left: checkButton) +} + +// MARK: - Configurable + +extension CreateProfileContentView { + struct Config { + let chooseAvatarAction: () -> Void + let markCheckAction: () -> Bool + let textChangedAction: (String, ProfileField) -> Void + let isValidAction: (String, ProfileField) -> Result + } + + func configure(config: CreateProfileContentView.Config) { + chooseAvatarAction = config.chooseAvatarAction + markCheckAction = config.markCheckAction + isValidAction = config.isValidAction + textChangedAction = config.textChangedAction + + _ = [avatarbutton, firstNameTextField, lastNameTextField, accountNameTextField, usernameTextField, descriptionLabel, checkBoxContainerView, checkButton, termsOfUseTextView] + } +} + +// MARK: - Public + +extension CreateProfileContentView { + func textFieldValueDidChange(value: String, field: ProfileField) { + switch field { + case .firstName: firstNameTextField.text = value + case .lastName: lastNameTextField.text = value + case .accountName: accountNameTextField.text = value + case .userName: usernameTextField.text = value + } + } + + func updateAvatar(with image: UIImage?) { + let avatar = image ?? UIImage(named: "ic_empty_avatar") + + avatarbutton.setImage(avatar, for: .normal) + } +} + +// MARK: - Actions + +extension CreateProfileContentView { + @objc func chooseAvatar(sender: UIButton) { + chooseAvatarAction?() + } + + @objc func markCheck(sender: UIButton) { + guard let result = markCheckAction?() else { + return + } + + let imageForSender = result + ? UIImage(named: "table_overrides_right_overrides_checkbox_ic_unchecked") + : nil + + sender.setImage(imageForSender, for: .normal) + } +} + +// MARK: - Private + +private extension CreateProfileContentView { + func textChangedHandler(field: ProfileField) -> MTITextChangedHandler { + return { [weak self] input in + self?.textChangedAction?(input.text, field) + } + } + + func shouldChangeTextHandler(field: ProfileField) -> MTIShouldChangeTextHandler { + return { [weak self] input, range, string in + guard let isValid = self?.isValidAction?((input.text as NSString).replacingCharacters(in: range, with: string), field) else { + return true + } + + isValid + .onSuccess { input.info = nil } + .onFailure { input.info = InputInfo(text: $0.localizedDescription, kind: .warning) } + + return true + } + } +} diff --git a/Nynja/Modules/Auth/CreateProfile/View/ViewsFactory/CreateProfileViewsFactory.swift b/Nynja/Modules/Auth/CreateProfile/View/ViewsFactory/CreateProfileViewsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..112757754768921d89262bcf5e6bda0ef451a770 --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/View/ViewsFactory/CreateProfileViewsFactory.swift @@ -0,0 +1,299 @@ +// +// CreateProfileViewsFactory.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol CreateProfileViewsFactoryProtocol { + typealias ChangeTextHandler = (ProfileField) -> MTITextChangedHandler + typealias ShouldChangeTextHandler = (ProfileField) -> MTIShouldChangeTextHandler + + // MARK: - CreateProfileViewController + + func makeTopLayoutGuide(on view: UIView) -> UILayoutGuide + func makeHeaderView(on view: UIView, topLayoutGuide: UILayoutGuide, navigationHandler: NavigationProtocol) -> NavigationView + func makeCreateButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeContainer(on view: UIView, headerView: UIView, footerView: UIView) -> UIView + func makeScrollView(on view: UIView) -> UIScrollView + func makeContentContainer(on view: UIView, widthView: UIView, presenter: CreateProfilePresenterProtocol) -> CreateProfileContentView + + // MARK: - CreateProfileContentView + + func makeAvatarButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeFirstNameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField + func makeLastNameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField + func makeAccountNameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField + func makeUsernameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField + func makeDescriptionLabel(on view: UIView, top: UIView) -> UILabel + func makeCheckBoxContainerView(on view: UIView, top: UIView) -> UIView + func makeCheckButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton + func makeTermsOfUseTextView(on view: UIView, left: UIView) -> UITextView +} + +final class CreateProfileViewsFactory: CreateProfileViewsFactoryProtocol { + + // MARK: - CreateProfileViewController + + func makeTopLayoutGuide(on view: UIView) -> UILayoutGuide { + let layoutGuide = UILayoutGuide() + view.addLayoutGuide(layoutGuide) + + layoutGuide.snp.makeConstraints { (make) in + make.top.left.right.equalToSuperview() + make.height.equalTo(20 + UIWindow.safeAreaTopPadding()) + } + + return layoutGuide + } + + func makeHeaderView(on view: UIView, topLayoutGuide: UILayoutGuide, navigationHandler: NavigationProtocol) -> NavigationView { + let navigationView = UIView.makeHeaderView( + on: view, + top: topLayoutGuide, + config: NavigationView.Config( + isVisibleSeparator: true, + isVisibleBackButton: true, + title: "Create profile".localized.uppercased(), + navigationHandler: navigationHandler, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + + return navigationView + } + + func makeCreateButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.setBackgroundImage(UIImage.makeImageFromColor(UIColor.nynja.mainRed), for: .normal) + button.setBackgroundImage(UIImage.makeImageFromColor(UIColor.nynja.darkRed), for: .disabled) + + button.setTitle("create".localized.uppercased(), for: .normal) + + button.setTitleColor(UIColor.nynja.white, for: .normal) + button.setTitleColor(UIColor.nynja.gray, for: .disabled) + + button.layer.cornerRadius = 22 + button.clipsToBounds = true + + button.isEnabled = false + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.left.right.equalToSuperview().inset(16) + make.bottom.equalToSuperview().offset(-UIWindow.safeAreaBottomPadding() - 28) + make.height.equalTo(44) + } + + return button + } + + func makeContainer(on view: UIView, headerView: UIView, footerView: UIView) -> UIView { + let container = UIView() + view.addSubview(container) + + container.clipsToBounds = true + + container.backgroundColor = UIColor.nynja.darkLight + + container.snp.makeConstraints { (make) in + make.top.equalTo(headerView.snp.bottom) + make.left.right.equalToSuperview() + make.bottom.equalTo(footerView.snp.top) + } + + return container + } + + func makeScrollView(on view: UIView) -> UIScrollView { + let scrollView = UIScrollView() + view.addSubview(scrollView) + + scrollView.backgroundColor = UIColor.nynja.clear + + scrollView.snp.makeConstraints { (make) in + make.edges.equalToSuperview() + } + + return scrollView + } + + func makeContentContainer(on view: UIView, widthView: UIView, presenter: CreateProfilePresenterProtocol) -> CreateProfileContentView { + let contentContainer = CreateProfileContentView(viewsFactory: self) + view.addSubview(contentContainer) + + let config = CreateProfileContentView.Config( + chooseAvatarAction: { + presenter.chooseAvatar(completion: contentContainer.updateAvatar) + }, + markCheckAction: presenter.checkTermsOfUse, + textChangedAction: presenter.setProfileField, + isValidAction: presenter.isValidValue) + + contentContainer.configure(config: config) + + contentContainer.snp.makeConstraints { (make) in + make.top.equalToSuperview().offset(UIWindow.safeAreaTopPadding()) + make.bottom.equalToSuperview().offset(-16) + make.left.right.equalTo(widthView) + } + + return contentContainer + } + + // MARK: - CreateProfileContentView + + func makeAvatarButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.setImage(UIImage(named: "ic_empty_avatar"), for: .normal) + button.addTarget(target, action: selector, for: .touchUpInside) + + button.layer.cornerRadius = 47 + button.clipsToBounds = true + + button.snp.makeConstraints { (make) in + make.centerX.equalToSuperview() + make.top.equalTo(32) + make.height.width.equalTo(94) + } + + return button + } + + func makeFirstNameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField { + return makeTextField(fieldType: .firstName, on: view, top: top, textChangedHandler: textChangedHandler, shouldChangeTextHandler: shouldChangeTextHandler) + } + + func makeLastNameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField { + return makeTextField(fieldType: .lastName, on: view, top: top, textChangedHandler: textChangedHandler, shouldChangeTextHandler: shouldChangeTextHandler) + } + + func makeAccountNameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField { + return makeTextField(fieldType: .accountName, on: view, top: top, textChangedHandler: textChangedHandler, shouldChangeTextHandler: shouldChangeTextHandler) + } + + func makeUsernameTextField(on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField { + return makeTextField(fieldType: .userName, on: view, top: top, textChangedHandler: textChangedHandler, shouldChangeTextHandler: shouldChangeTextHandler) + } + + func makeDescriptionLabel(on view: UIView, top: UIView) -> UILabel { + let label = UILabel() + view.addSubview(label) + + label.text = + "You can choose username on NYNJA. If you do, other people will be able to find you by this username and contact you without your phone number.".localized + + "\n" + + "You can use a-z, 0-9 and underscores. Minimum lenght is 2 characters.".localized + + label.font = FontFamily.NotoSans.regular.font(size: 14) + label.textColor = UIColor.nynja.dustyGray + + label.numberOfLines = 0 + + label.snp.makeConstraints { (make) in + make.left.right.equalToSuperview().inset(16) + make.top.equalTo(top.snp.bottom) + } + + return label + } + + func makeCheckBoxContainerView(on view: UIView, top: UIView) -> UIView { + let containerView = UIView() + view.addSubview(containerView) + containerView.backgroundColor = UIColor.nynja.clear + + containerView.snp.makeConstraints { (make) in + make.left.right.equalToSuperview().inset(16) + make.top.equalTo(top.snp.bottom) + make.height.equalTo(56) + make.bottom.equalToSuperview().offset(16) + } + + return containerView + } + + func makeCheckButton(on view: UIView, target: AnyObject, selector: Selector) -> UIButton { + let button = UIButton() + view.addSubview(button) + + button.layer.cornerRadius = 12 + button.layer.borderColor = UIColor.nynja.dustyGray.cgColor + button.layer.borderWidth = 1 + + button.addTarget(target, action: selector, for: .touchUpInside) + + button.snp.makeConstraints { (make) in + make.height.width.equalTo(24) + make.left.equalToSuperview() + make.centerY.equalToSuperview() + } + + return button + } + + func makeTermsOfUseTextView(on view: UIView, left: UIView) -> UITextView { + let textView = UITextView() + view.addSubview(textView) + + textView.backgroundColor = UIColor.nynja.clear + textView.isScrollEnabled = false + + textView.dataDetectorTypes = .link + textView.isEditable = false + + let beginOfStr = NSMutableAttributedString(string: "I agree at".localized) + beginOfStr.addAttributes([NSAttributedStringKey.foregroundColor : UIColor.nynja.dustyGray, + NSAttributedStringKey.font: FontFamily.NotoSans.regular.font(size: 14)], range: NSMakeRange(0, beginOfStr.length)) + + let attributes = [NSAttributedStringKey.link : "http://www.google.com", + NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue, + NSAttributedStringKey.underlineColor: UIColor.nynja.blue, + NSAttributedStringKey.foregroundColor: UIColor.nynja.blue, + NSAttributedStringKey.font: FontFamily.NotoSans.regular.font(size: 14)] as [NSAttributedStringKey : Any] + + let termsOfUseStr = NSMutableAttributedString(string: "terms of use".localized) + termsOfUseStr.addAttributes(attributes, range: NSMakeRange(0, termsOfUseStr.length)) + + beginOfStr.append(NSAttributedString(string: " ")) + beginOfStr.append(termsOfUseStr) + + textView.attributedText = beginOfStr + + textView.snp.makeConstraints { (make) in + make.left.equalTo(left.snp.right) + make.centerY.equalTo(left.snp.centerY) + make.right.equalToSuperview() + } + + textView.sizeToFit() + + return textView + } +} + +private extension CreateProfileViewsFactory { + func makeTextField(fieldType: ProfileField, on view: UIView, top: UIView, textChangedHandler: ChangeTextHandler, shouldChangeTextHandler: ShouldChangeTextHandler) -> MaterialTextField { + let textField = MaterialTextField() + view.addSubview(textField) + + textField.placeholder = fieldType.placeholder.localized + (fieldType.isRequired ? "*" : "") + + textField.snp.makeConstraints { (make) in + make.left.right.equalToSuperview().inset(16) + make.top.equalTo(top.snp.bottom).offset(22) + make.height.equalTo(65) + } + + textField.textChanged = textChangedHandler(fieldType) + textField.shouldTextChanged = shouldChangeTextHandler(fieldType) + + return textField + } +} diff --git a/Nynja/Modules/Auth/CreateProfile/Wireframe/CreateProfileWireframe.swift b/Nynja/Modules/Auth/CreateProfile/Wireframe/CreateProfileWireframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..f7e25b651f23115d6dd61825703fa140598d8603 --- /dev/null +++ b/Nynja/Modules/Auth/CreateProfile/Wireframe/CreateProfileWireframe.swift @@ -0,0 +1,57 @@ +// +// CreateProfileWireframe.swift +// Nynja +// +// Created by Ash on 10/11/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol CreateProfileCoordinatorProtocol { + func wireframe(_ wireframe: CreateProfileWireframe, didEndWithState state: CreateProfileWireframe.State) +} + +final class CreateProfileWireframe: CreateProfileWireframeProtocol { + struct Parameters {} + + struct Dependencies {} + + enum State { + case back + case next + case chooseAvatar(completion: (UIImage?) -> Void) + } + + private let coordinator: CreateProfileCoordinatorProtocol + + init(coordinator: CreateProfileCoordinatorProtocol) { + self.coordinator = coordinator + } + + func prepareModule(parameters: CreateProfileWireframe.Parameters, dependencies: CreateProfileWireframe.Dependencies) -> UIViewController { + let presenter = CreateProfilePresenter() + let view = CreateProfileViewController(dependencies: CreateProfileViewController.Dependencies(presenter: presenter, viewsFactory: CreateProfileViewsFactory())) + let interactor = CreateProfileInteractor() + + let presenterDep = CreateProfilePresenter.Dependencies(wireframe: self, interactor: interactor, view: view) + let interactorDep = CreateProfileInteractor.Dependencies(presenter: presenter) + + presenter.inject(dependencies: presenterDep) + interactor.inject(dependencies: interactorDep) + + return view + } + + func back() { + coordinator.wireframe(self, didEndWithState: .back) + } + + func end() { + coordinator.wireframe(self, didEndWithState: .next) + } + + func chooseAvatar(completion: @escaping (UIImage?) -> Void) { + coordinator.wireframe(self, didEndWithState: .chooseAvatar(completion: completion)) + } +} diff --git a/Nynja/Modules/LanguageSettings/LanguageSelector/Interactor/LanguageSelectroInteractor.swift b/Nynja/Modules/LanguageSettings/LanguageSelector/Interactor/LanguageSelectroInteractor.swift index 919c7f77c6113b1ce7af0fa419751a024d25afdf..41cdbf94691587a1eab0f1b63744f04a983d00af 100644 --- a/Nynja/Modules/LanguageSettings/LanguageSelector/Interactor/LanguageSelectroInteractor.swift +++ b/Nynja/Modules/LanguageSettings/LanguageSelector/Interactor/LanguageSelectroInteractor.swift @@ -38,9 +38,8 @@ class LanguageSelectorInteractor: BaseInteractor, LanguageSelectorInteractorInpu //MARK: - LanguageSelectorInteractorInputProtocol func fetchLanguages() { - translationService.availableLanguages { [weak self] (languages, error) in - guard let sSelf = self, let languages = languages else { return } - sSelf.languages = languages + translationService.availableLanguages { [weak self] result in + result.onSuccess { self?.languages = $0 } } } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift index 3aabc264fd8b56228b52c628383961ca54de8b55..f003e939a7c1ba1cdc49e44c20401ccf7287480f 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift @@ -117,9 +117,8 @@ extension MessageInteractor { text: result.text, from: nil, to: input.language.language, - timeout: TranslationService.Constants.translationTimeout) { (translatedText, error) in - - guard var translatedText = translatedText else { + timeout: TranslationService.Constants.translationTimeout) { response in + guard case var .success(translatedText) = response else { handler(.error(.failed)) return } @@ -260,8 +259,8 @@ extension MessageInteractor { text: result.text, from: nil, to: traslationLanguage, - timeout: TranslationService.Constants.translationTimeout) { [weak self] (translatedText, error) in - guard var translatedText = translatedText, !translatedText.isEmpty else { + timeout: TranslationService.Constants.translationTimeout) { [weak self] response in + guard case var .success(translatedText) = response, !translatedText.isEmpty else { self?.finishTranslation(for: id, with: .failed) return } diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift b/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift index aa0144eee28c312be67e71b944aa45fde2544449..d1ddcc821bf43921d7220ec418d27279a23c32af 100644 --- a/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift +++ b/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift @@ -138,9 +138,7 @@ TestableViewControllerProtocol, UICollectionViewDelegateFlowLayout { } private func setCurrentCellFrirstResponder() { - if let cell = currentCell { - cell.wordTextField.setTextFieldFirstResponder() - } + currentCell?.wordTextField.becomeFirstResponder() } // MARK: - Life Cycle diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..599c82dcf1928d3f2124128ddc4574346e7418ca --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : "1" + }, + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "Icons_General_ic_accept_call.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "Icons_General_ic_accept_call@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "Icons_General_ic_accept_call@3x.png" + } + ] +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call.png new file mode 100644 index 0000000000000000000000000000000000000000..a05e0dc946c154e0452985fc17903fc7553bf11d Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call@2x.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fa2948118838a122b3e275fbe97aeba2fe1f83f9 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call@3x.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f893d7451f8ee515e6cff2f55cb5d9485a469d78 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_accept_call.imageset/Icons_General_ic_accept_call@3x.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..599c82dcf1928d3f2124128ddc4574346e7418ca --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : "1" + }, + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "Icons_General_ic_accept_call.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "Icons_General_ic_accept_call@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "Icons_General_ic_accept_call@3x.png" + } + ] +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2af6690bcfb281bacf7fdccf3cabe46d038f9e Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call@2x.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b35bf06e5963a049ab72da5b7bf83a16df7ff57b Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call@3x.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a77524e9ec080d0ac9ec0dbad837f04b4870b35b Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_email.imageset/Icons_General_ic_accept_call@3x.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..24973f8b22aa852610d441e675fe5859109d0bcd --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : "1" + }, + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "Icons_General_ic_google.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "Icons_General_ic_google@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "Icons_General_ic_google@3x.png" + } + ] +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google.png new file mode 100644 index 0000000000000000000000000000000000000000..0387da2a5a379d1eddf3706a81564518a5ca32f2 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google@2x.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e67f67f272bfefb549bd56a60d7db5529d1dc0a Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google@3x.png b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8c85573fd4157caa99a9baea61bef538e999e6d5 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Icons_General_ic_google.imageset/Icons_General_ic_google@3x.png differ diff --git a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/Contents.json deleted file mode 100644 index f9494e456da4440aef4641751f98db24be098823..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "next-bttn.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "next-bttn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "next-bttn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn.png b/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn.png deleted file mode 100644 index f061ce4675c1a3b3567832bd3469f070a6412555..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn.png and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn@2x.png b/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn@2x.png deleted file mode 100644 index 5218593807eadb0f24fe48aa0517bbd14f54bfa3..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn@2x.png and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn@3x.png b/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn@3x.png deleted file mode 100644 index f146fc4fab21a000abeb394be241e6988f6e35bf..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/New Folder/next-bttn.imageset/next-bttn@3x.png and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/Contents.json deleted file mode 100644 index d92d618715aa942039bd0ccfbed260842e2b1220..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "qr-code.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "qr-code@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "qr-code@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code.png b/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code.png deleted file mode 100644 index 3efacc09e94b5daf50e5520cd3518f192451ee2f..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code.png and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code@2x.png b/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code@2x.png deleted file mode 100644 index c2b449fb527f886f9bdf647a189a8116c22f8017..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code@2x.png and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code@3x.png b/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code@3x.png deleted file mode 100644 index 5e37700c0ee3e79c9807123814b7240f5a330b37..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/New Folder/qr-code.imageset/qr-code@3x.png and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/Contents.json deleted file mode 100644 index bab7f7d16c0419003b4fba7627c67b3b587c5a66..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "left_image.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/left_image.pdf b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/left_image.pdf deleted file mode 100644 index a21b5c9f751612124205c9b533bb03172339cabe..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_left_image.imageset/left_image.pdf and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/Contents.json deleted file mode 100644 index 3c7f1cbf179166e031b0b0647596c78be9bb6300..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "right_image.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/right_image.pdf b/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/right_image.pdf deleted file mode 100644 index bf18d530ee760b9e9df2cc3b63238e98fc1a2391..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/WheelPosition/wheel_right_image.imageset/right_image.pdf and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/ic_camera_frame.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/ic_camera_frame.imageset/Contents.json deleted file mode 100644 index f958d87dfa2c25244a187761efd56f6da49fcd6e..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/ic_camera_frame.imageset/Contents.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "images" : [ - { - "resizing" : { - "mode" : "9-part", - "center" : { - "mode" : "tile", - "width" : 1, - "height" : 1 - }, - "cap-insets" : { - "bottom" : 31, - "top" : 31, - "right" : 30, - "left" : 32 - } - }, - "idiom" : "universal", - "filename" : "ic_camera_frame.pdf", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_camera_frame.imageset/ic_camera_frame.pdf b/Nynja/Resources/Assets.xcassets/ic_camera_frame.imageset/ic_camera_frame.pdf deleted file mode 100644 index 50a7b4b496db95a98c45d25b1f64f7a29a23426c..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/ic_camera_frame.imageset/ic_camera_frame.pdf and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..abc5db8768ce2e84e2b7857364b2a958ff47b4ed --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : "1" + }, + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "ic_empty_avatar.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ic_empty_avatar@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "ic_empty_avatar@3x.png" + } + ] +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar.png b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..35b90503c59c956096fdd7ec9757bfcf53f0ccb1 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar.png differ diff --git a/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar@2x.png b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a94227560bbc0d7d55310b4ef78d3a4c04ded2b0 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar@3x.png b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..24cbfdb0b2af9a9392c5306ac371abb670c69132 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/ic_empty_avatar.imageset/ic_empty_avatar@3x.png differ diff --git a/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..d0ace947d496cb987e5095cae04408f37d40ccd7 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : "1" + }, + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "ic_facebook.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ic_facebook@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "ic_facebook@3x.png" + } + ] +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook.png b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..37ced60f93f204574657223149e2485c53dbf3a4 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook.png differ diff --git a/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook@2x.png b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..95f367911766eb286810361aac05a96cbdd9d00b Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook@3x.png b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..865f8af06b3f92c1a584296e5c7d54e8414825ea Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/ic_facebook.imageset/ic_facebook@3x.png differ diff --git a/Nynja/Resources/Assets.xcassets/ic_new_group.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/ic_new_group.imageset/Contents.json deleted file mode 100644 index 0fc3cb5069687694f1d48deaa3a99c26a056b024..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/ic_new_group.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_new_group.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "preserves-vector-representation" : true - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_new_group.imageset/ic_new_group.pdf b/Nynja/Resources/Assets.xcassets/ic_new_group.imageset/ic_new_group.pdf deleted file mode 100644 index a70db86f74b1cf89aba6f0900029833911427ca6..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/ic_new_group.imageset/ic_new_group.pdf and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/ic_search.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/ic_search.imageset/Contents.json deleted file mode 100644 index 487152ca275246ef257719f5afcf3fe21da930c3..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/ic_search.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_search.pdf", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_search.imageset/ic_search.pdf b/Nynja/Resources/Assets.xcassets/ic_search.imageset/ic_search.pdf deleted file mode 100644 index 967881b11815ab328fcd947c3488a36d0fa5c404..0000000000000000000000000000000000000000 Binary files a/Nynja/Resources/Assets.xcassets/ic_search.imageset/ic_search.pdf and /dev/null differ diff --git a/Nynja/Resources/Assets.xcassets/logo-2.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/logo-2.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..77ad60e245a5e9c053f32369ae703aefe92bcfd2 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/logo-2.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : "1" + }, + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "logo.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "logo@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "logo@3x.png" + } + ] +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo.png b/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..360c88d803466260cde5f4590c2ed2b4a525d1c3 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo.png differ diff --git a/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo@2x.png b/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e072d60ab4eed4f8400f7a476e7c8a44e2dba396 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo@3x.png b/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..311b4cf0e2ffe27dbf8afd5c81460e824179c89a Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/logo-2.imageset/logo@3x.png differ diff --git a/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Contents.json index b6d1c0875e4c58236d341874bd3092109d15b014..35ef22821cde69efd4af107670839cf24b658041 100644 --- a/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Contents.json @@ -1,21 +1,23 @@ { + "info" : { + "author" : "xcode", + "version" : "1" + }, "images" : [ { "idiom" : "universal", - "filename" : "table_overrides_right_overrides_checkbox_ic_unchecked.pdf", - "scale" : "1x" + "scale" : "1x", + "filename" : "Table_Overrides_Right_Overrides_Checkbox_ic_unchecked.png" }, { "idiom" : "universal", - "scale" : "2x" + "scale" : "2x", + "filename" : "Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@2x.png" }, { "idiom" : "universal", - "scale" : "3x" + "scale" : "3x", + "filename" : "Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@3x.png" } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } + ] } \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked.png b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..87553fefef6bbf619aa525ee7f14c41e4a4d8ad2 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked.png differ diff --git a/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@2x.png b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..44c61b861dbb6614f4b5b6549c50c835dab45bc0 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@2x.png differ diff --git a/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@3x.png b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ce127723957eb0f2226481d4152790025a9b58f2 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/table_overrides_right_overrides_checkbox_ic_unchecked.imageset/Table_Overrides_Right_Overrides_Checkbox_ic_unchecked@3x.png differ diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index f581ec61b326b571744b34cbedbdcf94e48afb35..6196d552eb1695d1241ff85362a2d221f85cc172 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -25,6 +25,7 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeContactsProvider() -> ContactsProviding func makeConversationsProvider() -> ConversationsProviding func makeStickersProvider() -> StickersProviding + func makeCountriesProvider() -> CountriesProviding func makeTextInputValidationService() -> TextInputValidationServiceProtocol func makeWalletCreationTextInputValidationService() -> WalletCreationTextInputValidationServiceProtocol @@ -109,6 +110,10 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { ) } + func makeCountriesProvider() -> CountriesProviding { + return CountriesProvider() + } + func makePermissionManager() -> PermissionManager { return PermissionManager() } diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index 9ef71ae31dc8f7c563b4e79dca10c6b262092e9a..ea4cd33a341210d014db1eb375645435bd85e580 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -11,22 +11,34 @@ import GRDBCipher import CryptoSwift //MARK: - The Game is began -class StorageService { + +protocol StorageServiceProtocol { + var userDefaults: UserDefaults? { get } + var keychain: KeychainService { get } + + #if !SHARE_EXTENSION + var countries: [CountryModel] { get set } + #endif + + func setupDatabase(with name: String, application: UIApplication) + func clearStorage() +} + +class StorageService: StorageServiceProtocol { private let passphraseKey = KeychainService.Keys.dataBasePassphrase let userDefaults = UserDefaults(suiteName: Bundle.main.appGroupName) let keychain = KeychainService.standard + #if !SHARE_EXTENSION private let countriesProvider = CountriesProvider() - #if !SHARE_EXTENSION private let databaseManager = DatabaseManager() var dbPool: DatabasePool? { return databaseManager.dbPool } - #endif // MARK: - Properties @@ -34,6 +46,8 @@ class StorageService { return countriesProvider.fetchCountries() }() + #endif + /// It is used only for debug purposes. /// Note: You should make clear instalation (don't upgrade old version of app). private let shouldEncryptDB: Bool = true diff --git a/Nynja/TranslationService/TranslationService.swift b/Nynja/TranslationService/TranslationService.swift index 6866db53b54ddf8417b14853afdba7306c86d850..369325665ee9e7ec82d5d6c12f44f0642a3833c8 100644 --- a/Nynja/TranslationService/TranslationService.swift +++ b/Nynja/TranslationService/TranslationService.swift @@ -10,7 +10,6 @@ import Foundation import SwiftyJSON protocol TranslationServiceInterface { - typealias Result = (Value?, ServiceError?) typealias Lang = String associatedtype ServiceError: Error @@ -62,7 +61,7 @@ extension TranslationService { URLSession.shared.dataTask(with: request) { (data, response, error) in guard isSuccessMethod(data, response, error) else { - return completion((nil, .failedRequest)) + return completion(.failure(ServiceError.failedRequest)) } let result = parseAvailableLanguagesMethod(data) @@ -83,7 +82,7 @@ extension TranslationService { URLSession.shared.dataTask(with: request) { (data, response, error) in guard isSuccessMethod(data, response, error) else { - return completion((nil, .failedRequest)) + return completion(.failure(ServiceError.failedRequest)) } let result = parseDetectedLanguagesMethod(data) @@ -118,7 +117,7 @@ extension TranslationService { let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in guard isSuccessMethod(data, response, error) else { - return completion((nil, .failedRequest)) + return completion(.failure(ServiceError.failedRequest)) } let result = parseTranslationMethod(data) @@ -195,7 +194,7 @@ private extension TranslationService { let data = data, let json = try? JSON(data: data)["data"].dictionaryValue, let languages = json["languages"]?.arrayValue - else { return (nil, .wrongJsonSchema) } + else { return .failure(ServiceError.wrongJsonSchema) } let result = languages .map { $0.dictionaryValue }.map({ pair -> Language? in @@ -205,7 +204,7 @@ private extension TranslationService { return Language(language: language, name: name) }).compactMap { $0 } - return (result, nil) + return .success(result) } func parseDetectedLanguages(data: Data?) -> Result { @@ -213,15 +212,15 @@ private extension TranslationService { let data = data, let json = try? JSON(data: data)["data"].dictionaryValue, let detections = json["detections"]?.arrayValue - else { return (nil, .wrongJsonSchema) } + else { return .failure(ServiceError.wrongJsonSchema) } let innerArray = detections.map { $0.arrayValue }.first let dicts = innerArray?.first?.dictionaryValue guard let language = dicts?["language"]?.stringValue - else { return (nil, .wrongJsonSchema) } + else { return .failure(ServiceError.wrongJsonSchema) } - return (language, nil) + return .success(language) } func parseTranslation(data: Data?) -> Result { @@ -229,15 +228,15 @@ private extension TranslationService { let data = data, let json = try? JSON(data: data)["data"].dictionaryValue, let translations = json["translations"]?.arrayValue - else { return (nil, .wrongJsonSchema) } + else { return .failure(ServiceError.wrongJsonSchema) } let translation = translations.first?.dictionaryValue guard let result = translation?["translatedText"]?.stringValue - else { return (nil, .wrongJsonSchema) } + else { return .failure(ServiceError.wrongJsonSchema) } - return (result.htmlDecoded, nil) + return .success(result.htmlDecoded ?? "") } } diff --git a/Nynja/Viper/BaseModule/Wireframe/WireframeProtocol.swift b/Nynja/Viper/BaseModule/Wireframe/WireframeProtocol.swift index a109004a12c7f127f0432078f65034dc4f5a8301..4ec04dad28f83736ffaff27b4ce4a3afe10dc04c 100644 --- a/Nynja/Viper/BaseModule/Wireframe/WireframeProtocol.swift +++ b/Nynja/Viper/BaseModule/Wireframe/WireframeProtocol.swift @@ -8,7 +8,6 @@ import Foundation - protocol WireframeProtocol: class { associatedtype Parameters associatedtype Dependencies