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