diff --git a/Podfile b/Podfile index 5311a982..15d413ca 100644 --- a/Podfile +++ b/Podfile @@ -3,7 +3,7 @@ use_frameworks! inhibit_all_warnings! target 'ios-base' do - pod 'Alamofire', '~> 5.2.0' + pod 'RSSwiftNetworking/AlamofireProvider', '~> 1.1.0' pod 'IQKeyboardManagerSwift', '~> 6.1.1' pod 'RSFontSizes', '~> 1.2.0' pod 'R.swift', '~> 5.0.3' diff --git a/Podfile.lock b/Podfile.lock index af5b1b5d..4c7bfeca 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Alamofire (5.2.1) + - Alamofire (5.2.0) - Device (3.1.2) - FBSDKCoreKit (5.5.0): - FBSDKCoreKit/Basics (= 5.5.0) @@ -106,11 +106,14 @@ PODS: - R.swift.Library (5.0.1) - RSFontSizes (1.2.0): - Device (~> 3.1.2) + - RSSwiftNetworking/AlamofireProvider (1.1.0): + - Alamofire (= 5.2.0) + - RSSwiftNetworking/Core + - RSSwiftNetworking/Core (1.1.0) - Swifter (1.5.0) - SwiftLint (0.43.1) DEPENDENCIES: - - Alamofire (~> 5.2.0) - FBSDKCoreKit (~> 5.5.0) - FBSDKLoginKit (~> 5.5.0) - Firebase/Analytics (~> 8.6.0) @@ -119,6 +122,7 @@ DEPENDENCIES: - IQKeyboardManagerSwift (~> 6.1.1) - R.swift (~> 5.0.3) - RSFontSizes (~> 1.2.0) + - RSSwiftNetworking/AlamofireProvider (~> 1.1.0) - Swifter (~> 1.5.0) - SwiftLint (~> 0.43.1) @@ -143,11 +147,12 @@ SPEC REPOS: - R.swift - R.swift.Library - RSFontSizes + - RSSwiftNetworking - Swifter - SwiftLint SPEC CHECKSUMS: - Alamofire: e911732990610fe89af59ac0077f923d72dc3dfd + Alamofire: c1ca147559e730bfb2182c8c7aafbdd90a867987 Device: 62242076214c30fb5760174b3601cefafa70a481 FBSDKCoreKit: 7ade9cfea30eef2f6cf105a1aa904f89ea68fb7d FBSDKLoginKit: bb28062f24e79590c44ba03297ca9bea4b6436d2 @@ -166,9 +171,10 @@ SPEC CHECKSUMS: R.swift: f5a87643b91ea569d23d6afb3eee9c743edde239 R.swift.Library: cfe85d569d9bae6cb262922db130e7c3a7a5fad1 RSFontSizes: 78158061f9f6121c6715f746395b1d8390fcb18b + RSSwiftNetworking: 64b76bc39cbce96cd34476a6ce97f2caa164f9ed Swifter: e71dd674404923d7f03ebb03f3f222d1c570bc8e SwiftLint: 99f82d07b837b942dd563c668de129a03fc3fb52 -PODFILE CHECKSUM: ad8b2fa0cef07782327d22c0570f1659eddad970 +PODFILE CHECKSUM: 0979596c2c2fa4d9f8b647d437a0550781538f1a -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/ios-base.xcodeproj/project.pbxproj b/ios-base.xcodeproj/project.pbxproj index fed39d2c..c7826415 100644 --- a/ios-base.xcodeproj/project.pbxproj +++ b/ios-base.xcodeproj/project.pbxproj @@ -21,20 +21,15 @@ 0737296624AD38CE008C54D9 /* NetworkMocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737296424AD38CE008C54D9 /* NetworkMocker.swift */; }; 074889A72477251500A0029E /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074889A62477251500A0029E /* ActivityIndicatorPresenter.swift */; }; 0748CCCC6E80D2040F1C75D3 /* Pods_ios_base.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A449B00976D690BCC4003AA1 /* Pods_ios_base.framework */; }; - 074D20D7248E98EC002A39B4 /* BaseAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074D20D6248E98EC002A39B4 /* BaseAPIClient.swift */; }; 074D20D9248E993B002A39B4 /* AuthenticationServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074D20D8248E993B002A39B4 /* AuthenticationServices.swift */; }; 074D20DB248EA832002A39B4 /* UserServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074D20DA248EA832002A39B4 /* UserServices.swift */; }; 07741D72218CDED600DB3B97 /* FirstViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07741D71218CDED600DB3B97 /* FirstViewModel.swift */; }; 9B0C72711C738D3400BAF3B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0C72691C738D3400BAF3B1 /* AppDelegate.swift */; }; 9B0C72721C738D3400BAF3B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B0C726A1C738D3400BAF3B1 /* Assets.xcassets */; }; - 9B2D00F3278C8ACE000657BE /* HeaderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00F2278C8ACE000657BE /* HeaderProvider.swift */; }; - 9B2D00F5278C8C87000657BE /* SessionHeadersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00F4278C8C87000657BE /* SessionHeadersProvider.swift */; }; - 9B2D00F7278C8DEF000657BE /* RailsAPIHeadersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00F6278C8DEF000657BE /* RailsAPIHeadersProvider.swift */; }; - 9B2D00F9278C8F7D000657BE /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00F8278C8F7D000657BE /* APIEndpoint.swift */; }; - 9B2D00FB278CAED3000657BE /* APIClient+Product.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00FA278CAED3000657BE /* APIClient+Product.swift */; }; - 9B2D00FE278CB123000657BE /* RailsAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00FD278CB123000657BE /* RailsAPIEndpoint.swift */; }; 9B2D0100278CB1C2000657BE /* AuthEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D00FF278CB1C2000657BE /* AuthEndpoint.swift */; }; 9B2D0102278DE207000657BE /* UserEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2D0101278DE207000657BE /* UserEndpoint.swift */; }; + 9B2F322C28999F2100D9C710 /* APIClient+Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2F322B28999F2100D9C710 /* APIClient+Application.swift */; }; + 9B2F323028999FFE00D9C710 /* SessionHeadersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2F322F28999FFE00D9C710 /* SessionHeadersProvider.swift */; }; 9B3AA3991ED35007005A4D26 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B3AA3981ED35007005A4D26 /* SignInViewController.swift */; }; 9B3AA39B1ED35013005A4D26 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B3AA39A1ED35013005A4D26 /* SignUpViewController.swift */; }; 9B3AA3A11ED4D014005A4D26 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B3AA3A01ED4D014005A4D26 /* HomeViewController.swift */; }; @@ -52,21 +47,7 @@ 9B8EB5BF247590710082370F /* AuthViewModelStateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8EB5BE247590710082370F /* AuthViewModelStateDelegate.swift */; }; 9BAFB7BF2114D9910099DC61 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BAFB7BE2114D9910099DC61 /* HomeViewModel.swift */; }; 9BAFB7C32114E3CD0099DC61 /* SignInViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BAFB7C22114E3CD0099DC61 /* SignInViewModel.swift */; }; - 9BB0608B2788989000FF880B /* EncodingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB0608A2788989000FF880B /* EncodingConfiguration.swift */; }; - 9BB0608E27889AB000FF880B /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB0608D27889AB000FF880B /* APIClient.swift */; }; - 9BB0609127889BF000FF880B /* AlamofireNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB0609027889BF000FF880B /* AlamofireNetworkProvider.swift */; }; - 9BBB30DC2787791800BB8068 /* DecodingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30D02787791800BB8068 /* DecodingConfiguration.swift */; }; - 9BBB30DD2787791800BB8068 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30D12787791800BB8068 /* Network.swift */; }; - 9BBB30DE2787791800BB8068 /* JSONDecoder+DecodingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30D22787791800BB8068 /* JSONDecoder+DecodingConfiguration.swift */; }; - 9BBB30DF2787791800BB8068 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30D32787791800BB8068 /* Endpoint.swift */; }; - 9BBB30E12787791800BB8068 /* APIClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30D62787791800BB8068 /* APIClientError.swift */; }; - 9BBB30E22787791800BB8068 /* NetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30D72787791800BB8068 /* NetworkProvider.swift */; }; - 9BBB30E42787793B00BB8068 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30E32787793B00BB8068 /* APIError.swift */; }; - 9BBB30E727877AA000BB8068 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30E627877AA000BB8068 /* Cancellable.swift */; }; - 9BBB30E927877ADD00BB8068 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBB30E827877ADD00BB8068 /* HTTPHeader.swift */; }; 9BD01E361F01641E007255E3 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD01E351F01641E007255E3 /* DictionaryExtension.swift */; }; - 9BE3669D1F101737007CAECD /* MultipartMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3669C1F101736007CAECD /* MultipartMedia.swift */; }; - 9BE3669F1F10175E007CAECD /* Base64Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3669E1F10175E007CAECD /* Base64Media.swift */; }; 9BFA84F31C776827009F64E4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BFA84F21C776827009F64E4 /* LaunchScreen.storyboard */; }; BB83CFA64940E660BA4A4420 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; C0EC01A885BD14BD1552DE80 /* Pods_ios_base_ios_baseUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A1760A5E754FEA0591B3C8A /* Pods_ios_base_ios_baseUITests.framework */; }; @@ -84,7 +65,6 @@ FDFAAB0B269CD56D0007DB8B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FDFAAB0A269CD56D0007DB8B /* Localizable.strings */; }; FDFAAB0D269CDD540007DB8B /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFAAB0C269CDD540007DB8B /* ColorExtension.swift */; }; FE06840322CE7ED400C6294F /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE06840222CE7ED400C6294F /* R.generated.swift */; }; - FE251D0722A7FE0F00E0DE90 /* DataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE251D0622A7FE0F00E0DE90 /* DataExtension.swift */; }; FE4D635C22B2C75400685161 /* Navigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4D635B22B2C75400685161 /* Navigator.swift */; }; FE4D635E22B2CAFF00685161 /* AppNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4D635D22B2CAFF00685161 /* AppNavigator.swift */; }; FE4D636022B2CB3800685161 /* HomeRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4D635F22B2CB3800685161 /* HomeRoutes.swift */; }; @@ -128,7 +108,6 @@ 0737296324AD38CE008C54D9 /* NetworkMockerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMockerExtension.swift; sourceTree = ""; }; 0737296424AD38CE008C54D9 /* NetworkMocker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMocker.swift; sourceTree = ""; }; 074889A62477251500A0029E /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = ""; }; - 074D20D6248E98EC002A39B4 /* BaseAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseAPIClient.swift; sourceTree = ""; }; 074D20D8248E993B002A39B4 /* AuthenticationServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServices.swift; sourceTree = ""; }; 074D20DA248EA832002A39B4 /* UserServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserServices.swift; sourceTree = ""; }; 07741D71218CDED600DB3B97 /* FirstViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewModel.swift; sourceTree = ""; }; @@ -138,14 +117,10 @@ 9B0C726A1C738D3400BAF3B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9B0C726D1C738D3400BAF3B1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9B12AF6B1F269B03005FD465 /* ThirdPartyKeys.example.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ThirdPartyKeys.example.plist; sourceTree = ""; }; - 9B2D00F2278C8ACE000657BE /* HeaderProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderProvider.swift; sourceTree = ""; }; - 9B2D00F4278C8C87000657BE /* SessionHeadersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeadersProvider.swift; sourceTree = ""; }; - 9B2D00F6278C8DEF000657BE /* RailsAPIHeadersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RailsAPIHeadersProvider.swift; sourceTree = ""; }; - 9B2D00F8278C8F7D000657BE /* APIEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEndpoint.swift; sourceTree = ""; }; - 9B2D00FA278CAED3000657BE /* APIClient+Product.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIClient+Product.swift"; sourceTree = ""; }; - 9B2D00FD278CB123000657BE /* RailsAPIEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RailsAPIEndpoint.swift; sourceTree = ""; }; 9B2D00FF278CB1C2000657BE /* AuthEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthEndpoint.swift; sourceTree = ""; }; 9B2D0101278DE207000657BE /* UserEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEndpoint.swift; sourceTree = ""; }; + 9B2F322B28999F2100D9C710 /* APIClient+Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIClient+Application.swift"; sourceTree = ""; }; + 9B2F322F28999FFE00D9C710 /* SessionHeadersProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionHeadersProvider.swift; sourceTree = ""; }; 9B3AA3981ED35007005A4D26 /* SignInViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; }; 9B3AA39A1ED35013005A4D26 /* SignUpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; 9B3AA3A01ED4D014005A4D26 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; @@ -167,21 +142,7 @@ 9B9C6BFD20C7160700EB0523 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 9BAFB7BE2114D9910099DC61 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 9BAFB7C22114E3CD0099DC61 /* SignInViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewModel.swift; sourceTree = ""; }; - 9BB0608A2788989000FF880B /* EncodingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodingConfiguration.swift; sourceTree = ""; }; - 9BB0608D27889AB000FF880B /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = ""; }; - 9BB0609027889BF000FF880B /* AlamofireNetworkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlamofireNetworkProvider.swift; sourceTree = ""; }; - 9BBB30D02787791800BB8068 /* DecodingConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodingConfiguration.swift; sourceTree = ""; }; - 9BBB30D12787791800BB8068 /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; - 9BBB30D22787791800BB8068 /* JSONDecoder+DecodingConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+DecodingConfiguration.swift"; sourceTree = ""; }; - 9BBB30D32787791800BB8068 /* Endpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; - 9BBB30D62787791800BB8068 /* APIClientError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIClientError.swift; sourceTree = ""; }; - 9BBB30D72787791800BB8068 /* NetworkProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProvider.swift; sourceTree = ""; }; - 9BBB30E32787793B00BB8068 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; - 9BBB30E627877AA000BB8068 /* Cancellable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancellable.swift; sourceTree = ""; }; - 9BBB30E827877ADD00BB8068 /* HTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = ""; }; 9BD01E351F01641E007255E3 /* DictionaryExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; }; - 9BE3669C1F101736007CAECD /* MultipartMedia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartMedia.swift; sourceTree = ""; }; - 9BE3669E1F10175E007CAECD /* Base64Media.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Base64Media.swift; sourceTree = ""; tabWidth = 2; }; 9BFA84F21C776827009F64E4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; A143F3EC1A6E7D9DE1CA4A68 /* Pods-ios-base.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios-base.release.xcconfig"; path = "Target Support Files/Pods-ios-base/Pods-ios-base.release.xcconfig"; sourceTree = ""; }; A449B00976D690BCC4003AA1 /* Pods_ios_base.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_base.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -204,7 +165,6 @@ FDFAAB0A269CD56D0007DB8B /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; FDFAAB0C269CDD540007DB8B /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; FE06840222CE7ED400C6294F /* R.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = SOURCE_ROOT; }; - FE251D0622A7FE0F00E0DE90 /* DataExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtension.swift; sourceTree = ""; }; FE4D635B22B2C75400685161 /* Navigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigator.swift; sourceTree = ""; }; FE4D635D22B2CAFF00685161 /* AppNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigator.swift; sourceTree = ""; }; FE4D635F22B2CB3800685161 /* HomeRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRoutes.swift; sourceTree = ""; }; @@ -409,13 +369,28 @@ 9B2D00FC278CAFFC000657BE /* Endpoints */ = { isa = PBXGroup; children = ( - 9B2D00FD278CB123000657BE /* RailsAPIEndpoint.swift */, 9B2D00FF278CB1C2000657BE /* AuthEndpoint.swift */, 9B2D0101278DE207000657BE /* UserEndpoint.swift */, ); path = Endpoints; sourceTree = ""; }; + 9B2F322D28999FFE00D9C710 /* Types */ = { + isa = PBXGroup; + children = ( + 9B2F322E28999FFE00D9C710 /* API */, + ); + path = Types; + sourceTree = ""; + }; + 9B2F322E28999FFE00D9C710 /* API */ = { + isa = PBXGroup; + children = ( + 9B2F322F28999FFE00D9C710 /* SessionHeadersProvider.swift */, + ); + path = API; + sourceTree = ""; + }; 9B5AFAD11C7205EB002347D6 = { isa = PBXGroup; children = ( @@ -484,63 +459,10 @@ path = Localization; sourceTree = ""; }; - 9BB0608C2788989400FF880B /* API */ = { - isa = PBXGroup; - children = ( - 9BBB30D32787791800BB8068 /* Endpoint.swift */, - 9BBB30E827877ADD00BB8068 /* HTTPHeader.swift */, - 9BB0608A2788989000FF880B /* EncodingConfiguration.swift */, - 9BBB30D02787791800BB8068 /* DecodingConfiguration.swift */, - 9B2D00F4278C8C87000657BE /* SessionHeadersProvider.swift */, - 9B2D00F6278C8DEF000657BE /* RailsAPIHeadersProvider.swift */, - 9B2D00F8278C8F7D000657BE /* APIEndpoint.swift */, - ); - path = API; - sourceTree = ""; - }; - 9BB0608F27889B4600FF880B /* Network */ = { - isa = PBXGroup; - children = ( - 9BBB30D12787791800BB8068 /* Network.swift */, - 9BB0609027889BF000FF880B /* AlamofireNetworkProvider.swift */, - ); - path = Network; - sourceTree = ""; - }; - 9BBB30CF2787791800BB8068 /* Types */ = { - isa = PBXGroup; - children = ( - 9BB0608F27889B4600FF880B /* Network */, - 9BB0608C2788989400FF880B /* API */, - ); - path = Types; - sourceTree = ""; - }; - 9BBB30D42787791800BB8068 /* Errors */ = { - isa = PBXGroup; - children = ( - 9BBB30E32787793B00BB8068 /* APIError.swift */, - 9BBB30D62787791800BB8068 /* APIClientError.swift */, - ); - path = Errors; - sourceTree = ""; - }; - 9BBB30E527877A9500BB8068 /* Protocols */ = { - isa = PBXGroup; - children = ( - 9BBB30D72787791800BB8068 /* NetworkProvider.swift */, - 9BBB30E627877AA000BB8068 /* Cancellable.swift */, - 9BB0608D27889AB000FF880B /* APIClient.swift */, - 9B2D00F2278C8ACE000657BE /* HeaderProvider.swift */, - ); - path = Protocols; - sourceTree = ""; - }; 9BBB30EC27877BAF00BB8068 /* Extensions */ = { isa = PBXGroup; children = ( - 9BBB30D22787791800BB8068 /* JSONDecoder+DecodingConfiguration.swift */, - 9B2D00FA278CAED3000657BE /* APIClient+Product.swift */, + 9B2F322B28999F2100D9C710 /* APIClient+Application.swift */, ); path = Extensions; sourceTree = ""; @@ -548,11 +470,8 @@ 9BDA91DF1C7390C10003877D /* Networking */ = { isa = PBXGroup; children = ( + 9B2F322D28999FFE00D9C710 /* Types */, 9BBB30EC27877BAF00BB8068 /* Extensions */, - 9BBB30E527877A9500BB8068 /* Protocols */, - 9BE3669B1F1016BD007CAECD /* Models */, - 9BBB30D42787791800BB8068 /* Errors */, - 9BBB30CF2787791800BB8068 /* Types */, 9BFA84F11C776817009F64E4 /* Services */, ); path = Networking; @@ -568,15 +487,6 @@ path = Managers; sourceTree = ""; }; - 9BE3669B1F1016BD007CAECD /* Models */ = { - isa = PBXGroup; - children = ( - 9BE3669E1F10175E007CAECD /* Base64Media.swift */, - 9BE3669C1F101736007CAECD /* MultipartMedia.swift */, - ); - path = Models; - sourceTree = ""; - }; 9BFA84EE1C7767D9009F64E4 /* Models */ = { isa = PBXGroup; children = ( @@ -601,7 +511,6 @@ isa = PBXGroup; children = ( 9B2D00FC278CAFFC000657BE /* Endpoints */, - 074D20D6248E98EC002A39B4 /* BaseAPIClient.swift */, 074D20D8248E993B002A39B4 /* AuthenticationServices.swift */, 074D20DA248EA832002A39B4 /* UserServices.swift */, ); @@ -627,7 +536,6 @@ 9B8D306F20AB47E90050697F /* JSONEncodingExtension.swift */, 9B8574D8212C84130063A3E2 /* ImageExtension.swift */, 071CD2612228544700E6D385 /* FontExtension.swift */, - FE251D0622A7FE0F00E0DE90 /* DataExtension.swift */, 9B5EAB49232C146D00B9CE3C /* TextFieldExtension.swift */, FDFAAB06269CCCD30007DB8B /* LabelExtension.swift */, FDFAAB08269CCED70007DB8B /* ButtonExtension.swift */, @@ -846,6 +754,7 @@ "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework", "${BUILT_PRODUCTS_DIR}/RSFontSizes/RSFontSizes.framework", + "${BUILT_PRODUCTS_DIR}/RSSwiftNetworking/RSSwiftNetworking.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/Swifter/Swifter.framework", ); @@ -865,6 +774,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Rswift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSFontSizes.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSSwiftNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Swifter.framework", ); @@ -929,6 +839,7 @@ "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework", "${BUILT_PRODUCTS_DIR}/RSFontSizes/RSFontSizes.framework", + "${BUILT_PRODUCTS_DIR}/RSSwiftNetworking/RSSwiftNetworking.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -947,6 +858,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Rswift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSFontSizes.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSSwiftNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -1017,23 +929,17 @@ files = ( E8290BFE1D832D9200599960 /* ViewExtension.swift in Sources */, 07741D72218CDED600DB3B97 /* FirstViewModel.swift in Sources */, - 9BBB30E22787791800BB8068 /* NetworkProvider.swift in Sources */, FE583F0C22AFC9EA00EF3CDA /* AnalyticsService.swift in Sources */, FE583F0E22AFC9F300EF3CDA /* FirebaseAnalyticsService.swift in Sources */, - 9BB0608E27889AB000FF880B /* APIClient.swift in Sources */, FE4D636022B2CB3800685161 /* HomeRoutes.swift in Sources */, 9B3AA3A11ED4D014005A4D26 /* HomeViewController.swift in Sources */, - 9BB0609127889BF000FF880B /* AlamofireNetworkProvider.swift in Sources */, 9B77E0751E2FB66F0020E450 /* UserDataManager.swift in Sources */, + 9B2F323028999FFE00D9C710 /* SessionHeadersProvider.swift in Sources */, 9BD01E361F01641E007255E3 /* DictionaryExtension.swift in Sources */, E8290C021D8330A800599960 /* StringExtension.swift in Sources */, FE4D636222B2CBB000685161 /* OnboardingRoutes.swift in Sources */, - 9BBB30E927877ADD00BB8068 /* HTTPHeader.swift in Sources */, - 9BBB30DC2787791800BB8068 /* DecodingConfiguration.swift in Sources */, - 9BBB30DE2787791800BB8068 /* JSONDecoder+DecodingConfiguration.swift in Sources */, 071CD2622228544700E6D385 /* FontExtension.swift in Sources */, 9B8D307020AB47E90050697F /* JSONEncodingExtension.swift in Sources */, - 9B2D00F3278C8ACE000657BE /* HeaderProvider.swift in Sources */, 074D20D9248E993B002A39B4 /* AuthenticationServices.swift in Sources */, E81171921DE5EFB7003D3DF5 /* ViewControllerExtension.swift in Sources */, 9B3AA3991ED35007005A4D26 /* SignInViewController.swift in Sources */, @@ -1042,45 +948,31 @@ 9B77E0731E2FB6350020E450 /* User.swift in Sources */, FA54D5881E11C59600F0DBEA /* FirstViewController.swift in Sources */, 9B8EB5BF247590710082370F /* AuthViewModelStateDelegate.swift in Sources */, - 9B2D00F9278C8F7D000657BE /* APIEndpoint.swift in Sources */, FABDC9221EE1EB2B000DDAC3 /* ConfigurationManager.swift in Sources */, 9B8574D7212C81950063A3E2 /* SignUpViewModel.swift in Sources */, - 9BE3669D1F101737007CAECD /* MultipartMedia.swift in Sources */, 9B8574D9212C84130063A3E2 /* ImageExtension.swift in Sources */, - 9B2D00F7278C8DEF000657BE /* RailsAPIHeadersProvider.swift in Sources */, 9B2D0102278DE207000657BE /* UserEndpoint.swift in Sources */, FDFAAB0D269CDD540007DB8B /* ColorExtension.swift in Sources */, FE583F0A22AFC9E000EF3CDA /* AnalyticsManager.swift in Sources */, 9B3AA39B1ED35013005A4D26 /* SignUpViewController.swift in Sources */, FDFAAB09269CCED70007DB8B /* ButtonExtension.swift in Sources */, - 9BBB30DF2787791800BB8068 /* Endpoint.swift in Sources */, 9B5EAB4A232C146D00B9CE3C /* TextFieldExtension.swift in Sources */, 9B0C72711C738D3400BAF3B1 /* AppDelegate.swift in Sources */, FE06840322CE7ED400C6294F /* R.generated.swift in Sources */, - 9BBB30DD2787791800BB8068 /* Network.swift in Sources */, FE4D635C22B2C75400685161 /* Navigator.swift in Sources */, - FE251D0722A7FE0F00E0DE90 /* DataExtension.swift in Sources */, FE583F1022AFC9FA00EF3CDA /* AnalyticsEvent.swift in Sources */, - 9BBB30E727877AA000BB8068 /* Cancellable.swift in Sources */, FE4D635E22B2CAFF00685161 /* AppNavigator.swift in Sources */, - 9BB0608B2788989000FF880B /* EncodingConfiguration.swift in Sources */, - 9BBB30E42787793B00BB8068 /* APIError.swift in Sources */, 9B2D0100278CB1C2000657BE /* AuthEndpoint.swift in Sources */, 074889A72477251500A0029E /* ActivityIndicatorPresenter.swift in Sources */, E8FBB1C11DD21D18000D6740 /* SessionManager.swift in Sources */, - 9BE3669F1F10175E007CAECD /* Base64Media.swift in Sources */, - 9B2D00FE278CB123000657BE /* RailsAPIEndpoint.swift in Sources */, + 9B2F322C28999F2100D9C710 /* APIClient+Application.swift in Sources */, 9BAFB7C32114E3CD0099DC61 /* SignInViewModel.swift in Sources */, 9B8EB5BD2475890A0082370F /* NetworkState.swift in Sources */, - 9B2D00FB278CAED3000657BE /* APIClient+Product.swift in Sources */, 9B88CC671CAB305900AE60C5 /* Constants.swift in Sources */, FDFAAB07269CCCD30007DB8B /* LabelExtension.swift in Sources */, 9BAFB7BF2114D9910099DC61 /* HomeViewModel.swift in Sources */, 9B715A461E28083600C0C039 /* PlaceholderTextView.swift in Sources */, - 9B2D00F5278C8C87000657BE /* SessionHeadersProvider.swift in Sources */, FD8C0272269E2F2E003F86CE /* UIConstants.swift in Sources */, - 074D20D7248E98EC002A39B4 /* BaseAPIClient.swift in Sources */, - 9BBB30E12787791800BB8068 /* APIClientError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios-base/AppDelegate.swift b/ios-base/AppDelegate.swift index a1a432e7..dd65fde8 100644 --- a/ios-base/AppDelegate.swift +++ b/ios-base/AppDelegate.swift @@ -56,7 +56,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func unexpectedLogout() { UserDataManager.deleteUser() - SessionManager.deleteSession() + SessionManager.shared.deleteSession() // Clear any local data if needed // Take user to onboarding if needed, do NOT redirect the user // if is already in the landing to avoid losing the current VC stack state. diff --git a/ios-base/Common/Models/Session.swift b/ios-base/Common/Models/Session.swift index b3aaebfb..f4f0bb94 100644 --- a/ios-base/Common/Models/Session.swift +++ b/ios-base/Common/Models/Session.swift @@ -7,6 +7,7 @@ // import Foundation +import RSSwiftNetworking struct Session: Codable { var uid: String? @@ -31,12 +32,13 @@ struct Session: Codable { self.expiry = expires } - init?(headers: [String: Any]) { - var loweredHeaders = headers - loweredHeaders.lowercaseKeys() - guard let stringHeaders = loweredHeaders as? [String: String] else { + init?(headers: [AnyHashable: Any]) { + guard var stringHeaders = headers as? [String: String] else { return nil } + + stringHeaders.lowercaseKeys() + if let expiryString = stringHeaders[HTTPHeader.expiry.rawValue], let expiryNumber = Double(expiryString) { expiry = Date(timeIntervalSince1970: expiryNumber) diff --git a/ios-base/Extensions/DataExtension.swift b/ios-base/Extensions/DataExtension.swift deleted file mode 100644 index 10e45d57..00000000 --- a/ios-base/Extensions/DataExtension.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// DataExtension.swift -// ios-base -// -// Created by Mauricio Cousillas on 6/5/19. -// Copyright © 2019 Rootstrap Inc. All rights reserved. -// - -import Foundation - -// Helper to retrieve the right string value for base64 API uploaders -extension Data { - func asBase64Param(withType type: MimeType = .jpeg) -> String { - "data:\(type.rawValue);base64,\(self.base64EncodedString())" - } -} diff --git a/ios-base/Home/ViewModels/HomeViewModel.swift b/ios-base/Home/ViewModels/HomeViewModel.swift index fbd622b9..551215a7 100644 --- a/ios-base/Home/ViewModels/HomeViewModel.swift +++ b/ios-base/Home/ViewModels/HomeViewModel.swift @@ -29,11 +29,22 @@ class HomeViewModel { delegate?.didUpdateState(to: state) } } + + private let userServices: UserServices + private let authServices: AuthenticationServices + + init( + userServices: UserServices = UserServices(), + authServices: AuthenticationServices = AuthenticationServices() + ) { + self.userServices = userServices + self.authServices = authServices + } func loadUserProfile() { state = .network(state: .loading) - UserServices.getMyProfile { [weak self] (result: Result) in + userServices.getMyProfile { [weak self] (result: Result) in switch result { case .success(let user): self?.userEmail = user.email @@ -47,7 +58,7 @@ class HomeViewModel { func logoutUser() { state = .network(state: .loading) - AuthenticationServices.logout { [weak self] result in + authServices.logout { [weak self] result in switch result { case .success: self?.didlogOutAccount() @@ -60,7 +71,7 @@ class HomeViewModel { func deleteAccount() { state = .network(state: .loading) - AuthenticationServices.deleteAccount { [weak self] result in + authServices.deleteAccount { [weak self] result in switch result { case .success: self?.didlogOutAccount() diff --git a/ios-base/Managers/SessionManager.swift b/ios-base/Managers/SessionManager.swift index 0c3db729..4c554886 100644 --- a/ios-base/Managers/SessionManager.swift +++ b/ios-base/Managers/SessionManager.swift @@ -8,12 +8,20 @@ import UIKit -class SessionManager: NSObject { +internal class SessionManager: CurrentUserSessionProvider { - static var currentSession: Session? { + static let shared = SessionManager() + + private let userDefaults: UserDefaults + + init(userDefaults: UserDefaults = .standard) { + self.userDefaults = userDefaults + } + + var currentSession: Session? { get { if - let data = UserDefaults.standard.data(forKey: "ios-base-session"), + let data = userDefaults.data(forKey: "ios-base-session"), let session = try? JSONDecoder().decode(Session.self, from: data) { return session @@ -23,15 +31,15 @@ class SessionManager: NSObject { set { let session = try? JSONEncoder().encode(newValue) - UserDefaults.standard.set(session, forKey: "ios-base-session") + userDefaults.set(session, forKey: "ios-base-session") } } - class func deleteSession() { - UserDefaults.standard.removeObject(forKey: "ios-base-session") + func deleteSession() { + userDefaults.removeObject(forKey: "ios-base-session") } - static var validSession: Bool { + var validSession: Bool { if let session = currentSession, let uid = session.uid, let tkn = session.accessToken, let client = session.client { return !uid.isEmpty && !tkn.isEmpty && !client.isEmpty diff --git a/ios-base/Navigators/AppNavigator.swift b/ios-base/Navigators/AppNavigator.swift index 32f25975..dfe91a24 100644 --- a/ios-base/Navigators/AppNavigator.swift +++ b/ios-base/Navigators/AppNavigator.swift @@ -8,12 +8,14 @@ import Foundation -class AppNavigator: BaseNavigator { - static let shared = AppNavigator() +internal class AppNavigator: BaseNavigator { - init() { - let initialRoute: Route = SessionManager.validSession ? - HomeRoutes.home : OnboardingRoutes.firstScreen + static let shared = AppNavigator(isLoggedIn: SessionManager.shared.validSession) + + init(isLoggedIn: Bool) { + let initialRoute: Route = isLoggedIn + ? HomeRoutes.home + : OnboardingRoutes.firstScreen super.init(with: initialRoute) } diff --git a/ios-base/Networking/Errors/APIClientError.swift b/ios-base/Networking/Errors/APIClientError.swift deleted file mode 100644 index cbb887dd..00000000 --- a/ios-base/Networking/Errors/APIClientError.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -internal enum APIClientError: Error { - case invalidEmptyResponse - case statusCodeInvalid - - var domain: ErrorDomain { - .network - } - - var code: Int { - switch self { - case .invalidEmptyResponse: - return 1 - case .statusCodeInvalid: - return 2 - } - } - -} diff --git a/ios-base/Networking/Errors/APIError.swift b/ios-base/Networking/Errors/APIError.swift deleted file mode 100644 index 0a2e27a5..00000000 --- a/ios-base/Networking/Errors/APIError.swift +++ /dev/null @@ -1,74 +0,0 @@ -import Foundation - -/// A structure that represents a custom error returned by the API -/// in the request response. -internal struct APIError: Error { - let statusCode: Int - let underlayingError: RailsError - - init?( - response: Network.Response, - decodingConfiguration: DecodingConfiguration - ) { - let decoder = JSONDecoder(decodingConfig: decodingConfiguration) - guard - let data = response.data, - let decodedError = try? decoder.decode(RailsError.self, from: data) - else { - return nil - } - - self.statusCode = response.statusCode - self.underlayingError = decodedError - } - - /// Returns the first error returned by the API - var firstError: String? { - if let errors = underlayingError.errors, let firstMessage = errors.first { - return "\(firstMessage.key) \(firstMessage.value.first ?? "")" - } else if let errorString = underlayingError.error { - return errorString - } - - return nil - } - - /// Returns an array containing all error values returned from the API - var errors: [String] { - var flattenedErrors = underlayingError.errors? - .compactMap { $0.value } - .flatMap { $0 } - - if let errorString = underlayingError.error { - flattenedErrors?.append(errorString) - } - - return flattenedErrors ?? [] - } -} - -/// A structure that represents a Ruby on Rails API error object -internal struct RailsError: Decodable { - - let errors: [String: [String]]? - let error: String? - - enum CodingKeys: String, CodingKey { - case errors - case error - } - - init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - if let errors = try? values.decode([String: [String]].self, forKey: .errors) { - self.errors = errors - self.error = nil - } else if let error = try? values.decode(String.self, forKey: .errors) { - self.error = error - self.errors = nil - } else { - error = try? values.decode(String.self, forKey: .error) - errors = nil - } - } -} diff --git a/ios-base/Networking/Extensions/APIClient+Application.swift b/ios-base/Networking/Extensions/APIClient+Application.swift new file mode 100644 index 00000000..257913c6 --- /dev/null +++ b/ios-base/Networking/Extensions/APIClient+Application.swift @@ -0,0 +1,10 @@ +import RSSwiftNetworking + +/// Provides an easy-access APIClient implementation to use across the application +/// You can define and configure as many APIClients as needed +internal enum iOSBaseAPIClient { + static let shared = BaseAPIClient( + networkProvider: AlamofireNetworkProvider(), + headersProvider: RailsAPIHeadersProvider(sessionProvider: SessionHeadersProvider()) + ) +} diff --git a/ios-base/Networking/Extensions/APIClient+Product.swift b/ios-base/Networking/Extensions/APIClient+Product.swift deleted file mode 100644 index ef380219..00000000 --- a/ios-base/Networking/Extensions/APIClient+Product.swift +++ /dev/null @@ -1,11 +0,0 @@ -/// Definition for API clients used in the application. -/// i.e. `OtherAPIClient(networkProvider: URLSessionNetworkProvider())` -/// -extension BaseAPIClient { - static let `default` = BaseAPIClient( - networkProvider: AlamofireNetworkProvider(), - headersProvider: RailsAPIHeadersProvider( - sessionHeadersProvider: SessionHeadersProvider() - ) - ) -} diff --git a/ios-base/Networking/Extensions/JSONDecoder+DecodingConfiguration.swift b/ios-base/Networking/Extensions/JSONDecoder+DecodingConfiguration.swift deleted file mode 100644 index 68988004..00000000 --- a/ios-base/Networking/Extensions/JSONDecoder+DecodingConfiguration.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension JSONDecoder { - - convenience init(decodingConfig: DecodingConfiguration) { - self.init() - - dateDecodingStrategy = decodingConfig.dateStrategy - keyDecodingStrategy = decodingConfig.keyStrategy - dataDecodingStrategy = decodingConfig.dataStrategy - } - -} diff --git a/ios-base/Networking/Models/Base64Media.swift b/ios-base/Networking/Models/Base64Media.swift deleted file mode 100644 index a4a4021e..00000000 --- a/ios-base/Networking/Models/Base64Media.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Base64Media.swift -// ios-base -// -// Created by German on 7/7/17. -// Copyright © 2017 Rootstrap Inc. All rights reserved. -// - -import Foundation - -class Base64Media: MultipartMedia { - var base64: String - - override init(key: String, data: Data, type: MimeType = .jpeg) { - self.base64 = data.asBase64Param(withType: type) - super.init(key: key, data: data, type: type) - } -} diff --git a/ios-base/Networking/Models/MultipartMedia.swift b/ios-base/Networking/Models/MultipartMedia.swift deleted file mode 100644 index 4a2947e8..00000000 --- a/ios-base/Networking/Models/MultipartMedia.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// MultipartMedia.swift -// ios-base -// -// Created by German on 7/7/17. -// Copyright © 2017 Rootstrap Inc. All rights reserved. -// - -import Foundation - -// Basic media MIME types, add more if needed. -enum MimeType: String { - case jpeg = "image/jpeg" - case bmp = "image/bmp" - case png = "image/png" - - case mov = "video/quicktime" - case mpeg = "video/mpeg" - case avi = "video/avi" - case json = "application/json" - - func fileExtension() -> String { - switch self { - case .bmp: return ".bmp" - case .png: return ".png" - case .mov: return ".mov" - case .mpeg: return ".mpeg" - case .avi: return ".avi" - case .json: return ".json" - default: return ".jpg" - } - } -} - -class MultipartMedia { - var key: String - var data: Data - var type: MimeType - var toFile: String { - key.validFilename + type.fileExtension() - } - - init(key: String, data: Data, type: MimeType = .jpeg) { - self.key = key - self.data = data - self.type = type - } -} diff --git a/ios-base/Networking/Protocols/APIClient.swift b/ios-base/Networking/Protocols/APIClient.swift deleted file mode 100644 index 39cf2e40..00000000 --- a/ios-base/Networking/Protocols/APIClient.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation - -internal struct EmptyResponse: Decodable {} - -internal typealias CompletionCallback = ( - _ result: Result, - _ responseHeaders: [String: String] -) -> Void - -/// Defines the requirement for an API Client object -internal protocol APIClient { - - /// Returns the base encoding configration for all requests parameters. - /// Will be overriden by the `Endpoint` encoding configuration, if any. - var encodingConfiguration: EncodingConfiguration { get } - - /// Returns the base decoding configration for all request reponses. - /// Will be overriden by the `Endpoint` decoding configuration, if any. - var decodingConfiguration: DecodingConfiguration { get } - - /// Initializes an instance of the `APIClient` conformant object. - /// Any API Client concrete instance should be injected with the network provider. - init(networkProvider: NetworkProvider) - - /// Performs the request by using the provided `NetworkProvider`. - /// - Returns: A `Cancellable` request. - func request( - endpoint: Endpoint, - completion: @escaping CompletionCallback - ) -> Cancellable - - /// Performs a multipart request to upload one or many `MultipartMedia` objects. - /// The endpoint parameters will be encoded in the multipart form. - /// Note: Multipart requests do not support `Content-Type = application/json` headers. - /// If your API requires this header user base64 uploads instead. - func multipartRequest( - endpoint: Endpoint, - paramsRootKey: String, - media: [MultipartMedia], - completion: @escaping CompletionCallback - ) -> Cancellable -} diff --git a/ios-base/Networking/Protocols/Cancellable.swift b/ios-base/Networking/Protocols/Cancellable.swift deleted file mode 100644 index dc6ebe3b..00000000 --- a/ios-base/Networking/Protocols/Cancellable.swift +++ /dev/null @@ -1,4 +0,0 @@ -/// Defines a cancellable work. -internal protocol Cancellable { - func cancel() -> Self -} diff --git a/ios-base/Networking/Protocols/HeaderProvider.swift b/ios-base/Networking/Protocols/HeaderProvider.swift deleted file mode 100644 index c9e8e9af..00000000 --- a/ios-base/Networking/Protocols/HeaderProvider.swift +++ /dev/null @@ -1,3 +0,0 @@ -internal protocol HeadersProvider { - var requestHeaders: [String: String] { get } -} diff --git a/ios-base/Networking/Protocols/NetworkProvider.swift b/ios-base/Networking/Protocols/NetworkProvider.swift deleted file mode 100644 index 5e308138..00000000 --- a/ios-base/Networking/Protocols/NetworkProvider.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -/// Interface that provides an opportunity to define the HTTP -/// communication layer. -internal protocol NetworkProvider { - - /// Performs a HTTP request to the given `Endpoint`. - /// - Parameters: - /// - endpoint: An object conforming to `Endpoint` providing the request information. - /// - completion: The closure executed after the request is completed. - /// - Returns: A `Cancellable` request. - func request( - endpoint: Endpoint, - completion: @escaping (Result) -> Void - ) -> Cancellable - - /// Performs a multipart request to upload one or many `MultipartMedia` objects. - /// - Parameters: - /// - endpoint: An object conforming to `Endpoint` providing the request information. - /// - completion: The closure executed after the request is completed. - /// - Returns: A `Cancellable` request. - func multipartRequest( - endpoint: Endpoint, - multipartFormKey: String, - media: [MultipartMedia], - completion: @escaping (Result) -> Void - ) -> Cancellable -} diff --git a/ios-base/Networking/Services/AuthenticationServices.swift b/ios-base/Networking/Services/AuthenticationServices.swift index 233594b9..4fb4885b 100644 --- a/ios-base/Networking/Services/AuthenticationServices.swift +++ b/ios-base/Networking/Services/AuthenticationServices.swift @@ -8,27 +8,42 @@ import Foundation import UIKit +import RSSwiftNetworking -class AuthenticationServices { +internal class AuthenticationServices { enum AuthError: Error { case userSessionInvalid } + + // MARK: - Properties fileprivate static let usersUrl = "/users/" fileprivate static let currentUserUrl = "/user/" + + private let sessionManager: SessionManager + + private let apiClient: APIClient + + init( + sessionManager: SessionManager = .shared, + apiClient: APIClient = iOSBaseAPIClient.shared + ) { + self.sessionManager = sessionManager + self.apiClient = apiClient + } - class func login( + func login( email: String, password: String, completion: @escaping (Result) -> Void ) { - BaseAPIClient.default.request( + apiClient.request( endpoint: AuthEndpoint.signIn(email: email, password: password) - ) { (result: Result, responseHeaders: [String: String]) in + ) { [weak self] (result: Result, responseHeaders: [AnyHashable: Any]) in switch result { case .success(let user): - if saveUserSession(user, headers: responseHeaders) { + if self?.saveUserSession(user, headers: responseHeaders) ?? false { completion(.success(())) } else { completion(.failure(AuthError.userSessionInvalid)) @@ -41,7 +56,7 @@ class AuthenticationServices { /// Example Upload via Multipart requests. /// TODO: rails base backend not supporting multipart uploads yet - class func signup( + func signup( email: String, password: String, avatar: UIImage, @@ -69,14 +84,15 @@ class AuthenticationServices { picture: nil ) - BaseAPIClient.default.multipartRequest( - endpoint: endpoint, - paramsRootKey: "", - media: [image] - ) { (result: Result, responseHeaders: [String: String]) in + apiClient.multipartRequest( + endpoint: endpoint, paramsRootKey: "", media: [image] + ) { [weak self] (result: Result, responseHeaders: [AnyHashable: Any]) in switch result { case .success(let user): - if saveUserSession(user, headers: responseHeaders), let user = user { + if + let user = user, + self?.saveUserSession(user, headers: responseHeaders) ?? false + { completion(.success(user)) } else { completion(.failure(AuthError.userSessionInvalid)) @@ -88,23 +104,26 @@ class AuthenticationServices { } /// Example method that uploads base64 encoded image. - class func signup( + func signup( email: String, password: String, avatar64: UIImage, completion: @escaping (Result) -> Void ) { - BaseAPIClient.default.request( + apiClient.request( endpoint: AuthEndpoint.signUp( email: email, password: password, passwordConfirmation: password, picture: avatar64.jpegData(compressionQuality: 0.75) ) - ) { (result: Result, responseHeaders) in + ) { [weak self] (result: Result, responseHeaders) in switch result { case .success(let user): - if saveUserSession(user, headers: responseHeaders), let user = user { + if + let user = user, + self?.saveUserSession(user, headers: responseHeaders) ?? false + { completion(.success(user)) } else { completion(.failure(AuthError.userSessionInvalid)) @@ -115,14 +134,14 @@ class AuthenticationServices { } } - class func logout(completion: @escaping (Result) -> Void) { - BaseAPIClient.default.request( + func logout(completion: @escaping (Result) -> Void) { + apiClient.request( endpoint: AuthEndpoint.logout - ) { (result: Result, _) in + ) { [weak self] (result: Result, _) in switch result { case .success: UserDataManager.deleteUser() - SessionManager.deleteSession() + self?.sessionManager.deleteSession() completion(.success(())) case .failure(let error): completion(.failure(error)) @@ -130,14 +149,14 @@ class AuthenticationServices { } } - class func deleteAccount(completion: @escaping (Result) -> Void) { - BaseAPIClient.default.request( + func deleteAccount(completion: @escaping (Result) -> Void) { + apiClient.request( endpoint: AuthEndpoint.deleteAccount - ) { (result: Result, _) in + ) { [weak self] (result: Result, _) in switch result { case .success: UserDataManager.deleteUser() - SessionManager.deleteSession() + self?.sessionManager.deleteSession() completion(.success(())) case .failure(let error): completion(.failure(error)) @@ -145,13 +164,13 @@ class AuthenticationServices { } } - fileprivate class func saveUserSession( + private func saveUserSession( _ user: User?, - headers: [String: String] + headers: [AnyHashable: Any] ) -> Bool { UserDataManager.currentUser = user - SessionManager.currentSession = Session(headers: headers) + sessionManager.currentSession = Session(headers: headers) - return UserDataManager.currentUser != nil && SessionManager.validSession + return UserDataManager.currentUser != nil && sessionManager.validSession } } diff --git a/ios-base/Networking/Services/BaseAPIClient.swift b/ios-base/Networking/Services/BaseAPIClient.swift deleted file mode 100644 index 3e67ad0a..00000000 --- a/ios-base/Networking/Services/BaseAPIClient.swift +++ /dev/null @@ -1,132 +0,0 @@ -import Foundation - -/// Concrete implementation of API Client. -internal final class BaseAPIClient: APIClient { - - // MARK: - Properties - - private let emptyDataStatusCodes: Set = [204, 205] - - private(set) var encodingConfiguration: EncodingConfiguration = .default - - private(set) var decodingConfiguration: DecodingConfiguration = .default - - private(set) var headersProvider: HeadersProvider = RailsAPIHeadersProvider( - sessionHeadersProvider: SessionHeadersProvider() - ) - - private let networkProvider: NetworkProvider - - required init(networkProvider: NetworkProvider) { - self.networkProvider = networkProvider - } - - convenience init( - networkProvider: NetworkProvider, - headersProvider: HeadersProvider, - encodingConfiguration: EncodingConfiguration = .default, - decodingConfiguration: DecodingConfiguration = .default - ) { - self.init(networkProvider: networkProvider) - self.encodingConfiguration = encodingConfiguration - self.decodingConfiguration = decodingConfiguration - self.headersProvider = headersProvider - } - - @discardableResult - func request( - endpoint: Endpoint, - completion: @escaping CompletionCallback - ) -> Cancellable { - networkProvider.request( - endpoint: buildAPIEndpoint(from: endpoint) - ) { [weak self] result in - guard let self = self else { return } - - self.handle(result, for: endpoint, completion: completion) - } - } - - @discardableResult - func multipartRequest( - endpoint: Endpoint, - paramsRootKey: String, - media: [MultipartMedia], - completion: @escaping CompletionCallback - ) -> Cancellable { - networkProvider.multipartRequest( - endpoint: buildAPIEndpoint(from: endpoint), - multipartFormKey: paramsRootKey, - media: media - ) { [weak self] result in - guard let self = self else { return } - - self.handle(result, for: endpoint, completion: completion) - } - } - - private func handle( - _ result: Result, - for endpoint: Endpoint, - completion: CompletionCallback - ) { - switch result { - case .success(let response): - handle(response, with: endpoint.decodingConfiguration, completion: completion) - case .failure(let error): - completion(.failure(error), [:]) - } - } - - private func buildAPIEndpoint(from endpoint: Endpoint) -> APIEndpoint { - APIEndpoint(endpoint: endpoint, headersProvider: headersProvider) - } - - private var unexpectedResponseError: NSError { - App.error( - domain: .network, - localizedDescription: "Unexpected empty response".localized - ) - } - - private func handle( - _ response: Network.Response, - with configuration: DecodingConfiguration?, - completion: CompletionCallback - ) { - do { - guard let data = response.data, !data.isEmpty else { - guard emptyDataStatusCodes.contains(response.statusCode) else { - throw unexpectedResponseError - } - - return completion(.success(.none), response.headers) - } - - completion(.success(try decode(data, with: configuration)), response.headers) - } catch let error { - completion( - .failure(handleCustomAPIError(from: response) ?? error), - response.headers - ) - } - } - - private func decode( - _ data: Data, - with configuration: DecodingConfiguration? - ) throws -> M { - let decoder = JSONDecoder(decodingConfig: configuration ?? decodingConfiguration) - - return try decoder.decode(M.self, from: data) - } - - private func handleCustomAPIError(from response: Network.Response) -> APIError? { - if response.statusCode == Network.StatusCode.unauthorized { - AppDelegate.shared.unexpectedLogout() - } - - return APIError(response: response, decodingConfiguration: decodingConfiguration) - } - -} diff --git a/ios-base/Networking/Services/Endpoints/AuthEndpoint.swift b/ios-base/Networking/Services/Endpoints/AuthEndpoint.swift index 6fc9f021..95c5e35f 100644 --- a/ios-base/Networking/Services/Endpoints/AuthEndpoint.swift +++ b/ios-base/Networking/Services/Endpoints/AuthEndpoint.swift @@ -1,4 +1,5 @@ import Foundation +import RSSwiftNetworking internal enum AuthEndpoint: RailsAPIEndpoint { diff --git a/ios-base/Networking/Services/Endpoints/RailsAPIEndpoint.swift b/ios-base/Networking/Services/Endpoints/RailsAPIEndpoint.swift deleted file mode 100644 index bd145eaf..00000000 --- a/ios-base/Networking/Services/Endpoints/RailsAPIEndpoint.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -internal protocol RailsAPIEndpoint: Endpoint { - var baseURL: String { get } - var path: String { get } -} - -extension RailsAPIEndpoint { - var baseURL: String { - Bundle.main.object(forInfoDictionaryKey: "Base URL") as? String ?? "" - } - - var requestURL: URL { - guard let url = URL(string: baseURL)?.appendingPathComponent(path) else { - fatalError("URL for endpoint at \(path) could not be constructed") - } - - return url - } -} diff --git a/ios-base/Networking/Services/Endpoints/UserEndpoint.swift b/ios-base/Networking/Services/Endpoints/UserEndpoint.swift index 90c4eaa7..d260da53 100644 --- a/ios-base/Networking/Services/Endpoints/UserEndpoint.swift +++ b/ios-base/Networking/Services/Endpoints/UserEndpoint.swift @@ -1,4 +1,5 @@ import Foundation +import RSSwiftNetworking internal enum UserEndpoint: RailsAPIEndpoint { diff --git a/ios-base/Networking/Services/UserServices.swift b/ios-base/Networking/Services/UserServices.swift index 7c48cc93..0b7cb764 100644 --- a/ios-base/Networking/Services/UserServices.swift +++ b/ios-base/Networking/Services/UserServices.swift @@ -7,11 +7,18 @@ // import Foundation +import RSSwiftNetworking -class UserServices { - - class func getMyProfile(completion: @escaping (Result) -> Void) { - BaseAPIClient.default.request( +internal class UserServices { + + private let apiClient: APIClient + + init(apiClient: APIClient = iOSBaseAPIClient.shared) { + self.apiClient = apiClient + } + + func getMyProfile(completion: @escaping (Result) -> Void) { + apiClient.request( endpoint: UserEndpoint.profile ) { (result: Result, _) in switch result { diff --git a/ios-base/Networking/Types/API/APIEndpoint.swift b/ios-base/Networking/Types/API/APIEndpoint.swift deleted file mode 100644 index efd67f38..00000000 --- a/ios-base/Networking/Types/API/APIEndpoint.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -internal struct APIEndpoint: Endpoint { - - private let headersProvider: HeadersProvider - private let endpoint: Endpoint - - init(endpoint: Endpoint, headersProvider: HeadersProvider) { - self.endpoint = endpoint - self.headersProvider = headersProvider - } - - var requestURL: URL { - endpoint.requestURL - } - - var method: Network.HTTPMethod { - endpoint.method - } - - var headers: [String: String] { - headersProvider.requestHeaders + endpoint.headers - } - - var parameters: [String: Any] { - endpoint.parameters - } - - var decodingConfiguration: DecodingConfiguration? { - endpoint.decodingConfiguration - } - -} diff --git a/ios-base/Networking/Types/API/DecodingConfiguration.swift b/ios-base/Networking/Types/API/DecodingConfiguration.swift deleted file mode 100644 index 5dddd2cb..00000000 --- a/ios-base/Networking/Types/API/DecodingConfiguration.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -/// -internal struct DecodingConfiguration { - - static let `default` = DecodingConfiguration() - - let dateStrategy: JSONDecoder.DateDecodingStrategy - let keyStrategy: JSONDecoder.KeyDecodingStrategy - let dataStrategy: JSONDecoder.DataDecodingStrategy - - init( - dateStrategy: JSONDecoder.DateDecodingStrategy = .iso8601, - keyStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, - dataStrategy: JSONDecoder.DataDecodingStrategy = .base64 - ) { - self.dateStrategy = dateStrategy - self.keyStrategy = keyStrategy - self.dataStrategy = dataStrategy - } -} diff --git a/ios-base/Networking/Types/API/EncodingConfiguration.swift b/ios-base/Networking/Types/API/EncodingConfiguration.swift deleted file mode 100644 index bf7d3f34..00000000 --- a/ios-base/Networking/Types/API/EncodingConfiguration.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -/// Gives an opportunity to define custom encoding for network requests -internal struct EncodingConfiguration { - static let `default` = EncodingConfiguration() -} diff --git a/ios-base/Networking/Types/API/Endpoint.swift b/ios-base/Networking/Types/API/Endpoint.swift deleted file mode 100644 index 1c055ad4..00000000 --- a/ios-base/Networking/Types/API/Endpoint.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation - -internal enum EndpointError: Error { - case invalidURL -} - -/// Base requirements for any network request endpoint. -internal protocol Endpoint { - - var requestURL: URL { get } - var method: Network.HTTPMethod { get } - - var headers: [String: String] { get } - var parameters: [String: Any] { get } - var decodingConfiguration: DecodingConfiguration? { get } - -} - -extension Endpoint { - - // MARK: - Defaults - - var decodingConfiguration: DecodingConfiguration? { - nil - } - - var parameters: [String: Any] { - [:] - } - - var headers: [String: String] { - [:] - } - -} diff --git a/ios-base/Networking/Types/API/HTTPHeader.swift b/ios-base/Networking/Types/API/HTTPHeader.swift deleted file mode 100644 index cb43b993..00000000 --- a/ios-base/Networking/Types/API/HTTPHeader.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -internal enum HTTPHeader: String { - case uid - case client - case expiry - case token = "access-token" - case accept = "Accept" - case contentType = "Content-Type" -} diff --git a/ios-base/Networking/Types/API/RailsAPIHeadersProvider.swift b/ios-base/Networking/Types/API/RailsAPIHeadersProvider.swift deleted file mode 100644 index aec8fde2..00000000 --- a/ios-base/Networking/Types/API/RailsAPIHeadersProvider.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -internal final class RailsAPIHeadersProvider: HeadersProvider { - - private let sessionHeadersProvider: SessionHeadersProvider - - private let contentTypeJSON = "application/json" - - var requestHeaders: [String: String] { - sessionHeadersProvider.requestHeaders + [ - HTTPHeader.accept.rawValue: contentTypeJSON, - HTTPHeader.contentType.rawValue: contentTypeJSON - ] - } - - init(sessionHeadersProvider: SessionHeadersProvider) { - self.sessionHeadersProvider = sessionHeadersProvider - } - -} diff --git a/ios-base/Networking/Types/API/SessionHeadersProvider.swift b/ios-base/Networking/Types/API/SessionHeadersProvider.swift index 354fe53c..2f85d4d3 100644 --- a/ios-base/Networking/Types/API/SessionHeadersProvider.swift +++ b/ios-base/Networking/Types/API/SessionHeadersProvider.swift @@ -1,16 +1,35 @@ import Foundation +import RSSwiftNetworking -internal class SessionHeadersProvider: HeadersProvider { +internal protocol CurrentUserSessionProvider { + var currentSession: Session? { get } +} + +internal class SessionHeadersProvider: SessionProvider { + + // MARK: - Properties + + var uid: String? { + session?.uid + } + + var client: String? { + session?.client + } + + var accessToken: String? { + session?.accessToken + } + + private let currentSessionProvider: CurrentUserSessionProvider + + private var session: Session? { + currentSessionProvider.currentSession + } - var requestHeaders: [String: String] { - if let session = SessionManager.currentSession { - return [ - HTTPHeader.uid.rawValue: session.uid ?? "", - HTTPHeader.client.rawValue: session.client ?? "", - HTTPHeader.token.rawValue: session.accessToken ?? "" - ] - } + // MARK: - - return [:] + init(currentSessionProvider: CurrentUserSessionProvider = SessionManager.shared) { + self.currentSessionProvider = currentSessionProvider } } diff --git a/ios-base/Networking/Types/Network/AlamofireNetworkProvider.swift b/ios-base/Networking/Types/Network/AlamofireNetworkProvider.swift deleted file mode 100644 index e7842749..00000000 --- a/ios-base/Networking/Types/Network/AlamofireNetworkProvider.swift +++ /dev/null @@ -1,146 +0,0 @@ -import Foundation -import Alamofire - -/// This should be the only place where the `Alamofire` dependency is imported -internal final class AlamofireNetworkProvider: NetworkProvider { - - func request( - endpoint: Endpoint, - completion: @escaping (Result) -> Void - ) -> Cancellable { - let headers = HTTPHeaders(endpoint.headers) - - return AF.request( - endpoint.requestURL, - method: endpoint.method.alamofireMethod, - parameters: endpoint.parameters, - headers: headers - ) - .validate() - .response { [weak self] afResponse in - switch afResponse.result { - case.success: - self?.handleAlamofireResponse(afResponse, completion: completion) - case .failure(let error): - completion(.failure(error)) - } - } - } - - func multipartRequest( - endpoint: Endpoint, - multipartFormKey: String, - media: [MultipartMedia], - completion: @escaping (Result) -> Void - ) -> Cancellable { - AF.upload( - multipartFormData: { [weak self] multipartForm in - self?.encodeParameters( - endpoint.parameters, - intoMultipartFormData: multipartForm, - rootKey: multipartFormKey - ) - for elem in media { - elem.embed(inForm: multipartForm) - } - }, - to: endpoint.requestURL, - method: endpoint.method.alamofireMethod, - headers: HTTPHeaders(endpoint.headers) - ) - .validate() - .response { [weak self] afResponse in - switch afResponse.result { - case.success: - self?.handleAlamofireResponse(afResponse, completion: completion) - case .failure(let error): - completion(.failure(error)) - } - } - } - - private func handleAlamofireResponse( - _ afResponse: AFDataResponse, - completion: @escaping (Result) -> Void - ) { - guard let response = afResponse.response else { - completion(.failure(Network.ProviderError.noResponse)) - return - } - - completion(.success(Network.Response( - statusCode: response.statusCode, - data: afResponse.data, - headers: response.headers.dictionary - ))) - } - - /// Recursively builds the parameters into the multipart form - /// to send along with media in upload requests. - /// - Parameters: - /// - multipartForm: The mutable form data to append the parameters - /// - parameters: The input parementers to encode into the multipart form. - /// - rootKey: The root key for the encoded parameters or an empty string. - /// If `parameters` is a dictionary that already includes the desired root key - /// you can omit the. - private func encodeParameters( - _ parameters: Any, - intoMultipartFormData multipartForm: MultipartFormData, - rootKey: String = "" - ) { - switch parameters.self { - case let array as [Any]: - for value in array { - encodeParameters( - value, - intoMultipartFormData: multipartForm, - rootKey: rootKey.isEmpty ? "array[]" : rootKey + "[]" - ) - } - case let dict as [String: Any]: - for (key, value) in dict { - encodeParameters( - value, - intoMultipartFormData: multipartForm, - rootKey: rootKey.isEmpty ? key : rootKey + "[\(key)]" - ) - } - default: - if let uploadData = "\(parameters)".data( - using: String.Encoding.utf8, - allowLossyConversion: false - ) { - let forwardRootKey = rootKey.isEmpty - ? "\(type(of: parameters))".lowercased() - : rootKey - - multipartForm.append(uploadData, withName: forwardRootKey) - } - } - } -} - -extension Network.HTTPMethod { - var alamofireMethod: Alamofire.HTTPMethod { - switch self { - case .get: - return .get - case .post: - return .post - case .put: - return .put - case .delete: - return .delete - case .patch: - return .patch - } - } -} - -extension DataRequest: Cancellable {} - -fileprivate extension MultipartMedia { - func embed(inForm multipart: MultipartFormData) { - multipart.append(data, withName: key, fileName: toFile, mimeType: type.rawValue) - } -} diff --git a/ios-base/Networking/Types/Network/Network.swift b/ios-base/Networking/Types/Network/Network.swift deleted file mode 100644 index fb4de6f9..00000000 --- a/ios-base/Networking/Types/Network/Network.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -internal enum Network { - - enum HTTPMethod { - case get - case post - case put - case patch - case delete - } - - enum StatusCode { - static let unauthorized = 401 - } - - internal struct Response { - let statusCode: Int - let data: Data? - let headers: [String: String] - } - - enum ProviderError: Error { - case noResponse - } - -} diff --git a/ios-base/Onboarding/ViewModels/SignInViewModel.swift b/ios-base/Onboarding/ViewModels/SignInViewModel.swift index 8528892a..8dcbaaba 100644 --- a/ios-base/Onboarding/ViewModels/SignInViewModel.swift +++ b/ios-base/Onboarding/ViewModels/SignInViewModel.swift @@ -12,8 +12,8 @@ protocol SignInViewModelDelegate: AuthViewModelStateDelegate { func didUpdateCredentials() } -class SignInViewModelWithCredentials { - +internal class SignInViewModelWithCredentials { + private var state: AuthViewModelState = .network(state: .idle) { didSet { delegate?.didUpdateState(to: state) @@ -37,10 +37,16 @@ class SignInViewModelWithCredentials { var hasValidCredentials: Bool { email.isEmailFormatted() && !password.isEmpty } + + private let authServices: AuthenticationServices + + init(authServices: AuthenticationServices = AuthenticationServices()) { + self.authServices = authServices + } func login() { state = .network(state: .loading) - AuthenticationServices.login( + authServices.login( email: email, password: password ) { [weak self] result in diff --git a/ios-base/Onboarding/ViewModels/SignUpViewModel.swift b/ios-base/Onboarding/ViewModels/SignUpViewModel.swift index b0c33c96..01e0ae5b 100644 --- a/ios-base/Onboarding/ViewModels/SignUpViewModel.swift +++ b/ios-base/Onboarding/ViewModels/SignUpViewModel.swift @@ -19,6 +19,12 @@ enum AuthViewModelState { } class SignUpViewModelWithEmail { + + private let authServices: AuthenticationServices + + init(authServices: AuthenticationServices = AuthenticationServices()) { + self.authServices = authServices + } private var state: AuthViewModelState = .network(state: .idle) { didSet { @@ -52,7 +58,7 @@ class SignUpViewModelWithEmail { func signup() { state = .network(state: .loading) - AuthenticationServices.signup( + authServices.signup( email: email, password: password, avatar64: UIImage.random()