diff --git a/Netable/Netable.xcodeproj/project.pbxproj b/Netable/Netable.xcodeproj/project.pbxproj index 9610918..31fe6fb 100644 --- a/Netable/Netable.xcodeproj/project.pbxproj +++ b/Netable/Netable.xcodeproj/project.pbxproj @@ -7,19 +7,13 @@ objects = { /* Begin PBXBuildFile section */ - 23B627082435329800BD888D /* DownloadCatImageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B627072435329800BD888D /* DownloadCatImageRequest.swift */; }; - 23B6270A2435332200BD888D /* SampleDownloadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B627092435332200BD888D /* SampleDownloadViewController.swift */; }; 3B00B3C726D7EA3C00A1DF79 /* DecodingError+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B00B3C626D7EA3C00A1DF79 /* DecodingError+Logging.swift */; }; A63ABCCA24ABB402004DE84E /* RetryConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63ABCC924ABB402004DE84E /* RetryConfiguration.swift */; }; A63ABCCC24ABBFCC004DE84E /* DelayedOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63ABCCB24ABBFCC004DE84E /* DelayedOperations.swift */; }; B822C8EE23F20E8900D7BDAD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B822C8ED23F20E8900D7BDAD /* AppDelegate.swift */; }; B822C8F023F20E8900D7BDAD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B822C8EF23F20E8900D7BDAD /* SceneDelegate.swift */; }; - B822C8F223F20E8900D7BDAD /* SampleGetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B822C8F123F20E8900D7BDAD /* SampleGetViewController.swift */; }; B822C8F523F20E8900D7BDAD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B822C8F323F20E8900D7BDAD /* Main.storyboard */; }; B822C8FA23F20E8B00D7BDAD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B822C8F823F20E8B00D7BDAD /* LaunchScreen.storyboard */; }; - B822C90023F210D800D7BDAD /* GetCatRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B822C8FF23F210D800D7BDAD /* GetCatRequest.swift */; }; - B888FD4B23ECD20800026A7F /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B888FD4A23ECD20800026A7F /* OHHTTPStubs */; }; - B888FD4D23ECD20800026A7F /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B888FD4C23ECD20800026A7F /* OHHTTPStubsSwift */; }; B8A18C0D23F2201000941EA6 /* Netable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8C9288023E9F68000DB2B37 /* Netable.framework */; }; B8A18C0E23F2201000941EA6 /* Netable.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B8C9288023E9F68000DB2B37 /* Netable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B8C9288A23E9F68000DB2B37 /* Netable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8C9288023E9F68000DB2B37 /* Netable.framework */; }; @@ -33,30 +27,39 @@ B8C928A723E9FC8D00DB2B37 /* URLRequest+EncodeURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C928A623E9FC8D00DB2B37 /* URLRequest+EncodeURL.swift */; }; B8C928A923E9FDCC00DB2B37 /* Netable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C928A823E9FDCC00DB2B37 /* Netable.swift */; }; C635DCB426D95D2A009F82A0 /* SmartUnwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = C635DCB326D95D2A009F82A0 /* SmartUnwrap.swift */; }; - C635DCB626D95DB4009F82A0 /* SmartUnwrapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C635DCB526D95DB4009F82A0 /* SmartUnwrapViewController.swift */; }; - C635DCB826D95DDB009F82A0 /* SmartUnwrapRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C635DCB726D95DDB009F82A0 /* SmartUnwrapRequest.swift */; }; - C64689A726DE9D5300D2AFDD /* FallbackDecoderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64689A626DE9D5300D2AFDD /* FallbackDecoderViewController.swift */; }; - C64689A926DE9DFD00D2AFDD /* FallbackDecoderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64689A826DE9DFD00D2AFDD /* FallbackDecoderRequest.swift */; }; - C64689A526DD934400D2AFDD /* CombineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64689A426DD934400D2AFDD /* CombineViewController.swift */; }; + C64EAB6926F2828E0093850A /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB6826F2828E0093850A /* Post.swift */; }; + C64EAB6B26F283440093850A /* GetPostsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB6A26F283440093850A /* GetPostsRequest.swift */; }; + C64EAB6D26F29AFD0093850A /* UnauthorizedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB6C26F29AFD0093850A /* UnauthorizedRequest.swift */; }; + C64EAB6F26F2AD4F0093850A /* FailedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB6E26F2AD4F0093850A /* FailedRequest.swift */; }; + C64EAB7126F2AE970093850A /* RootTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB7026F2AE970093850A /* RootTabBarController.swift */; }; + C64EAB7326F2B1B30093850A /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB7226F2B1B30093850A /* Version.swift */; }; + C64EAB7526F2B2000093850A /* VersionCheckRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB7426F2B2000093850A /* VersionCheckRequest.swift */; }; + C64EAB7726F2B2E00093850A /* version.json in Resources */ = {isa = PBXBuildFile; fileRef = C64EAB7626F2B2E00093850A /* version.json */; }; + C64EAB7A26F3AD370093850A /* PostOverviewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB7926F3AD370093850A /* PostOverviewCell.swift */; }; + C64EAB7C26F3B1FC0093850A /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB7B26F3B1FC0093850A /* NewPostViewController.swift */; }; + C64EAB7E26F3B8160093850A /* CreatePostRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64EAB7D26F3B8160093850A /* CreatePostRequest.swift */; }; + C64EAB8026F3C0A00093850A /* createPost.json in Resources */ = {isa = PBXBuildFile; fileRef = C64EAB7F26F3C0A00093850A /* createPost.json */; }; C64F8590241FE4670028E0E9 /* Netable.podspec in Resources */ = {isa = PBXBuildFile; fileRef = C64F858F241FE4670028E0E9 /* Netable.podspec */; }; C64F8592241FE4870028E0E9 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = C64F8591241FE4870028E0E9 /* CHANGELOG.md */; }; - C64F8594241FE70E0028E0E9 /* ExamplesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64F8593241FE70E0028E0E9 /* ExamplesTableViewController.swift */; }; - C64F859C241FF09D0028E0E9 /* LoginRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64F859B241FF09D0028E0E9 /* LoginRequest.swift */; }; - C64F859E241FF8E60028E0E9 /* PostLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C64F859D241FF8E60028E0E9 /* PostLoginViewController.swift */; }; C65289F526D01829009D486B /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65289F426D01829009D486B /* Config.swift */; }; C66901AF241C1B0A002954C5 /* CustomLogDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66901AE241C1B0A002954C5 /* CustomLogDestination.swift */; }; - C66A8BF72436619F0052A1AF /* CancelRequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66A8BF62436619F0052A1AF /* CancelRequestViewController.swift */; }; - C66A8BFD24367E910052A1AF /* DecodeSnakeCaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66A8BFA24367E910052A1AF /* DecodeSnakeCaseViewController.swift */; }; - C66A8BFE24367E910052A1AF /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66A8BFB24367E910052A1AF /* CustomLoggerViewController.swift */; }; - C66A8BFF24367E910052A1AF /* EmptyLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66A8BFC24367E910052A1AF /* EmptyLoggerViewController.swift */; }; - C66A8C0224367EBA0052A1AF /* SampleGETJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66A8C0124367EBA0052A1AF /* SampleGETJSON.swift */; }; C66FC07424379A990047D864 /* RequestIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C66FC07324379A990047D864 /* RequestIdentifier.swift */; }; + C692787926F120E100917E65 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692787826F120E100917E65 /* User.swift */; }; + C692787B26F1210500917E65 /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692787A26F1210500917E65 /* UserRepository.swift */; }; + C692787E26F121CD00917E65 /* PostRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692787D26F121CD00917E65 /* PostRepository.swift */; }; + C692788126F121FB00917E65 /* ExampleNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692788026F121FB00917E65 /* ExampleNetworkService.swift */; }; + C692788326F124CF00917E65 /* LoginRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692788226F124CF00917E65 /* LoginRequest.swift */; }; + C692788626F1254800917E65 /* Swifter in Frameworks */ = {isa = PBXBuildFile; productRef = C692788526F1254800917E65 /* Swifter */; }; + C692788926F1259800917E65 /* login.json in Resources */ = {isa = PBXBuildFile; fileRef = C692788826F1259800917E65 /* login.json */; }; + C692788B26F125B400917E65 /* posts.json in Resources */ = {isa = PBXBuildFile; fileRef = C692788A26F125B400917E65 /* posts.json */; }; + C692788D26F126CC00917E65 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692788C26F126CC00917E65 /* LoginViewController.swift */; }; + C692789326F1374000917E65 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692789226F1374000917E65 /* HomeViewController.swift */; }; + C692789526F13F9800917E65 /* ProfileNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692789426F13F9800917E65 /* ProfileNavigationController.swift */; }; + C692789726F141A000917E65 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692789626F141A000917E65 /* ProfileViewController.swift */; }; + C692789B26F1463B00917E65 /* user.json in Resources */ = {isa = PBXBuildFile; fileRef = C692789A26F1463B00917E65 /* user.json */; }; + C692789D26F147FC00917E65 /* GetUserDetailsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692789C26F147FC00917E65 /* GetUserDetailsRequest.swift */; }; C6953F42241A95830044D278 /* LogDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6953F41241A95830044D278 /* LogDestination.swift */; }; - C6D5C2F624E206C800A543CB /* DeleteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D5C2F524E206C800A543CB /* DeleteRequest.swift */; }; - C6D5C2F824E2085900A543CB /* SampleDeleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D5C2F724E2085900A543CB /* SampleDeleteViewController.swift */; }; C6F4CFB026D582E8004E6BB8 /* RequestFailedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F4CFAF26D582E8004E6BB8 /* RequestFailedDelegate.swift */; }; - C6F4CFB226D5874F004E6BB8 /* GlobalRequestFailureDelegateExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F4CFB126D5874F004E6BB8 /* GlobalRequestFailureDelegateExample.swift */; }; - C6F4CFB426D5875F004E6BB8 /* GlobalRequestFailurePublisherExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F4CFB326D5875F004E6BB8 /* GlobalRequestFailurePublisherExample.swift */; }; C6F4CFB626D598B3004E6BB8 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C6F4CFB526D598B3004E6BB8 /* README.md */; }; /* End PBXBuildFile section */ @@ -92,19 +95,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 23B627072435329800BD888D /* DownloadCatImageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadCatImageRequest.swift; sourceTree = ""; }; - 23B627092435332200BD888D /* SampleDownloadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleDownloadViewController.swift; sourceTree = ""; }; 3B00B3C626D7EA3C00A1DF79 /* DecodingError+Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DecodingError+Logging.swift"; sourceTree = ""; }; A63ABCC924ABB402004DE84E /* RetryConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryConfiguration.swift; sourceTree = ""; }; A63ABCCB24ABBFCC004DE84E /* DelayedOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelayedOperations.swift; sourceTree = ""; }; B822C8EB23F20E8900D7BDAD /* NetableExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetableExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; B822C8ED23F20E8900D7BDAD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B822C8EF23F20E8900D7BDAD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - B822C8F123F20E8900D7BDAD /* SampleGetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleGetViewController.swift; sourceTree = ""; }; B822C8F423F20E8900D7BDAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B822C8F923F20E8B00D7BDAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B822C8FB23F20E8B00D7BDAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B822C8FF23F210D800D7BDAD /* GetCatRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetCatRequest.swift; sourceTree = ""; }; B8A18C0C23F21FFE00941EA6 /* NetableExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetableExample.entitlements; sourceTree = ""; }; B8C9288023E9F68000DB2B37 /* Netable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Netable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B8C9288323E9F68000DB2B37 /* Netable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Netable.h; sourceTree = ""; }; @@ -120,30 +119,38 @@ B8C928A623E9FC8D00DB2B37 /* URLRequest+EncodeURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+EncodeURL.swift"; sourceTree = ""; }; B8C928A823E9FDCC00DB2B37 /* Netable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Netable.swift; sourceTree = ""; }; C635DCB326D95D2A009F82A0 /* SmartUnwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartUnwrap.swift; sourceTree = ""; }; - C635DCB526D95DB4009F82A0 /* SmartUnwrapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartUnwrapViewController.swift; sourceTree = ""; }; - C635DCB726D95DDB009F82A0 /* SmartUnwrapRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartUnwrapRequest.swift; sourceTree = ""; }; - C64689A626DE9D5300D2AFDD /* FallbackDecoderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackDecoderViewController.swift; sourceTree = ""; }; - C64689A826DE9DFD00D2AFDD /* FallbackDecoderRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackDecoderRequest.swift; sourceTree = ""; }; - C64689A426DD934400D2AFDD /* CombineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineViewController.swift; sourceTree = ""; }; + C64EAB6826F2828E0093850A /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; + C64EAB6A26F283440093850A /* GetPostsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPostsRequest.swift; sourceTree = ""; }; + C64EAB6C26F29AFD0093850A /* UnauthorizedRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnauthorizedRequest.swift; sourceTree = ""; }; + C64EAB6E26F2AD4F0093850A /* FailedRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedRequest.swift; sourceTree = ""; }; + C64EAB7026F2AE970093850A /* RootTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootTabBarController.swift; sourceTree = ""; }; + C64EAB7226F2B1B30093850A /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; + C64EAB7426F2B2000093850A /* VersionCheckRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionCheckRequest.swift; sourceTree = ""; }; + C64EAB7626F2B2E00093850A /* version.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = version.json; sourceTree = ""; }; + C64EAB7926F3AD370093850A /* PostOverviewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOverviewCell.swift; sourceTree = ""; }; + C64EAB7B26F3B1FC0093850A /* NewPostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPostViewController.swift; sourceTree = ""; }; + C64EAB7D26F3B8160093850A /* CreatePostRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostRequest.swift; sourceTree = ""; }; + C64EAB7F26F3C0A00093850A /* createPost.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = createPost.json; sourceTree = ""; }; C64F858F241FE4670028E0E9 /* Netable.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Netable.podspec; path = ../../Netable.podspec; sourceTree = ""; }; C64F8591241FE4870028E0E9 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../../CHANGELOG.md; sourceTree = ""; }; - C64F8593241FE70E0028E0E9 /* ExamplesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesTableViewController.swift; sourceTree = ""; }; - C64F859B241FF09D0028E0E9 /* LoginRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequest.swift; sourceTree = ""; }; - C64F859D241FF8E60028E0E9 /* PostLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostLoginViewController.swift; sourceTree = ""; }; C65289F426D01829009D486B /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; C66901AE241C1B0A002954C5 /* CustomLogDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLogDestination.swift; sourceTree = ""; }; - C66A8BF62436619F0052A1AF /* CancelRequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelRequestViewController.swift; sourceTree = ""; }; - C66A8BFA24367E910052A1AF /* DecodeSnakeCaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodeSnakeCaseViewController.swift; sourceTree = ""; }; - C66A8BFB24367E910052A1AF /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = ""; }; - C66A8BFC24367E910052A1AF /* EmptyLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyLoggerViewController.swift; sourceTree = ""; }; - C66A8C0124367EBA0052A1AF /* SampleGETJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleGETJSON.swift; sourceTree = ""; }; C66FC07324379A990047D864 /* RequestIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestIdentifier.swift; sourceTree = ""; }; + C692787826F120E100917E65 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + C692787A26F1210500917E65 /* UserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepository.swift; sourceTree = ""; }; + C692787D26F121CD00917E65 /* PostRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostRepository.swift; sourceTree = ""; }; + C692788026F121FB00917E65 /* ExampleNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleNetworkService.swift; sourceTree = ""; }; + C692788226F124CF00917E65 /* LoginRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequest.swift; sourceTree = ""; }; + C692788826F1259800917E65 /* login.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = login.json; sourceTree = ""; }; + C692788A26F125B400917E65 /* posts.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = posts.json; sourceTree = ""; }; + C692788C26F126CC00917E65 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + C692789226F1374000917E65 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + C692789426F13F9800917E65 /* ProfileNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNavigationController.swift; sourceTree = ""; }; + C692789626F141A000917E65 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; + C692789A26F1463B00917E65 /* user.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = user.json; sourceTree = ""; }; + C692789C26F147FC00917E65 /* GetUserDetailsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserDetailsRequest.swift; sourceTree = ""; }; C6953F41241A95830044D278 /* LogDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDestination.swift; sourceTree = ""; }; - C6D5C2F524E206C800A543CB /* DeleteRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteRequest.swift; sourceTree = ""; }; - C6D5C2F724E2085900A543CB /* SampleDeleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleDeleteViewController.swift; sourceTree = ""; }; C6F4CFAF26D582E8004E6BB8 /* RequestFailedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestFailedDelegate.swift; sourceTree = ""; }; - C6F4CFB126D5874F004E6BB8 /* GlobalRequestFailureDelegateExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalRequestFailureDelegateExample.swift; sourceTree = ""; }; - C6F4CFB326D5875F004E6BB8 /* GlobalRequestFailurePublisherExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalRequestFailurePublisherExample.swift; sourceTree = ""; }; C6F4CFB526D598B3004E6BB8 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = ""; }; /* End PBXFileReference section */ @@ -153,6 +160,7 @@ buildActionMask = 2147483647; files = ( B8A18C0D23F2201000941EA6 /* Netable.framework in Frameworks */, + C692788626F1254800917E65 /* Swifter in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -160,8 +168,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B888FD4D23ECD20800026A7F /* OHHTTPStubsSwift in Frameworks */, - B888FD4B23ECD20800026A7F /* OHHTTPStubs in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -179,10 +185,14 @@ B822C8EC23F20E8900D7BDAD /* NetableExample */ = { isa = PBXGroup; children = ( + C64EAB7826F3AD280093850A /* View */, + C692787F26F121F100917E65 /* Service */, + C692787726F1209500917E65 /* Repository */, + C692787626F1209100917E65 /* Model */, + C692787426F1208400917E65 /* ViewController */, C64F8597241FED610028E0E9 /* Helpers */, - C64F8596241FED3F0028E0E9 /* Requests */, + C64F8596241FED3F0028E0E9 /* Request */, C64F8595241FECD40028E0E9 /* Resources */, - C64F8598241FED6C0028E0E9 /* View Controllers */, ); path = NetableExample; sourceTree = ""; @@ -194,7 +204,6 @@ B8C9288D23E9F68000DB2B37 /* NetableTests */, B822C8EC23F20E8900D7BDAD /* NetableExample */, B8C9288123E9F68000DB2B37 /* Products */, - E670F3A3E320C28D82338D75 /* Pods */, ); sourceTree = ""; }; @@ -214,7 +223,6 @@ C6F4CFB526D598B3004E6BB8 /* README.md */, C64F8591241FE4870028E0E9 /* CHANGELOG.md */, C65289F426D01829009D486B /* Config.swift */, - 3B00B3C626D7EA3C00A1DF79 /* DecodingError+Logging.swift */, A63ABCCB24ABBFCC004DE84E /* DelayedOperations.swift */, B8C9289C23E9FA0E00DB2B37 /* Error.swift */, B8C928A223E9FBEC00DB2B37 /* HTTPMethod.swift */, @@ -245,9 +253,18 @@ path = NetableTests; sourceTree = ""; }; + C64EAB7826F3AD280093850A /* View */ = { + isa = PBXGroup; + children = ( + C64EAB7926F3AD370093850A /* PostOverviewCell.swift */, + ); + path = View; + sourceTree = ""; + }; C64F8595241FECD40028E0E9 /* Resources */ = { isa = PBXGroup; children = ( + C692788726F1257400917E65 /* JsonResponse */, B822C8ED23F20E8900D7BDAD /* AppDelegate.swift */, B822C8FB23F20E8B00D7BDAD /* Info.plist */, B822C8F823F20E8B00D7BDAD /* LaunchScreen.storyboard */, @@ -258,20 +275,18 @@ path = Resources; sourceTree = ""; }; - C64F8596241FED3F0028E0E9 /* Requests */ = { + C64F8596241FED3F0028E0E9 /* Request */ = { isa = PBXGroup; children = ( - C6D5C2F524E206C800A543CB /* DeleteRequest.swift */, - 23B627072435329800BD888D /* DownloadCatImageRequest.swift */, - B822C8FF23F210D800D7BDAD /* GetCatRequest.swift */, - C64F859B241FF09D0028E0E9 /* LoginRequest.swift */, - C66A8C0124367EBA0052A1AF /* SampleGETJSON.swift */, - C635DCB726D95DDB009F82A0 /* SmartUnwrapRequest.swift */, - 23B627072435329800BD888D /* DownloadCatImageRequest.swift */, - C6D5C2F524E206C800A543CB /* DeleteRequest.swift */, - C64689A826DE9DFD00D2AFDD /* FallbackDecoderRequest.swift */, - ); - path = Requests; + C64EAB6E26F2AD4F0093850A /* FailedRequest.swift */, + C64EAB6A26F283440093850A /* GetPostsRequest.swift */, + C692789C26F147FC00917E65 /* GetUserDetailsRequest.swift */, + C692788226F124CF00917E65 /* LoginRequest.swift */, + C64EAB6C26F29AFD0093850A /* UnauthorizedRequest.swift */, + C64EAB7426F2B2000093850A /* VersionCheckRequest.swift */, + C64EAB7D26F3B8160093850A /* CreatePostRequest.swift */, + ); + path = Request; sourceTree = ""; }; C64F8597241FED610028E0E9 /* Helpers */ = { @@ -282,34 +297,56 @@ path = Helpers; sourceTree = ""; }; - C64F8598241FED6C0028E0E9 /* View Controllers */ = { + C692787426F1208400917E65 /* ViewController */ = { isa = PBXGroup; children = ( - C66A8BF62436619F0052A1AF /* CancelRequestViewController.swift */, - C64689A426DD934400D2AFDD /* CombineViewController.swift */, - C66A8BFB24367E910052A1AF /* CustomLoggerViewController.swift */, - C66A8BFA24367E910052A1AF /* DecodeSnakeCaseViewController.swift */, - C66A8BFC24367E910052A1AF /* EmptyLoggerViewController.swift */, - C64F8593241FE70E0028E0E9 /* ExamplesTableViewController.swift */, - C6F4CFB126D5874F004E6BB8 /* GlobalRequestFailureDelegateExample.swift */, - C6F4CFB326D5875F004E6BB8 /* GlobalRequestFailurePublisherExample.swift */, - C64F859D241FF8E60028E0E9 /* PostLoginViewController.swift */, - C6D5C2F724E2085900A543CB /* SampleDeleteViewController.swift */, - 23B627092435332200BD888D /* SampleDownloadViewController.swift */, - B822C8F123F20E8900D7BDAD /* SampleGetViewController.swift */, - C635DCB526D95DB4009F82A0 /* SmartUnwrapViewController.swift */, - C6F4CFB126D5874F004E6BB8 /* GlobalRequestFailureDelegateExample.swift */, - C6F4CFB326D5875F004E6BB8 /* GlobalRequestFailurePublisherExample.swift */, - C64689A626DE9D5300D2AFDD /* FallbackDecoderViewController.swift */, - ); - path = "View Controllers"; + C692788C26F126CC00917E65 /* LoginViewController.swift */, + C692789226F1374000917E65 /* HomeViewController.swift */, + C692789426F13F9800917E65 /* ProfileNavigationController.swift */, + C692789626F141A000917E65 /* ProfileViewController.swift */, + C64EAB7026F2AE970093850A /* RootTabBarController.swift */, + C64EAB7B26F3B1FC0093850A /* NewPostViewController.swift */, + ); + path = ViewController; sourceTree = ""; }; - E670F3A3E320C28D82338D75 /* Pods */ = { + C692787626F1209100917E65 /* Model */ = { isa = PBXGroup; children = ( + C692787826F120E100917E65 /* User.swift */, + C64EAB6826F2828E0093850A /* Post.swift */, + C64EAB7226F2B1B30093850A /* Version.swift */, ); - path = Pods; + path = Model; + sourceTree = ""; + }; + C692787726F1209500917E65 /* Repository */ = { + isa = PBXGroup; + children = ( + C692787A26F1210500917E65 /* UserRepository.swift */, + C692787D26F121CD00917E65 /* PostRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; + C692787F26F121F100917E65 /* Service */ = { + isa = PBXGroup; + children = ( + C692788026F121FB00917E65 /* ExampleNetworkService.swift */, + ); + path = Service; + sourceTree = ""; + }; + C692788726F1257400917E65 /* JsonResponse */ = { + isa = PBXGroup; + children = ( + C692788826F1259800917E65 /* login.json */, + C692788A26F125B400917E65 /* posts.json */, + C692789A26F1463B00917E65 /* user.json */, + C64EAB7626F2B2E00093850A /* version.json */, + C64EAB7F26F3C0A00093850A /* createPost.json */, + ); + path = JsonResponse; sourceTree = ""; }; /* End PBXGroup section */ @@ -341,6 +378,9 @@ B8A18C1023F2201000941EA6 /* PBXTargetDependency */, ); name = NetableExample; + packageProductDependencies = ( + C692788526F1254800917E65 /* Swifter */, + ); productName = NetableExample; productReference = B822C8EB23F20E8900D7BDAD /* NetableExample.app */; productType = "com.apple.product-type.application"; @@ -360,8 +400,6 @@ ); name = Netable; packageProductDependencies = ( - B888FD4A23ECD20800026A7F /* OHHTTPStubs */, - B888FD4C23ECD20800026A7F /* OHHTTPStubsSwift */, ); productName = Netable; productReference = B8C9288023E9F68000DB2B37 /* Netable.framework */; @@ -417,7 +455,7 @@ ); mainGroup = B8C9287623E9F68000DB2B37; packageReferences = ( - B888FD4923ECD20800026A7F /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + C692788426F1254800917E65 /* XCRemoteSwiftPackageReference "swifter" */, ); productRefGroup = B8C9288123E9F68000DB2B37 /* Products */; projectDirPath = ""; @@ -437,6 +475,11 @@ files = ( B822C8FA23F20E8B00D7BDAD /* LaunchScreen.storyboard in Resources */, B822C8F523F20E8900D7BDAD /* Main.storyboard in Resources */, + C692788926F1259800917E65 /* login.json in Resources */, + C692788B26F125B400917E65 /* posts.json in Resources */, + C64EAB7726F2B2E00093850A /* version.json in Resources */, + C692789B26F1463B00917E65 /* user.json in Resources */, + C64EAB8026F3C0A00093850A /* createPost.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -464,30 +507,29 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C66A8BFF24367E910052A1AF /* EmptyLoggerViewController.swift in Sources */, + C692788D26F126CC00917E65 /* LoginViewController.swift in Sources */, + C64EAB7526F2B2000093850A /* VersionCheckRequest.swift in Sources */, + C64EAB6B26F283440093850A /* GetPostsRequest.swift in Sources */, + C692787B26F1210500917E65 /* UserRepository.swift in Sources */, + C64EAB6D26F29AFD0093850A /* UnauthorizedRequest.swift in Sources */, + C692787926F120E100917E65 /* User.swift in Sources */, + C692789D26F147FC00917E65 /* GetUserDetailsRequest.swift in Sources */, + C692789726F141A000917E65 /* ProfileViewController.swift in Sources */, + C64EAB6F26F2AD4F0093850A /* FailedRequest.swift in Sources */, + C64EAB7A26F3AD370093850A /* PostOverviewCell.swift in Sources */, + C64EAB7126F2AE970093850A /* RootTabBarController.swift in Sources */, + C64EAB7C26F3B1FC0093850A /* NewPostViewController.swift in Sources */, C66901AF241C1B0A002954C5 /* CustomLogDestination.swift in Sources */, - C64689A526DD934400D2AFDD /* CombineViewController.swift in Sources */, - B822C90023F210D800D7BDAD /* GetCatRequest.swift in Sources */, - C64F8594241FE70E0028E0E9 /* ExamplesTableViewController.swift in Sources */, - C66A8C0224367EBA0052A1AF /* SampleGETJSON.swift in Sources */, - C66A8BFD24367E910052A1AF /* DecodeSnakeCaseViewController.swift in Sources */, - C66A8BF72436619F0052A1AF /* CancelRequestViewController.swift in Sources */, - C64689A726DE9D5300D2AFDD /* FallbackDecoderViewController.swift in Sources */, - B822C8F223F20E8900D7BDAD /* SampleGetViewController.swift in Sources */, - 23B6270A2435332200BD888D /* SampleDownloadViewController.swift in Sources */, - C6F4CFB426D5875F004E6BB8 /* GlobalRequestFailurePublisherExample.swift in Sources */, B822C8EE23F20E8900D7BDAD /* AppDelegate.swift in Sources */, - C6D5C2F624E206C800A543CB /* DeleteRequest.swift in Sources */, - C64689A926DE9DFD00D2AFDD /* FallbackDecoderRequest.swift in Sources */, - C66A8BFE24367E910052A1AF /* CustomLoggerViewController.swift in Sources */, - C64F859C241FF09D0028E0E9 /* LoginRequest.swift in Sources */, - C6D5C2F824E2085900A543CB /* SampleDeleteViewController.swift in Sources */, - C64F859E241FF8E60028E0E9 /* PostLoginViewController.swift in Sources */, - C635DCB626D95DB4009F82A0 /* SmartUnwrapViewController.swift in Sources */, - C635DCB826D95DDB009F82A0 /* SmartUnwrapRequest.swift in Sources */, - C6F4CFB226D5874F004E6BB8 /* GlobalRequestFailureDelegateExample.swift in Sources */, + C692787E26F121CD00917E65 /* PostRepository.swift in Sources */, + C64EAB6926F2828E0093850A /* Post.swift in Sources */, + C64EAB7326F2B1B30093850A /* Version.swift in Sources */, + C692788126F121FB00917E65 /* ExampleNetworkService.swift in Sources */, + C692789526F13F9800917E65 /* ProfileNavigationController.swift in Sources */, + C64EAB7E26F3B8160093850A /* CreatePostRequest.swift in Sources */, B822C8F023F20E8900D7BDAD /* SceneDelegate.swift in Sources */, - 23B627082435329800BD888D /* DownloadCatImageRequest.swift in Sources */, + C692788326F124CF00917E65 /* LoginRequest.swift in Sources */, + C692789326F1374000917E65 /* HomeViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -859,26 +901,21 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - B888FD4923ECD20800026A7F /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + C692788426F1254800917E65 /* XCRemoteSwiftPackageReference "swifter" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/AliSoftware/OHHTTPStubs.git"; + repositoryURL = "https://github.com/httpswift/swifter.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 9.0.0; + minimumVersion = 1.5.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - B888FD4A23ECD20800026A7F /* OHHTTPStubs */ = { - isa = XCSwiftPackageProductDependency; - package = B888FD4923ECD20800026A7F /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; - productName = OHHTTPStubs; - }; - B888FD4C23ECD20800026A7F /* OHHTTPStubsSwift */ = { + C692788526F1254800917E65 /* Swifter */ = { isa = XCSwiftPackageProductDependency; - package = B888FD4923ECD20800026A7F /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; - productName = OHHTTPStubsSwift; + package = C692788426F1254800917E65 /* XCRemoteSwiftPackageReference "swifter" */; + productName = Swifter; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Netable/Netable.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Netable/Netable.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 08dceeb..57c4b92 100644 --- a/Netable/Netable.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Netable/Netable.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -2,12 +2,12 @@ "object": { "pins": [ { - "package": "OHHTTPStubs", - "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", + "package": "Swifter", + "repositoryURL": "https://github.com/httpswift/swifter.git", "state": { "branch": null, - "revision": "e92b5a5746ef16add2a1424f1fc19529d9a75cde", - "version": "9.0.0" + "revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version": "1.5.0" } } ] diff --git a/Netable/NetableExample/Model/Post.swift b/Netable/NetableExample/Model/Post.swift new file mode 100644 index 0000000..7ed4afc --- /dev/null +++ b/Netable/NetableExample/Model/Post.swift @@ -0,0 +1,14 @@ +// +// Post.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Foundation + +struct Post: Decodable { + let title: String + let content: String +} diff --git a/Netable/NetableExample/Model/User.swift b/Netable/NetableExample/Model/User.swift new file mode 100644 index 0000000..d9ccbf5 --- /dev/null +++ b/Netable/NetableExample/Model/User.swift @@ -0,0 +1,17 @@ +// +// User.swift +// NetableExample +// +// Created by Brendan on 2021-09-02. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Foundation + +struct User: Decodable { + let email: String + let token: String + let firstName: String? + let lastName: String? + let location: String? +} diff --git a/Netable/NetableExample/Model/Version.swift b/Netable/NetableExample/Model/Version.swift new file mode 100644 index 0000000..5967233 --- /dev/null +++ b/Netable/NetableExample/Model/Version.swift @@ -0,0 +1,18 @@ +// +// Version.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Foundation + +struct SimpleVersion: Decodable { + let buildNumber: String +} + +struct Version: Decodable { + let buildNumber: String + let deprecatedBuilds: [String] +} diff --git a/Netable/NetableExample/Repository/PostRepository.swift b/Netable/NetableExample/Repository/PostRepository.swift new file mode 100644 index 0000000..2ea5918 --- /dev/null +++ b/Netable/NetableExample/Repository/PostRepository.swift @@ -0,0 +1,57 @@ +// +// PostRepository.swift +// NetableExample +// +// Created by Brendan on 2021-09-03. +// + +import Combine +import Foundation +import Netable + +class PostRepository { + static var shared = PostRepository() + + /// If we aren't concerned with logging results from a particular instance, pass in the EmptyLogDestination as the logDestination + private let netable = Netable(baseURL: URL(string: "http://localhost:8080/posts/")!) + + var posts: CurrentValueSubject<[Post], Never> + var cancellables = [AnyCancellable]() + + private init() { + posts = CurrentValueSubject<[Post], Never>([]) + } + + func checkVersion() { + netable.request(VersionCheckRequest()) { result in + switch result { + case .success: + print("Version check successful!") + case .failure(let error): + if case NetableError.fallbackDecode = error { + print("Version check fallback successful!") + return + } + + print("Version check failed. Better handle that.") + } + } + } + + func getPosts() { + netable.request(GetPostsRequest()) { [weak self] result in + if case .success(let posts) = result { + self?.posts.send(posts) + } + } + } + + func create(_ title: String, content: String, onComplete: @escaping () -> Void) { + let params = CreatePostParams(title: title, content: content) + + netable.request(CreatePostRequest(parameters: params)) { result in + print(result) + onComplete() + } + } +} diff --git a/Netable/NetableExample/Repository/UserRepository.swift b/Netable/NetableExample/Repository/UserRepository.swift new file mode 100644 index 0000000..879fb4e --- /dev/null +++ b/Netable/NetableExample/Repository/UserRepository.swift @@ -0,0 +1,74 @@ +// +// UserRepository.swift +// NetableExample +// +// Created by Brendan on 2021-09-03. +// + +import Combine +import Foundation +import Netable + +class UserRepository { + static var shared = UserRepository() + + let netable = Netable(baseURL: URL(string: "http://localhost:8080/user/")!) + + var user: CurrentValueSubject + var cancellables = [AnyCancellable]() + + private init() { + user = CurrentValueSubject(nil) + + // Listen for 401 errors and if we get one, clear the current user + netable.requestFailurePublisher.sink { [weak self] error in + guard case let NetableError.httpError(statusCode, _) = error, statusCode == 401 else { + return + } + + self?.user.send(nil) + }.store(in: &cancellables) + } + + func login(email: String, password: String) { + let params = LoginParams(email: email, password: password) + + netable.request(LoginRequest(parameters: params)) { [weak self] result in + guard let self = self else { return } + + if case .success(let user) = result { + self.user.send(user) + } + } + } + + func getUserDetails() { + let params = UserDetailsParams(email: "", token: "") + + netable.request(GetUserDetailsRequest(parameters: params)) { [weak self] result in + guard let self = self else { return } + + if case .success(let user) = result { + self.user.send(user) + } + } + } + + public func unauthorizedRequest() { + netable.request(UnauthorizedRequest()) { _ in + // Since we know this request is going to fail, do nothing. + return + } + } + + public func failedRequest() { + netable.request(FailedRequest()) { _ in + // Since we know this request is going to fail, do nothing. + return + } + } + + public func logout() { + self.user.send(nil) + } +} diff --git a/Netable/NetableExample/Request/CreatePostRequest.swift b/Netable/NetableExample/Request/CreatePostRequest.swift new file mode 100644 index 0000000..ffe9040 --- /dev/null +++ b/Netable/NetableExample/Request/CreatePostRequest.swift @@ -0,0 +1,25 @@ +// +// CreatePostRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-16. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable + +struct CreatePostParams: Encodable { + let title: String + let content: String +} + +struct CreatePostRequest: Request { + typealias Parameters = CreatePostParams + typealias RawResource = Empty + + var method = HTTPMethod.post + + var path = "create" + + var parameters: CreatePostParams +} diff --git a/Netable/NetableExample/Request/FailedRequest.swift b/Netable/NetableExample/Request/FailedRequest.swift new file mode 100644 index 0000000..bda62a3 --- /dev/null +++ b/Netable/NetableExample/Request/FailedRequest.swift @@ -0,0 +1,18 @@ +// +// FailedRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable + +struct FailedRequest: Request { + typealias Parameters = Empty + typealias RawResource = Empty + + var method = HTTPMethod.get + + var path = "failed" +} diff --git a/Netable/NetableExample/Request/GetPostsRequest.swift b/Netable/NetableExample/Request/GetPostsRequest.swift new file mode 100644 index 0000000..bec2d52 --- /dev/null +++ b/Netable/NetableExample/Request/GetPostsRequest.swift @@ -0,0 +1,23 @@ +// +// GetPostsRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable + +struct GetPostsRequest: Request { + typealias Parameters = Empty + typealias RawResource = SmartUnwrap<[Post]> + typealias FinalResource = [Post] + + var method = HTTPMethod.get + + var path = "all" + + var unredactedParameterKeys: Set { + ["title", "content"] + } +} diff --git a/Netable/NetableExample/Request/GetUserDetailsRequest.swift b/Netable/NetableExample/Request/GetUserDetailsRequest.swift new file mode 100644 index 0000000..52be64c --- /dev/null +++ b/Netable/NetableExample/Request/GetUserDetailsRequest.swift @@ -0,0 +1,26 @@ +// +// GetUserDetailsRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-14. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable + +struct UserDetailsParams: Encodable { + let email: String + let token: String +} + +struct GetUserDetailsRequest: Request { + typealias Parameters = UserDetailsParams + typealias RawResource = SmartUnwrap + typealias FinalResource = User + + var method = HTTPMethod.get + + var path = "me" + + var parameters: UserDetailsParams +} diff --git a/Netable/NetableExample/Request/LoginRequest.swift b/Netable/NetableExample/Request/LoginRequest.swift new file mode 100644 index 0000000..b541132 --- /dev/null +++ b/Netable/NetableExample/Request/LoginRequest.swift @@ -0,0 +1,24 @@ +// +// LoginRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-03. +// + +import Netable + +struct LoginParams: Encodable { + let email: String + let password: String +} + +struct LoginRequest: Request { + typealias Parameters = LoginParams + typealias RawResource = User + + var method = HTTPMethod.get + + var path = "login" + + var parameters: LoginParams +} diff --git a/Netable/NetableExample/Request/UnauthorizedRequest.swift b/Netable/NetableExample/Request/UnauthorizedRequest.swift new file mode 100644 index 0000000..04f91fa --- /dev/null +++ b/Netable/NetableExample/Request/UnauthorizedRequest.swift @@ -0,0 +1,18 @@ +// +// UnauthorizedRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable + +struct UnauthorizedRequest: Request { + typealias Parameters = Empty + typealias RawResource = Empty + + var method = HTTPMethod.get + + var path = "unauthorized" +} diff --git a/Netable/NetableExample/Request/VersionCheckRequest.swift b/Netable/NetableExample/Request/VersionCheckRequest.swift new file mode 100644 index 0000000..535dade --- /dev/null +++ b/Netable/NetableExample/Request/VersionCheckRequest.swift @@ -0,0 +1,19 @@ +// +// VersionCheckRequest.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable + +struct VersionCheckRequest: Request { + typealias Parameters = Empty + typealias RawResource = Version + typealias FallbackResource = SimpleVersion + + var method = HTTPMethod.get + + var path = "version" +} diff --git a/Netable/NetableExample/Requests/DeleteRequest.swift b/Netable/NetableExample/Requests/DeleteRequest.swift deleted file mode 100644 index d83e0cb..0000000 --- a/Netable/NetableExample/Requests/DeleteRequest.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// DeleteRequest.swift -// NetableExample -// -// Created by Brendan on 2020-08-10. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable - -struct DeleteRequest: Request { - typealias Parameters = Empty - typealias RawResource = Empty - - public var method: HTTPMethod { return .delete } - - public var path: String { - return "/delete" - } -} diff --git a/Netable/NetableExample/Requests/DownloadCatImageRequest.swift b/Netable/NetableExample/Requests/DownloadCatImageRequest.swift deleted file mode 100644 index ff941d4..0000000 --- a/Netable/NetableExample/Requests/DownloadCatImageRequest.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// DownloadCatImageRequest.swift -// NetableExample -// -// Created by Jake Miner on 2020-04-01. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -struct DownloadCatImageRequest: Request { - typealias Parameters = Empty - typealias FinalResource = UIImage - typealias RawResource = Data - - let imageUrl: String - - public var method: HTTPMethod { return .get } - - var enforceBaseURL: Bool { return false } - - public var path: String { - return imageUrl - } - - func decode(_ data: Data?) -> Result { - if let data = data { - return .success(data) - } else { - return .failure(.noData) - } - } - - func finalize(raw: Data) -> Result { - if let image = UIImage(data: raw) { - return .success(image) - } else { - return .failure(NetableError.resourceExtractionError("Could not create image from the cat image data")) - } - } -} diff --git a/Netable/NetableExample/Requests/FallbackDecoderRequest.swift b/Netable/NetableExample/Requests/FallbackDecoderRequest.swift deleted file mode 100644 index 5053c52..0000000 --- a/Netable/NetableExample/Requests/FallbackDecoderRequest.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// FallbackDecoderRequest.swift -// NetableExample -// -// Created by Brendan on 2021-08-31. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Netable - -struct Version: Codable { - let id: String - let someField: Int -} - -struct SimpleVersion: Codable { - let id: String -} - -struct DecoderResult: Decodable { - let json: Version -} - -struct FallbackDecoderResult: Decodable { - let json: SimpleVersion -} - -struct FallbackDecoderRequest: Request { - typealias Parameters = SimpleVersion - typealias RawResource = DecoderResult - typealias FinalResource = Version - typealias FallbackResource = FallbackDecoderResult - - public var method: HTTPMethod { return .post } - public var jsonKeyEncodingStrategy: JSONEncoder.KeyEncodingStrategy { - return .convertToSnakeCase - } - - public var path: String { - return "/post" - } - - public var parameters: SimpleVersion - - public var unredactedParameterKeys: Set { - ["id"] - } - - func finalize(raw: DecoderResult) -> Result { - .success(raw.json) - } -} diff --git a/Netable/NetableExample/Requests/GetCatRequest.swift b/Netable/NetableExample/Requests/GetCatRequest.swift deleted file mode 100644 index bf472f9..0000000 --- a/Netable/NetableExample/Requests/GetCatRequest.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// GetCatRequest.swift -// NetableExample -// -// Created by Jeremy Chiang on 2020-02-10. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -struct CatImage: Decodable { - let id: String - let url: String -} - -struct GetCatRequest: Request { - typealias Parameters = [String: String] - typealias RawResource = [CatImage] - typealias FinalResource = UIImage - - public var method: HTTPMethod { return .get } - - public var path: String { - return "images/search" - } - - public var parameters: [String: String] { - return ["mime_type": "jpg,png"] - } - - func finalize(raw: RawResource) -> Result { - guard let catImage = raw.first else { - return .failure(NetableError.resourceExtractionError("The expected cat image array is empty")) - } - - guard let url = URL(string: catImage.url) else { - return .failure(NetableError.resourceExtractionError("The expected cat image url is invalid")) - } - - do { - let data = try Data(contentsOf: url) - - if let image = UIImage(data: data) { - return .success(image) - } else { - return .failure(NetableError.resourceExtractionError("Could not create image from the cat image data")) - } - } catch { - return .failure(NetableError.resourceExtractionError("Could not load contents of the cat image url")) - } - } -} diff --git a/Netable/NetableExample/Requests/LoginRequest.swift b/Netable/NetableExample/Requests/LoginRequest.swift deleted file mode 100644 index 6cd1391..0000000 --- a/Netable/NetableExample/Requests/LoginRequest.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// LoginRequest.swift -// NetableExample -// -// Created by Brendan on 2020-03-16. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable - -struct LoginParams: Encodable { - let username: String - let password: String - let firstName: String -} - -struct LoginResponse: Decodable { - let url: String -} - -struct LoginRequest: Request { - typealias Parameters = LoginParams - typealias RawResource = LoginResponse - - public var method: HTTPMethod { return .post } - public var jsonKeyEncodingStrategy: JSONEncoder.KeyEncodingStrategy { - return .convertToSnakeCase - } - - public var path: String { - return "/post" - } - - public var parameters: LoginParams - - public var unredactedParameterKeys: Set { - ["first_name", "username"] - } -} diff --git a/Netable/NetableExample/Requests/SampleGETJSON.swift b/Netable/NetableExample/Requests/SampleGETJSON.swift deleted file mode 100644 index 692b23e..0000000 --- a/Netable/NetableExample/Requests/SampleGETJSON.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// SampleGETJSON.swift -// NetableExample -// -// Created by Brendan on 2020-03-19. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable - -struct SampleResponse: Decodable { - struct User: Decodable { - let email: String - let firstName: String - let lastName: String - } - - let data: User -} - -struct SampleGetJSON: Request { - typealias Parameters = [String: String] - typealias RawResource = SampleResponse - - var method: HTTPMethod { return .get } - - var path: String { - return "api/users/2" - } - - var parameters: [String : String] -} diff --git a/Netable/NetableExample/Requests/SmartUnwrapRequest.swift b/Netable/NetableExample/Requests/SmartUnwrapRequest.swift deleted file mode 100644 index 14b034f..0000000 --- a/Netable/NetableExample/Requests/SmartUnwrapRequest.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// SmartUnwrapRequest.swift -// NetableExample -// -// Created by Brendan on 2021-08-27. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Netable - -struct User: Decodable { - let username: String - let firstName: String - let password: String -} - -struct SmartUnwrapRequest: Request { - typealias Parameters = LoginParams - typealias RawResource = SmartUnwrap - typealias FinalResource = User - - public var smartUnwrapKey: String? { - return "json" - } - - public var method: HTTPMethod { return .post } - public var jsonKeyEncodingStrategy: JSONEncoder.KeyEncodingStrategy { - return .convertToSnakeCase - } - - public var path: String { - return "/post" - } - - public var parameters: LoginParams - - public var unredactedParameterKeys: Set { - ["first_name", "username"] - } -} diff --git a/Netable/NetableExample/Resources/AppDelegate.swift b/Netable/NetableExample/Resources/AppDelegate.swift index 20ea239..ceeec49 100644 --- a/Netable/NetableExample/Resources/AppDelegate.swift +++ b/Netable/NetableExample/Resources/AppDelegate.swift @@ -12,6 +12,10 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + // Poke our swifter manager to wake up the mock server + _ = ExampleNetworkService.shared + return true } } diff --git a/Netable/NetableExample/Resources/Base.lproj/Main.storyboard b/Netable/NetableExample/Resources/Base.lproj/Main.storyboard index 8e9fa5e..7546e16 100644 --- a/Netable/NetableExample/Resources/Base.lproj/Main.storyboard +++ b/Netable/NetableExample/Resources/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -9,406 +9,373 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + - - - - - - + + + + + - - - - + + + - + - + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + - + + + - + - - + + + + + + + + + + + - + + + + - + - + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - - + + - + - - + + - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - - + + + - - + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + - - + + + + + + - + - + - - + + + + + - + + - + + - + - - + + - - - - - - - - - - + + + + + + + + - + + - - + + - + - - + + - - - - - - - - - - - - + + + + + + + + + + + + + - + + + + + + + diff --git a/Netable/NetableExample/Resources/Info.plist b/Netable/NetableExample/Resources/Info.plist index 385690d..12d46c2 100644 --- a/Netable/NetableExample/Resources/Info.plist +++ b/Netable/NetableExample/Resources/Info.plist @@ -2,6 +2,13 @@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + LSApplicationCategoryType + UIUserInterfaceStyle Light CFBundleDevelopmentRegion diff --git a/Netable/NetableExample/Resources/JsonResponse/createPost.json b/Netable/NetableExample/Resources/JsonResponse/createPost.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/Netable/NetableExample/Resources/JsonResponse/createPost.json @@ -0,0 +1 @@ +{} diff --git a/Netable/NetableExample/Resources/JsonResponse/login.json b/Netable/NetableExample/Resources/JsonResponse/login.json new file mode 100644 index 0000000..42c3228 --- /dev/null +++ b/Netable/NetableExample/Resources/JsonResponse/login.json @@ -0,0 +1,4 @@ +{ + "email": "folcoboffin@example.com", + "token": "1a2a3a4a" +} diff --git a/Netable/NetableExample/Resources/JsonResponse/posts.json b/Netable/NetableExample/Resources/JsonResponse/posts.json new file mode 100644 index 0000000..8c647fc --- /dev/null +++ b/Netable/NetableExample/Resources/JsonResponse/posts.json @@ -0,0 +1,24 @@ +{ + "posts": [ + { + "title": "first post", + "content": "em ipsum dolor sit amet, consectetur adipiscing elit. Proin in mattis magna. Pellentesque ac tortor nec lectus auctor egestas. Proin erat felis, finibus vitae condimentum at, viverra sed arcu." + }, + { + "title": "second post", + "content": "Sed iaculis ut diam sodales molestie. Ut efficitur purus massa, at consectetur tortor sodales ultricies. Donec rhoncus nisi et libero egestas finibus." + }, + { + "title": "third post", + "content": "Fusce placerat velit sapien, eu pellentesque magna facilisis vitae. Sed purus diam, congue sed ullamcorper sed, aliquam a justo. Mauris at ipsum rutrum, placerat nunc sit amet, facilisis lorem." + }, + { + "title": "fourth post", + "content": "Etiam eros turpis, euismod at tincidunt quis, gravida nec augue." + }, + { + "title": "fifth post", + "content": "Suspendisse vel nibh enim. Ut id semper magna. Sed nec velit orci." + }, + ] +} diff --git a/Netable/NetableExample/Resources/JsonResponse/user.json b/Netable/NetableExample/Resources/JsonResponse/user.json new file mode 100644 index 0000000..2ace428 --- /dev/null +++ b/Netable/NetableExample/Resources/JsonResponse/user.json @@ -0,0 +1,9 @@ +{ + "user": { + "email": "folcoboffin@example.com", + "token": "1a2a3a4a", + "firstName": "Folco", + "lastName": "Boffin", + "location": "Bag End, ME" + } +} diff --git a/Netable/NetableExample/Resources/JsonResponse/version.json b/Netable/NetableExample/Resources/JsonResponse/version.json new file mode 100644 index 0000000..03f9245 --- /dev/null +++ b/Netable/NetableExample/Resources/JsonResponse/version.json @@ -0,0 +1,3 @@ +{ + "buildNumber": "1.2.3" +} diff --git a/Netable/NetableExample/Service/ExampleNetworkService.swift b/Netable/NetableExample/Service/ExampleNetworkService.swift new file mode 100644 index 0000000..f58045c --- /dev/null +++ b/Netable/NetableExample/Service/ExampleNetworkService.swift @@ -0,0 +1,60 @@ +// +// ExampleNetworkService.swift +// NetableExample +// +// Created by Brendan on 2021-09-03. +// + +import Foundation +import Swifter + +class ExampleNetworkService { + static let shared = ExampleNetworkService() + + private var server: HttpServer + + private init() { + server = HttpServer() + + server["/user/login"] = { _ in + .ok(self.loadJson(from: "login")) + } + + server["/user/me"] = { _ in + .ok(self.loadJson(from: "user")) + } + + server["/user/unauthorized"] = { _ in + .unauthorized + } + + server["/user/failed"] = { _ in + .internalServerError + } + + server["/posts/all"] = { _ in + .ok(self.loadJson(from: "posts")) + } + + server["/posts/create"] = { _ in + .ok(self.loadJson(from: "createPost")) + } + + server["/posts/version"] = { _ in + .ok(self.loadJson(from: "version")) + } + + try! server.start() + } + + private func loadJson(from path: String) -> HttpResponseBody { + guard let path = Bundle.main.path(forResource: path, ofType: "json"), + let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe), + let jsonResult = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves), + let jsonResult = jsonResult as? Dictionary else { + fatalError("Failed to load response JSON for: \(path)") + } + + return .json(jsonResult) + } +} diff --git a/Netable/NetableExample/View Controllers/CancelRequestViewController.swift b/Netable/NetableExample/View Controllers/CancelRequestViewController.swift deleted file mode 100644 index cfdd2a1..0000000 --- a/Netable/NetableExample/View Controllers/CancelRequestViewController.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// CancelRequestViewController.swift -// NetableExample -// -// Created by Brendan on 2020-04-02. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class CancelRequestViewController: UIViewController { - - /// Create a Netable instance using the default log destination. - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/v1/")!) - - override func viewDidLoad() { - super.viewDidLoad() - - // Make your request as you normally would, but store the `RequestIdentifier` provided by `Request` to refer to later. - let taskId = netable.request(GetCatRequest()) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success: - print("Request success!") - case .failure: - // Handle the cancelled request here - let alert = UIAlertController( - title: "Uh oh!", - message: "Request cancelled.", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - - // Use that `RequestIdentifier` to cancel your request - netable.cancel(byId: taskId) - - // Or cancel using the `RequestIdentifier`, if you don't want to store a reference to your `Netable` object. - // taskId.cancel() - } -} diff --git a/Netable/NetableExample/View Controllers/CombineViewController.swift b/Netable/NetableExample/View Controllers/CombineViewController.swift deleted file mode 100644 index a423445..0000000 --- a/Netable/NetableExample/View Controllers/CombineViewController.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// CombineViewController.swift -// NetableExample -// -// Created by Brendan on 2021-08-30. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Combine -import Netable -import UIKit - -class CombineViewController: UIViewController { - @IBOutlet var label: UILabel! - - /// Create a Netable instance using the default log destination. - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/v1/")!) - private var cancellables = [AnyCancellable]() - - override func viewDidLoad() { - super.viewDidLoad() - - netable.request(GetCatRequest()).sink( - receiveCompletion: { error in - let alert = UIAlertController( - title: "Uh oh!", - message: "GET failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - }, receiveValue: { finalResource in - self.label.text = "\(finalResource)" - }).store(in: &cancellables) - } -} diff --git a/Netable/NetableExample/View Controllers/CustomLoggerViewController.swift b/Netable/NetableExample/View Controllers/CustomLoggerViewController.swift deleted file mode 100644 index 3fcb6d6..0000000 --- a/Netable/NetableExample/View Controllers/CustomLoggerViewController.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SampleCustomLoggerViewController.swift -// NetableExample -// -// Created by Brendan on 2020-03-20. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class CustomLoggerViewController: UIViewController { - @IBOutlet weak var imageView: UIImageView! - - /// Create a Netable instance that won't record any logs and pass it in with the Netable constructor. - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/v1/")!, logDestination: CustomLogDestination()) - - override func viewDidLoad() { - super.viewDidLoad() - - netable.request(GetCatRequest()) { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let image): - self.imageView.image = image - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Get cat image failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} diff --git a/Netable/NetableExample/View Controllers/DecodeSnakeCaseViewController.swift b/Netable/NetableExample/View Controllers/DecodeSnakeCaseViewController.swift deleted file mode 100644 index fc91233..0000000 --- a/Netable/NetableExample/View Controllers/DecodeSnakeCaseViewController.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// DecodeCustomLoggingViewController.swift -// NetableExample -// -// Created by Brendan on 2020-03-20. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class DecodeSnakeCaseViewController: UIViewController { - @IBOutlet private var paramsLabel: UILabel! - @IBOutlet private var resultLabel: UILabel! - - private let client = Netable( - baseURL: URL(string: "https://reqres.in/")!, - config: Config(jsonDecodingStrategy: .convertFromSnakeCase) - ) - - override func viewDidLoad() { - super.viewDidLoad() - - let sampleGet = SampleGetJSON(parameters: ["sample_param": "1a2a3a4a"]) - paramsLabel.text = "Sending params \(sampleGet.parameters)" - - client.request(sampleGet) { result in - switch result { - case .success(_): - self.resultLabel.text = "Result: \(result)" - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Get failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - - } - } - -} diff --git a/Netable/NetableExample/View Controllers/EmptyLoggerViewController.swift b/Netable/NetableExample/View Controllers/EmptyLoggerViewController.swift deleted file mode 100644 index 912d167..0000000 --- a/Netable/NetableExample/View Controllers/EmptyLoggerViewController.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// EmptyLoggerViewController.swift -// NetableExample -// -// Created by Brendan on 2020-03-20. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class EmptyLoggerViewController: UIViewController { - @IBOutlet weak var imageView: UIImageView! - - /// Pass in an instance of `EmptyLogDestination` to have Netable not print any logs. - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/v1/")!, logDestination: EmptyLogDestination()) - - override func viewDidLoad() { - super.viewDidLoad() - - netable.request(GetCatRequest()) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let image): - self.imageView.image = image - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Get cat image failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} diff --git a/Netable/NetableExample/View Controllers/ExamplesTableViewController.swift b/Netable/NetableExample/View Controllers/ExamplesTableViewController.swift deleted file mode 100644 index f5eebb6..0000000 --- a/Netable/NetableExample/View Controllers/ExamplesTableViewController.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// ExamplesTableViewController.swift -// NetableExample -// -// Created by Brendan on 2020-03-16. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import UIKit - -class ExamplesTableViewController: UITableViewController { - private struct RequestRow { - let title: String - let vcIdentifier: String - } - - private struct RequestSet { - let sectionTitle: String - let requestRows: [RequestRow] - } - - private let contents: [RequestSet] = [ - RequestSet( - sectionTitle: "GET", - requestRows: [ - RequestRow(title: "GET Cat Image", vcIdentifier: "SampleGetViewController"), - RequestRow(title: "Download Cat Image", vcIdentifier: "SampleDownloadViewController"), - RequestRow(title: "Custom Logger Example", vcIdentifier: "CustomLoggerViewController"), - RequestRow(title: "Empty Logger Example", vcIdentifier: "EmptyLoggerViewController"), - RequestRow(title: "Decode_snake_case", vcIdentifier: "DecodeSnakeCaseViewController"), - RequestRow(title: "Cancel Request", vcIdentifier: "CancelRequestViewController"), - RequestRow(title: "Delete Example", vcIdentifier: "SampleDeleteViewController"), - RequestRow(title: "Smart Unwrapper", vcIdentifier: "SmartUnwrapViewController"), - RequestRow(title: "Global Error Delegate", vcIdentifier: "GlobalRequestFailureDelegateExample"), - RequestRow(title: "Global Error Publisher", vcIdentifier: "GlobalRequestFailurePublisherExample"), - RequestRow(title: "Fallback Decoder Example", vcIdentifier: "FallbackDecoderViewController"), - RequestRow(title: "Combine Example", vcIdentifier: "CombineViewController") - ] - ), - RequestSet(sectionTitle: "POST", requestRows: [RequestRow(title: "POST Sample Login", vcIdentifier: "PostLoginViewController")]), - ] - - // MARK: UITableViewDelegate and UITableViewDataSource Overrides - - override func numberOfSections(in tableView: UITableView) -> Int { - return contents.count - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return contents[section].sectionTitle - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return contents[section].requestRows.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = UITableViewCell() - cell.textLabel?.text = contents[indexPath.section].requestRows[indexPath.row].title - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - if let vc = storyboard?.instantiateViewController(withIdentifier: contents[indexPath.section].requestRows[indexPath.row].vcIdentifier) { - navigationController?.pushViewController(vc, animated: true) - } - } -} diff --git a/Netable/NetableExample/View Controllers/FallbackDecoderViewController.swift b/Netable/NetableExample/View Controllers/FallbackDecoderViewController.swift deleted file mode 100644 index cedd82c..0000000 --- a/Netable/NetableExample/View Controllers/FallbackDecoderViewController.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// FallbackDecoderViewController.swift -// NetableExample -// -// Created by Brendan on 2021-08-31. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class FallbackDecoderViewController: UIViewController { - @IBOutlet private var label: UILabel! - - private let netable = Netable(baseURL: URL(string: "https://httpbin.org/")!) - - override func viewDidLoad() { - // Bundle your login params up to pass into your request. - let params = SimpleVersion(id: "1.2.3") - - // Call `request()`, passing in your parameters. - netable.request(FallbackDecoderRequest(parameters: params)) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let response): - self.label.text = "\(response)" - case .failure(let error): - switch error { - case .fallbackDecode(let fallbackResponse): - self.label.text = "\(fallbackResponse)" - default: - let alert = UIAlertController( - title: "Uh oh!", - message: "Fetch failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } - } -} diff --git a/Netable/NetableExample/View Controllers/GlobalRequestFailureDelegateExample.swift b/Netable/NetableExample/View Controllers/GlobalRequestFailureDelegateExample.swift deleted file mode 100644 index e76e6c9..0000000 --- a/Netable/NetableExample/View Controllers/GlobalRequestFailureDelegateExample.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// GlobalRequestFailureDelegateExample.swift -// NetableExample -// -// Created by Brendan on 2021-08-24. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Netable -import SwiftUI - -class GlobalRequestFailureDelegateExample: UIViewController { - - @IBOutlet private var catImageView: UIImageView! - - /// Create a Netable instance using the default log destination - /// Note we're intentionally using an endpoint here we know will return a 404 to test error handling - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/404")!) - - override func viewDidLoad() { - super.viewDidLoad() - - netable.requestFailureDelegate = self - - netable.request(GetCatRequest()) { [weak self] result in - guard let self = self else { return } - - // Since we don't care about the failure case, we can swap our usual switch statement out for an `if case`! - if case .success(let image) = result { - self.catImageView.image = image - } - } - } -} - -extension GlobalRequestFailureDelegateExample: RequestFailureDelegate { - func requestDidFail(_ request: T, error: NetableError) where T : Request { - let alert = UIAlertController(title: "Uh oh!", message: error.errorDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - present(alert, animated: true) - } -} - - diff --git a/Netable/NetableExample/View Controllers/GlobalRequestFailurePublisherExample.swift b/Netable/NetableExample/View Controllers/GlobalRequestFailurePublisherExample.swift deleted file mode 100644 index 283a1be..0000000 --- a/Netable/NetableExample/View Controllers/GlobalRequestFailurePublisherExample.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// GlobalRequestFailurePublisherExample.swift -// NetableExample -// -// Created by Brendan on 2021-08-24. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Combine -import Netable -import SwiftUI - -class GlobalRequestFailurePublisherExample: UIViewController { - - @IBOutlet private var catImageView: UIImageView! - - /// Create a Netable instance using the default log destination - /// Note we're intentionally using an endpoint here we know will return a 404 to test error handling - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/404")!) - private var cancellables = [AnyCancellable]() - - override func viewDidLoad() { - super.viewDidLoad() - - netable.requestFailurePublisher.sink { error in - let alert = UIAlertController(title: "Uh oh!", message: error.errorDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - self.present(alert, animated: true) - }.store(in: &cancellables) - - netable.request(GetCatRequest()) { [weak self] result in - guard let self = self else { return } - - // Since we don't care about the failure case, we can swap our usual switch statement out for an `if case`! - if case .success(let image) = result { - self.catImageView.image = image - } - } - } -} diff --git a/Netable/NetableExample/View Controllers/PostLoginViewController.swift b/Netable/NetableExample/View Controllers/PostLoginViewController.swift deleted file mode 100644 index 8f2f595..0000000 --- a/Netable/NetableExample/View Controllers/PostLoginViewController.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// PostLoginViewController.swift -// NetableExample -// -// Created by Brendan on 2020-03-16. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class PostLoginViewController: UIViewController { - @IBOutlet private var usernameField: UITextField! - @IBOutlet private var passwordField: UITextField! - @IBOutlet private var submitButton: UIButton! - @IBOutlet private var resultLabel: UILabel! - - private let netable = Netable(baseURL: URL(string: "https://httpbin.org/")!) - - override func viewDidLoad() { - super.viewDidLoad() - - usernameField.delegate = self - passwordField.delegate = self - } - - @IBAction func submitPressed(_ sender: Any) { - guard let username = usernameField.text, - let password = passwordField.text else { - fatalError("Managed to press submit without a username or password, something's gone wrong.") - } - - // Bundle your login params up to pass into your request. - let params = LoginParams(username: username, password: password, firstName: "Beep boop") - - // Call `request()`, passing in your parameters. - netable.request(LoginRequest(parameters: params)) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(_): - // Login was a success! Store the user's credentials and redirect to your home screen. - self.resultLabel.text = "Success!" - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Login failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} - -extension PostLoginViewController: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - submitButton.isEnabled = usernameField.text?.isEmpty == false && usernameField.text?.isEmpty == false - return true - } -} diff --git a/Netable/NetableExample/View Controllers/SampleDeleteViewController.swift b/Netable/NetableExample/View Controllers/SampleDeleteViewController.swift deleted file mode 100644 index 0cd370b..0000000 --- a/Netable/NetableExample/View Controllers/SampleDeleteViewController.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// SampleDeleteViewController.swift -// NetableExample -// -// Created by Brendan on 2020-08-10. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class SampleDeleteViewController: UIViewController { - @IBOutlet var label: UILabel! - - /// Create a Netable instance using the default log destination. - private let netable = Netable(baseURL: URL(string: "https://httpbin.org/")!) - - override func viewDidLoad() { - super.viewDidLoad() - - netable.request(DeleteRequest()) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success: - self.label.text = "Deletion successful!" - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Delete failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} - diff --git a/Netable/NetableExample/View Controllers/SampleDownloadViewController.swift b/Netable/NetableExample/View Controllers/SampleDownloadViewController.swift deleted file mode 100644 index c9be781..0000000 --- a/Netable/NetableExample/View Controllers/SampleDownloadViewController.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ViewController.swift -// NetableExample -// -// Created by Jeremy Chiang on 2020-02-10. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class SampleDownloadViewController: UIViewController { - - @IBOutlet private var catImageView: UIImageView! - - /// Create a Netable instance using the default log destination. - private let netable = Netable(baseURL: URL(string: "https://cdn2.thecatapi.com/")!) - - override func viewDidLoad() { - super.viewDidLoad() - - netable.request(DownloadCatImageRequest(imageUrl: "https://cdn2.thecatapi.com/images/se.jpg")) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let image): - self.catImageView.image = image - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Get cat image failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} - diff --git a/Netable/NetableExample/View Controllers/SampleGetViewController.swift b/Netable/NetableExample/View Controllers/SampleGetViewController.swift deleted file mode 100644 index 3909722..0000000 --- a/Netable/NetableExample/View Controllers/SampleGetViewController.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ViewController.swift -// NetableExample -// -// Created by Jeremy Chiang on 2020-02-10. -// Copyright © 2020 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class SampleGetViewController: UIViewController { - - @IBOutlet private var catImageView: UIImageView! - - /// Create a Netable instance using the default log destination - private let netable = Netable(baseURL: URL(string: "https://api.thecatapi.com/v1/")!) - - override func viewDidLoad() { - super.viewDidLoad() - - netable.request(GetCatRequest()) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let image): - self.catImageView.image = image - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Get cat image failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} - diff --git a/Netable/NetableExample/View Controllers/SmartUnwrapViewController.swift b/Netable/NetableExample/View Controllers/SmartUnwrapViewController.swift deleted file mode 100644 index 24290f9..0000000 --- a/Netable/NetableExample/View Controllers/SmartUnwrapViewController.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SmartUnwrapViewController.swift -// NetableExample -// -// Created by Brendan on 2021-08-27. -// Copyright © 2021 Steamclock Software. All rights reserved. -// - -import Netable -import UIKit - -class SmartUnwrapViewController: UIViewController { - - @IBOutlet var resultLabel: UILabel! - - /// Create a Netable instance using the default log destination - private let netable = Netable(baseURL: URL(string: "https://httpbin.org/")!) - - override func viewDidLoad() { - super.viewDidLoad() - - let params = LoginParams(username: "username", password: "password", firstName: "Beep boop") - netable.request(SmartUnwrapRequest(parameters: params)) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let result): - self.resultLabel.text = "Success! \(result.firstName)" - case .failure(let error): - let alert = UIAlertController( - title: "Uh oh!", - message: "Get cat image failed with error: \(error)", - preferredStyle: .alert - ) - - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - self.present(alert, animated: true, completion: nil) - } - } - } -} - diff --git a/Netable/NetableExample/View/PostOverviewCell.swift b/Netable/NetableExample/View/PostOverviewCell.swift new file mode 100644 index 0000000..bbac4e7 --- /dev/null +++ b/Netable/NetableExample/View/PostOverviewCell.swift @@ -0,0 +1,14 @@ +// +// PostOverviewCell.swift +// NetableExample +// +// Created by Brendan on 2021-09-16. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import UIKit + +class PostOverviewCell: UITableViewCell { + @IBOutlet var titleLabel: UILabel! + @IBOutlet var contentLabel: UILabel! +} diff --git a/Netable/NetableExample/ViewController/HomeNavigationController.swift b/Netable/NetableExample/ViewController/HomeNavigationController.swift new file mode 100644 index 0000000..f1c4f1f --- /dev/null +++ b/Netable/NetableExample/ViewController/HomeNavigationController.swift @@ -0,0 +1,35 @@ +// +// HomeNavigationController.swift +// NetableExample +// +// Created by Brendan on 2021-09-14. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Combine +import Netable +import UIKit + +class HomeNavigationController: UINavigationController { + private var cancellables = [AnyCancellable]() + + override func viewDidLoad() { + super.viewDidLoad() + print("--- vdl") + UserRepository.shared.netable.requestFailureDelegate = self + } +} + +extension HomeNavigationController: RequestFailureDelegate { + func requestDidFail(_ request: T, error: NetableError) where T : Request { + print("==== request did fail") + // Ignore 401 unauthorized errors, we'll handle those in the UserRepository + if case let NetableError.httpError(statusCode, _) = error, statusCode == 401 { + return + } + + let alert = UIAlertController(title: "Error", message: error.errorDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Ok", style: .default)) + present(alert, animated: true, completion: nil) + } +} diff --git a/Netable/NetableExample/ViewController/HomeViewController.swift b/Netable/NetableExample/ViewController/HomeViewController.swift new file mode 100644 index 0000000..8ee7ca7 --- /dev/null +++ b/Netable/NetableExample/ViewController/HomeViewController.swift @@ -0,0 +1,56 @@ +// +// HomeViewController.swift +// NetableExample +// +// Created by Brendan on 2021-09-14. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Combine +import UIKit + +class HomeViewController: UITableViewController { + private var posts = [Post]() + private var cancellables = [AnyCancellable]() + + override func viewDidLoad() { + super.viewDidLoad() + + bindRepository() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + PostRepository.shared.getPosts() + } + + private func bindRepository() { + PostRepository.shared.posts.sink { posts in + self.posts = posts + self.tableView.reloadData() + }.store(in: &cancellables) + } + @IBAction func createNewPost(_ sender: Any) { + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return posts.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostOverviewCell") as? PostOverviewCell else { + return UITableViewCell() + } + + let post = posts[indexPath.row] + + cell.titleLabel.text = post.title + cell.contentLabel.text = post.content + return cell + } +} diff --git a/Netable/NetableExample/ViewController/LoginViewController.swift b/Netable/NetableExample/ViewController/LoginViewController.swift new file mode 100644 index 0000000..9b66ff6 --- /dev/null +++ b/Netable/NetableExample/ViewController/LoginViewController.swift @@ -0,0 +1,24 @@ +// +// LoginViewController.swift +// NetableExample +// +// Created by Brendan on 2021-09-14. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import UIKit + +class LoginViewController: UIViewController { + @IBOutlet private var emailField: UITextField! + @IBOutlet private var passwordField: UITextField! + @IBOutlet private var submitButton: UIButton! + + @IBAction private func login(_ sender: Any) { + guard let email = emailField.text, let password = passwordField.text else { + // TODO: handle error + return + } + + UserRepository.shared.login(email: email, password: password) + } +} diff --git a/Netable/NetableExample/ViewController/NewPostViewController.swift b/Netable/NetableExample/ViewController/NewPostViewController.swift new file mode 100644 index 0000000..bfa1fd3 --- /dev/null +++ b/Netable/NetableExample/ViewController/NewPostViewController.swift @@ -0,0 +1,29 @@ +// +// NewPostViewController.swift +// NetableExample +// +// Created by Brendan on 2021-09-16. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import UIKit + +class NewPostViewController: UIViewController { + @IBOutlet var titleField: UITextField! + @IBOutlet var contentField: UITextView! + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true, completion: nil) + } + + @IBAction func submit(_ sender: Any) { + guard let title = titleField.text, + let content = contentField.text else { + return + } + + PostRepository.shared.create(title, content: content) { + self.dismiss(animated: true, completion: nil) + } + } +} diff --git a/Netable/NetableExample/ViewController/ProfileNavigationController.swift b/Netable/NetableExample/ViewController/ProfileNavigationController.swift new file mode 100644 index 0000000..1e5fca6 --- /dev/null +++ b/Netable/NetableExample/ViewController/ProfileNavigationController.swift @@ -0,0 +1,37 @@ +// +// ProfileNavigationController.swift +// NetableExample +// +// Created by Brendan on 2021-09-14. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Combine +import UIKit + +enum ProfileSegue: String { + case toProfile +} + +class ProfileNavigationController: UINavigationController { + private var cancellables = [AnyCancellable]() + + private var previousUser: User? + + override func viewDidLoad() { + super.viewDidLoad() + + UserRepository.shared.user.sink { user in + guard user != nil else { + self.previousUser = nil + self.popToRootViewController(animated: true) + return + } + + if self.previousUser == nil { + self.performSegue(withIdentifier: ProfileSegue.toProfile.rawValue, sender: self) + } + self.previousUser = user + }.store(in: &cancellables) + } +} diff --git a/Netable/NetableExample/ViewController/ProfileViewController.swift b/Netable/NetableExample/ViewController/ProfileViewController.swift new file mode 100644 index 0000000..7ef2c92 --- /dev/null +++ b/Netable/NetableExample/ViewController/ProfileViewController.swift @@ -0,0 +1,49 @@ +// +// ProfileViewController.swift +// NetableExample +// +// Created by Brendan on 2021-09-14. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Combine +import UIKit + +class ProfileViewController: UIViewController { + @IBOutlet private var firstNameLabel: UILabel! + @IBOutlet private var lastNameLabel: UILabel! + @IBOutlet private var emailLabel: UILabel! + @IBOutlet private var locationLabel: UILabel! + + private var cancellables = [AnyCancellable]() + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.hidesBackButton = true + + bindUserRepository() + } + + @IBAction private func logout(_ sender: Any) { + UserRepository.shared.logout() + } + + @IBAction private func trigger401Error(_ sender: Any) { + UserRepository.shared.unauthorizedRequest() + } + + @IBAction func triggerOtherError(_ sender: Any) { + UserRepository.shared.failedRequest() + } + + private func bindUserRepository() { + UserRepository.shared.getUserDetails() + UserRepository.shared.user.sink { user in + self.emailLabel.text = user?.email + self.firstNameLabel.text = user?.firstName + self.lastNameLabel.text = user?.lastName + self.locationLabel.text = user?.location + }.store(in: &cancellables) + } +} diff --git a/Netable/NetableExample/ViewController/RootTabBarController.swift b/Netable/NetableExample/ViewController/RootTabBarController.swift new file mode 100644 index 0000000..7292ac3 --- /dev/null +++ b/Netable/NetableExample/ViewController/RootTabBarController.swift @@ -0,0 +1,33 @@ +// +// RootTabBarController.swift +// NetableExample +// +// Created by Brendan on 2021-09-15. +// Copyright © 2021 Steamclock Software. All rights reserved. +// + +import Netable +import UIKit + +class RootTabBarController: UITabBarController { + override func viewDidLoad() { + super.viewDidLoad() + + UserRepository.shared.netable.requestFailureDelegate = self + + PostRepository.shared.checkVersion() + } +} + +extension RootTabBarController: RequestFailureDelegate { + func requestDidFail(_ request: T, error: NetableError) where T : Request { + // Ignore 401 unauthorized errors, we'll handle those in the UserRepository + if case let NetableError.httpError(statusCode, _) = error, statusCode == 401 { + return + } + + let alert = UIAlertController(title: "Error", message: error.errorDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Ok", style: .default)) + present(alert, animated: true, completion: nil) + } +} diff --git a/README.md b/README.md index de5bbf1..9eab15b 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ In addition to handling errors locally through the `completion` callback provide #### Using `requestFailureDelegate` -See [GlobalRequestFailureDelegate](https://github.com/steamclock/netable/blob/master/Netable/NetableExample/View%20Controllers/GlobalRequestFailureDelegateExample.swift) in the Example project for a more detailed example. +See [GlobalRequestFailureDelegate](https://github.com/steamclock/netable/blob/master/Netable/NetableExample/ViewController/RootTabBarController.swift) in the Example project for a more detailed example. ```swift extension GlobalRequestFailureDelegateExample: RequestFailureDelegate { @@ -196,7 +196,7 @@ extension GlobalRequestFailureDelegateExample: RequestFailureDelegate { If you prefer `Combine`, you can subscribe to this publisher to recieve `NetableErrors` from elsewhere in your app. -See [GlobalRequestFailurePublisher](https://github.com/steamclock/netable/blob/master/Netable/NetableExample/View%20Controllers/GlobalRequestFailurePublisherExample.swift) in the Example project for a more detailed example. +See [GlobalRequestFailurePublisher](https://github.com/steamclock/netable/blob/master/Netable/NetableExample/Repository/UserRepository.swift) in the Example project for a more detailed example. ```swift netable.requestFailurePublisher.sink { error in @@ -213,7 +213,7 @@ Sometimes, you may want to specify a backup type to try and decode your response `Request` allows you to optionally declare a `FallbackResource: Decodable` associated type when creating your request. If you do and your request fails to decode the `RawResource`, it will try to decode your fallback resource, and if successful, throw a `NetableError.fallbackDecode` with your successful decoding. -See [FallbackDecoderViewController](https://github.com/steamclock/netable/blob/master/Netable/NetableExample/View%20Controllers/FallbackDecoderViewController.swift) in the Example project for a more detailed example. +See [FallbackDecoderViewController](https://github.com/steamclock/netable/blob/master/Netable/NetableExample/Request/VersionCheckRequest.swift) in the Example project for a more detailed example. ### Full Documentation @@ -226,7 +226,7 @@ To run the example project, clone the repo, and run `pod install` from inside th ## Requirements -- iOS 10.0+ +- iOS 13.0+ - MacOS 10.15+ - Xcode 11.0+